Unverified Commit c07661cb authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] share bootstrap module between run and test (#70903)

parent 92052857
...@@ -31,6 +31,7 @@ import '../cache.dart'; ...@@ -31,6 +31,7 @@ import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../project.dart'; import '../project.dart';
import '../web/bootstrap.dart';
import '../web/chrome.dart'; import '../web/chrome.dart';
import '../web/compile.dart'; import '../web/compile.dart';
import '../web/memory_fs.dart'; import '../web/memory_fs.dart';
...@@ -42,6 +43,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -42,6 +43,7 @@ class FlutterWebPlatform extends PlatformPlugin {
FlutterProject flutterProject, FlutterProject flutterProject,
String shellPath, String shellPath,
this.updateGoldens, this.updateGoldens,
this.nullAssertions,
@required this.buildInfo, @required this.buildInfo,
@required this.webMemoryFS, @required this.webMemoryFS,
@required FileSystem fileSystem, @required FileSystem fileSystem,
...@@ -83,12 +85,26 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -83,12 +85,26 @@ class FlutterWebPlatform extends PlatformPlugin {
final ChromiumLauncher _chromiumLauncher; final ChromiumLauncher _chromiumLauncher;
final Logger _logger; final Logger _logger;
final Artifacts _artifacts; final Artifacts _artifacts;
final bool updateGoldens;
final bool nullAssertions;
final OneOffHandler _webSocketHandler = OneOffHandler();
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
final String _root;
/// Allows only one test suite (typically one test file) to be loaded and run
/// at any given point in time. Loading more than one file at a time is known
/// to lead to flaky tests.
final Pool _suiteLock = Pool(1);
BrowserManager _browserManager;
TestGoldenComparator _testGoldenComparator;
static Future<FlutterWebPlatform> start(String root, { static Future<FlutterWebPlatform> start(String root, {
FlutterProject flutterProject, FlutterProject flutterProject,
String shellPath, String shellPath,
bool updateGoldens = false, bool updateGoldens = false,
bool pauseAfterLoad = false, bool pauseAfterLoad = false,
bool nullAssertions = false,
@required BuildInfo buildInfo, @required BuildInfo buildInfo,
@required WebMemoryFS webMemoryFS, @required WebMemoryFS webMemoryFS,
@required FileSystem fileSystem, @required FileSystem fileSystem,
...@@ -121,9 +137,12 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -121,9 +137,12 @@ class FlutterWebPlatform extends PlatformPlugin {
chromiumLauncher: chromiumLauncher, chromiumLauncher: chromiumLauncher,
artifacts: artifacts, artifacts: artifacts,
logger: logger, logger: logger,
nullAssertions: nullAssertions,
); );
} }
bool get _closed => _closeMemo.hasRun;
/// Uri of the test package. /// Uri of the test package.
Uri get testUri => _flutterToolPackageConfig['test'].packageUriRoot; Uri get testUri => _flutterToolPackageConfig['test'].packageUriRoot;
...@@ -186,14 +205,18 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -186,14 +205,18 @@ class FlutterWebPlatform extends PlatformPlugin {
if (request.url.path.endsWith('.dart.browser_test.dart.js')) { if (request.url.path.endsWith('.dart.browser_test.dart.js')) {
final String leadingPath = request.url.path.split('.browser_test.dart.js')[0]; final String leadingPath = request.url.path.split('.browser_test.dart.js')[0];
final String generatedFile = _fileSystem.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: <String, String>{ return shelf.Response.ok(generateTestBootstrapFileContents('/' + generatedFile, 'require.js', 'dart_stack_trace_mapper.js'), headers: <String, String>{
HttpHeaders.contentTypeHeader: 'text/javascript', HttpHeaders.contentTypeHeader: 'text/javascript',
}); });
} }
if (request.url.path.endsWith('.dart.bootstrap.js')) { if (request.url.path.endsWith('.dart.bootstrap.js')) {
final String leadingPath = request.url.path.split('.dart.bootstrap.js')[0]; final String leadingPath = request.url.path.split('.dart.bootstrap.js')[0];
final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js'; 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: <String, String>{ return shelf.Response.ok(generateMainModule(
nullAssertions: nullAssertions,
bootstrapModule: _fileSystem.path.basename(leadingPath) + '.dart.bootstrap',
entrypoint: '/' + generatedFile
), headers: <String, String>{
HttpHeaders.contentTypeHeader: 'text/javascript', HttpHeaders.contentTypeHeader: 'text/javascript',
}); });
} }
...@@ -276,9 +299,6 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -276,9 +299,6 @@ class FlutterWebPlatform extends PlatformPlugin {
return shelf.Response.notFound('Not Found'); return shelf.Response.notFound('Not Found');
} }
final bool updateGoldens;
TestGoldenComparator _testGoldenComparator;
Future<shelf.Response> _goldenFileHandler(shelf.Request request) async { Future<shelf.Response> _goldenFileHandler(shelf.Request request) async {
if (request.url.path.contains('flutter_goldens')) { if (request.url.path.contains('flutter_goldens')) {
final Map<String, Object> body = json.decode(await request.readAsString()) as Map<String, Object>; final Map<String, Object> body = json.decode(await request.readAsString()) as Map<String, Object>;
...@@ -327,14 +347,6 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -327,14 +347,6 @@ class FlutterWebPlatform extends PlatformPlugin {
} }
} }
final OneOffHandler _webSocketHandler = OneOffHandler();
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
final String _root;
bool get _closed => _closeMemo.hasRun;
BrowserManager _browserManager;
// A handler that serves wrapper files used to bootstrap tests. // A handler that serves wrapper files used to bootstrap tests.
shelf.Response _wrapperHandler(shelf.Request request) { shelf.Response _wrapperHandler(shelf.Request request) {
final String path = _fileSystem.path.fromUri(request.url); final String path = _fileSystem.path.fromUri(request.url);
...@@ -356,11 +368,6 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -356,11 +368,6 @@ class FlutterWebPlatform extends PlatformPlugin {
return shelf.Response.notFound('Not found.'); return shelf.Response.notFound('Not found.');
} }
/// Allows only one test suite (typically one test file) to be loaded and run
/// at any given point in time. Loading more than one file at a time is known
/// to lead to flaky tests.
final Pool _suiteLock = Pool(1);
@override @override
Future<RunnerSuite> load( Future<RunnerSuite> load(
String path, String path,
...@@ -775,68 +782,3 @@ class _BrowserEnvironment implements Environment { ...@@ -775,68 +782,3 @@ class _BrowserEnvironment implements Environment {
@override @override
CancelableOperation<dynamic> displayPause() => _manager._displayPause(); CancelableOperation<dynamic> displayPause() => _manager._displayPause();
} }
String bootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) {
return '''
(function() {
if (typeof document != 'undefined') {
var el = document.createElement("script");
el.defer = true;
el.async = false;
el.src = '$mapperUrl';
document.head.appendChild(el);
el = document.createElement("script");
el.defer = true;
el.async = false;
el.src = '$requireUrl';
el.setAttribute("data-main", '$mainUri');
document.head.appendChild(el);
} else {
importScripts('$mapperUrl', '$requireUrl');
require.config({
baseUrl: baseUrl,
});
window = self;
require(['$mainUri']);
}
})();
''';
}
String generatedActualMain(String bootstrapUrl, String mainUri) {
return '''
/* ENTRYPOINT_EXTENTION_MARKER */
// Create the main module loaded below.
define("$bootstrapUrl", ["$mainUri", "dart_sdk"], function(app, dart_sdk) {
dart_sdk.dart.setStartAsyncSynchronously(true);
dart_sdk._debugger.registerDevtoolsFormatter();
if (false) {
dart_sdk.dart.nonNullAsserts(true);
}
// See the generateMainModule doc comment.
var child = {};
child.main = app[Object.keys(app)[0]].main;
/* MAIN_EXTENSION_MARKER */
child.main();
window.\$dartLoader = {};
window.\$dartLoader.rootDirectories = [];
if (window.\$requireLoader) {
window.\$requireLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries;
}
if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) {
window.\$dartStackTraceUtility.ready = true;
let dart = dart_sdk.dart;
window.\$dartStackTraceUtility.setSourceMapProvider(function(url) {
var baseUrl = window.location.protocol + '//' + window.location.host;
url = url.replace(baseUrl + '/', '');
if (url == 'dart_sdk.js') {
return dart.getSourceMap('dart_sdk');
}
url = url.replace(".lib.js", "");
return dart.getSourceMap(url);
});
}
});
''';
}
...@@ -148,6 +148,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...@@ -148,6 +148,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
shellPath: shellPath, shellPath: shellPath,
flutterProject: flutterProject, flutterProject: flutterProject,
pauseAfterLoad: startPaused, pauseAfterLoad: startPaused,
nullAssertions: nullAssertions,
buildInfo: buildInfo, buildInfo: buildInfo,
webMemoryFS: result, webMemoryFS: result,
logger: globals.logger, logger: globals.logger,
......
...@@ -41,6 +41,8 @@ document.head.appendChild(requireEl); ...@@ -41,6 +41,8 @@ document.head.appendChild(requireEl);
/// Generate a synthetic main module which captures the application's main /// Generate a synthetic main module which captures the application's main
/// method. /// method.
/// ///
/// If a [bootstrapModule] name is not provided, defaults to 'main_module.bootstrap'.
///
/// RE: Object.keys usage in app.main: /// RE: Object.keys usage in app.main:
/// This attaches the main entrypoint and hot reload functionality to the window. /// This attaches the main entrypoint and hot reload functionality to the window.
/// The app module will have a single property which contains the actual application /// The app module will have a single property which contains the actual application
...@@ -51,12 +53,13 @@ document.head.appendChild(requireEl); ...@@ -51,12 +53,13 @@ document.head.appendChild(requireEl);
String generateMainModule({ String generateMainModule({
@required String entrypoint, @required String entrypoint,
@required bool nullAssertions, @required bool nullAssertions,
String bootstrapModule = 'main_module.bootstrap',
}) { }) {
// TODO(jonahwilliams): fix typo in dwds and update. // TODO(jonahwilliams): fix typo in dwds and update.
return ''' return '''
/* ENTRYPOINT_EXTENTION_MARKER */ /* ENTRYPOINT_EXTENTION_MARKER */
// Create the main module loaded below. // Create the main module loaded below.
define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
dart_sdk.dart.setStartAsyncSynchronously(true); dart_sdk.dart.setStartAsyncSynchronously(true);
dart_sdk._debugger.registerDevtoolsFormatter(); dart_sdk._debugger.registerDevtoolsFormatter();
if ($nullAssertions) { if ($nullAssertions) {
...@@ -91,3 +94,32 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_ ...@@ -91,3 +94,32 @@ define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_
}); });
'''; ''';
} }
/// Generate the unit test bootstrap file.
String generateTestBootstrapFileContents(String mainUri, String requireUrl, String mapperUrl) {
return '''
(function() {
if (typeof document != 'undefined') {
var el = document.createElement("script");
el.defer = true;
el.async = false;
el.src = '$mapperUrl';
document.head.appendChild(el);
el = document.createElement("script");
el.defer = true;
el.async = false;
el.src = '$requireUrl';
el.setAttribute("data-main", '$mainUri');
document.head.appendChild(el);
} else {
importScripts('$mapperUrl', '$requireUrl');
require.config({
baseUrl: baseUrl,
});
window = self;
require(['$mainUri']);
}
})();
''';
}
...@@ -30,6 +30,17 @@ void main() { ...@@ -30,6 +30,17 @@ void main() {
'function(app, dart_sdk) {')); 'function(app, dart_sdk) {'));
}); });
test('generateMainModule can set bootstrap name', () {
final String result = generateMainModule(
entrypoint: 'foo/bar/main.js',
nullAssertions: false,
bootstrapModule: 'foo_module.bootstrap',
);
// bootstrap main module has correct defined module.
expect(result, contains('define("foo_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], '
'function(app, dart_sdk) {'));
});
test('generateMainModule includes null safety switches', () { test('generateMainModule includes null safety switches', () {
final String result = generateMainModule( final String result = generateMainModule(
entrypoint: 'foo/bar/main.js', entrypoint: 'foo/bar/main.js',
...@@ -40,4 +51,10 @@ void main() { ...@@ -40,4 +51,10 @@ void main() {
if (true) { if (true) {
dart_sdk.dart.nonNullAsserts(true);''')); dart_sdk.dart.nonNullAsserts(true);'''));
}); });
test('generateTestBootstrapFileContents embeds urls correctly', () {
final String result = generateTestBootstrapFileContents('foo.dart.js', 'require.js', 'mapper.js');
expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');'));
});
} }
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