From 4832e64cadd38ac939f723d3f8cf41e896daaf1f Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 19 Nov 2020 13:06:30 -0800 Subject: [PATCH] [flutter_tools] remove globals from flutter web platform (#70863) --- .../lib/src/test/flutter_web_goldens.dart | 195 +++++++++ .../lib/src/test/flutter_web_platform.dart | 373 +++++------------- .../flutter_tools/lib/src/test/runner.dart | 14 + .../web/golden_comparator_process_test.dart | 2 +- .../web/golden_comparator_test.dart | 2 +- 5 files changed, 306 insertions(+), 280 deletions(-) create mode 100644 packages/flutter_tools/lib/src/test/flutter_web_goldens.dart diff --git a/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart b/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart new file mode 100644 index 00000000000..5c2e0f84373 --- /dev/null +++ b/packages/flutter_tools/lib/src/test/flutter_web_goldens.dart @@ -0,0 +1,195 @@ +// 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. + +// ignore_for_file: implementation_imports + +import 'dart:async'; +import 'dart:typed_data'; + +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../convert.dart'; +import '../globals.dart' as globals; +import 'test_compiler.dart'; +import 'test_config.dart'; + +/// Helper class to start golden file comparison in a separate process. +/// +/// 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 { + /// Creates a [TestGoldenComparator] instance. + TestGoldenComparator(this.shellPath, this.compilerFactory) + : tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_web_platform.'); + + final String shellPath; + final Directory tempDir; + final TestCompiler Function() compilerFactory; + + TestCompiler _compiler; + TestGoldenComparatorProcess _previousComparator; + Uri _previousTestUri; + + Future close() async { + tempDir.deleteSync(recursive: true); + await _compiler?.dispose(); + await _previousComparator?.close(); + } + + /// Start golden comparator in a separate process. Start one file per test file + /// to reduce the overhead of starting `flutter_tester`. + Future _processForTestFile(Uri testUri) async { + if (testUri == _previousTestUri) { + return _previousComparator; + } + + final String bootstrap = TestGoldenComparatorProcess.generateBootstrap(testUri); + final Process process = await _startProcess(bootstrap); + unawaited(_previousComparator?.close()); + _previousComparator = TestGoldenComparatorProcess(process); + _previousTestUri = testUri; + + return _previousComparator; + } + + 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'); + await listenerFile.writeAsString(testBootstrap); + + // Lazily create the compiler + _compiler = _compiler ?? compilerFactory(); + final String output = await _compiler.compile(listenerFile.uri); + final List command = [ + shellPath, + '--disable-observatory', + '--non-interactive', + '--packages=${globals.fs.path.join('.dart_tool', 'package_config.json')}', + output, + ]; + + final Map environment = { + // Chrome is the only supported browser currently. + 'FLUTTER_TEST_BROWSER': 'chrome', + }; + return globals.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); + final TestGoldenComparatorProcess process = await _processForTestFile(testUri); + process.sendCommand(imageFile, goldenKey, updateGoldens); + + final Map result = await process.getResponse(); + + if (result == null) { + return 'unknown error'; + } else { + return (result['success'] as bool) ? null : ((result['message'] as String) ?? 'does not match'); + } + } +} + +/// Represents a `flutter_tester` process started for golden comparison. Also +/// handles communication with the child process. +class TestGoldenComparatorProcess { + /// Creates a [TestGoldenComparatorProcess] backed by [process]. + TestGoldenComparatorProcess(this.process) { + // Pipe stdout and stderr to printTrace and printError. + // Also parse stdout as a stream of JSON objects. + streamIterator = StreamIterator>( + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .where((String line) { + globals.printTrace('<<< $line'); + return line.isNotEmpty && line[0] == '{'; + }) + .map(jsonDecode) + .cast>()); + + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .forEach((String line) { + globals.printError('<<< $line'); + }); + } + + final Process process; + StreamIterator> streamIterator; + + Future close() async { + await process.stdin.close(); + process.kill(); + } + + void sendCommand(File imageFile, Uri goldenKey, bool updateGoldens) { + final Object command = jsonEncode({ + 'imageFile': imageFile.path, + 'key': goldenKey.toString(), + 'update': updateGoldens, + }); + globals.printTrace('Preparing to send command: $command'); + process.stdin.writeln(command); + } + + Future> getResponse() async { + final bool available = await streamIterator.moveNext(); + assert(available); + return streamIterator.current; + } + + static String generateBootstrap(Uri testUri) { + final File testConfigFile = findTestConfigFile(globals.fs.file(testUri)); + // Generate comparator process for the file. + return ''' +// @dart=2.9 +import 'dart:convert'; // ignore: dart_convert_import +import 'dart:io'; // ignore: dart_io_import + +import 'package:flutter_test/flutter_test.dart'; + +${testConfigFile != null ? "import '${Uri.file(testConfigFile.path)}' as test_config;" : ""} + +void main() async { + LocalFileComparator comparator = LocalFileComparator(Uri.parse('$testUri')); + goldenFileComparator = comparator; + + ${testConfigFile != null ? 'test_config.testExecutable(() async {' : ''} + final commands = stdin + .transform(utf8.decoder) + .transform(const LineSplitter()) + .map(jsonDecode); + await for (final Object command in commands) { + if (command is Map) { + File imageFile = File(command['imageFile']); + Uri goldenKey = Uri.parse(command['key']); + bool update = command['update']; + + final bytes = await File(imageFile.path).readAsBytes(); + if (update) { + await goldenFileComparator.update(goldenKey, bytes); + print(jsonEncode({'success': true})); + } else { + try { + bool success = await goldenFileComparator.compare(bytes, goldenKey); + print(jsonEncode({'success': success})); + } on Exception catch (ex) { + print(jsonEncode({'success': false, 'message': '\$ex'})); + } + } + } else { + print('object type is not right'); + } + } + ${testConfigFile != null ? '});' : ''} +} + '''; + } +} 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 38ed59c7fca..8f7786da9ab 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -11,7 +11,6 @@ import 'package:async/async.dart'; import 'package:http_multi_server/http_multi_server.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config.dart'; -import 'package:path/path.dart' as p; // ignore: package_path_import import 'package:pool/pool.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; @@ -26,16 +25,16 @@ import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; +import '../base/logger.dart'; import '../build_info.dart'; import '../cache.dart'; import '../convert.dart'; import '../dart/package_map.dart'; -import '../globals.dart' as globals; import '../project.dart'; import '../web/chrome.dart'; import '../web/memory_fs.dart'; +import 'flutter_web_goldens.dart'; import 'test_compiler.dart'; -import 'test_config.dart'; class FlutterWebPlatform extends PlatformPlugin { FlutterWebPlatform._(this._server, this._config, this._root, { @@ -44,11 +43,20 @@ class FlutterWebPlatform extends PlatformPlugin { this.updateGoldens, @required this.buildInfo, @required this.webMemoryFS, - }) { + @required FileSystem fileSystem, + @required PackageConfig flutterToolPackageConfig, + @required ChromiumLauncher chromiumLauncher, + @required Logger logger, + @required Artifacts artifacts, + }) : _fileSystem = fileSystem, + _flutterToolPackageConfig = flutterToolPackageConfig, + _chromiumLauncher = chromiumLauncher, + _logger = logger, + _artifacts = artifacts { final shelf.Cascade cascade = shelf.Cascade() .add(_webSocketHandler.handler) .add(createStaticHandler( - globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'), + fileSystem.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'), serveFilesOutsidePath: true, )) .add(_handleStaticArtifact) @@ -56,12 +64,11 @@ class FlutterWebPlatform extends PlatformPlugin { .add(_wrapperHandler) .add(_handleTestRequest) .add(createStaticHandler( - p.join(p.current, 'test'), + fileSystem.path.join(fileSystem.currentDirectory.path, 'test'), serveFilesOutsidePath: true, )) .add(_packageFilesHandler); _server.mount(cascade.handler); - _testGoldenComparator = TestGoldenComparator( shellPath, () => TestCompiler(buildInfo, flutterProject), @@ -70,6 +77,11 @@ class FlutterWebPlatform extends PlatformPlugin { final WebMemoryFS webMemoryFS; final BuildInfo buildInfo; + final FileSystem _fileSystem; + final PackageConfig _flutterToolPackageConfig; + final ChromiumLauncher _chromiumLauncher; + final Logger _logger; + final Artifacts _artifacts; static Future start(String root, { FlutterProject flutterProject, @@ -78,8 +90,22 @@ class FlutterWebPlatform extends PlatformPlugin { bool pauseAfterLoad = false, @required BuildInfo buildInfo, @required WebMemoryFS webMemoryFS, + @required FileSystem fileSystem, + @required Logger logger, + @required ChromiumLauncher chromiumLauncher, + @required Artifacts artifacts, }) async { final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + fileSystem.file(fileSystem.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + '.dart_tool', + 'package_config.json', + )), + logger: logger, + ); return FlutterWebPlatform._( server, Configuration.current.change(pauseAfterLoad: pauseAfterLoad), @@ -89,81 +115,65 @@ class FlutterWebPlatform extends PlatformPlugin { updateGoldens: updateGoldens, buildInfo: buildInfo, webMemoryFS: webMemoryFS, + flutterToolPackageConfig: packageConfig, + fileSystem: fileSystem, + chromiumLauncher: chromiumLauncher, + artifacts: artifacts, + logger: logger, ); } - final Future _flutterToolsPackageMap = loadPackageConfigWithLogging( - globals.fs.file(globals.fs.path.join( - Cache.flutterRoot, - 'packages', - 'flutter_tools', - '.packages', - )), - logger: globals.logger, - ); - /// Uri of the test package. - Future get testUri async => (await _flutterToolsPackageMap)['test']?.packageUriRoot; + Uri get testUri => _flutterToolPackageConfig['test'].packageUriRoot; - /// The test runner configuration. final Configuration _config; - - @visibleForTesting - Configuration get config => _config; - - /// The underlying server. final shelf.Server _server; - - @visibleForTesting - shelf.Server get server => _server; - - /// The URL for this server. Uri get url => _server.url; /// The ahem text file. - File get ahem => globals.fs.file(globals.fs.path.join( - Cache.flutterRoot, - 'packages', - 'flutter_tools', - 'static', - 'Ahem.ttf', - )); + File get _ahem => _fileSystem.file(_fileSystem.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + 'static', + 'Ahem.ttf', + )); /// The require js binary. - File get requireJs => globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath), - 'lib', - 'dev_compiler', - 'kernel', - 'amd', - 'require.js', - )); + File get _requireJs => _fileSystem.file(_fileSystem.path.join( + _artifacts.getArtifactPath(Artifact.engineDartSdkPath), + 'lib', + 'dev_compiler', + 'kernel', + 'amd', + 'require.js', + )); /// The ddc to dart stack trace mapper. - File get stackTraceMapper => globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath), - 'lib', - 'dev_compiler', - 'web', - 'dart_stack_trace_mapper.js', - )); + File get _stackTraceMapper => _fileSystem.file(_fileSystem.path.join( + _artifacts.getArtifactPath(Artifact.engineDartSdkPath), + 'lib', + 'dev_compiler', + 'web', + 'dart_stack_trace_mapper.js', + )); /// The precompiled dart sdk. - File get dartSdk => globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd', - 'dart_sdk.js', - )); + File get _dartSdk => _fileSystem.file(_fileSystem.path.join( + _artifacts.getArtifactPath(Artifact.flutterWebSdk), + 'kernel', + 'amd', + 'dart_sdk.js', + )); /// The precompiled test javascript. - Future get testDartJs async => globals.fs.file(globals.fs.path.join( - (await testUri).toFilePath(), + File get _testDartJs => _fileSystem.file(_fileSystem.path.join( + testUri.toFilePath(), 'dart.js', )); - Future get testHostDartJs async => globals.fs.file(globals.fs.path.join( - (await testUri).toFilePath(), + File get _testHostDartJs => _fileSystem.file(_fileSystem.path.join( + testUri.toFilePath(), 'src', 'runner', 'browser', @@ -174,15 +184,15 @@ class FlutterWebPlatform extends PlatformPlugin { Future _handleTestRequest(shelf.Request request) async { if (request.url.path.endsWith('.dart.browser_test.dart.js')) { final String leadingPath = request.url.path.split('.browser_test.dart.js')[0]; - final String generatedFile = globals.fs.path.split(leadingPath).join('_') + '.bootstrap.js'; + final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.bootstrap.js'; return shelf.Response.ok(bootstrapFileContents('/' + generatedFile, 'require.js', 'dart_stack_trace_mapper.js'), headers: { HttpHeaders.contentTypeHeader: 'text/javascript', }); } if (request.url.path.endsWith('.dart.bootstrap.js')) { final String leadingPath = request.url.path.split('.dart.bootstrap.js')[0]; - final String generatedFile = globals.fs.path.split(leadingPath).join('_') + '.dart.test.dart.js'; - return shelf.Response.ok(generatedActualMain(globals.fs.path.basename(leadingPath) + '.dart.bootstrap', '/' + generatedFile), headers: { + final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js'; + return shelf.Response.ok(generatedActualMain(_fileSystem.path.basename(leadingPath) + '.dart.bootstrap', '/' + generatedFile), headers: { HttpHeaders.contentTypeHeader: 'text/javascript', }); } @@ -203,30 +213,30 @@ class FlutterWebPlatform extends PlatformPlugin { Future _handleStaticArtifact(shelf.Request request) async { if (request.requestedUri.path.contains('require.js')) { return shelf.Response.ok( - requireJs.openRead(), + _requireJs.openRead(), headers: {'Content-Type': 'text/javascript'}, ); } else if (request.requestedUri.path.contains('ahem.ttf')) { - return shelf.Response.ok(ahem.openRead()); + return shelf.Response.ok(_ahem.openRead()); } else if (request.requestedUri.path.contains('dart_sdk.js')) { return shelf.Response.ok( - dartSdk.openRead(), + _dartSdk.openRead(), headers: {'Content-Type': 'text/javascript'}, ); } else if (request.requestedUri.path .contains('dart_stack_trace_mapper.js')) { return shelf.Response.ok( - stackTraceMapper.openRead(), + _stackTraceMapper.openRead(), headers: {'Content-Type': 'text/javascript'}, ); } else if (request.requestedUri.path.contains('static/dart.js')) { return shelf.Response.ok( - (await testDartJs).openRead(), + _testDartJs.openRead(), headers: {'Content-Type': 'text/javascript'}, ); } else if (request.requestedUri.path.contains('host.dart.js')) { return shelf.Response.ok( - (await testHostDartJs).openRead(), + _testHostDartJs.openRead(), headers: {'Content-Type': 'text/javascript'}, ); } else { @@ -241,8 +251,8 @@ class FlutterWebPlatform extends PlatformPlugin { pathSegments: request.requestedUri.pathSegments.skip(1), )); if (fileUri != null) { - final String dirname = p.dirname(fileUri.toFilePath()); - final String basename = p.basename(fileUri.toFilePath()); + final String dirname = _fileSystem.path.dirname(fileUri.toFilePath()); + final String basename = _fileSystem.path.basename(fileUri.toFilePath()); final shelf.Handler handler = createStaticHandler(dirname); final shelf.Request modifiedRequest = shelf.Request( request.method, @@ -293,10 +303,10 @@ class FlutterWebPlatform extends PlatformPlugin { }); bytes = base64.decode(response.result['data'] as String); } on WipError catch (ex) { - globals.printError('Caught WIPError: $ex'); + _logger.printError('Caught WIPError: $ex'); return shelf.Response.ok('WIP error: $ex'); } on FormatException catch (ex) { - globals.printError('Caught FormatException: $ex'); + _logger.printError('Caught FormatException: $ex'); return shelf.Response.ok('Caught exception: $ex'); } @@ -321,10 +331,10 @@ class FlutterWebPlatform extends PlatformPlugin { // A handler that serves wrapper files used to bootstrap tests. shelf.Response _wrapperHandler(shelf.Request request) { - final String path = globals.fs.path.fromUri(request.url); + final String path = _fileSystem.path.fromUri(request.url); if (path.endsWith('.html')) { - final String test = globals.fs.path.withoutExtension(path) + '.dart'; - final String scriptBase = htmlEscape.convert(globals.fs.path.basename(test)); + final String test = _fileSystem.path.withoutExtension(path) + '.dart'; + final String scriptBase = htmlEscape.convert(_fileSystem.path.basename(test)); final String link = ''; return shelf.Response.ok(''' @@ -369,10 +379,9 @@ class FlutterWebPlatform extends PlatformPlugin { throw StateError('Load called on a closed FlutterWebPlatform'); } - final Uri suiteUrl = url.resolveUri(globals.fs.path.toUri(globals.fs.path.withoutExtension( - globals.fs.path.relative(path, from: globals.fs.path.join(_root, 'test'))) + - '.html')); - final String relativePath = globals.fs.path.relative(globals.fs.path.normalize(path), from: globals.fs.currentDirectory.path); + final Uri suiteUrl = url.resolveUri(_fileSystem.path.toUri(_fileSystem.path.withoutExtension( + _fileSystem.path.relative(path, from: _fileSystem.path.join(_root, 'test'))) + '.html')); + final String relativePath = _fileSystem.path.relative(_fileSystem.path.normalize(path), from: _fileSystem.currentDirectory.path); final RunnerSuite suite = await _browserManager.load(relativePath, suiteUrl, suiteConfig, message, onDone: () async { await _browserManager.close(); _browserManager = null; @@ -406,9 +415,10 @@ class FlutterWebPlatform extends PlatformPlugin { 'debug': _config.pauseAfterLoad.toString(), }); - globals.printTrace('Serving tests at $hostUrl'); + _logger.printTrace('Serving tests at $hostUrl'); return BrowserManager.start( + _chromiumLauncher, browser, hostUrl, completer.future, @@ -459,7 +469,7 @@ class OneOffHandler { /// Dispatches [request] to the appropriate handler. FutureOr _onRequest(shelf.Request request) { - final List components = p.url.split(request.url.path); + final List components = request.url.path.split('/'); if (components.isEmpty) { return shelf.Response.notFound(null); } @@ -513,10 +523,6 @@ class BrowserManager { /// The browser instance that this is connected to via [_channel]. final Chromium _browser; - - // TODO(nweiz): Consider removing the duplication between this and - // [_browser.name]. - /// The [Runtime] for [_browser]. final Runtime _runtime; /// The channel used to communicate with the browser. @@ -574,23 +580,14 @@ class BrowserManager { /// Returns the browser manager, or throws an [ApplicationException] if a /// connection fails to be established. static Future start( + ChromiumLauncher chromiumLauncher, Runtime runtime, Uri url, Future future, { bool debug = false, bool headless = true, }) async { - final ChromiumLauncher chromiumLauncher = ChromiumLauncher( - browserFinder: findChromeExecutable, - fileSystem: globals.fs, - operatingSystemUtils: globals.os, - platform: globals.platform, - processManager: globals.processManager, - logger: globals.logger, - ); - final Chromium chrome = - await chromiumLauncher.launch(url.toString(), headless: headless); - + final Chromium chrome = await chromiumLauncher.launch(url.toString(), headless: headless); final Completer completer = Completer(); unawaited(chrome.onExit.then((int browserExitCode) { @@ -773,186 +770,6 @@ class _BrowserEnvironment implements Environment { CancelableOperation displayPause() => _manager._displayPause(); } -/// Helper class to start golden file comparison in a separate process. -/// -/// 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 { - /// Creates a [TestGoldenComparator] instance. - TestGoldenComparator(this.shellPath, this.compilerFactory) - : tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_web_platform.'); - - final String shellPath; - final Directory tempDir; - final TestCompiler Function() compilerFactory; - - TestCompiler _compiler; - TestGoldenComparatorProcess _previousComparator; - Uri _previousTestUri; - - Future close() async { - tempDir.deleteSync(recursive: true); - await _compiler?.dispose(); - await _previousComparator?.close(); - } - - /// Start golden comparator in a separate process. Start one file per test file - /// to reduce the overhead of starting `flutter_tester`. - Future _processForTestFile(Uri testUri) async { - if (testUri == _previousTestUri) { - return _previousComparator; - } - - final String bootstrap = TestGoldenComparatorProcess.generateBootstrap(testUri); - final Process process = await _startProcess(bootstrap); - unawaited(_previousComparator?.close()); - _previousComparator = TestGoldenComparatorProcess(process); - _previousTestUri = testUri; - - return _previousComparator; - } - - 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'); - await listenerFile.writeAsString(testBootstrap); - - // Lazily create the compiler - _compiler = _compiler ?? compilerFactory(); - final String output = await _compiler.compile(listenerFile.uri); - final List command = [ - shellPath, - '--disable-observatory', - '--non-interactive', - '--packages=${globals.fs.path.join('.dart_tool', 'package_config.json')}', - output, - ]; - - final Map environment = { - // Chrome is the only supported browser currently. - 'FLUTTER_TEST_BROWSER': 'chrome', - }; - return globals.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); - - final TestGoldenComparatorProcess process = await _processForTestFile(testUri); - process.sendCommand(imageFile, goldenKey, updateGoldens); - - final Map result = await process.getResponse(); - - if (result == null) { - return 'unknown error'; - } else { - return (result['success'] as bool) ? null : ((result['message'] as String) ?? 'does not match'); - } - } -} - -/// Represents a `flutter_tester` process started for golden comparison. Also -/// handles communication with the child process. -class TestGoldenComparatorProcess { - /// Creates a [TestGoldenComparatorProcess] backed by [process]. - TestGoldenComparatorProcess(this.process) { - // Pipe stdout and stderr to printTrace and printError. - // Also parse stdout as a stream of JSON objects. - streamIterator = StreamIterator>( - process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .where((String line) { - globals.printTrace('<<< $line'); - return line.isNotEmpty && line[0] == '{'; - }) - .map(jsonDecode) - .cast>()); - - process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .forEach((String line) { - globals.printError('<<< $line'); - }); - } - - final Process process; - StreamIterator> streamIterator; - - Future close() async { - await process.stdin.close(); - process.kill(); - } - - void sendCommand(File imageFile, Uri goldenKey, bool updateGoldens) { - final Object command = jsonEncode({ - 'imageFile': imageFile.path, - 'key': goldenKey.toString(), - 'update': updateGoldens, - }); - globals.printTrace('Preparing to send command: $command'); - process.stdin.writeln(command); - } - - Future> getResponse() async { - final bool available = await streamIterator.moveNext(); - assert(available); - return streamIterator.current; - } - - static String generateBootstrap(Uri testUri) { - final File testConfigFile = findTestConfigFile(globals.fs.file(testUri)); - // Generate comparator process for the file. - return ''' -// @dart=2.9 -import 'dart:convert'; // ignore: dart_convert_import -import 'dart:io'; // ignore: dart_io_import - -import 'package:flutter_test/flutter_test.dart'; - -${testConfigFile != null ? "import '${Uri.file(testConfigFile.path)}' as test_config;" : ""} - -void main() async { - LocalFileComparator comparator = LocalFileComparator(Uri.parse('$testUri')); - goldenFileComparator = comparator; - - ${testConfigFile != null ? 'test_config.testExecutable(() async {' : ''} - final commands = stdin - .transform(utf8.decoder) - .transform(const LineSplitter()) - .map(jsonDecode); - await for (final Object command in commands) { - if (command is Map) { - File imageFile = File(command['imageFile']); - Uri goldenKey = Uri.parse(command['key']); - bool update = command['update']; - - final bytes = await File(imageFile.path).readAsBytes(); - if (update) { - await goldenFileComparator.update(goldenKey, bytes); - print(jsonEncode({'success': true})); - } else { - try { - bool success = await goldenFileComparator.compare(bytes, goldenKey); - print(jsonEncode({'success': success})); - } on Exception catch (ex) { - print(jsonEncode({'success': false, 'message': '\$ex'})); - } - } - } else { - print('object type is not right'); - } - } - ${testConfigFile != null ? '});' : ''} -} - '''; - } -} - String bootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) { return ''' (function() { diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index 7ec323a4437..79c18357d4d 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -11,6 +11,7 @@ import '../base/io.dart'; import '../build_info.dart'; import '../globals.dart' as globals; import '../project.dart'; +import '../web/chrome.dart'; import '../web/compile.dart'; import '../web/memory_fs.dart'; import 'flutter_platform.dart' as loader; @@ -139,6 +140,8 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { testWrapper.registerPlatformPlugin( [Runtime.chrome], () { + // TODO(jonahwilliams): refactor this into a factory that handles + // providing dependencies. return FlutterWebPlatform.start( flutterProject.directory.path, updateGoldens: updateGoldens, @@ -147,6 +150,17 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { pauseAfterLoad: startPaused, buildInfo: buildInfo, webMemoryFS: result, + logger: globals.logger, + fileSystem: globals.fs, + artifacts: globals.artifacts, + chromiumLauncher: ChromiumLauncher( + fileSystem: globals.fs, + platform: globals.platform, + processManager: globals.processManager, + operatingSystemUtils: globals.os, + browserFinder: findChromeExecutable, + logger: globals.logger, + ), ); }, ); diff --git a/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart b/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart index 127a6357155..fe9a70426b1 100644 --- a/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart +++ b/packages/flutter_tools/test/general.shard/web/golden_comparator_process_test.dart @@ -5,8 +5,8 @@ import 'dart:convert'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/test/flutter_web_platform.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/test/flutter_web_goldens.dart'; import '../../src/common.dart'; import '../../src/mocks.dart'; 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 index b962c641da9..76681f7de0d 100644 --- a/packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart +++ b/packages/flutter_tools/test/general.shard/web/golden_comparator_test.dart @@ -5,7 +5,7 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:flutter_tools/src/test/flutter_web_platform.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/globals.dart' as globals; import 'package:mockito/mockito.dart';