diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 75f21ef5a4..048ef00750 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -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: { + // 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 _closeMemo = AsyncMemoizer(); @@ -154,11 +158,11 @@ class FlutterWebPlatform extends PlatformPlugin { } static Future 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'); } diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index ec795cc9ed..be85f268b1 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -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 testArgs = [ @@ -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, diff --git a/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart b/packages/flutter_tools/lib/src/test/test_golden_comparator.dart similarity index 50% rename from packages/flutter_tools/lib/src/test/flutter_web_goldens.dart rename to packages/flutter_tools/lib/src/test/test_golden_comparator.dart index 049714c9ef..865a686a71 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart +++ b/packages/flutter_tools/lib/src/test/test_golden_comparator.dart @@ -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 environment = const {}, + }) : _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 _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 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 _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 command = [ - shellPath!, + _flutterTesterBinPath, '--disable-vm-service', '--non-interactive', '--packages=${_fileSystem.path.join('.dart_tool', 'package_config.json')}', output, ]; - final Map environment = { - // 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 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 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 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 _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 { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart index bbbbff53cb..9714b6c58b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart @@ -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.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, diff --git a/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart b/packages/flutter_tools/test/general.shard/test/test_golden_comparator_process_test.dart similarity index 98% rename from packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart rename to packages/flutter_tools/test/general.shard/test/test_golden_comparator_process_test.dart index 3e653572b2..8b13a05cb4 100644 --- a/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart +++ b/packages/flutter_tools/test/general.shard/test/test_golden_comparator_process_test.dart @@ -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'; diff --git a/packages/flutter_tools/test/general.shard/test/test_golden_comparator_test.dart b/packages/flutter_tools/test/general.shard/test/test_golden_comparator_test.dart new file mode 100644 index 0000000000..02934e795d --- /dev/null +++ b/packages/flutter_tools/test/general.shard/test/test_golden_comparator_test.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([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([ + _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([ + _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([ + _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([ + _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([ + _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([ + _fakeFluterTester( + 'flutter_tester', + stdout: _encodeStdout(success: true), + environment: { + 'THE_ANSWER': '42', + } + ) + ]), + fileSystem: fileSystem, + logger: logger, + environment: { + '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([ + _fakeFluterTester( + 'flutter_tester', + stdout: [ + _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([ + _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? environment, + Completer? waitUntil, +}) { + return FakeCommand( + command: [ + 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({ + 'success': success, + if (message != null) + 'message': message, + }); +} + +final class _FakeTestCompiler extends Fake implements TestCompiler { + bool disposed = false; + + @override + Future compile(Uri mainDart) { + return Future.value('compiler_output'); + } + + @override + Future dispose() async { + disposed = true; + } +} diff --git a/packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart b/packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart deleted file mode 100644 index 7f3570e53f..0000000000 --- a/packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart +++ /dev/null @@ -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([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 expectedResponse = { - 'success': true, - 'message': 'some message', - }; - processManager.addCommand(FakeCommand( - command: const [ - 'shell', - '--disable-vm-service', - '--non-interactive', - '--packages=.dart_tool/package_config.json', - 'compiler_output', - ], - stdout: '${jsonEncode(expectedResponse)}\n', - environment: const { - '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 expectedResponse = { - 'success': false, - 'message': 'some message', - }; - - processManager.addCommand(FakeCommand( - command: const [ - '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 expectedResponse1 = { - 'success': false, - 'message': 'some message', - }; - final Map expectedResponse2 = { - 'success': false, - 'message': 'some other message', - }; - - processManager.addCommand(FakeCommand( - command: const [ - '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 expectedResponse1 = { - 'success': false, - 'message': 'some message', - }; - final Map expectedResponse2 = { - 'success': false, - 'message': 'some other message', - }; - - processManager.addCommand(FakeCommand( - command: const [ - 'shell', - '--disable-vm-service', - '--non-interactive', - '--packages=.dart_tool/package_config.json', - 'compiler_output', - ], stdout: '${jsonEncode(expectedResponse1)}\n', - )); - processManager.addCommand(FakeCommand( - command: const [ - '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 expectedResponse = { - 'success': true, - 'message': 'some message', - }; - final StreamController> controller = StreamController>(); - final IOSink stdin = IOSink(controller.sink); - processManager.addCommand(FakeCommand( - command: const [ - '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 compile(Uri mainDart) { - return Future.value('compiler_output'); - } - - @override - Future dispose() async { } -}