| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:io'; | |
| import 'package:http_multi_server/http_multi_server.dart'; | |
| import 'package:path/path.dart' as p; | |
| 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:test_api/backend.dart' | |
| show Compiler, StackTraceMapper, SuitePlatform; | |
| import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports | |
| import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports | |
| import 'package:test_core/src/util/package_config.dart'; // ignore: implementation_imports | |
| import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports | |
| import 'package:web_socket_channel/web_socket_channel.dart'; | |
| import '../../../util/math.dart'; | |
| import '../../../util/one_off_handler.dart'; | |
| import '../../../util/package_map.dart'; | |
| import '../../../util/path_handler.dart'; | |
| import 'compiler_support.dart'; | |
| class JsPrecompiledSupport = PrecompiledSupport with JsHtmlWrapper; | |
| class WasmPrecompiledSupport = PrecompiledSupport with WasmHtmlWrapper; | |
| /// Support for precompiled test files. | |
| abstract class PrecompiledSupport extends CompilerSupport { | |
| /// Whether [close] has been called. | |
| bool _closed = false; | |
| /// Mappers for Dartifying stack traces, indexed by test path. | |
| final _mappers = <String, StackTraceMapper>{}; | |
| /// The root directory served statically by the server. | |
| final String _root; | |
| /// Each compiler serves its tests under a different randomly-generated | |
| /// secret URI to ensure that other users on the same system can't snoop | |
| /// on data being served through this server, as well as distinguish tests | |
| /// from different compilers from each other. | |
| final String _secret = randomUrlSecret(); | |
| /// The underlying server. | |
| final shelf.Server _server; | |
| /// A [OneOffHandler] for servicing WebSocket connections for | |
| /// [BrowserManager]s. | |
| /// | |
| /// This is one-off because each [BrowserManager] can only connect to a single | |
| /// WebSocket. | |
| final _webSocketHandler = OneOffHandler(); | |
| /// The URL at which this compiler serves its tests. | |
| /// | |
| /// Each compiler serves its tests under a different directory. | |
| @override | |
| Uri get serverUrl => _server.url.resolve('$_secret/'); | |
| PrecompiledSupport._( | |
| super.config, | |
| super.defaultTemplatePath, | |
| this._server, | |
| this._root, | |
| String faviconPath, | |
| ) { | |
| var cascade = shelf.Cascade() | |
| .add(_webSocketHandler.handler) | |
| .add(createStaticHandler(_root, serveFilesOutsidePath: true)) | |
| // TODO: This packages dir handler should not be necessary? | |
| .add(packagesDirHandler()) | |
| // Even for precompiled tests, we will auto-create a bootstrap html file | |
| // if none was present. | |
| .add(htmlWrapperHandler); | |
| var pipeline = const shelf.Pipeline() | |
| .addMiddleware(PathHandler.nestedIn(_secret)) | |
| .addHandler(cascade.handler); | |
| _server.mount( | |
| shelf.Cascade().add(createFileHandler(faviconPath)).add(pipeline).handler, | |
| ); | |
| } | |
| static Future<PrecompiledSupport> start({ | |
| required Compiler compiler, | |
| required Configuration config, | |
| required String defaultTemplatePath, | |
| required String root, | |
| required String faviconPath, | |
| }) async { | |
| var server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); | |
| return switch (compiler) { | |
| Compiler.dart2js => JsPrecompiledSupport._( | |
| config, | |
| defaultTemplatePath, | |
| server, | |
| root, | |
| faviconPath, | |
| ), | |
| Compiler.dart2wasm => WasmPrecompiledSupport._( | |
| config, | |
| defaultTemplatePath, | |
| server, | |
| root, | |
| faviconPath, | |
| ), | |
| Compiler.exe || | |
| Compiler.cli || | |
| Compiler.kernel || | |
| Compiler.source => throw UnsupportedError( | |
| 'The browser platform does not support $compiler', | |
| ), | |
| }; | |
| } | |
| /// Compiles [dartPath] using [suiteConfig] for [platform]. | |
| @override | |
| Future<void> compileSuite( | |
| String dartPath, | |
| SuiteConfiguration suiteConfig, | |
| SuitePlatform platform, | |
| ) async { | |
| if (suiteConfig.jsTrace) return; | |
| var mapPath = p.join( | |
| suiteConfig.precompiledPath!, | |
| '$dartPath.browser_test.dart.js.map', | |
| ); | |
| var mapFile = File(mapPath); | |
| if (mapFile.existsSync()) { | |
| _mappers[dartPath] = JSStackTraceMapper( | |
| mapFile.readAsStringSync(), | |
| mapUrl: p.toUri(mapPath), | |
| sdkRoot: Uri.parse(r'/packages/$sdk'), | |
| packageMap: (await currentPackageConfig).toPackageMap(), | |
| ); | |
| } | |
| } | |
| /// Retrieves a stack trace mapper for [path] if available. | |
| @override | |
| StackTraceMapper? stackTraceMapperForPath(String dartPath) => | |
| _mappers[dartPath]; | |
| /// Closes down anything necessary for this implementation. | |
| @override | |
| Future<void> close() async { | |
| if (_closed) return; | |
| _closed = true; | |
| await _server.close(); | |
| } | |
| @override | |
| (Uri, Future<WebSocketChannel>) get webSocket { | |
| var completer = Completer<WebSocketChannel>.sync(); | |
| // Note: the WebSocketChannel type below is needed for compatibility with | |
| // package:shelf_web_socket v2. | |
| var path = _webSocketHandler.create( | |
| webSocketHandler((WebSocketChannel ws, _) { | |
| completer.complete(ws); | |
| }), | |
| ); | |
| var webSocketUrl = serverUrl.replace(scheme: 'ws').resolve(path); | |
| return (webSocketUrl, completer.future); | |
| } | |
| } |