delete golden files; switch to flutter/goldens (#12434)
Delete golden files from flutter/engine; switch to flutter/goldens
@ -20,3 +20,15 @@ or:
|
||||
```
|
||||
felt build --watch
|
||||
```
|
||||
|
||||
## Configuration files
|
||||
|
||||
`chrome_lock.yaml` contains the version of Chrome we use to test Flutter for
|
||||
web. Chrome is not automatically updated whenever a new release is available.
|
||||
Instead, we update this file manually once in a while.
|
||||
|
||||
`goldens_lock.yaml` refers to a revision in the https://github.com/flutter/goldens
|
||||
repo. Screenshot tests are compared with the golden files at that revision.
|
||||
When making engine changes that affect screenshots, first submit a PR to
|
||||
flutter/goldens updating the screenshots. Then update this file pointing to
|
||||
the new revision.
|
||||
|
||||
@ -103,4 +103,17 @@ class Environment {
|
||||
webUiRootDir.path,
|
||||
'.dart_tool',
|
||||
));
|
||||
|
||||
/// Path to the "dev" directory containing engine developer tools and
|
||||
/// configuration files.
|
||||
io.Directory get webUiDevDir => io.Directory(pathlib.join(
|
||||
webUiRootDir.path,
|
||||
'dev',
|
||||
));
|
||||
|
||||
/// Path to the clone of the flutter/goldens repository.
|
||||
io.Directory get webUiGoldensRepositoryDirectory => io.Directory(pathlib.join(
|
||||
webUiDartToolDir.path,
|
||||
'goldens',
|
||||
));
|
||||
}
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
// found in the LICENSE file.
|
||||
import 'dart:io' as io;
|
||||
import 'package:image/image.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'environment.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
final io.File fileA = io.File(args[0]);
|
||||
@ -10,7 +15,7 @@ void main(List<String> args) {
|
||||
final Image imageA = decodeNamedImage(fileA.readAsBytesSync(), 'a.png');
|
||||
final Image imageB = decodeNamedImage(fileB.readAsBytesSync(), 'b.png');
|
||||
final ImageDiff diff = ImageDiff(golden: imageA, other: imageB);
|
||||
print('Diff: ${(diff.rate * 100).toStringAsFixed(4)}');
|
||||
print('Diff: ${(diff.rate * 100).toStringAsFixed(4)}%');
|
||||
}
|
||||
|
||||
/// This class encapsulates visually diffing an Image with any other.
|
||||
@ -140,3 +145,72 @@ class ImageDiff {
|
||||
String getPrintableDiffFilesInfo(double diffRate, double maxRate) =>
|
||||
'(${((diffRate) * 100).toStringAsFixed(4)}% of pixels were different. '
|
||||
'Maximum allowed rate is: ${(maxRate * 100).toStringAsFixed(4)}%).';
|
||||
|
||||
/// Fetches golden files from github.com/flutter/goldens, cloning the repository if necessary.
|
||||
///
|
||||
/// The repository is cloned into web_ui/.dart_tool.
|
||||
Future<void> fetchGoldens() async {
|
||||
await _GoldensRepoFetcher().fetch();
|
||||
}
|
||||
|
||||
class _GoldensRepoFetcher {
|
||||
String _repository;
|
||||
String _revision;
|
||||
|
||||
Future<void> fetch() async {
|
||||
final io.File lockFile = io.File(
|
||||
path.join(environment.webUiDevDir.path, 'goldens_lock.yaml')
|
||||
);
|
||||
final YamlMap lock = loadYaml(lockFile.readAsStringSync());
|
||||
_repository = lock['repository'];
|
||||
_revision = lock['revision'];
|
||||
|
||||
final String localRevision = await _getLocalRevision();
|
||||
if (localRevision == _revision) {
|
||||
return;
|
||||
}
|
||||
|
||||
print('Fetching $_repository@$_revision');
|
||||
|
||||
if (!environment.webUiGoldensRepositoryDirectory.existsSync()) {
|
||||
environment.webUiGoldensRepositoryDirectory.createSync(recursive: true);
|
||||
await runProcess(
|
||||
'git',
|
||||
<String>['init'],
|
||||
workingDirectory: environment.webUiGoldensRepositoryDirectory.path,
|
||||
mustSucceed: true,
|
||||
);
|
||||
await runProcess(
|
||||
'git',
|
||||
<String>['remote', 'add', 'origin', _repository],
|
||||
workingDirectory: environment.webUiGoldensRepositoryDirectory.path,
|
||||
mustSucceed: true,
|
||||
);
|
||||
}
|
||||
|
||||
await runProcess(
|
||||
'git',
|
||||
<String>['fetch', 'origin', 'master'],
|
||||
workingDirectory: environment.webUiGoldensRepositoryDirectory.path,
|
||||
mustSucceed: true,
|
||||
);
|
||||
await runProcess(
|
||||
'git',
|
||||
<String>['checkout', _revision],
|
||||
workingDirectory: environment.webUiGoldensRepositoryDirectory.path,
|
||||
mustSucceed: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getLocalRevision() async {
|
||||
final io.File head = io.File(path.join(
|
||||
environment.webUiGoldensRepositoryDirectory.path, '.git', 'HEAD'
|
||||
));
|
||||
|
||||
if (!head.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return head.readAsStringSync().trim();
|
||||
}
|
||||
}
|
||||
|
||||
2
lib/web_ui/dev/goldens_lock.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
repository: https://github.com/flutter/goldens.git
|
||||
revision: dd993a32c23c5c542f083134467e7cda09cac975
|
||||
@ -147,10 +147,28 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
}
|
||||
|
||||
Future<String> _diffScreenshot(String filename, bool write, [ Map<String, dynamic> region ]) async {
|
||||
const String _kGoldensDirectory = 'test/golden_files';
|
||||
String goldensDirectory;
|
||||
if (filename.startsWith('__local__')) {
|
||||
filename = filename.substring('__local__/'.length);
|
||||
goldensDirectory = p.join(
|
||||
env.environment.webUiRootDir.path,
|
||||
'test',
|
||||
'golden_files',
|
||||
);
|
||||
} else {
|
||||
await fetchGoldens();
|
||||
goldensDirectory = p.join(
|
||||
env.environment.webUiGoldensRepositoryDirectory.path,
|
||||
'engine',
|
||||
'web',
|
||||
);
|
||||
}
|
||||
|
||||
// Bail out fast if golden doesn't exist, and user doesn't want to create it.
|
||||
final File file = File(p.join(_kGoldensDirectory, filename));
|
||||
final File file = File(p.join(
|
||||
goldensDirectory,
|
||||
filename,
|
||||
));
|
||||
if (!file.existsSync() && !write) {
|
||||
return '''
|
||||
Golden file $filename does not exist.
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'environment.dart';
|
||||
@ -31,17 +32,54 @@ class FilePath {
|
||||
String toString() => _absolutePath;
|
||||
}
|
||||
|
||||
/// Runs [executable] merging its output into the current process' standard out and standard error.
|
||||
Future<int> runProcess(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
String workingDirectory,
|
||||
bool mustSucceed: false,
|
||||
}) async {
|
||||
final io.Process process = await io.Process.start(
|
||||
executable,
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
return _forwardIOAndWait(process);
|
||||
final int exitCode = await _forwardIOAndWait(process);
|
||||
if (mustSucceed && exitCode != 0) {
|
||||
throw ProcessException(
|
||||
description: 'Sub-process failed.',
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/// Runs [executable] and returns its standard output as a string.
|
||||
///
|
||||
/// If the process fails, throws a [ProcessException].
|
||||
Future<String> evalProcess(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
String workingDirectory,
|
||||
}) async {
|
||||
final io.ProcessResult result = await io.Process.run(
|
||||
executable,
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
description: result.stderr,
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
exitCode: result.exitCode,
|
||||
);
|
||||
}
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
Future<int> _forwardIOAndWait(io.Process process) {
|
||||
@ -53,3 +91,31 @@ Future<int> _forwardIOAndWait(io.Process process) {
|
||||
return exitCode;
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ProcessException implements Exception {
|
||||
ProcessException({
|
||||
@required this.description,
|
||||
@required this.executable,
|
||||
@required this.arguments,
|
||||
@required this.workingDirectory,
|
||||
@required this.exitCode,
|
||||
});
|
||||
|
||||
final String description;
|
||||
final String executable;
|
||||
final List<String> arguments;
|
||||
final String workingDirectory;
|
||||
final int exitCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final StringBuffer message = StringBuffer();
|
||||
message
|
||||
..writeln(description)
|
||||
..writeln('Command: $executable ${arguments.join(' ')}')
|
||||
..writeln('Working directory: ${workingDirectory ?? io.Directory.current.path}')
|
||||
..writeln('Exit code: $exitCode');
|
||||
return '$message';
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 907 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 909 B |
|
Before Width: | Height: | Size: 909 B |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |