Unverified Commit be09a200 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

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).
parent d820e5f3
...@@ -125,8 +125,7 @@ class LocalFileComparator implements GoldenFileComparator { ...@@ -125,8 +125,7 @@ class LocalFileComparator implements GoldenFileComparator {
/// ///
/// The [testFile] URI must represent a file. /// The [testFile] URI must represent a file.
LocalFileComparator(Uri testFile, {path.Style pathStyle}) LocalFileComparator(Uri testFile, {path.Style pathStyle})
: assert(testFile.scheme == 'file'), : basedir = _getBasedir(testFile, pathStyle),
basedir = _getBasedir(testFile, pathStyle),
_path = _getPath(pathStyle); _path = _getPath(pathStyle);
static path.Context _getPath(path.Style style) { static path.Context _getPath(path.Style style) {
...@@ -135,7 +134,9 @@ class LocalFileComparator implements GoldenFileComparator { ...@@ -135,7 +134,9 @@ class LocalFileComparator implements GoldenFileComparator {
static Uri _getBasedir(Uri testFile, path.Style pathStyle) { static Uri _getBasedir(Uri testFile, path.Style pathStyle) {
final path.Context context = _getPath(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. /// The directory in which the test was loaded.
......
...@@ -78,6 +78,11 @@ void main() { ...@@ -78,6 +78,11 @@ void main() {
expect(comparator.basedir, fs.directory(fix('/foo/bar/')).uri); 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', () { group('compare', () {
Future<bool> doComparison([String golden = 'golden.png']) { Future<bool> doComparison([String golden = 'golden.png']) {
final Uri uri = fs.file(fix(golden)).uri; final Uri uri = fs.file(fix(golden)).uri;
...@@ -101,6 +106,28 @@ void main() { ...@@ -101,6 +106,28 @@ void main() {
final bool success = await doComparison('sub/foo.png'); final bool success = await doComparison('sub/foo.png');
expect(success, isTrue); 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', () { group('fails', () {
......
...@@ -95,6 +95,95 @@ void installHook({ ...@@ -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 _InitialResult { crashed, timedOut, connected }
enum _TestResult { crashed, harnessBailed, testBailed } enum _TestResult { crashed, harnessBailed, testBailed }
typedef Future<Null> _Finalizer(); typedef Future<Null> _Finalizer();
...@@ -581,7 +670,6 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -581,7 +670,6 @@ class _FlutterPlatform extends PlatformPlugin {
listenerFile.createSync(); listenerFile.createSync();
listenerFile.writeAsStringSync(_generateTestMain( listenerFile.writeAsStringSync(_generateTestMain(
testUrl: fs.path.toUri(fs.path.absolute(testPath)), testUrl: fs.path.toUri(fs.path.absolute(testPath)),
encodedWebsocketUrl: Uri.encodeComponent(_getWebSocketUrl()),
)); ));
return listenerFile.path; return listenerFile.path;
} }
...@@ -621,15 +709,8 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -621,15 +709,8 @@ class _FlutterPlatform extends PlatformPlugin {
return tempBundleDirectory.path; return tempBundleDirectory.path;
} }
String _getWebSocketUrl() {
return host.type == InternetAddressType.IP_V4 // ignore: deprecated_member_use
? 'ws://${host.address}'
: 'ws://[${host.address}]';
}
String _generateTestMain({ String _generateTestMain({
Uri testUrl, Uri testUrl,
String encodedWebsocketUrl,
}) { }) {
assert(testUrl.scheme == 'file'); assert(testUrl.scheme == 'file');
File testConfigFile; File testConfigFile;
...@@ -648,63 +729,12 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -648,63 +729,12 @@ class _FlutterPlatform extends PlatformPlugin {
} }
directory = directory.parent; directory = directory.parent;
} }
final StringBuffer buffer = new StringBuffer(); return generateTestBootstrap(
buffer.write(''' testUrl: testUrl,
import 'dart:convert'; testConfigFile: testConfigFile,
import 'dart:io'; // ignore: dart_io_import host: host,
updateGoldens: updateGoldens,
// 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();
} }
File _cachedFontConfig; File _cachedFontConfig;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment