From be09a200ee19024db34257653b067dcf5b998dc2 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Fri, 4 May 2018 10:31:53 -0700 Subject: [PATCH] Expose generateTestBootstrap() as public API in test harness (#17290) This will allow external tools that wrap our test harness to share the code that generates the test bootstrap. This change exposed an issue whereby the LocalGoldenFileComparator was being too strict in its URI handling, so this changes relaxes that constraint as well (and adds associated tests). --- packages/flutter_test/lib/src/goldens.dart | 7 +- packages/flutter_test/test/goldens_test.dart | 27 +++ .../lib/src/test/flutter_platform.dart | 158 +++++++++++------- 3 files changed, 125 insertions(+), 67 deletions(-) diff --git a/packages/flutter_test/lib/src/goldens.dart b/packages/flutter_test/lib/src/goldens.dart index 537cca03343..8a141a9e2c0 100644 --- a/packages/flutter_test/lib/src/goldens.dart +++ b/packages/flutter_test/lib/src/goldens.dart @@ -125,8 +125,7 @@ class LocalFileComparator implements GoldenFileComparator { /// /// The [testFile] URI must represent a file. LocalFileComparator(Uri testFile, {path.Style pathStyle}) - : assert(testFile.scheme == 'file'), - basedir = _getBasedir(testFile, pathStyle), + : basedir = _getBasedir(testFile, pathStyle), _path = _getPath(pathStyle); static path.Context _getPath(path.Style style) { @@ -135,7 +134,9 @@ class LocalFileComparator implements GoldenFileComparator { static Uri _getBasedir(Uri testFile, path.Style pathStyle) { final path.Context context = _getPath(pathStyle); - return context.toUri(context.dirname(context.fromUri(testFile)) + context.separator); + final String testFilePath = context.fromUri(testFile); + final String testDirectoryPath = context.dirname(testFilePath); + return context.toUri(testDirectoryPath + context.separator); } /// The directory in which the test was loaded. diff --git a/packages/flutter_test/test/goldens_test.dart b/packages/flutter_test/test/goldens_test.dart index b84ed0921a6..e93945b1e60 100644 --- a/packages/flutter_test/test/goldens_test.dart +++ b/packages/flutter_test/test/goldens_test.dart @@ -78,6 +78,11 @@ void main() { expect(comparator.basedir, fs.directory(fix('/foo/bar/')).uri); }); + test('can be instantiated with uri that represents file in same folder', () { + comparator = new LocalFileComparator(Uri.parse('foo_test.dart'), pathStyle: fs.path.style); + expect(comparator.basedir, Uri.parse('./')); + }); + group('compare', () { Future doComparison([String golden = 'golden.png']) { final Uri uri = fs.file(fix(golden)).uri; @@ -101,6 +106,28 @@ void main() { final bool success = await doComparison('sub/foo.png'); expect(success, isTrue); }); + + group('when comparator instantiated with uri that represents file in same folder', () { + test('and golden file is in same folder as test', () async { + fs.file(fix('/foo/bar/golden.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(_kExpectedBytes); + fs.currentDirectory = fix('/foo/bar'); + comparator = new LocalFileComparator(Uri.parse('local_test.dart'), pathStyle: fs.path.style); + final bool success = await doComparison('golden.png'); + expect(success, isTrue); + }); + + test('and golden file is in subfolder of test', () async { + fs.file(fix('/foo/bar/baz/golden.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(_kExpectedBytes); + fs.currentDirectory = fix('/foo/bar'); + comparator = new LocalFileComparator(Uri.parse('local_test.dart'), pathStyle: fs.path.style); + final bool success = await doComparison('baz/golden.png'); + expect(success, isTrue); + }); + }); }); group('fails', () { diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index b2e68a04c2f..bc2c8c127a7 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -95,6 +95,95 @@ void installHook({ ); } +/// Generates the bootstrap entry point script that will be used to launch an +/// individual test file. +/// +/// The [testUrl] argument specifies the path to the test file that is being +/// launched. +/// +/// The [host] argument specifies the address at which the test harness is +/// running. +/// +/// If [testConfigFile] is specified, it must follow the conventions of test +/// configuration files as outlined in the [flutter_test] library. By default, +/// the test file will be launched directly. +/// +/// The [updateGoldens] argument will set the [autoUpdateGoldens] global +/// variable in the [flutter_test] package before invoking the test. +String generateTestBootstrap({ + @required Uri testUrl, + @required InternetAddress host, + File testConfigFile, + bool updateGoldens: false, +}) { + assert(testUrl != null); + assert(host != null); + assert(updateGoldens != null); + + final String websocketUrl = host.type == InternetAddressType.IP_V4 // ignore: deprecated_member_use + ? 'ws://${host.address}' + : 'ws://[${host.address}]'; + final String encodedWebsocketUrl = Uri.encodeComponent(websocketUrl); + + final StringBuffer buffer = new StringBuffer(); + buffer.write(''' +import 'dart:convert'; +import 'dart:io'; // ignore: dart_io_import + +// We import this library first in order to trigger an import error for +// package:test (rather than package:stream_channel) when the developer forgets +// to add a dependency on package:test. +import 'package:test/src/runner/plugin/remote_platform_helpers.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/src/runner/vm/catch_isolate_errors.dart'; + +import '$testUrl' as test; +''' + ); + if (testConfigFile != null) { + buffer.write(''' +import '${new Uri.file(testConfigFile.path)}' as test_config; +''' + ); + } + buffer.write(''' + +void main() { + print('$_kStartTimeoutTimerMessage'); + String serverPort = Platform.environment['SERVER_PORT']; + String server = Uri.decodeComponent('$encodedWebsocketUrl:\$serverPort'); + StreamChannel channel = serializeSuite(() { + catchIsolateErrors(); + goldenFileComparator = new LocalFileComparator(Uri.parse('$testUrl')); + autoUpdateGoldenFiles = $updateGoldens; +''' + ); + if (testConfigFile != null) { + buffer.write(''' + return () => test_config.main(test.main); +'''); + } else { + buffer.write(''' + return test.main; +'''); + } + buffer.write(''' + }); + WebSocket.connect(server).then((WebSocket socket) { + socket.map((dynamic x) { + assert(x is String); + return json.decode(x); + }).pipe(channel.sink); + socket.addStream(channel.stream.map(json.encode)); + }); +} +''' + ); + return buffer.toString(); +} + enum _InitialResult { crashed, timedOut, connected } enum _TestResult { crashed, harnessBailed, testBailed } typedef Future _Finalizer(); @@ -581,7 +670,6 @@ class _FlutterPlatform extends PlatformPlugin { listenerFile.createSync(); listenerFile.writeAsStringSync(_generateTestMain( testUrl: fs.path.toUri(fs.path.absolute(testPath)), - encodedWebsocketUrl: Uri.encodeComponent(_getWebSocketUrl()), )); return listenerFile.path; } @@ -621,15 +709,8 @@ class _FlutterPlatform extends PlatformPlugin { return tempBundleDirectory.path; } - String _getWebSocketUrl() { - return host.type == InternetAddressType.IP_V4 // ignore: deprecated_member_use - ? 'ws://${host.address}' - : 'ws://[${host.address}]'; - } - String _generateTestMain({ Uri testUrl, - String encodedWebsocketUrl, }) { assert(testUrl.scheme == 'file'); File testConfigFile; @@ -648,63 +729,12 @@ class _FlutterPlatform extends PlatformPlugin { } directory = directory.parent; } - final StringBuffer buffer = new StringBuffer(); - buffer.write(''' -import 'dart:convert'; -import 'dart:io'; // ignore: dart_io_import - -// We import this library first in order to trigger an import error for -// package:test (rather than package:stream_channel) when the developer forgets -// to add a dependency on package:test. -import 'package:test/src/runner/plugin/remote_platform_helpers.dart'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_channel/stream_channel.dart'; -import 'package:test/src/runner/vm/catch_isolate_errors.dart'; - -import '$testUrl' as test; -''' + return generateTestBootstrap( + testUrl: testUrl, + testConfigFile: testConfigFile, + host: host, + updateGoldens: updateGoldens, ); - if (testConfigFile != null) { - buffer.write(''' -import '${new Uri.file(testConfigFile.path)}' as test_config; -''' - ); - } - buffer.write(''' - -void main() { - print('$_kStartTimeoutTimerMessage'); - String serverPort = Platform.environment['SERVER_PORT']; - String server = Uri.decodeComponent('$encodedWebsocketUrl:\$serverPort'); - StreamChannel channel = serializeSuite(() { - catchIsolateErrors(); - goldenFileComparator = new LocalFileComparator(Uri.parse('$testUrl')); - autoUpdateGoldenFiles = $updateGoldens; -''' - ); - if (testConfigFile != null) { - buffer.write(''' - return () => test_config.main(test.main); -'''); - } else { - buffer.write(''' - return test.main; -'''); - } - buffer.write(''' - }); - WebSocket.connect(server).then((WebSocket socket) { - socket.map((dynamic x) { - assert(x is String); - return json.decode(x); - }).pipe(channel.sink); - socket.addStream(channel.stream.map(json.encode)); - }); -} -''' - ); - return buffer.toString(); } File _cachedFontConfig;