From 4db845fb6a8fbe73e04a89073ad91b05293af5ea Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 31 May 2019 15:55:51 -0700 Subject: [PATCH] Add capability to flutter test --platform=chrome (#33525) --- packages/flutter_tools/build.yaml | 3 + .../web_compilation_delegate.dart | 162 +++++++- .../flutter_tools/lib/src/commands/test.dart | 16 + .../lib/src/resident_web_runner.dart | 2 +- .../lib/src/test/flutter_web_platform.dart | 383 ++++++++++++++++++ .../flutter_tools/lib/src/test/runner.dart | 31 ++ .../flutter_tools/lib/src/web/compile.dart | 3 +- packages/flutter_tools/pubspec.yaml | 2 +- packages/flutter_tools/static/Ahem.ttf | Bin 0 -> 12480 bytes packages/flutter_tools/static/index.html | 23 ++ 10 files changed, 613 insertions(+), 12 deletions(-) create mode 100644 packages/flutter_tools/lib/src/test/flutter_web_platform.dart create mode 100644 packages/flutter_tools/static/Ahem.ttf create mode 100644 packages/flutter_tools/static/index.html diff --git a/packages/flutter_tools/build.yaml b/packages/flutter_tools/build.yaml index 69fa907496e..c2d33c80433 100644 --- a/packages/flutter_tools/build.yaml +++ b/packages/flutter_tools/build.yaml @@ -1,5 +1,8 @@ targets: $default: + builders: + build_web_compilers|entrypoint: + enabled: false sources: exclude: - "test/data/**" diff --git a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart index 913746b1172..b23985e5179 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart @@ -12,17 +12,21 @@ import 'package:build_modules/src/platform.dart'; import 'package:build_runner_core/build_runner_core.dart' as core; import 'package:build_runner_core/src/generate/build_impl.dart'; import 'package:build_runner_core/src/generate/options.dart'; +import 'package:build_test/builder.dart'; +import 'package:build_test/src/debug_test_builder.dart'; import 'package:build_web_compilers/build_web_compilers.dart'; import 'package:build_web_compilers/builders.dart'; import 'package:build_web_compilers/src/dev_compiler_bootstrap.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +import 'package:test_core/backend.dart'; import 'package:watcher/watcher.dart'; import '../artifacts.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; +import '../base/platform.dart'; import '../compile.dart'; import '../dart/package_map.dart'; import '../globals.dart'; @@ -65,6 +69,20 @@ final DartPlatform flutterWebPlatform = /// The build application to compile a flutter application to the web. final List builders = [ + core.apply( + 'flutter_tools|test_bootstrap', + [ + (BuilderOptions options) => const DebugTestBuilder(), + (BuilderOptions options) => const FlutterWebTestBootstrapBuilder(), + ], + core.toRoot(), + hideOutput: true, + defaultGenerateFor: const InputSet( + include: [ + 'test/**', + ], + ), + ), core.apply( 'flutter_tools|module_library', [moduleLibraryBuilder], @@ -109,7 +127,7 @@ final List builders = [ 'flutter_tools|entrypoint', [ (BuilderOptions options) => FlutterWebEntrypointBuilder( - options.config['target'] ?? 'lib/main.dart'), + options.config['targets'] ?? ['lib/main.dart']), ], core.toRoot(), hideOutput: true, @@ -117,6 +135,7 @@ final List builders = [ include: [ 'lib/**', 'web/**', + 'test/**_test.dart.browser_test.dart', ], ), ), @@ -135,13 +154,14 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { @override Future initialize({ @required Directory projectDirectory, - @required String target, + @required List targets, + String testOutputDir, }) async { // Override the generated output directory so this does not conflict with // other build_runner output. core.overrideGeneratedOutputDirectory('flutter_web'); _packageUriMapper = PackageUriMapper( - path.absolute(target), PackageMap.globalPackagesPath, null, null); + path.absolute('lib/main.dart'), PackageMap.globalPackagesPath, null, null); _packageGraph = core.PackageGraph.forPath(projectDirectory.path); final core.BuildEnvironment buildEnvironment = core.OverrideableEnvironment( core.IOEnvironment(_packageGraph), onLog: (LogRecord record) { @@ -163,8 +183,18 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { trackPerformance: false, deleteFilesByDefault: true, ); + final Set buildDirs = { + if (testOutputDir != null) + core.BuildDirectory( + 'test', + outputLocation: core.OutputLocation( + testOutputDir, + useSymlinks: !platform.isWindows, + ), + ), + }; final Status status = - logger.startProgress('Compiling $target for the Web...', timeout: null); + logger.startProgress('Compiling ${targets.first} for the Web...', timeout: null); try { _builder = await BuildImpl.create( buildOptions, @@ -172,12 +202,12 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { builders, >{ 'flutter_tools|entrypoint': { - 'target': target, + 'targets': targets, } }, isReleaseBuild: false, ); - await _builder.run(const {}); + await _builder.run(const {}, buildDirs: buildDirs); } finally { status.stop(); } @@ -205,9 +235,9 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { /// A ddc-only entrypoint builder that respects the Flutter target flag. class FlutterWebEntrypointBuilder implements Builder { - const FlutterWebEntrypointBuilder(this.target); + const FlutterWebEntrypointBuilder(this.targets); - final String target; + final List targets; @override Map> get buildExtensions => const >{ @@ -222,10 +252,124 @@ class FlutterWebEntrypointBuilder implements Builder { @override Future build(BuildStep buildStep) async { - if (!buildStep.inputId.path.contains(target)) { + bool matches = false; + for (String target in targets) { + if (buildStep.inputId.path.contains(target)) { + matches = true; + break; + } + } + if (!matches) { return; } log.info('building for target ${buildStep.inputId.path}'); await bootstrapDdc(buildStep, platform: flutterWebPlatform); } } + +class FlutterWebTestBootstrapBuilder implements Builder { + const FlutterWebTestBootstrapBuilder(); + + @override + Map> get buildExtensions => const >{ + '_test.dart': [ + '_test.dart.browser_test.dart', + ] + }; + + @override + Future build(BuildStep buildStep) async { + final AssetId id = buildStep.inputId; + final String contents = await buildStep.readAsString(id); + final String assetPath = id.pathSegments.first == 'lib' + ? path.url.join('packages', id.package, id.path) + : id.path; + final Metadata metadata = parseMetadata( + assetPath, contents, Runtime.builtIn.map((Runtime runtime) => runtime.name).toSet()); + + if (metadata.testOn.evaluate(SuitePlatform(Runtime.chrome))) { + await buildStep.writeAsString(id.addExtension('.browser_test.dart'), ''' +import 'dart:ui' as ui; +import 'dart:html'; +import 'dart:js'; + +import 'package:stream_channel/stream_channel.dart'; +import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports +import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports +import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports +import 'package:test_api/src/suite_channel_manager.dart'; // ignore: implementation_imports + +import "${path.url.basename(id.path)}" as test; + +Future main() async { + // Extra initialization for flutter_web. + // The following parameters are hard-coded in Flutter's test embedder. Since + // we don't have an embedder yet this is the lowest-most layer we can put + // this stuff in. + await ui.webOnlyTestSetup(); + internalBootstrapBrowserTest(() => test.main); +} + +void internalBootstrapBrowserTest(Function getMain()) { + var channel = + serializeSuite(getMain, hidePrints: false, beforeLoad: () async { + var serialized = + await suiteChannel("test.browser.mapper").stream.first as Map; + if (serialized == null) return; + }); + postMessageChannel().pipe(channel); +} +StreamChannel serializeSuite(Function getMain(), + {bool hidePrints = true, Future beforeLoad()}) => + RemoteListener.start(getMain, + hidePrints: hidePrints, beforeLoad: beforeLoad); + +StreamChannel suiteChannel(String name) { + var manager = SuiteChannelManager.current; + if (manager == null) { + throw StateError('suiteChannel() may only be called within a test worker.'); + } + + return manager.connectOut(name); +} + +StreamChannel postMessageChannel() { + var controller = StreamChannelController(sync: true); + window.onMessage.firstWhere((message) { + return message.origin == window.location.origin && message.data == "port"; + }).then((message) { + var port = message.ports.first; + var portSubscription = port.onMessage.listen((message) { + controller.local.sink.add(message.data); + }); + + controller.local.stream.listen((data) { + port.postMessage({"data": data}); + }, onDone: () { + port.postMessage({"event": "done"}); + portSubscription.cancel(); + }); + }); + + context['parent'].callMethod('postMessage', [ + JsObject.jsify({"href": window.location.href, "ready": true}), + window.location.origin, + ]); + return controller.foreign; +} + +void setStackTraceMap +per(StackTraceMapper mapper) { + var formatter = StackTraceFormatter.current; + if (formatter == null) { + throw StateError( + 'setStackTraceMapper() may only be called within a test worker.'); + } + + formatter.configure(mapper: mapper); +} +'''); + } + } +} + diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 6f1caa480f4..d30266d6a0d 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -99,6 +99,11 @@ class TestCommand extends FastFlutterCommand { negatable: true, help: 'Whether to build the assets bundle for testing.\n' 'Consider using --no-test-assets if assets are not required.', + ) + ..addOption('platform', + allowed: const ['tester', 'chrome'], + defaultsTo: 'tester', + help: 'The platform to run the unit tests on. Defaults to "tester".' ); } @@ -166,6 +171,16 @@ class TestCommand extends FastFlutterCommand { 'Test files must be in that directory and end with the pattern "_test.dart".' ); } + } else { + final List fileCopy = []; + for (String file in files) { + if (file.endsWith(platform.pathSeparator)) { + fileCopy.addAll(_findTests(fs.directory(file))); + } else { + fileCopy.add(file); + } + } + files = fileCopy; } CoverageCollector collector; @@ -222,6 +237,7 @@ class TestCommand extends FastFlutterCommand { concurrency: jobs, buildTestAssets: buildTestAssets, flutterProject: flutterProject, + web: argResults['platform'] == 'chrome', ); if (collector != null) { diff --git a/packages/flutter_tools/lib/src/resident_web_runner.dart b/packages/flutter_tools/lib/src/resident_web_runner.dart index 1652628c72c..ba774a6444c 100644 --- a/packages/flutter_tools/lib/src/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/resident_web_runner.dart @@ -112,7 +112,7 @@ class ResidentWebRunner extends ResidentRunner { // Start the web compiler and build the assets. await webCompilationProxy.initialize( projectDirectory: currentProject.directory, - target: target, + targets: [target], ); _lastCompiled = DateTime.now(); final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart new file mode 100644 index 00000000000..e96cbee7003 --- /dev/null +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -0,0 +1,383 @@ +// Copyright 2019 The Chromium 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 'package:async/async.dart'; +import 'package:http_multi_server/http_multi_server.dart'; +import 'package:path/path.dart' as p; // ignore: package_path_import +import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_packages_handler/shelf_packages_handler.dart'; +import 'package:shelf_static/shelf_static.dart'; +import 'package:shelf_web_socket/shelf_web_socket.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/src/runner/browser/browser_manager.dart'; +import 'package:test/src/runner/browser/default_settings.dart'; +import 'package:test/src/runner/executable_settings.dart'; +import 'package:test_api/src/backend/runtime.dart'; +import 'package:test_api/src/backend/suite_platform.dart'; +import 'package:test_api/src/util/stack_trace_mapper.dart'; +import 'package:test_core/src/runner/configuration.dart'; +import 'package:test_core/src/runner/platform.dart'; +import 'package:test_core/src/runner/runner_suite.dart'; +import 'package:test_core/src/runner/suite.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../artifacts.dart'; +import '../base/file_system.dart'; +import '../cache.dart'; +import '../convert.dart'; +import '../dart/package_map.dart'; +import '../globals.dart'; + +// TODO(jonahwilliams): remove shelf and test dependencies. +class FlutterWebPlatform extends PlatformPlugin { + FlutterWebPlatform._(this._server, this._config, this._root) { + // Look up the location of the testing resources. + final Map packageMap = PackageMap(fs.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + )).map; + testUri = packageMap['test']; + final shelf.Cascade cascade = shelf.Cascade() + .add(_webSocketHandler.handler) + .add(packagesDirHandler()) + .add(_jsHandler.handler) + .add(createStaticHandler( + fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'), + serveFilesOutsidePath: true, + )) + .add(createStaticHandler(_config.suiteDefaults.precompiledPath, + serveFilesOutsidePath: true)) + .add(_handleStaticArtifact) + .add(_wrapperHandler); + _server.mount(cascade.handler); + } + + static Future start(String root) async { + final shelf_io.IOServer server = + shelf_io.IOServer(await HttpMultiServer.loopback(0)); + return FlutterWebPlatform._( + server, + Configuration.current, + root, + ); + } + + Uri testUri; + + /// The test runner configuration. + final Configuration _config; + + /// The underlying server. + final shelf.Server _server; + + /// The URL for this server. + Uri get url => _server.url; + + /// The ahem text file. + File get ahem => fs.file(fs.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + 'static', + 'Ahem.ttf', + )); + + /// The require js binary. + File get requireJs => fs.file(fs.path.join( + artifacts.getArtifactPath(Artifact.engineDartSdkPath), + 'lib', + 'dev_compiler', + 'amd', + 'require.js', + )); + + /// The ddc to dart stack trace mapper. + File get stackTraceMapper => fs.file(fs.path.join( + artifacts.getArtifactPath(Artifact.engineDartSdkPath), + 'lib', + 'dev_compiler', + 'web', + 'dart_stack_trace_mapper.js', + )); + + /// The precompiled dart sdk. + File get dartSdk => fs.file(fs.path.join( + artifacts.getArtifactPath(Artifact.flutterWebSdk), + 'kernel', + 'amd', + 'dart_sdk.js', + )); + + /// The precompiled test javascript. + File get testDartJs => fs.file(fs.path.join( + testUri.toFilePath(), + 'lib', + 'dart.js', + )); + + Future _handleStaticArtifact(shelf.Request request) async { + if (request.requestedUri.path.contains('require.js')) { + return shelf.Response.ok( + requireJs.openRead(), + headers: {'Content-Type': 'text/javascript'}, + ); + } else if (request.requestedUri.path.contains('Ahem.ttf')) { + return shelf.Response.ok(ahem.openRead()); + } else if (request.requestedUri.path.contains('dart_sdk.js')) { + return shelf.Response.ok( + dartSdk.openRead(), + headers: {'Content-Type': 'text/javascript'}, + ); + } else if (request.requestedUri.path + .contains('stack_trace_mapper.dart.js')) { + return shelf.Response.ok( + stackTraceMapper.openRead(), + headers: {'Content-Type': 'text/javascript'}, + ); + } else if (request.requestedUri.path.contains('static/dart.js')) { + return shelf.Response.ok( + testDartJs.openRead(), + headers: {'Content-Type': 'text/javascript'}, + ); + } else { + return shelf.Response.notFound('Not Found'); + } + } + + final OneOffHandler _webSocketHandler = OneOffHandler(); + final PathHandler _jsHandler = PathHandler(); + final AsyncMemoizer _closeMemo = AsyncMemoizer(); + final String _root; + final Map _browserSettings = + Map.from(defaultSettings); + + bool get _closed => _closeMemo.hasRun; + + // A map from browser identifiers to futures that will complete to the + // [BrowserManager]s for those browsers, or `null` if they failed to load. + final Map> _browserManagers = + >{}; + + // Mappers for Dartifying stack traces, indexed by test path. + final Map _mappers = {}; + + // A handler that serves wrapper files used to bootstrap tests. + shelf.Response _wrapperHandler(shelf.Request request) { + final String path = fs.path.fromUri(request.url); + if (path.endsWith('.html')) { + final String test = fs.path.withoutExtension(path) + '.dart'; + final String scriptBase = htmlEscape.convert(fs.path.basename(test)); + final String link = ''; + return shelf.Response.ok(''' + + + + ${htmlEscape.convert(test)} Test + $link + + + + ''', headers: {'Content-Type': 'text/html'}); + } + printTrace('Did not find anything for request: ${request.url}'); + return shelf.Response.notFound('Not found.'); + } + + @override + Future load(String path, SuitePlatform platform, + SuiteConfiguration suiteConfig, Object message) async { + if (_closed) { + return null; + } + final Runtime browser = platform.runtime; + final BrowserManager browserManager = await _browserManagerFor(browser); + if (_closed || browserManager == null) { + return null; + } + + final Uri suiteUrl = url.resolveUri(fs.path.toUri(fs.path.withoutExtension( + fs.path.relative(path, from: fs.path.join(_root, 'test'))) + + '.html')); + final RunnerSuite suite = await browserManager + .load(path, suiteUrl, suiteConfig, message, mapper: _mappers[path]); + if (_closed) { + return null; + } + return suite; + } + + @override + StreamChannel loadChannel(String path, SuitePlatform platform) => + throw UnimplementedError(); + + /// Returns the [BrowserManager] for [runtime], which should be a browser. + /// + /// If no browser manager is running yet, starts one. + Future _browserManagerFor(Runtime browser) { + final Future managerFuture = _browserManagers[browser]; + if (managerFuture != null) { + return managerFuture; + } + final Completer completer = + Completer.sync(); + final String path = + _webSocketHandler.create(webSocketHandler(completer.complete)); + final Uri webSocketUrl = url.replace(scheme: 'ws').resolve(path); + final Uri hostUrl = url + .resolve('static/index.html') + .replace(queryParameters: { + 'managerUrl': webSocketUrl.toString(), + 'debug': _config.pauseAfterLoad.toString() + }); + + printTrace('Serving tests at $hostUrl'); + + final Future future = BrowserManager.start( + browser, + hostUrl, + completer.future, + _browserSettings[browser], + debug: _config.pauseAfterLoad, + ); + + // Store null values for browsers that error out so we know not to load them + // again. + _browserManagers[browser] = future.catchError((dynamic _) => null); + + return future; + } + + @override + Future closeEphemeral() { + final List> managers = + _browserManagers.values.toList(); + _browserManagers.clear(); + return Future.wait(managers.map((Future manager) async { + final BrowserManager result = await manager; + if (result == null) { + return; + } + await result.close(); + })); + } + + @override + Future close() => _closeMemo.runOnce(() async { + final List> futures = _browserManagers.values + .map>((Future future) async { + final BrowserManager result = await future; + if (result == null) { + return; + } + await result.close(); + }).toList(); + futures.add(_server.close()); + await Future.wait(futures); + }); +} + +class OneOffHandler { + /// A map from URL paths to handlers. + final Map _handlers = {}; + + /// The counter of handlers that have been activated. + int _counter = 0; + + /// The actual [shelf.Handler] that dispatches requests. + shelf.Handler get handler => _onRequest; + + /// Creates a new one-off handler that forwards to [handler]. + /// + /// Returns a string that's the URL path for hitting this handler, relative to + /// the URL for the one-off handler itself. + /// + /// [handler] will be unmounted as soon as it receives a request. + String create(shelf.Handler handler) { + final String path = _counter.toString(); + _handlers[path] = handler; + _counter++; + return path; + } + + /// Dispatches [request] to the appropriate handler. + FutureOr _onRequest(shelf.Request request) { + final List components = p.url.split(request.url.path); + if (components.isEmpty) { + return shelf.Response.notFound(null); + } + final String path = components.removeAt(0); + final FutureOr Function(shelf.Request) handler = + _handlers.remove(path); + if (handler == null) { + return shelf.Response.notFound(null); + } + return handler(request.change(path: path)); + } +} + +class PathHandler { + /// A trie of path components to handlers. + final _Node _paths = _Node(); + + /// The shelf handler. + shelf.Handler get handler => _onRequest; + + /// Returns middleware that nests all requests beneath the URL prefix + /// [beneath]. + static shelf.Middleware nestedIn(String beneath) { + return (FutureOr Function(shelf.Request) handler) { + final PathHandler pathHandler = PathHandler()..add(beneath, handler); + return pathHandler.handler; + }; + } + + /// Routes requests at or under [path] to [handler]. + /// + /// If [path] is a parent or child directory of another path in this handler, + /// the longest matching prefix wins. + void add(String path, shelf.Handler handler) { + _Node node = _paths; + for (String component in p.url.split(path)) { + node = node.children.putIfAbsent(component, () => _Node()); + } + node.handler = handler; + } + + FutureOr _onRequest(shelf.Request request) { + shelf.Handler handler; + int handlerIndex; + _Node node = _paths; + final List components = p.url.split(request.url.path); + for (int i = 0; i < components.length; i++) { + node = node.children[components[i]]; + if (node == null) { + break; + } + if (node.handler == null) { + continue; + } + handler = node.handler; + handlerIndex = i; + } + + if (handler == null) { + return shelf.Response.notFound('Not found.'); + } + + return handler( + request.change(path: p.url.joinAll(components.take(handlerIndex + 1)))); + } +} + +/// A trie node. +class _Node { + shelf.Handler handler; + final Map children = {}; +} diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index 2e1bc764738..4fc19cc832a 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -5,7 +5,9 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:test_api/backend.dart'; import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports +import 'package:test_core/src/runner/hack_register_platform.dart' as hack; // ignore: implementation_imports import '../artifacts.dart'; import '../base/common.dart'; @@ -16,7 +18,9 @@ import '../base/terminal.dart'; import '../dart/package_map.dart'; import '../globals.dart'; import '../project.dart'; +import '../web/compile.dart'; import 'flutter_platform.dart' as loader; +import 'flutter_web_platform.dart'; import 'watcher.dart'; /// Runs tests using package:test and the Flutter engine. @@ -40,6 +44,7 @@ Future runTests( FlutterProject flutterProject, String icudtlPath, Directory coverageDirectory, + bool web, }) async { // Compute the command-line arguments for package:test. final List testArgs = []; @@ -62,6 +67,32 @@ Future runTests( for (String plainName in plainNames) { testArgs..add('--plain-name')..add(plainName); } + if (web) { + final String tempBuildDir = fs.systemTempDirectory + .createTempSync('_flutter_test') + .absolute + .uri + .toFilePath(); + await webCompilationProxy.initialize( + projectDirectory: flutterProject.directory, + testOutputDir: tempBuildDir, + targets: testFiles.map((String testFile) { + return fs.path.relative(testFile, from: flutterProject.directory.path); + }).toList(), + ); + testArgs.add('--platform=chrome'); + testArgs.add('--precompiled=$tempBuildDir'); + testArgs.add('--'); + testArgs.addAll(testFiles); + hack.registerPlatformPlugin( + [Runtime.chrome], + () { + return FlutterWebPlatform.start(flutterProject.directory.path); + } + ); + await test.main(testArgs); + return exitCode; + } testArgs.add('--'); testArgs.addAll(testFiles); diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index b814d407792..ec56dd96d4e 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -90,7 +90,8 @@ class WebCompilationProxy { /// `projectDirectory`. Future initialize({ @required Directory projectDirectory, - @required String target, + @required List targets, + String testOutputDir, }) async { throw UnimplementedError(); } diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 8521cedb8b6..2c8b87bf614 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: # this, make sure the tests are still running correctly. test_api: 0.2.5 test_core: 0.2.5 + test: 1.6.3 # Code generation dependencies build_runner_core: 3.0.5 @@ -103,7 +104,6 @@ dev_dependencies: mockito: 4.0.0 file_testing: 2.1.0 vm_service_lib: 3.17.0 - test: 1.6.3 build_runner: 1.4.0 build_vm_compilers: 1.0.0 build_test: 0.10.7+3 diff --git a/packages/flutter_tools/static/Ahem.ttf b/packages/flutter_tools/static/Ahem.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ac81cb03165ab831a36abb59145ff7a2f5675fb9 GIT binary patch literal 12480 zcmeHNZFm&b6@KSqXFq`?V#J8_W{Z(327;C%rN~F5ND+}BqNU0vJIMyKyK#48f&xWr z)kgU!T8&C6B7%Y<3R(n85fK4VsitTVmo!>UZB&Y2eGpjRJ3A2++dqB$VLP+W?3{b< zy=Tt7=bd}c?z~U{_%Rb2`d(7jr(eU^QL_ML0JW1VqM_O@yYfc>t{T8dRE3hYI0asy z?@HF^RMkwayl7QwHx0A^1>uMp3h!DpraSA_^Xwm?!tK-ZDIeh3GZIZTJR>lj_L~5$ zxh7r_5_M+=fud6C+M}U{T6`+)E8W;$#6nSXM%O#m0KNM1{*l^vGBs=G>5V`!`>Wj` zeOE9n@03fcuI@7EAiP=|HCPqD`U-j7c+T>RfXo1`O%p6?P^Fd!`)MLGfZtYoeoITo z9|tYXL3r#wU#;iuwKiogTol%;^ayjZSLJ#1Q#5L!a~$%R2|S(yG14mV>+*Al14AJ8 zskRRSTLqNdA*|Dc<*Bed<2+?OlwLZgGY1X^zTFs*k8}3aN82k-cWSt{s6{YTpM2g~ z`084ta52dq9ap<}!X?{NgU)ryfSaqAT!VaE-vj7`&gg<1CKG*VYI28pr z4c&1%df+<{C`1t7MNgc8BAkg{_#V!}+31aPP>eq4ixTw1xi}BsM}PbP18_bD;)fW7 zA7L;q;JP*x!!R7BxDX@oV~oT_7=@o;G%m&%T!OK<6ys2a@wf~Va5*O83S5bwViJCa zt8g_Y)87!vQGqZ_RH6zIRAUORK@FmaA&y#Hiv*HLp$^xf9u1g^>v037abKB%pL4l= z6F1@}`~o-QE&LL<;8xs*xA7}9;(gqXJJ5z%_y^iC8*}gu-o@Y0jK5pKVui(z+doNEWq#ZAr@jMTJZo{@K-A$d$0(v zV>kBVK`h2YSc0W^7>{5Xmg7-8#x^VPI9B2bJc(6Uji>N5evdVH25Yg7JEu-GfU`A( zQS8hp%s@sdQOcACWuE$ix=npcyGwgSTd!>tIijm55Isat6p6lKpeQX278V87pb_j6 z%nKF-dj!u8jtou?n!){NY~QD5GPElg(}lD*&a$_K_TD(o-btdH=q`k1@BA;>%MG6T zg}tz451CBn?My3o*c&s`G7~cw07u$+4nA_$;n$9I-J5CI1hlMgS=X|r}@&0b7ZTImF2#&i@u-pb6Wt4KES}_c3-4GU^hK zw-b>|iOI!`=5jll8;H;qRz&9$sm~KxPgrrS!bWT&?y8C1Da7qHMD9Fdx0dLY*i8|= z61#sSdZ!Y<*Au%p5J`73($g&jKa1yxs#U~778&b^lC>7zUMF5R)7SSLlwIg)X8d0ou)RbP3jVLwYo{&sixI-^^oS#^0l5? zKW&IMMw_Hf(HgW{_#S(awo==m?a+49(@%6=&(nootPj#h=@a#^p3rCNv-SDgDS1Ds_!>O?Jgx(_D?NCf5?zYS$*$PFLF1?mA?6jC`Y~(a#uS zj4>t|Q;Y`V7UM2sk+IU)VC*n<8*RoXZrz>d7VcvAAonQuM0ce-<-W;1*S)~~sC%t@ zt9zIG9ru0@Je@t=J!g3a@H4_Q-c#wVjOZ}{Hz9q_CE9DfggZ~s95NdE+X*q`vv^w0Ls_b>CW@o(`r`&<3{{6_ul3(%P!uhX4o>vqA#{(jzxde zmN^!Etu1pb`fgk1SoBs~=2-MkZ8--Xm;4`7+RsI2m&ca#sLv-OCT{Uq1i1wfvJ4VD zE65HoEq;rzG$sT!5$XibD8G~B5Y*Xqy)A76dzoYxq*y8_g)Iq%2(Eaogq z8&R^AOtO&Vm}G<|Sgob!S+7gzX$h@$wC=cmeLK7MV)9r;MI@96Riw;BQiKwwNJUIx zMn&@4I+l71v!TMQO^KolhgQ_3Oc9EOMX)y5ThvFYDB4 zsAZ)*lr(FqV`j0qENNEO)riV?LZr-OsybFBD&kSHXsc7z@t8}O@731UKuow}FBw}VF+mntBFDnyep=ezqRDEVrlqcf#N!Hpni-$(c=s2^gt|pY2 zz~M;KCB-E|s^xi{u0v(FmPO|&_Xp^ zka_nJ_Tit{kB`anKZsAPxJh>7I&$S6Ca>*&i|4r6VmdxgM&2f}@0OEmw}A|}^<>R8 zk!g1?xpwo&ubgACFPD;w_bOR*&yq>6D!M-AveBc}k>j<}3rEwsX0h^fppeg~eGU!H Bc@h8s literal 0 HcmV?d00001 diff --git a/packages/flutter_tools/static/index.html b/packages/flutter_tools/static/index.html new file mode 100644 index 00000000000..f69739d92bf --- /dev/null +++ b/packages/flutter_tools/static/index.html @@ -0,0 +1,23 @@ + + + + test Browser Host + + + + + + + + + + +
+ + + + + + + +