mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
delete golden files; switch to flutter/goldens (flutter/engine#12434)
Delete golden files from flutter/engine; switch to flutter/goldens
This commit is contained in:
parent
72d05a0db7
commit
bcfe5948b1
@ -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
engine/src/flutter/lib/web_ui/dev/goldens_lock.yaml
Normal file
2
engine/src/flutter/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';
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ void main() async {
|
||||
}
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
await matchGoldenFile('engine/canvas_rrect_round_square.png', region: region);
|
||||
await matchGoldenFile('canvas_rrect_round_square.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('round rect with big radius scale down smaller radius', () async {
|
||||
@ -58,7 +58,7 @@ void main() async {
|
||||
}
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
await matchGoldenFile('engine/canvas_rrect_overlapping_radius.png', region: region);
|
||||
await matchGoldenFile('canvas_rrect_overlapping_radius.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('diff round rect with big radius scale down smaller radius', () async {
|
||||
@ -81,6 +81,6 @@ void main() async {
|
||||
}
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
await matchGoldenFile('engine/canvas_drrect_overlapping_radius.png', region: region);
|
||||
await matchGoldenFile('canvas_drrect_overlapping_radius.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
|
||||
await matchGoldenFile('engine/misaligned_pixels_in_canvas_test.png', region: region);
|
||||
await matchGoldenFile('misaligned_pixels_in_canvas_test.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('compensates for misalignment of the canvas', () async {
|
||||
@ -83,7 +83,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
|
||||
await matchGoldenFile('engine/misaligned_canvas_test.png', region: region);
|
||||
await matchGoldenFile('misaligned_canvas_test.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('fill the whole canvas with color even when transformed', () async {
|
||||
@ -94,7 +94,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
|
||||
await matchGoldenFile('engine/bitmap_canvas_fills_color_when_transformed.png', region: region);
|
||||
await matchGoldenFile('bitmap_canvas_fills_color_when_transformed.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('fill the whole canvas with paint even when transformed', () async {
|
||||
@ -107,6 +107,6 @@ void main() async {
|
||||
|
||||
html.document.body.append(canvas.rootElement);
|
||||
|
||||
await matchGoldenFile('engine/bitmap_canvas_fills_paint_when_transformed.png', region: region);
|
||||
await matchGoldenFile('bitmap_canvas_fills_paint_when_transformed.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_shifted_clip_rect.png', region: region);
|
||||
await matchGoldenFile('compositing_shifted_clip_rect.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('pushClipRect with offset and transform', () async {
|
||||
@ -54,7 +54,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_clip_rect_with_offset_and_transform.png', region: region);
|
||||
await matchGoldenFile('compositing_clip_rect_with_offset_and_transform.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('pushClipRRect', () async {
|
||||
@ -67,7 +67,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_shifted_clip_rrect.png', region: region);
|
||||
await matchGoldenFile('compositing_shifted_clip_rrect.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
test('pushPhysicalShape', () async {
|
||||
@ -95,7 +95,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_shifted_physical_shape_clip.png', region: region);
|
||||
await matchGoldenFile('compositing_shifted_physical_shape_clip.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
group('Cull rect computation', () {
|
||||
@ -222,7 +222,7 @@ void _testCullRectComputation() {
|
||||
builder.pop(); // pushClipRect
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_cull_rect_fills_layer_clip.png', region: region);
|
||||
await matchGoldenFile('compositing_cull_rect_fills_layer_clip.png', region: region);
|
||||
|
||||
final PersistedStandardPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 70, 70));
|
||||
@ -250,7 +250,7 @@ void _testCullRectComputation() {
|
||||
builder.pop(); // pushClipRect
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_cull_rect_intersects_clip_and_paint_bounds.png', region: region);
|
||||
await matchGoldenFile('compositing_cull_rect_intersects_clip_and_paint_bounds.png', region: region);
|
||||
|
||||
final PersistedStandardPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(50, 40, 70, 70));
|
||||
@ -280,7 +280,7 @@ void _testCullRectComputation() {
|
||||
builder.pop(); // pushClipRect
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_cull_rect_offset_inside_layer_clip.png', region: region);
|
||||
await matchGoldenFile('compositing_cull_rect_offset_inside_layer_clip.png', region: region);
|
||||
|
||||
final PersistedStandardPicture picture = enumeratePictures().single;
|
||||
expect(picture.optimalLocalCullRect,
|
||||
@ -353,7 +353,7 @@ void _testCullRectComputation() {
|
||||
builder.pop(); // pushOffset
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_cull_rect_rotated.png', region: region);
|
||||
await matchGoldenFile('compositing_cull_rect_rotated.png', region: region);
|
||||
|
||||
final PersistedStandardPicture picture = enumeratePictures().single;
|
||||
expect(
|
||||
@ -375,7 +375,7 @@ void _testCullRectComputation() {
|
||||
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_clip_path.png', region: region);
|
||||
await matchGoldenFile('compositing_clip_path.png', region: region);
|
||||
}, timeout: const Timeout(Duration(seconds: 10)));
|
||||
|
||||
// Draw a picture inside a rotated clip. Verify that the cull rect is big
|
||||
@ -477,7 +477,7 @@ void _testCullRectComputation() {
|
||||
builder.pop(); // pushTransform scale
|
||||
html.document.body.append(builder.build().webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('engine/compositing_3d_rotate1.png', region: region);
|
||||
await matchGoldenFile('compositing_3d_rotate1.png', region: region);
|
||||
|
||||
final PersistedStandardPicture picture = enumeratePictures().single;
|
||||
// TODO(https://github.com/flutter/flutter/issues/40395):
|
||||
|
||||
@ -33,7 +33,7 @@ void main() async {
|
||||
|
||||
html.document.body.append(bitmapCanvas.rootElement);
|
||||
canvas.apply(bitmapCanvas);
|
||||
await matchGoldenFile('engine/$scubaFileName.png', region: region);
|
||||
await matchGoldenFile('$scubaFileName.png', region: region);
|
||||
bitmapCanvas.rootElement.remove();
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ void main() async {
|
||||
|
||||
canvas.apply(bitmapCanvas);
|
||||
|
||||
await matchGoldenFile('engine/$scubaFileName.png', region: region);
|
||||
await matchGoldenFile('$scubaFileName.png', region: region);
|
||||
|
||||
bitmapCanvas.rootElement.remove();
|
||||
svgElement.remove();
|
||||
|
||||
@ -44,7 +44,7 @@ void main() async {
|
||||
try {
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
html.document.body.append(sceneElement);
|
||||
await matchGoldenFile('engine/paint_bounds_for_$fileName.png', region: region);
|
||||
await matchGoldenFile('paint_bounds_for_$fileName.png', region: region);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// Scuba screenshot.
|
||||
|
||||
@ -40,7 +40,7 @@ class EngineScubaTester {
|
||||
}
|
||||
|
||||
Future<void> diffScreenshot(String fileName) async {
|
||||
await matchGoldenFile('engine/$fileName.png', region: ui.Rect.fromLTWH(0, 0, viewportSize.width, viewportSize.height));
|
||||
await matchGoldenFile('$fileName.png', region: ui.Rect.fromLTWH(0, 0, viewportSize.width, viewportSize.height));
|
||||
}
|
||||
|
||||
/// Prepares the DOM and inserts all the necessary nodes, then invokes scuba's
|
||||
|
||||
@ -11,6 +11,6 @@ import 'package:web_engine_tester/golden_tester.dart';
|
||||
void main() {
|
||||
test('screenshot test reports failure', () async {
|
||||
html.document.body.innerHtml = 'Text that does not appear on the screenshot!';
|
||||
await matchGoldenFile('smoke_test.png', region: Rect.fromLTWH(0, 0, 320, 200));
|
||||
await matchGoldenFile('__local__/smoke_test.png', region: Rect.fromLTWH(0, 0, 320, 200));
|
||||
});
|
||||
}
|
||||
|
||||
@ -11,6 +11,6 @@ import 'package:web_engine_tester/golden_tester.dart';
|
||||
void main() {
|
||||
test('screenshot test reports success', () async {
|
||||
html.document.body.innerHtml = 'Hello world!';
|
||||
await matchGoldenFile('smoke_test.png', region: Rect.fromLTWH(0, 0, 320, 200));
|
||||
await matchGoldenFile('__local__/smoke_test.png', region: Rect.fromLTWH(0, 0, 320, 200));
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user