Refactor TestGoldenComparator
to be useful for non-web (Android, iOS) integration tests (#160215)
Part of https://github.com/flutter/flutter/issues/160043, makes it easier to add https://github.com/flutter/flutter/pull/160131. This PR has no functional changes to any of the code, but does refactor both the code and tests: - Makes a number of always non-null but not migrated to non-null properties, well, not-null - Creates two concrete methods (`update` and `compare` versus a positional nullable boolean) - Uses type signatures instead of `String?` to explain the possible results of the methods - Renames the mysterious `shellPath` variable to `flutterTesterBinPath` - Expands and rewrites internally-facing doc comments - Moves `WebRenderer` environment variable setting to `flutter_web_platform.dart` - Makes the tests have less duplication, and check for update/compare cases After this PR, I can use it in the non-web branch of the Flutter tool without any hacks or TODOS :) /cc @eyebrowsoffire (trivial web refactoring), @camsim99 (changes being made to tool).
This commit is contained in:
parent
8fee7cb832
commit
18f56e3224
@ -33,8 +33,8 @@ import '../web/bootstrap.dart';
|
||||
import '../web/chrome.dart';
|
||||
import '../web/compile.dart';
|
||||
import '../web/memory_fs.dart';
|
||||
import 'flutter_web_goldens.dart';
|
||||
import 'test_compiler.dart';
|
||||
import 'test_golden_comparator.dart';
|
||||
import 'test_time_recorder.dart';
|
||||
|
||||
shelf.Handler createDirectoryHandler(Directory directory, { required bool crossOriginIsolated} ) {
|
||||
@ -73,12 +73,12 @@ shelf.Handler createDirectoryHandler(Directory directory, { required bool crossO
|
||||
|
||||
class FlutterWebPlatform extends PlatformPlugin {
|
||||
FlutterWebPlatform._(this._server, this._config, this._root, {
|
||||
FlutterProject? flutterProject,
|
||||
String? shellPath,
|
||||
this.updateGoldens,
|
||||
this.nullAssertions,
|
||||
required this.updateGoldens,
|
||||
required this.buildInfo,
|
||||
required this.webMemoryFS,
|
||||
required FlutterProject flutterProject,
|
||||
required String flutterTesterBinPath,
|
||||
required FileSystem fileSystem,
|
||||
required Directory buildDirectory,
|
||||
required File testDartJs,
|
||||
@ -115,12 +115,16 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
.add(_packageFilesHandler);
|
||||
_server.mount(cascade.handler);
|
||||
_testGoldenComparator = TestGoldenComparator(
|
||||
shellPath,
|
||||
() => TestCompiler(buildInfo, flutterProject, testTimeRecorder: testTimeRecorder),
|
||||
compilerFactory: () => TestCompiler(buildInfo, flutterProject, testTimeRecorder: testTimeRecorder),
|
||||
flutterTesterBinPath: flutterTesterBinPath,
|
||||
fileSystem: _fileSystem,
|
||||
logger: _logger,
|
||||
processManager: processManager,
|
||||
webRenderer: webRenderer,
|
||||
environment: <String, String>{
|
||||
// Chrome is the only supported browser currently.
|
||||
'FLUTTER_TEST_BROWSER': 'chrome',
|
||||
'FLUTTER_WEB_RENDERER': webRenderer.name,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -133,7 +137,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
final ChromiumLauncher _chromiumLauncher;
|
||||
final Logger _logger;
|
||||
final Artifacts? _artifacts;
|
||||
final bool? updateGoldens;
|
||||
final bool updateGoldens;
|
||||
final bool? nullAssertions;
|
||||
final OneOffHandler _webSocketHandler = OneOffHandler();
|
||||
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
|
||||
@ -154,11 +158,11 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
}
|
||||
|
||||
static Future<FlutterWebPlatform> start(String root, {
|
||||
FlutterProject? flutterProject,
|
||||
String? shellPath,
|
||||
bool updateGoldens = false,
|
||||
bool pauseAfterLoad = false,
|
||||
bool nullAssertions = false,
|
||||
required FlutterProject flutterProject,
|
||||
required String flutterTesterBinPath,
|
||||
required BuildInfo buildInfo,
|
||||
required WebMemoryFS webMemoryFS,
|
||||
required FileSystem fileSystem,
|
||||
@ -195,7 +199,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
Configuration.current.change(pauseAfterLoad: pauseAfterLoad),
|
||||
root,
|
||||
flutterProject: flutterProject,
|
||||
shellPath: shellPath,
|
||||
flutterTesterBinPath: flutterTesterBinPath,
|
||||
updateGoldens: updateGoldens,
|
||||
buildInfo: buildInfo,
|
||||
webMemoryFS: webMemoryFS,
|
||||
@ -456,8 +460,17 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
return shelf.Response.ok('Caught exception: $ex');
|
||||
}
|
||||
}
|
||||
final String? errorMessage = await _testGoldenComparator.compareGoldens(testUri, bytes, goldenKey, updateGoldens);
|
||||
return shelf.Response.ok(errorMessage ?? 'true');
|
||||
if (updateGoldens) {
|
||||
return switch (await _testGoldenComparator.update(testUri, bytes, goldenKey)) {
|
||||
TestGoldenUpdateDone() => shelf.Response.ok('true'),
|
||||
TestGoldenUpdateError(error: final String error) => shelf.Response.ok(error),
|
||||
};
|
||||
} else {
|
||||
return switch (await _testGoldenComparator.compare(testUri, bytes, goldenKey)) {
|
||||
TestGoldenComparisonDone(matched: final bool matched) => shelf.Response.ok('$matched'),
|
||||
TestGoldenComparisonError(error: final String error) => shelf.Response.ok(error),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return shelf.Response.notFound('Not Found');
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
BuildInfo? buildInfo,
|
||||
}) async {
|
||||
// Configure package:test to use the Flutter engine for child processes.
|
||||
final String shellPath = globals.artifacts!.getArtifactPath(Artifact.flutterTester);
|
||||
final String flutterTesterBinPath = globals.artifacts!.getArtifactPath(Artifact.flutterTester);
|
||||
|
||||
// Compute the command-line arguments for package:test.
|
||||
final List<String> testArgs = <String>[
|
||||
@ -203,7 +203,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
return FlutterWebPlatform.start(
|
||||
flutterProject.directory.path,
|
||||
updateGoldens: updateGoldens,
|
||||
shellPath: shellPath,
|
||||
flutterTesterBinPath: flutterTesterBinPath,
|
||||
flutterProject: flutterProject,
|
||||
pauseAfterLoad: debuggingOptions.startPaused,
|
||||
nullAssertions: debuggingOptions.nullAssertions,
|
||||
@ -241,7 +241,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
|
||||
|
||||
final loader.FlutterPlatform platform = loader.installHook(
|
||||
testWrapper: testWrapper,
|
||||
shellPath: shellPath,
|
||||
shellPath: flutterTesterBinPath,
|
||||
debuggingOptions: debuggingOptions,
|
||||
watcher: watcher,
|
||||
enableVmService: enableVmService,
|
||||
|
@ -5,50 +5,74 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../convert.dart';
|
||||
import '../web/compile.dart';
|
||||
import 'test_compiler.dart';
|
||||
import 'test_config.dart';
|
||||
|
||||
/// Helper class to start golden file comparison in a separate process.
|
||||
/// Runs a [GoldenFileComparator] (that may depend on `dart:ui`) in a `flutter_tester`.
|
||||
///
|
||||
/// The golden file comparator is configured using flutter_test_config.dart and that
|
||||
/// file can contain arbitrary Dart code that depends on dart:ui. Thus it has to
|
||||
/// be executed in a `flutter_tester` environment. This helper class generates a
|
||||
/// Dart file configured with flutter_test_config.dart to perform the comparison
|
||||
/// of golden files.
|
||||
class TestGoldenComparator {
|
||||
/// The [`goldenFileComparator`](https://api.flutter.dev/flutter/flutter_test/goldenFileComparator.html)
|
||||
/// is configured using [`flutter_test_config.dart`](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html)
|
||||
/// and that file often contains arbitrary Dart code that depends on [`dart:ui`](https://api.flutter.dev/flutter/dart-ui/dart-ui-library.html).
|
||||
///
|
||||
/// This proxying comparator creates a minimal application that runs on a
|
||||
/// `flutter_tester` instance, runs a golden comparison, and then returns the
|
||||
/// results through [compareGoldens].
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```dart
|
||||
/// final comparator = TestGoldenComparator(
|
||||
/// flutterTesterBinPath: '/path/to/flutter_tester',
|
||||
/// logger: ...,
|
||||
/// fileSystem: ...,
|
||||
/// processManager: ...,
|
||||
/// )
|
||||
///
|
||||
/// final result = await comparator.compare(testUri, bytes, goldenKey);
|
||||
/// ```
|
||||
final class TestGoldenComparator {
|
||||
/// Creates a [TestGoldenComparator] instance.
|
||||
TestGoldenComparator(this.shellPath, this.compilerFactory, {
|
||||
TestGoldenComparator({
|
||||
required String flutterTesterBinPath,
|
||||
required TestCompiler Function() compilerFactory,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
required ProcessManager processManager,
|
||||
required this.webRenderer,
|
||||
}) : tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_web_platform.'),
|
||||
Map<String, String> environment = const <String, String>{},
|
||||
}) : _tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_web_platform.'),
|
||||
_flutterTesterBinPath = flutterTesterBinPath,
|
||||
_compilerFactory = compilerFactory,
|
||||
_logger = logger,
|
||||
_fileSystem = fileSystem,
|
||||
_processManager = processManager;
|
||||
_processManager = processManager,
|
||||
_environment = environment;
|
||||
|
||||
final String? shellPath;
|
||||
final Directory tempDir;
|
||||
final TestCompiler Function() compilerFactory;
|
||||
final String _flutterTesterBinPath;
|
||||
final Directory _tempDir;
|
||||
final Logger _logger;
|
||||
final FileSystem _fileSystem;
|
||||
final ProcessManager _processManager;
|
||||
final WebRendererMode webRenderer;
|
||||
final Map<String, String> _environment;
|
||||
|
||||
final TestCompiler Function() _compilerFactory;
|
||||
late final TestCompiler _compiler = _compilerFactory();
|
||||
|
||||
TestCompiler? _compiler;
|
||||
TestGoldenComparatorProcess? _previousComparator;
|
||||
Uri? _previousTestUri;
|
||||
|
||||
/// Closes the comparator.
|
||||
///
|
||||
/// Any operation in process is terminated and the comparator can no longer be used.
|
||||
Future<void> close() async {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
await _compiler?.dispose();
|
||||
_tempDir.deleteSync(recursive: true);
|
||||
await _compiler.dispose();
|
||||
await _previousComparator?.close();
|
||||
}
|
||||
|
||||
@ -73,33 +97,46 @@ class TestGoldenComparator {
|
||||
|
||||
Future<Process?> _startProcess(String testBootstrap) async {
|
||||
// Prepare the Dart file that will talk to us and start the test.
|
||||
final File listenerFile = (await tempDir.createTemp('listener')).childFile('listener.dart');
|
||||
final File listenerFile = (await _tempDir.createTemp('listener')).childFile('listener.dart');
|
||||
await listenerFile.writeAsString(testBootstrap);
|
||||
|
||||
// Lazily create the compiler
|
||||
_compiler = _compiler ?? compilerFactory();
|
||||
final String? output = await _compiler!.compile(listenerFile.uri);
|
||||
final String? output = await _compiler.compile(listenerFile.uri);
|
||||
if (output == null) {
|
||||
return null;
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
shellPath!,
|
||||
_flutterTesterBinPath,
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=${_fileSystem.path.join('.dart_tool', 'package_config.json')}',
|
||||
output,
|
||||
];
|
||||
|
||||
final Map<String, String> environment = <String, String>{
|
||||
// Chrome is the only supported browser currently.
|
||||
'FLUTTER_TEST_BROWSER': 'chrome',
|
||||
'FLUTTER_WEB_RENDERER': webRenderer.name,
|
||||
};
|
||||
return _processManager.start(command, environment: environment);
|
||||
return _processManager.start(command, environment: _environment);
|
||||
}
|
||||
|
||||
Future<String?> compareGoldens(Uri testUri, Uint8List bytes, Uri goldenKey, bool? updateGoldens) async {
|
||||
final File imageFile = await (await tempDir.createTemp('image')).childFile('image').writeAsBytes(bytes);
|
||||
/// Compares the golden file designated by [goldenKey], relative to [testUri], to the provide [bytes].
|
||||
Future<TestGoldenComparison> compare(Uri testUri, Uint8List bytes, Uri goldenKey) async {
|
||||
final String? result = await _compareGoldens(testUri, bytes, goldenKey, false);
|
||||
return switch (result) {
|
||||
null => const TestGoldenComparisonDone(matched: true),
|
||||
'does not match' => const TestGoldenComparisonDone(matched: false),
|
||||
final String error => TestGoldenComparisonError(error: error),
|
||||
};
|
||||
}
|
||||
|
||||
/// Updates the golden file designated by [goldenKey], relative to [testUri], to the provide [bytes].
|
||||
Future<TestGoldenUpdate> update(Uri testUri, Uint8List bytes, Uri goldenKey) async {
|
||||
final String? result = await _compareGoldens(testUri, bytes, goldenKey, true);
|
||||
return switch (result) {
|
||||
null => const TestGoldenUpdateDone(),
|
||||
final String error => TestGoldenUpdateError(error: error),
|
||||
};
|
||||
}
|
||||
|
||||
@useResult
|
||||
Future<String?> _compareGoldens(Uri testUri, Uint8List bytes, Uri goldenKey, bool? updateGoldens) async {
|
||||
final File imageFile = await (await _tempDir.createTemp('image')).childFile('image').writeAsBytes(bytes);
|
||||
final TestGoldenComparatorProcess? process = await _processForTestFile(testUri);
|
||||
if (process == null) {
|
||||
return 'process was null';
|
||||
@ -112,6 +149,105 @@ class TestGoldenComparator {
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of [TestGoldenComparator.compare].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TestGoldenComparisonDone]
|
||||
/// * [TestGoldenComparisonError]
|
||||
@immutable
|
||||
sealed class TestGoldenComparison {}
|
||||
|
||||
/// A successful comparison that resulted in [matched].
|
||||
final class TestGoldenComparisonDone implements TestGoldenComparison {
|
||||
const TestGoldenComparisonDone({required this.matched});
|
||||
|
||||
/// Whether the bytes matched the file specified.
|
||||
///
|
||||
/// A value of `true` is a match, and `false` is a "did not match".
|
||||
final bool matched;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TestGoldenComparisonDone && matched == other.matched;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => matched.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TestGoldenComparisonDone(matched: $matched)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A failed comparison that could not be completed for a reason in [error].
|
||||
final class TestGoldenComparisonError implements TestGoldenComparison {
|
||||
const TestGoldenComparisonError({required this.error});
|
||||
|
||||
/// Why the comparison failed, which should be surfaced to the user as an error.
|
||||
final String error;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TestGoldenComparisonError && error == other.error;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => error.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TestGoldenComparisonError(error: $error)';
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of [TestGoldenComparator.update].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TestGoldenUpdateDone]
|
||||
/// * [TestGoldenUpdateError]
|
||||
@immutable
|
||||
sealed class TestGoldenUpdate {}
|
||||
|
||||
/// A successful update.
|
||||
final class TestGoldenUpdateDone implements TestGoldenUpdate {
|
||||
const TestGoldenUpdateDone();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is TestGoldenUpdateDone;
|
||||
|
||||
@override
|
||||
int get hashCode => (TestGoldenUpdateDone).hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TestGoldenUpdateDone()';
|
||||
}
|
||||
}
|
||||
|
||||
/// A failed update that could not be completed for a reason in [error].
|
||||
final class TestGoldenUpdateError implements TestGoldenUpdate {
|
||||
const TestGoldenUpdateError({required this.error});
|
||||
|
||||
/// Why the comparison failed, which should be surfaced to the user as an error.
|
||||
final String error;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TestGoldenUpdateError && error == other.error;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => error.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TestGoldenUpdateError(error: $error)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a `flutter_tester` process started for golden comparison. Also
|
||||
/// handles communication with the child process.
|
||||
class TestGoldenComparatorProcess {
|
@ -8,13 +8,14 @@ import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/test/flutter_web_platform.dart';
|
||||
import 'package:flutter_tools/src/web/chrome.dart';
|
||||
import 'package:flutter_tools/src/web/compile.dart';
|
||||
import 'package:flutter_tools/src/web/memory_fs.dart';
|
||||
import 'package:shelf/shelf.dart' as shelf;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fakes.dart';
|
||||
|
||||
@ -40,6 +41,7 @@ void main() {
|
||||
late Artifacts artifacts;
|
||||
late ProcessManager processManager;
|
||||
late FakeOperatingSystemUtils operatingSystemUtils;
|
||||
late Directory tempDir;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
@ -48,6 +50,7 @@ void main() {
|
||||
artifacts = Artifacts.test(fileSystem: fileSystem);
|
||||
processManager = FakeProcessManager.empty();
|
||||
operatingSystemUtils = FakeOperatingSystemUtils();
|
||||
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_web_platform_test.');
|
||||
|
||||
for (final HostArtifact artifact in <HostArtifact>[
|
||||
HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk,
|
||||
@ -69,6 +72,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'FlutterWebPlatform serves the correct dart_sdk.js (amd module system) for the passed web renderer',
|
||||
() async {
|
||||
@ -81,15 +88,16 @@ void main() {
|
||||
logger: logger,
|
||||
);
|
||||
final MockServer server = MockServer();
|
||||
fileSystem.directory('/test').createSync();
|
||||
final FlutterWebPlatform webPlatform = await FlutterWebPlatform.start(
|
||||
'ProjectRoot',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(tempDir),
|
||||
buildInfo: BuildInfo.debug,
|
||||
webMemoryFS: WebMemoryFS(),
|
||||
fileSystem: fileSystem,
|
||||
buildDirectory: fileSystem.directory('build'),
|
||||
logger: logger,
|
||||
chromiumLauncher: chromiumLauncher,
|
||||
flutterTesterBinPath: artifacts.getArtifactPath(Artifact.flutterTester),
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
@ -125,9 +133,9 @@ void main() {
|
||||
logger: logger,
|
||||
);
|
||||
final MockServer server = MockServer();
|
||||
fileSystem.directory('/test').createSync();
|
||||
final FlutterWebPlatform webPlatform = await FlutterWebPlatform.start(
|
||||
'ProjectRoot',
|
||||
flutterProject: FlutterProject.fromDirectoryTest(tempDir),
|
||||
buildInfo: const BuildInfo(
|
||||
BuildMode.debug,
|
||||
'',
|
||||
@ -140,6 +148,7 @@ void main() {
|
||||
buildDirectory: fileSystem.directory('build'),
|
||||
logger: logger,
|
||||
chromiumLauncher: chromiumLauncher,
|
||||
flutterTesterBinPath: artifacts.getArtifactPath(Artifact.flutterTester),
|
||||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
|
@ -7,7 +7,7 @@ import 'dart:convert';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/test/flutter_web_goldens.dart';
|
||||
import 'package:flutter_tools/src/test/test_golden_comparator.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/fakes.dart';
|
@ -0,0 +1,290 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/test/test_compiler.dart';
|
||||
import 'package:flutter_tools/src/test/test_golden_comparator.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
final Uri testUri1 = Uri(scheme: 'file', path: 'test_file_1');
|
||||
final Uri testUri2 = Uri(scheme: 'file', path: 'test_file_2');
|
||||
final Uri goldenKey1 = Uri(path: 'golden_key_1');
|
||||
final Uri goldenKey2 = Uri(path: 'golden_key_2');
|
||||
final Uint8List imageBytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
||||
|
||||
late FileSystem fileSystem;
|
||||
late BufferLogger logger;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
logger = BufferLogger.test();
|
||||
});
|
||||
|
||||
testWithoutContext('should succeed when a golden-file comparison matched', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: true),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenComparison result = await comparator.compare(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenComparisonDone(matched: true));
|
||||
});
|
||||
|
||||
testWithoutContext('should succeed when a golden-file comparison does not match', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: false),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenComparison result = await comparator.compare(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenComparisonDone(matched: false));
|
||||
});
|
||||
|
||||
testWithoutContext('should return an error when a golden-file comparison errors', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: false, message: 'Did a bad'),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenComparison result = await comparator.compare(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenComparisonError(error: 'Did a bad'));
|
||||
});
|
||||
|
||||
testWithoutContext('should succeed when a golden-file update completes', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: true),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenUpdate result = await comparator.update(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenUpdateDone());
|
||||
});
|
||||
|
||||
testWithoutContext('should error when a golden-file update errors', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: false, message: 'Did a bad'),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenUpdate result = await comparator.update(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenUpdateError(error: 'Did a bad'));
|
||||
});
|
||||
|
||||
testWithoutContext('provides environment variables to the process', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: true),
|
||||
environment: <String, String>{
|
||||
'THE_ANSWER': '42',
|
||||
}
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
environment: <String, String>{
|
||||
'THE_ANSWER': '42',
|
||||
},
|
||||
);
|
||||
|
||||
final TestGoldenUpdate result = await comparator.update(
|
||||
testUri1,
|
||||
imageBytes,
|
||||
goldenKey1,
|
||||
);
|
||||
expect(result, const TestGoldenUpdateDone());
|
||||
});
|
||||
|
||||
testWithoutContext('reuses the process for the same test file', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: <String>[
|
||||
_encodeStdout(success: false, message: '1 Did a bad'),
|
||||
_encodeStdout(success: false, message: '2 Did a bad'),
|
||||
].join('\n'),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenComparison result1 = await comparator.compare(testUri1, imageBytes, goldenKey1);
|
||||
expect(result1, const TestGoldenComparisonError(error: '1 Did a bad'));
|
||||
|
||||
final TestGoldenComparison result2 = await comparator.compare(testUri1, imageBytes, goldenKey2);
|
||||
expect(result2, const TestGoldenComparisonError(error: '2 Did a bad'));
|
||||
});
|
||||
|
||||
testWithoutContext('does not reuse the process for different test file', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: false, message: '1 Did a bad'),
|
||||
),
|
||||
_fakeFluterTester(
|
||||
'flutter_tester',
|
||||
stdout: _encodeStdout(success: false, message: '2 Did a bad'),
|
||||
)
|
||||
]),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final TestGoldenComparison result1 = await comparator.compare(testUri1, imageBytes, goldenKey1);
|
||||
expect(result1, const TestGoldenComparisonError(error: '1 Did a bad'));
|
||||
|
||||
final TestGoldenComparison result2 = await comparator.compare(testUri2, imageBytes, goldenKey2);
|
||||
expect(result2, const TestGoldenComparisonError(error: '2 Did a bad'));
|
||||
});
|
||||
|
||||
testWithoutContext('deletes the temporary directory when closed', () async {
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: _FakeTestCompiler.new,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.empty(),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
expect(fileSystem.systemTempDirectory.listSync(recursive: true), isNotEmpty);
|
||||
await comparator.close();
|
||||
expect(fileSystem.systemTempDirectory.listSync(recursive: true), isEmpty);
|
||||
});
|
||||
|
||||
testWithoutContext('disposes the test compiler when closed', () async {
|
||||
final _FakeTestCompiler testCompiler = _FakeTestCompiler();
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
compilerFactory: () => testCompiler,
|
||||
flutterTesterBinPath: 'flutter_tester',
|
||||
processManager: FakeProcessManager.empty(),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
expect(testCompiler.disposed, false);
|
||||
await comparator.close();
|
||||
expect(testCompiler.disposed, true);
|
||||
});
|
||||
}
|
||||
|
||||
FakeCommand _fakeFluterTester(String pathToBinTool, {
|
||||
required String stdout,
|
||||
Map<String, String>? environment,
|
||||
Completer<void>? waitUntil,
|
||||
}) {
|
||||
return FakeCommand(
|
||||
command: <String>[
|
||||
pathToBinTool,
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
],
|
||||
stdout: stdout,
|
||||
environment: environment,
|
||||
completer: waitUntil,
|
||||
);
|
||||
}
|
||||
|
||||
String _encodeStdout({required bool success, String? message}) {
|
||||
return jsonEncode(<String, Object?>{
|
||||
'success': success,
|
||||
if (message != null)
|
||||
'message': message,
|
||||
});
|
||||
}
|
||||
|
||||
final class _FakeTestCompiler extends Fake implements TestCompiler {
|
||||
bool disposed = false;
|
||||
|
||||
@override
|
||||
Future<String> compile(Uri mainDart) {
|
||||
return Future<String>.value('compiler_output');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
disposed = true;
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/test/flutter_web_goldens.dart';
|
||||
import 'package:flutter_tools/src/test/test_compiler.dart';
|
||||
import 'package:flutter_tools/src/web/compile.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
final Uri goldenKey = Uri.parse('file://golden_key');
|
||||
final Uri goldenKey2 = Uri.parse('file://second_golden_key');
|
||||
final Uri testUri = Uri.parse('file://test_uri');
|
||||
final Uri testUri2 = Uri.parse('file://second_test_uri');
|
||||
final Uint8List imageBytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]);
|
||||
|
||||
void main() {
|
||||
|
||||
group('Test that TestGoldenComparator', () {
|
||||
late FakeProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
processManager = FakeProcessManager.empty();
|
||||
});
|
||||
|
||||
testWithoutContext('succeed when golden comparison succeed', () async {
|
||||
final Map<String, dynamic> expectedResponse = <String, dynamic>{
|
||||
'success': true,
|
||||
'message': 'some message',
|
||||
};
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
],
|
||||
stdout: '${jsonEncode(expectedResponse)}\n',
|
||||
environment: const <String, String>{
|
||||
'FLUTTER_TEST_BROWSER': 'chrome',
|
||||
'FLUTTER_WEB_RENDERER': 'html',
|
||||
},
|
||||
));
|
||||
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
'shell',
|
||||
() => FakeTestCompiler(),
|
||||
processManager: processManager,
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
logger: BufferLogger.test(),
|
||||
webRenderer: WebRendererMode.html,
|
||||
);
|
||||
|
||||
final String? result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
|
||||
expect(result, null);
|
||||
});
|
||||
|
||||
testWithoutContext('fail with error message when golden comparison failed', () async {
|
||||
final Map<String, dynamic> expectedResponse = <String, dynamic>{
|
||||
'success': false,
|
||||
'message': 'some message',
|
||||
};
|
||||
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
], stdout: '${jsonEncode(expectedResponse)}\n',
|
||||
));
|
||||
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
'shell',
|
||||
() => FakeTestCompiler(),
|
||||
processManager: processManager,
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
logger: BufferLogger.test(),
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
);
|
||||
|
||||
final String? result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
|
||||
expect(result, 'some message');
|
||||
});
|
||||
|
||||
testWithoutContext('reuse the process for the same test file', () async {
|
||||
final Map<String, dynamic> expectedResponse1 = <String, dynamic>{
|
||||
'success': false,
|
||||
'message': 'some message',
|
||||
};
|
||||
final Map<String, dynamic> expectedResponse2 = <String, dynamic>{
|
||||
'success': false,
|
||||
'message': 'some other message',
|
||||
};
|
||||
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
], stdout: '${jsonEncode(expectedResponse1)}\n${jsonEncode(expectedResponse2)}\n',
|
||||
));
|
||||
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
'shell',
|
||||
() => FakeTestCompiler(),
|
||||
processManager: processManager,
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
logger: BufferLogger.test(),
|
||||
webRenderer: WebRendererMode.html,
|
||||
);
|
||||
|
||||
final String? result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
|
||||
expect(result1, 'some message');
|
||||
|
||||
final String? result2 = await comparator.compareGoldens(testUri, imageBytes, goldenKey2, false);
|
||||
expect(result2, 'some other message');
|
||||
});
|
||||
|
||||
testWithoutContext('does not reuse the process for different test file', () async {
|
||||
final Map<String, dynamic> expectedResponse1 = <String, dynamic>{
|
||||
'success': false,
|
||||
'message': 'some message',
|
||||
};
|
||||
final Map<String, dynamic> expectedResponse2 = <String, dynamic>{
|
||||
'success': false,
|
||||
'message': 'some other message',
|
||||
};
|
||||
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
], stdout: '${jsonEncode(expectedResponse1)}\n',
|
||||
));
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
], stdout: '${jsonEncode(expectedResponse2)}\n',
|
||||
));
|
||||
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
'shell',
|
||||
() => FakeTestCompiler(),
|
||||
processManager: processManager,
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
logger: BufferLogger.test(),
|
||||
webRenderer: WebRendererMode.canvaskit,
|
||||
);
|
||||
|
||||
final String? result1 = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
|
||||
expect(result1, 'some message');
|
||||
|
||||
final String? result2 = await comparator.compareGoldens(testUri2, imageBytes, goldenKey2, false);
|
||||
expect(result2, 'some other message');
|
||||
});
|
||||
|
||||
testWithoutContext('removes all temporary files when closed', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Map<String, dynamic> expectedResponse = <String, dynamic>{
|
||||
'success': true,
|
||||
'message': 'some message',
|
||||
};
|
||||
final StreamController<List<int>> controller = StreamController<List<int>>();
|
||||
final IOSink stdin = IOSink(controller.sink);
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: const <String>[
|
||||
'shell',
|
||||
'--disable-vm-service',
|
||||
'--non-interactive',
|
||||
'--packages=.dart_tool/package_config.json',
|
||||
'compiler_output',
|
||||
], stdout: '${jsonEncode(expectedResponse)}\n',
|
||||
stdin: stdin,
|
||||
));
|
||||
|
||||
final TestGoldenComparator comparator = TestGoldenComparator(
|
||||
'shell',
|
||||
() => FakeTestCompiler(),
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
webRenderer: WebRendererMode.html,
|
||||
);
|
||||
|
||||
final String? result = await comparator.compareGoldens(testUri, imageBytes, goldenKey, false);
|
||||
expect(result, null);
|
||||
|
||||
await comparator.close();
|
||||
expect(fileSystem.systemTempDirectory.listSync(recursive: true), isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeTestCompiler extends Fake implements TestCompiler {
|
||||
@override
|
||||
Future<String> compile(Uri mainDart) {
|
||||
return Future<String>.value('compiler_output');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async { }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user