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 {
///
/// 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.
......
......@@ -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<bool> 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', () {
......
......@@ -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<Null> _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;
'''
);
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 generateTestBootstrap(
testUrl: testUrl,
testConfigFile: testConfigFile,
host: host,
updateGoldens: updateGoldens,
);
return buffer.toString();
}
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