Add infrastructure and tests for evaluation with frontend_server (#902)
- Create new non-public package:frontend_server_common
- logic is a fork of the Frontend Server compiler logic from Flutter
- Long term this package will contain a shared set of logic for use from package:dwds, package:webdev and package:flutter_tools
- Add new tests to stress the Frontend Server compiler
- Tag those tests as `frontend-server` and skip for now
- Waiting for https://dart-review.googlesource.com/c/sdk/+/138010 to land in the dev channel
- Update `modules.dart` to handle both compilers (Frontend Server, package:build*)
- As this directly impacts breakpoint logic, split these tests out and make them explicit
diff --git a/.travis.yml b/.travis.yml
index f93d56d..2938fcd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,10 +16,10 @@
jobs:
include:
- stage: analyzer_and_format
- name: "SDK: 2.6.0; PKGS: dwds, example, webdev; TASKS: `dartanalyzer --fatal-warnings .`"
+ name: "SDK: 2.6.0; PKGS: dwds, example, frontend_server_common, webdev; TASKS: `dartanalyzer --fatal-warnings .`"
dart: "2.6.0"
os: linux
- env: PKGS="dwds example webdev"
+ env: PKGS="dwds example frontend_server_common webdev"
script: ./tool/travis.sh dartanalyzer_1
- stage: analyzer_and_format
name: "SDK: dev; PKG: dwds; TASKS: [`dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`, `pub run test test/build/ensure_version_test.dart`]"
@@ -28,17 +28,17 @@
env: PKGS="dwds"
script: ./tool/travis.sh dartfmt dartanalyzer_0 test_0
- stage: analyzer_and_format
- name: "SDK: dev; PKG: example; TASKS: [`dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`]"
+ name: "SDK: dev; PKGS: example, frontend_server_common; TASKS: [`dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`]"
dart: dev
os: linux
- env: PKGS="example"
+ env: PKGS="example frontend_server_common"
script: ./tool/travis.sh dartfmt dartanalyzer_0
- stage: analyzer_and_format
name: "SDK: dev; PKG: webdev; TASKS: [`dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`, `pub run test test/build/ensure_build_test.dart`]"
dart: dev
os: linux
env: PKGS="webdev"
- script: ./tool/travis.sh dartfmt dartanalyzer_0 test_3
+ script: ./tool/travis.sh dartfmt dartanalyzer_0 test_2
- stage: unit_test
name: "SDK: 2.6.0; PKG: dwds; TASKS: `pub run test -x frontend-server`"
dart: "2.6.0"
@@ -46,35 +46,35 @@
env: PKGS="dwds"
script: ./tool/travis.sh test_1
- stage: unit_test
- name: "SDK: 2.6.0; PKG: dwds; TASKS: `pub run test`"
+ name: "SDK: 2.6.0; PKG: dwds; TASKS: `pub run test -x frontend-server`"
dart: "2.6.0"
os: windows
env: PKGS="dwds"
- script: ./tool/travis.sh test_2
+ script: ./tool/travis.sh test_1
- stage: unit_test
- name: "SDK: dev; PKG: dwds; TASKS: `pub run test`"
+ name: "SDK: dev; PKG: dwds; TASKS: `pub run test -x frontend-server`"
dart: dev
os: linux
env: PKGS="dwds"
- script: ./tool/travis.sh test_2
+ script: ./tool/travis.sh test_1
- stage: unit_test
name: "SDK: 2.6.0; PKG: webdev; TASKS: `pub run test -j 1`"
dart: "2.6.0"
os: linux
env: PKGS="webdev"
- script: ./tool/travis.sh test_4
+ script: ./tool/travis.sh test_3
- stage: unit_test
name: "SDK: 2.6.0; PKG: webdev; TASKS: `pub run test -j 1`"
dart: "2.6.0"
os: windows
env: PKGS="webdev"
- script: ./tool/travis.sh test_4
+ script: ./tool/travis.sh test_3
- stage: unit_test
name: "SDK: dev; PKG: webdev; TASKS: `pub run test -j 1`"
dart: dev
os: linux
env: PKGS="webdev"
- script: ./tool/travis.sh test_4
+ script: ./tool/travis.sh test_3
stages:
- analyzer_and_format
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 26ba7c5..f7d85e2 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,6 +1,6 @@
## 2.0.0-dev
-- Depend on the latest `package:vm_service` version `2.3.1`.
+- Depend on the latest `package:vm_service` version `3.0.0+1`.
**Breaking Changes:**
- Now require a `LoadStrategy` to `Dwds.start`. This package defines two
diff --git a/dwds/dart_test.yaml b/dwds/dart_test.yaml
index 479dd58..72d335d 100644
--- a/dwds/dart_test.yaml
+++ b/dwds/dart_test.yaml
@@ -3,3 +3,4 @@
tags:
extension: # Extension tests require configuration, so we may exclude.
+ frontend-server: #Frontend server test require dev version of SDK, so we may exclude for stable SDK
diff --git a/dwds/lib/src/debugging/execution_context.dart b/dwds/lib/src/debugging/execution_context.dart
index 117843a..868f62a 100644
--- a/dwds/lib/src/debugging/execution_context.dart
+++ b/dwds/lib/src/debugging/execution_context.dart
@@ -7,8 +7,13 @@
import 'package:async/async.dart';
import 'package:dwds/src/debugging/remote_debugger.dart';
+abstract class ExecutionContext {
+ /// Returns the context ID that contains the running Dart application.
+ Future<int> get id;
+}
+
/// The execution context in which to do remote evaluations.
-class ExecutionContext {
+class RemoteDebuggerExecutionContext extends ExecutionContext {
final RemoteDebugger _remoteDebugger;
// Contexts that may contain a Dart application.
@@ -16,7 +21,7 @@
int _id;
- /// Returns the context ID that contains the running Dart application.
+ @override
Future<int> get id async {
if (_id != null) return _id;
while (await _contexts.hasNext
@@ -43,7 +48,7 @@
return _id;
}
- ExecutionContext(this._id, this._remoteDebugger) {
+ RemoteDebuggerExecutionContext(this._id, this._remoteDebugger) {
var contextController = StreamController<int>();
_remoteDebugger
.eventStream('Runtime.executionContextsCleared', (e) => e)
diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart
index 3969555..4dda704 100644
--- a/dwds/lib/src/debugging/location.dart
+++ b/dwds/lib/src/debugging/location.dart
@@ -153,7 +153,6 @@
/// Returns all [Location] data for a provided JS scriptId.
Future<Set<Location>> locationsForJs(String scriptId) async {
var module = await _modules.moduleForScriptId(scriptId);
-
var cache = _scriptIdToLocation[scriptId];
if (cache != null) return cache;
@@ -231,12 +230,17 @@
var moduleExtension = await _modules.moduleExtension;
var modulePath = '$module$moduleExtension';
if (modulePath.endsWith('dart_sdk.js') ||
- modulePath.endsWith('dart_sdk.ddk.js')) {
+ modulePath.endsWith('dart_sdk.ddk.js') ||
+ // .lib.js extensions come from frontend server
+ modulePath.endsWith('dart_sdk.lib.js')) {
return result;
}
+
+ modulePath = _modules.adjustForRoot(modulePath);
+ var scriptLocation = p.url.dirname(modulePath);
+
var sourceMapContents =
await _assetReader.sourceMapContents('$modulePath.map');
- var scriptLocation = p.url.dirname('/$modulePath');
if (sourceMapContents == null) return result;
var scriptId = await _modules.scriptIdForModule(module);
if (scriptId == null) return result;
@@ -255,7 +259,8 @@
var relativeSegments = p.split(mapping.urls[index]);
var path = p.url
.normalize(p.url.joinAll([scriptLocation, ...relativeSegments]));
- var dartUri = DartUri(path, _root);
+ var uri = _modules.adjustForRoot(path);
+ var dartUri = DartUri('/$uri', _root);
result.add(Location.from(
scriptId,
lineEntry,
diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart
index c2d4980..c3300d1 100644
--- a/dwds/lib/src/debugging/modules.dart
+++ b/dwds/lib/src/debugging/modules.dart
@@ -20,7 +20,8 @@
// The Dart server path to containing module.
final _sourceToModule = <String, String>{};
// The Dart server path to library import uri
- final _sourceToLibrary = <String, String>{};
+ final _sourceToLibrary = <String, Uri>{};
+ Completer<bool> _moduleCompleter = Completer<bool>();
// The Chrome script ID to corresponding module.
final _scriptIdToModule = <String, String>{};
@@ -30,7 +31,8 @@
final _moduleExtensionCompleter = Completer<String>();
- Modules(this._remoteDebugger, this._root, this._executionContext);
+ Modules(this._remoteDebugger, String root, this._executionContext)
+ : _root = root == '' ? '/' : root;
/// Completes with the module extension i.e. `.ddc.js` or `.ddk.js`.
///
@@ -47,6 +49,8 @@
// across hot reloads.
_sourceToModule.clear();
_sourceToLibrary.clear();
+ _moduleCompleter = Completer<bool>();
+ _initializeMapping();
}
/// Returns the module for the Chrome script ID.
@@ -59,26 +63,19 @@
/// Returns the containing module for the provided Dart server path.
Future<String> moduleForSource(String serverPath) async {
- if (_sourceToModule.isEmpty) {
- await _initializeMapping();
- }
+ await _moduleCompleter.future;
return _sourceToModule[serverPath];
}
/// Returns the containing library importUri for the provided Dart server path.
- Future<String> libraryForSource(String serverPath) async {
- if (_sourceToLibrary.isEmpty) {
- await _initializeMapping();
- }
+ Future<Uri> libraryForSource(String serverPath) async {
+ await _moduleCompleter.future;
return _sourceToLibrary[serverPath];
}
// Returns mapping from server paths to library paths
Future<Map<String, String>> modules() async {
- if (_sourceToModule.isEmpty) {
- await _initializeMapping();
- }
-
+ await _moduleCompleter.future;
return _sourceToModule;
}
@@ -86,7 +83,9 @@
Future<Null> noteModule(String url, String scriptId) async {
var path = Uri.parse(url).path;
if (path == null ||
- !(path.endsWith('.ddc.js') || path.endsWith('.ddk.js'))) {
+ !(path.endsWith('.ddc.js') ||
+ path.endsWith('.ddk.js') ||
+ path.endsWith('.lib.js'))) {
return;
}
@@ -94,21 +93,77 @@
if (!_moduleExtensionCompleter.isCompleted) {
if (path.endsWith('.ddc.js')) {
_moduleExtensionCompleter.complete('.ddc.js');
- } else {
+ } else if (path.endsWith('.ddk.js')) {
_moduleExtensionCompleter.complete('.ddk.js');
+ } else {
+ _moduleExtensionCompleter.complete('.lib.js');
}
}
- var module =
- // Remove the DDC extension (e.g. .ddc.js) from the path.
- _moduleFor(p.withoutExtension(p.withoutExtension(path)));
+ // TODO(annagrin): redirect modulePath->moduleName query to load strategy
+ //
+ // The code below is trying to guess the module name from js module path
+ // by assuming we can find a dart server path with a matching name located
+ // at the same server directory. Then it uses source->moduleName map to get
+ // the module name.
+ // [issue #917](https://github.com/dart-lang/webdev/issues/917)
+ // [issue #910](https://github.com/dart-lang/webdev/issues/910)
+ var serverPath = _jsModulePathToServerPath(path);
+ var module = await moduleForSource(serverPath);
_scriptIdToModule[scriptId] = module;
_moduleToScriptId[module] = scriptId;
}
+ String _jsModulePathToServerPath(String path) {
+ // remove extensions, such as '.ddc.js'
+ var serverPath = p.withoutExtension(p.withoutExtension(path));
+ // server path does not contain leading '/'
+ serverPath =
+ serverPath.startsWith('/') ? serverPath.substring(1) : serverPath;
+ // server path has '.dart' extension
+ serverPath = serverPath.endsWith('.dart') ? serverPath : '$serverPath.dart';
+ // server path should be relative to the asset server root
+ serverPath = adjustForRoot(serverPath);
+ return DartUri('/$serverPath', _root).serverPath;
+ }
+
+ /// Make path relative to the asset server's serving root.
+ ///
+ /// Remove the asset server root directory for non-package files, if any,
+ /// but do not remove the _root, which is the directory off the
+ /// asset server root. This is needed to produce paths that will be used
+ /// in requests to the asset server, such as dart script paths, dart
+ /// locations, or source map paths. Asset server is serving from the asset
+ /// server root directory, so it expects the requests to be relative to it.
+ // Note: This is a temporary workaround until we solve inconsistencies in
+ // different configurations by introducing module name and path translation
+ // interfaces between compiler, asset server, and the debugger.
+ // TODO(annagrin): module interface
+ // [issue #910](https://github.com/dart-lang/webdev/issues/910)
+ String adjustForRoot(String path) {
+ // path == 'dir/main.dart' => pathRoot == 'dir'
+ // path == 'main.dart' => pathRoot == '.'
+ var segments = p.split(path);
+ var pathRoot = p.split(p.dirname(path))[0];
+
+ // _root == 'http:/localhost:port/dir/index.html' => indexRoot == 'dir'
+ // _root == 'http:/localhost:port/index.html' => indexRoot == '.'
+ var indexPath = Uri.parse(_root).path.substring(1);
+ var indexRoot = p.split(p.dirname(indexPath))[0];
+
+ // remove the root from path only if not equal to packages or indexRoot
+ var result = pathRoot == 'packages' || pathRoot == indexRoot
+ // Module paths are consistent across platforms so join with a
+ // forward slash.
+ ? p.url.joinAll(segments)
+ : p.url.joinAll(segments.skip(1));
+ return result;
+ }
+
/// Initializes [_sourceToModule].
Future<void> _initializeMapping() async {
+ if (_moduleCompleter.isCompleted) return;
var expression = '''
(function() {
var dart = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart;
@@ -116,7 +171,7 @@
dart.getModuleNames().forEach(function(module){
Object.keys(dart.getModuleLibraries(module)).forEach(
function(script){
- result[script] = '/' + module;
+ result[script] = module;
});
});
return result;
@@ -133,10 +188,20 @@
for (var dartScript in value.keys) {
if (!dartScript.endsWith('.dart')) continue;
var serverPath = DartUri(dartScript, _root).serverPath;
- _sourceToModule[serverPath] = _moduleFor(value[dartScript] as String,
- skipRoot: dartScript.startsWith('org-dartlang-app:///'));
- _sourceToLibrary[serverPath] = Uri.parse(dartScript).path;
+
+ // get module name from module Uri
+ // Note: This is a temporary workaround until we solve inconsistencies
+ // in different configurations by introducing module name and path
+ // translation interfaces between compiler, asset server, and the
+ // debugger.
+ // TODO(annagrin): module interface
+ // [issue #910](https://github.com/dart-lang/webdev/issues/910)
+ var moduleUri = Uri.parse(value[dartScript] as String);
+ var module = _moduleFor(moduleUri.path);
+ _sourceToModule[serverPath] = module;
+ _sourceToLibrary[serverPath] = Uri.parse(dartScript);
}
+ _moduleCompleter.complete();
}
/// Returns the module for the provided path.
@@ -147,6 +212,7 @@
/// some/root/bar/module
///
String _moduleFor(String path, {bool skipRoot}) {
+ path = '/$path';
skipRoot ??= false;
var result = '';
if (path.contains('/packages/')) {
diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart
index 0b05093..27585c6 100644
--- a/dwds/lib/src/handlers/dev_handler.dart
+++ b/dwds/lib/src/handlers/dev_handler.dart
@@ -137,7 +137,7 @@
var evaluatedAppId = result.result['result']['value'];
if (evaluatedAppId == appInstanceId) {
appTab = tab;
- executionContext = ExecutionContext(
+ executionContext = RemoteDebuggerExecutionContext(
context, WebkitDebugger(WipDebugger(tabConnection)));
break;
}
diff --git a/dwds/lib/src/loaders/frontend_server_require.dart b/dwds/lib/src/loaders/frontend_server_require.dart
new file mode 100644
index 0000000..e5bfcd6
--- /dev/null
+++ b/dwds/lib/src/loaders/frontend_server_require.dart
@@ -0,0 +1,44 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:dwds/dwds.dart';
+
+/// Provides a [RequireStrategy] suitable for use with Frontend Server.
+class FrontendServerRequireStrategyProvider {
+ final ReloadConfiguration _configuration;
+ final Iterable<String> _modules;
+ RequireStrategy _requireStrategy;
+
+ FrontendServerRequireStrategyProvider(this._modules, this._configuration);
+
+ RequireStrategy get strategy => _requireStrategy ??= RequireStrategy(
+ _configuration, '.lib.js', _moduleProvider, _digestsProvider);
+
+ Future<Map<String, String>> _digestsProvider(String entrypoint) async {
+ return {};
+ }
+
+ Future<Map<String, String>> _moduleProvider(String entrypoint) async {
+ final modulePaths = <String, String>{};
+ for (var module in _modules) {
+ // We are currently 'guessing' module names from js module paths,
+ // which is not reliable.
+ // example:
+ // module: /web/main.dart.lib.js'
+ // name: web/main.dart
+ // path: web/main.dart.lib
+ // Note: This is a temporary workaround until we solve inconsistencies
+ // in different configurations by introducing module name and path
+ // translation interfaces between compiler, asset server, and the
+ // debugger.
+ // TODO(annagrin): module interface
+ // [issue #910](https://github.com/dart-lang/webdev/issues/910)
+ module = module.startsWith('/') ? module.substring(1) : module;
+ var name = module.replaceAll('.lib.js', '');
+ var path = module.replaceAll('.js', '');
+ modulePaths[name] = path;
+ }
+ return modulePaths;
+ }
+}
diff --git a/dwds/lib/src/servers/extension_debugger.dart b/dwds/lib/src/servers/extension_debugger.dart
index 57641d0..30e19bc 100644
--- a/dwds/lib/src/servers/extension_debugger.dart
+++ b/dwds/lib/src/servers/extension_debugger.dart
@@ -86,7 +86,8 @@
}
} else if (message is DevToolsRequest) {
instanceId = message.instanceId;
- _executionContext = ExecutionContext(message.contextId, this);
+ _executionContext =
+ RemoteDebuggerExecutionContext(message.contextId, this);
_devToolsRequestController.sink.add(message);
}
}, onError: (_) {
diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart
index 59540dc..2d2fbf8 100644
--- a/dwds/lib/src/services/expression_evaluator.dart
+++ b/dwds/lib/src/services/expression_evaluator.dart
@@ -106,14 +106,18 @@
for (var serverPath in modules.keys) {
var module = modules[serverPath];
var library = await _modules.libraryForSource(serverPath);
- var name = pathToJSIdentifier(library.replaceAll('.dart', ''));
+ var libraryPath = library.path;
+ if (library.scheme == 'package') {
+ libraryPath = libraryPath.split('/').skip(1).join('/');
+ }
+ var name = pathToJSIdentifier(libraryPath.replaceAll('.dart', ''));
jsModules[name] = module;
}
_printTrace('Expression evaluator: js modules: $jsModules');
var compilationResult = await _compiler.compileExpressionToJs(
isolateId,
- libraryUri,
+ libraryUri.toString(),
dartLocation.line,
dartLocation.column,
jsModules,
@@ -138,15 +142,16 @@
// [issue 40449](https://github.com/dart-lang/sdk/issues/40449)
var error = jsExpression;
- if (jsExpression.startsWith('[')) {
- jsExpression = jsExpression.substring(1);
+ if (error.startsWith('[')) {
+ error = error.substring(1);
}
- if (jsExpression.endsWith(']')) {
- jsExpression = jsExpression.substring(0, jsExpression.lastIndexOf(']'));
+ if (error.endsWith(']')) {
+ error = error.substring(0, error.lastIndexOf(']'));
}
- jsExpression.replaceAll(
- r'org-dartlang-debug:synthetic_debug_expression:', '');
+ error = error.replaceAll(
+ RegExp('org-dartlang-debug:synthetic_debug_expression:.* Error: '),
+ '');
return _createError('Compilation error', error);
}
@@ -202,7 +207,7 @@
}
}
- var scopeChain = frame.getScopeChain();
+ var scopeChain = List<WipScope>.from(frame.getScopeChain()).reversed;
// skip library and main scope
for (var scope in scopeChain.skip(2)) {
diff --git a/dwds/mono_pkg.yaml b/dwds/mono_pkg.yaml
index e764dcc..90daa11 100644
--- a/dwds/mono_pkg.yaml
+++ b/dwds/mono_pkg.yaml
@@ -16,8 +16,8 @@
- unit_test:
- test: -x frontend-server
dart: 2.6.0
- - test:
+ - test: -x frontend-server
dart: dev
- - test:
+ - test: -x frontend-server
os: windows
dart: 2.6.0
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index 7acbe5b..8003f2a 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -17,6 +17,7 @@
# devtools_server indirectly depends on devtools so keep this around.
devtools: ^0.2.0
devtools_server: ^0.2.0
+ file: ^5.1.0
http: ^0.12.0
http_multi_server: ^2.0.0
logging: ^0.11.3
@@ -34,7 +35,7 @@
sse: ^3.2.0
vm_service: 3.0.0+1
web_socket_channel: ^1.0.0
- webkit_inspection_protocol: '>=0.4.0 <0.6.0'
+ webkit_inspection_protocol: '>=0.5.0+1 <0.6.0'
dev_dependencies:
args: ^1.0.0
@@ -45,6 +46,9 @@
build_web_compilers: '>=1.0.0 <3.0.0'
built_value_generator: '>=6.4.0 <8.0.0'
graphs: ^0.2.0
+ frontend_server_common:
+ path: ../frontend_server_common
+ sync_http: 0.2.0
js: ^0.6.1
pubspec_parse: ^0.1.5
stream_channel: ^2.0.0
diff --git a/dwds/test/build_daemon_breakpoint_test.dart b/dwds/test/build_daemon_breakpoint_test.dart
new file mode 100644
index 0000000..470d377
--- /dev/null
+++ b/dwds/test/build_daemon_breakpoint_test.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, 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.
+
+@TestOn('vm')
+import 'dart:async';
+
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+import 'fixtures/context.dart';
+
+final context = TestContext(
+ directory: '../fixtures/_testPackage',
+ entry: '../fixtures/_testPackage/web/main.dart',
+ path: 'index.html',
+ pathToServe: 'web');
+
+ChromeProxyService get service =>
+ fetchChromeProxyService(context.debugConnection);
+WipConnection get tabConnection => context.tabConnection;
+
+void main() {
+ group('shared context', () {
+ setUpAll(() async {
+ await context.setUp();
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ group('breakpoint', () {
+ VM vm;
+ Isolate isolate;
+ ScriptList scripts;
+ ScriptRef mainScript;
+ Stream<Event> stream;
+
+ setUp(() async {
+ vm = await service.getVM();
+ isolate = await service.getIsolate(vm.isolates.first.id);
+ scripts = await service.getScripts(isolate.id);
+
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+
+ mainScript = scripts.scripts
+ .firstWhere((each) => each.uri.contains('main.dart'));
+ });
+
+ tearDown(() async {
+ await service.resume(isolate.id);
+ });
+
+ test('set breakpoint', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('set breakpoint again', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+ });
+ });
+}
diff --git a/dwds/test/build_daemon_nested_directory_breakpoint_test.dart b/dwds/test/build_daemon_nested_directory_breakpoint_test.dart
new file mode 100644
index 0000000..0e51764
--- /dev/null
+++ b/dwds/test/build_daemon_nested_directory_breakpoint_test.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, 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.
+
+@TestOn('vm')
+import 'dart:async';
+
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+import 'fixtures/context.dart';
+
+final context = TestContext(
+ directory: '../fixtures/_test',
+ entry: '../fixtures/_test/example/hello_world/main.dart',
+ path: 'hello_world/index.html',
+ pathToServe: 'example');
+
+ChromeProxyService get service =>
+ fetchChromeProxyService(context.debugConnection);
+WipConnection get tabConnection => context.tabConnection;
+
+void main() {
+ group('shared context with evaluation', () {
+ setUpAll(() async {
+ await context.setUp();
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ group('breakpoint', () {
+ VM vm;
+ Isolate isolate;
+ ScriptList scripts;
+ ScriptRef mainScript;
+ Stream<Event> stream;
+
+ setUp(() async {
+ vm = await service.getVM();
+ isolate = await service.getIsolate(vm.isolates.first.id);
+ scripts = await service.getScripts(isolate.id);
+
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+
+ mainScript = scripts.scripts
+ .firstWhere((each) => each.uri.contains('main.dart'));
+ });
+
+ tearDown(() async {
+ await service.resume(isolate.id);
+ });
+
+ test('set breakpoint', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('set breakpoint again', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+ });
+ });
+}
diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart
index dbed7e6..31a8cd7 100644
--- a/dwds/test/chrome_proxy_service_test.dart
+++ b/dwds/test/chrome_proxy_service_test.dart
@@ -19,7 +19,6 @@
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import 'fixtures/context.dart';
-import 'fixtures/fakes.dart';
final context = TestContext();
ChromeProxyService get service =>
@@ -1153,17 +1152,9 @@
});
});
- group('shared context with evaluation', () {
+ group('shared context with fake evaluation', () {
setUpAll(() async {
- await context.setUp(
- // TODO(annagrin): use actual compilation via frontend when supported
- //
- // This group of tests currently omits actual compilation
- // from dart to js, so expressions given to evaluateInFrame
- // in all tests below are written in javascript.
- // Note frontend server has compilation tests, so the tests
- // below make sure that the rest of the evaluation logic works.
- expressionCompiler: FakeExpressionCompiler());
+ await context.setUp(useFakeExpressionCompiler: true);
});
tearDownAll(() async {
diff --git a/dwds/test/debugging/modules_test.dart b/dwds/test/debugging/modules_test.dart
index 1d71b15..f75d631 100644
--- a/dwds/test/debugging/modules_test.dart
+++ b/dwds/test/debugging/modules_test.dart
@@ -3,40 +3,46 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:dwds/src/debugging/modules.dart';
+
import 'package:test/test.dart';
+import '../fixtures/fakes.dart';
+
void main() {
group('noteModule', () {
+ var webkitDebugger = FakeWebkitDebugger();
+ var executionContext = FakeExecutionContext();
+
test('handles Google3 URLs', () async {
- var modules = Modules(null, '', null);
+ var modules = Modules(webkitDebugger, '', executionContext)..initialize();
await modules.noteModule('foo/google3/bar/blah.ddc.js', '10');
expect(await modules.scriptIdForModule('bar/blah'), equals('10'));
- });
+ }, skip: '[issue #917](https://github.com/dart-lang/webdev/issues/917)');
test('ignores non-module paths', () async {
- var modules = Modules(null, '', null);
+ var modules = Modules(webkitDebugger, '', executionContext)..initialize();
await modules.noteModule('foo/bar', '10');
expect(await modules.scriptIdForModule('foo/bar'), isNull);
});
test('rewrites third_party Google3 paths', () async {
- var modules = Modules(null, '', null);
+ var modules = Modules(webkitDebugger, '', executionContext)..initialize();
await modules.noteModule('/third_party/dart/test/lib/test.ddc.js', '10');
expect(
await modules.scriptIdForModule('packages/test/test'), equals('10'));
- });
+ }, skip: '[issue #917](https://github.com/dart-lang/webdev/issues/917)');
test('handles package paths', () async {
- var modules = Modules(null, '', null);
+ var modules = Modules(webkitDebugger, '', executionContext)..initialize();
await modules.noteModule('/packages/shelf/shelf.ddc.js', '10');
expect(await modules.scriptIdForModule('packages/shelf/shelf'),
equals('10'));
});
test('handles absolute paths', () async {
- var modules = Modules(null, '', null);
+ var modules = Modules(webkitDebugger, '', executionContext)..initialize();
await modules.noteModule('/foo/bar.ddc.js', '10');
expect(await modules.scriptIdForModule('foo/bar'), equals('10'));
- });
+ }, skip: '[issue #917](https://github.com/dart-lang/webdev/issues/917)');
});
}
diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart
index 31239f4..01c0f13 100644
--- a/dwds/test/fixtures/context.dart
+++ b/dwds/test/fixtures/context.dart
@@ -14,31 +14,42 @@
import 'package:dwds/src/services/expression_compiler.dart';
import 'package:dwds/src/utilities/dart_uri.dart';
import 'package:dwds/src/utilities/shared.dart';
+import 'package:dwds/src/loaders/frontend_server_require.dart';
+import 'package:frontend_server_common/src/resident_runner.dart';
+import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart';
+import 'package:shelf_proxy/shelf_proxy.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webdriver/io.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+import 'fakes.dart';
import 'server.dart';
import 'utilities.dart';
final _batExt = Platform.isWindows ? '.bat' : '';
final _exeExt = Platform.isWindows ? '.exe' : '';
+enum CompilationMode { buildDaemon, frontendServer }
+
class TestContext {
String appUrl;
WipConnection tabConnection;
WipConnection extensionConnection;
TestServer testServer;
BuildDaemonClient daemonClient;
+ ResidentWebRunner webRunner;
WebDriver webDriver;
Process chromeDriver;
AppConnection appConnection;
DebugConnection debugConnection;
WebkitDebugger webkitDebugger;
int port;
+ Directory _outputDir;
File _entryFile;
+ String _packagesFilePath;
String _entryContents;
/// Top level directory in which we run the test server..
@@ -52,16 +63,23 @@
TestContext(
{String directory,
+ String entry,
this.path = 'hello_world/index.html',
this.pathToServe = 'example'}) {
- workingDirectory = p.normalize(p.absolute(
- directory ?? p.relative('../fixtures/_test', from: p.current)));
+ var relativeDirectory = p.join('..', 'fixtures', '_test');
+
+ var relativeEntry = p.join(
+ '..', 'fixtures', '_test', 'example', 'append_body', 'main.dart');
+
+ workingDirectory = p.normalize(p
+ .absolute(directory ?? p.relative(relativeDirectory, from: p.current)));
+
DartUri.currentDirectory = workingDirectory;
- _entryFile = File(p.absolute(p.join(
- p.relative('../fixtures/_test', from: p.current),
- 'example',
- 'append_body',
- 'main.dart')));
+ _packagesFilePath = p.join(workingDirectory, '.packages');
+
+ _entryFile = File(p.normalize(
+ p.absolute(entry ?? p.relative(relativeEntry, from: p.current))));
+
_entryContents = _entryFile.readAsStringSync();
}
@@ -75,20 +93,21 @@
bool waitToDebug,
UrlEncoder urlEncoder,
bool restoreBreakpoints,
- bool useBuildDaemon,
- ExpressionCompiler expressionCompiler}) async {
+ CompilationMode compilationMode,
+ bool useFakeExpressionCompiler,
+ LogWriter logWriter}) async {
reloadConfiguration ??= ReloadConfiguration.none;
serveDevTools ??= false;
enableDebugExtension ??= false;
autoRun ??= true;
enableDebugging ??= true;
waitToDebug ??= false;
- useBuildDaemon ??= true;
+ compilationMode ??= CompilationMode.buildDaemon;
+ useFakeExpressionCompiler ??= false;
+ logWriter ??= (Level level, String message) => printOnFailure(message);
- // TODO(grouma) - Support testing with the Frontend Server.
- if (!useBuildDaemon) {
- throw StateError('Only Build Daemon is supported with testing.');
- }
+ var systemTempDir = Directory.systemTemp;
+ _outputDir = systemTempDir.createTempSync('foo bar');
var chromeDriverPort = await findUnusedPort();
var chromeDriverUrlBase = 'wd/hub';
@@ -109,16 +128,73 @@
await Process.run('pub$_batExt', ['upgrade'],
workingDirectory: workingDirectory);
- daemonClient = await connectClient(
- workingDirectory, [], (log) => printOnFailure(log.toString()));
- daemonClient.registerBuildTarget(
- DefaultBuildTarget((b) => b..target = pathToServe));
- daemonClient.startBuild();
+ ExpressionCompiler expressionCompiler;
+ AssetReader assetReader;
+ Handler assetHandler;
+ Stream<BuildResults> buildResults;
+ RequireStrategy requireStrategy;
- await daemonClient.buildResults
- .firstWhere((results) => results.results
- .any((result) => result.status == BuildStatus.succeeded))
- .timeout(const Duration(seconds: 60));
+ switch (compilationMode) {
+ case CompilationMode.buildDaemon:
+ {
+ daemonClient = await connectClient(
+ workingDirectory, [], (log) => printOnFailure(log.toString()));
+ daemonClient.registerBuildTarget(
+ DefaultBuildTarget((b) => b..target = pathToServe));
+ daemonClient.startBuild();
+
+ await daemonClient.buildResults
+ .firstWhere((results) => results.results
+ .any((result) => result.status == BuildStatus.succeeded))
+ .timeout(const Duration(seconds: 60));
+
+ var assetServerPort = daemonPort(workingDirectory);
+ assetHandler =
+ proxyHandler('http://localhost:$assetServerPort/$pathToServe/');
+ assetReader = ProxyServerAssetReader(assetServerPort, logWriter,
+ root: pathToServe);
+ requireStrategy = BuildRunnerRequireStrategyProvider(
+ assetHandler, reloadConfiguration)
+ .strategy;
+
+ buildResults = daemonClient.buildResults;
+ }
+ break;
+ case CompilationMode.frontendServer:
+ {
+ var fileSystemRoot = p.dirname(_packagesFilePath);
+ var entryPath = _entryFile.path.substring(fileSystemRoot.length + 1);
+ webRunner = ResidentWebRunner(
+ entryPath,
+ urlEncoder,
+ fileSystemRoot,
+ _packagesFilePath,
+ [fileSystemRoot],
+ 'org-dartlang-app',
+ _outputDir.path,
+ logWriter);
+
+ var assetServerPort = await findUnusedPort();
+ await webRunner.run(hostname, assetServerPort, pathToServe);
+
+ expressionCompiler = webRunner.expressionCompiler;
+ assetReader = webRunner.devFS.assetServer;
+ assetHandler = webRunner.devFS.assetServer.handleRequest;
+
+ requireStrategy = FrontendServerRequireStrategyProvider(
+ webRunner.modules, reloadConfiguration)
+ .strategy;
+
+ buildResults = const Stream<BuildResults>.empty();
+ }
+ break;
+ default:
+ throw Exception('Unsupported compilation mode: $compilationMode');
+ }
+
+ expressionCompiler = useFakeExpressionCompiler
+ ? FakeExpressionCompiler()
+ : expressionCompiler;
var debugPort = await findUnusedPort();
// If the environment variable DWDS_DEBUG_CHROME is set to the string true
@@ -148,18 +224,20 @@
testServer = await TestServer.start(
hostname,
port,
- daemonPort(workingDirectory),
+ assetHandler,
+ assetReader,
+ requireStrategy,
pathToServe,
- daemonClient.buildResults,
+ buildResults,
() async => connection,
- reloadConfiguration,
serveDevTools,
enableDebugExtension,
autoRun,
enableDebugging,
urlEncoder,
restoreBreakpoints,
- expressionCompiler);
+ expressionCompiler,
+ logWriter);
appUrl = 'http://localhost:$port/$path';
await webDriver.get(appUrl);
@@ -191,7 +269,9 @@
DartUri.currentDirectory = p.current;
_entryFile.writeAsStringSync(_entryContents);
await daemonClient?.close();
+ await webRunner?.stop();
await testServer?.stop();
+ await _outputDir?.delete(recursive: true);
}
Future<void> changeInput() async {
diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart
index e23fc3c..b97ff76 100644
--- a/dwds/test/fixtures/fakes.dart
+++ b/dwds/test/fixtures/fakes.dart
@@ -6,6 +6,7 @@
import 'package:async/src/stream_sink_transformer.dart';
import 'package:dwds/dwds.dart';
+import 'package:dwds/src/debugging/execution_context.dart';
import 'package:dwds/src/debugging/inspector.dart';
import 'package:dwds/src/debugging/instance.dart';
import 'package:dwds/src/debugging/webkit_debugger.dart';
@@ -182,6 +183,30 @@
'result': {'result': <String, dynamic>{}}
});
}
+ if (method == 'Runtime.evaluate') {
+ // Fake response adapted from modules query at google3
+ return WipResponse({
+ 'id': 42,
+ 'result': {
+ 'result': <String, dynamic>{
+ 'type': 'object',
+ 'value': <String, dynamic>{
+ // dart source Uri : js module name
+ 'dart:io': 'dart_sdk',
+ 'google3:///dart/tools/iblaze/web/hello_world.dart':
+ 'dart/tools/iblaze/web/hello_world_angular_library',
+ 'package:ads.acx2.rpc.proto_mixin/ess_proto_mixin.dart':
+ 'ads/acx2/rpc/proto_mixin/lib/proto_mixin',
+ 'package:collection/collection.dart: ':
+ 'third_party/dart/collection/lib/collection',
+ 'package:collection/src/algorithms.dart':
+ 'third_party/dart/collection/lib/collection',
+ 'package:shelf/shelf.dart': 'packages/shelf/shelf',
+ }
+ }
+ }
+ });
+ }
return null;
}
@@ -216,6 +241,16 @@
Future<void> enablePage() => null;
}
+/// Fake execution context that is needed for id only
+class FakeExecutionContext extends ExecutionContext {
+ @override
+ Future<int> get id async {
+ return 0;
+ }
+
+ FakeExecutionContext();
+}
+
/// Fake expression compiler that simply passes expression through,
/// without actual compilation
class FakeExpressionCompiler implements ExpressionCompiler {
diff --git a/dwds/test/fixtures/server.dart b/dwds/test/fixtures/server.dart
index a3bf5ca..324db3c 100644
--- a/dwds/test/fixtures/server.dart
+++ b/dwds/test/fixtures/server.dart
@@ -8,14 +8,22 @@
import 'package:dwds/data/build_result.dart';
import 'package:dwds/dwds.dart';
import 'package:dwds/src/services/expression_compiler.dart';
+import 'package:dwds/src/utilities/shared.dart';
import 'package:http_multi_server/http_multi_server.dart';
-import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
-import 'package:shelf_proxy/shelf_proxy.dart';
-import 'package:test/test.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+Handler _interceptFavicon(Handler handler) {
+ return (request) async {
+ if (request.url.pathSegments.isNotEmpty &&
+ request.url.pathSegments.last == 'favicon.ico') {
+ return Response.ok('');
+ }
+ return handler(request);
+ };
+}
+
class TestServer {
final HttpServer _server;
final String target;
@@ -49,20 +57,24 @@
static Future<TestServer> start(
String hostname,
int port,
- int assetServerPort,
+ Handler assetHandler,
+ AssetReader assetReader,
+ RequireStrategy strategy,
String target,
Stream<daemon.BuildResults> buildResults,
Future<ChromeConnection> Function() chromeConnection,
- ReloadConfiguration reloadConfiguration,
bool serveDevTools,
bool enableDebugExtension,
bool autoRun,
bool enableDebugging,
UrlEncoder urlEncoder,
bool restoreBreakpoints,
- ExpressionCompiler expressionCompiler) async {
+ ExpressionCompiler expressionCompiler,
+ LogWriter logWriter) async {
var pipeline = const Pipeline();
+ pipeline = pipeline.addMiddleware(_interceptFavicon);
+
var filteredBuildResults = buildResults.asyncMap<BuildResult>((results) {
var result =
results.results.firstWhere((result) => result.target == target);
@@ -77,22 +89,12 @@
throw StateError('Unexpected Daemon build result: $result');
});
- var logWriter = (Level level, String message) => printOnFailure(message);
-
- var assetReader =
- ProxyServerAssetReader(assetServerPort, logWriter, root: target);
-
- var assetHandler =
- proxyHandler('http://localhost:$assetServerPort/$target/');
-
var dwds = await Dwds.start(
assetReader: assetReader,
buildResults: filteredBuildResults,
chromeConnection: chromeConnection,
logWriter: logWriter,
- loadStrategy:
- BuildRunnerRequireStrategyProvider(assetHandler, reloadConfiguration)
- .strategy,
+ loadStrategy: strategy,
serveDevTools: serveDevTools,
enableDebugExtension: enableDebugExtension,
enableDebugging: enableDebugging,
diff --git a/dwds/test/fixtures/utilities.dart b/dwds/test/fixtures/utilities.dart
index dd2f32a..904056e 100644
--- a/dwds/test/fixtures/utilities.dart
+++ b/dwds/test/fixtures/utilities.dart
@@ -32,6 +32,7 @@
return aboveExecutable;
})();
+final String dartSdkPath = _sdkDir;
final String dartPath = p.join(_sdkDir, 'bin', 'dart');
final String pubSnapshot =
p.join(_sdkDir, 'bin', 'snapshots', 'pub.dart.snapshot');
diff --git a/dwds/test/frontend_server_breakpoint_test.dart b/dwds/test/frontend_server_breakpoint_test.dart
new file mode 100644
index 0000000..406377a
--- /dev/null
+++ b/dwds/test/frontend_server_breakpoint_test.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, 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.
+
+@Tags(['frontend-server'])
+@TestOn('vm')
+import 'dart:async';
+
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+import 'fixtures/context.dart';
+
+final context = TestContext(
+ directory: '../fixtures/_testPackage',
+ entry: '../fixtures/_testPackage/web/main.dart',
+ path: 'index.html',
+ pathToServe: 'web');
+
+ChromeProxyService get service =>
+ fetchChromeProxyService(context.debugConnection);
+WipConnection get tabConnection => context.tabConnection;
+
+void main() {
+ group('shared context with evaluation', () {
+ setUpAll(() async {
+ await context.setUp(compilationMode: CompilationMode.frontendServer);
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ group('breakpoint', () {
+ VM vm;
+ Isolate isolate;
+ ScriptList scripts;
+ ScriptRef mainScript;
+ Stream<Event> stream;
+
+ setUp(() async {
+ vm = await service.getVM();
+ isolate = await service.getIsolate(vm.isolates.first.id);
+ scripts = await service.getScripts(isolate.id);
+
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+
+ mainScript = scripts.scripts
+ .firstWhere((each) => each.uri.contains('main.dart'));
+ });
+
+ tearDown(() async {
+ await service.resume(isolate.id);
+ });
+
+ test('set breakpoint', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('set breakpoint again', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ expect(bp, isNotNull);
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+ });
+ });
+}
diff --git a/dwds/test/frontend_server_evaluate_test.dart b/dwds/test/frontend_server_evaluate_test.dart
new file mode 100644
index 0000000..5020056
--- /dev/null
+++ b/dwds/test/frontend_server_evaluate_test.dart
@@ -0,0 +1,168 @@
+// Copyright (c) 2019, 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.
+
+@Tags(['frontend-server'])
+@TestOn('vm')
+import 'dart:async';
+
+import 'package:dwds/src/connections/debug_connection.dart';
+import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+import 'fixtures/context.dart';
+
+final context = TestContext(
+ directory: '../fixtures/_testPackage',
+ entry: '../fixtures/_testPackage/web/main.dart',
+ path: 'index.html',
+ pathToServe: 'web');
+
+ChromeProxyService get service =>
+ fetchChromeProxyService(context.debugConnection);
+WipConnection get tabConnection => context.tabConnection;
+
+void main() {
+ group('shared context with evaluation', () {
+ setUpAll(() async {
+ await context.setUp(compilationMode: CompilationMode.frontendServer);
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ group('evaluateInFrame', () {
+ VM vm;
+ Isolate isolate;
+ ScriptList scripts;
+ ScriptRef mainScript;
+ Stream<Event> stream;
+
+ setUp(() async {
+ vm = await service.getVM();
+ isolate = await service.getIsolate(vm.isolates.first.id);
+ scripts = await service.getScripts(isolate.id);
+
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+
+ mainScript = scripts.scripts
+ .firstWhere((each) => each.uri.contains('main.dart'));
+ });
+
+ tearDown(() async {
+ await service.resume(isolate.id);
+ });
+
+ test('local', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ var event = await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ var result = await service.evaluateInFrame(
+ isolate.id, event.topFrame.index, 'local');
+
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', '42'));
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('global', () async {
+ var line = await context.findBreakpointLine(
+ 'printGlobal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ var event = await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ var result = await service.evaluateInFrame(
+ isolate.id, event.topFrame.index, 'valueFromTestPackage');
+
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', '3'));
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('call core function', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ var event = await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ var result = await service.evaluateInFrame(
+ isolate.id, event.topFrame.index, 'print(local)');
+
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', 'null'));
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('call library function', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ var event = await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ var result = await service.evaluateInFrame(
+ isolate.id, event.topFrame.index, 'testLibraryFunction(local)');
+
+ expect(
+ result,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString, 'valueAsString', '42'));
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+
+ test('error', () async {
+ var line = await context.findBreakpointLine(
+ 'printLocal', isolate.id, mainScript);
+ var bp = await service.addBreakpointWithScriptUri(
+ isolate.id, mainScript.uri, line);
+
+ var event = await stream.firstWhere(
+ (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+ var error = await service.evaluateInFrame(
+ isolate.id, event.topFrame.index, 'typo');
+
+ expect(
+ error,
+ const TypeMatcher<InstanceRef>().having(
+ (instance) => instance.valueAsString,
+ 'valueAsString',
+ 'Compilation error: Getter not found: \'typo\'.\ntypo\n^^^^'));
+
+ // Remove breakpoint so it doesn't impact other tests.
+ await service.removeBreakpoint(isolate.id, bp.id);
+ });
+ });
+ });
+}
diff --git a/dwds/test/readers/proxy_server_asset_reader_test.dart b/dwds/test/readers/proxy_server_asset_reader_test.dart
index 5365d16..0bae993 100644
--- a/dwds/test/readers/proxy_server_asset_reader_test.dart
+++ b/dwds/test/readers/proxy_server_asset_reader_test.dart
@@ -11,7 +11,7 @@
final context = TestContext();
ProxyServerAssetReader assetReader;
setUpAll(() async {
- await context.setUp(useBuildDaemon: true);
+ await context.setUp();
assetReader = context.testServer.assetReader as ProxyServerAssetReader;
});
diff --git a/fixtures/_testPackage/lib/test_library.dart b/fixtures/_testPackage/lib/test_library.dart
index 2eecb43..e9a850d 100644
--- a/fixtures/_testPackage/lib/test_library.dart
+++ b/fixtures/_testPackage/lib/test_library.dart
@@ -3,3 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
int valueFromTestPackage = 3;
+
+int testLibraryFunction(int formal) {
+ return formal;
+}
diff --git a/fixtures/_testPackage/web/main.dart b/fixtures/_testPackage/web/main.dart
index 57173c9..5c1537d 100644
--- a/fixtures/_testPackage/web/main.dart
+++ b/fixtures/_testPackage/web/main.dart
@@ -15,5 +15,16 @@
print(valueFromTestPackage);
});
+ // for evaluation
+ Timer.periodic(const Duration(seconds: 1), (_) {
+ printSomething();
+ });
+
document.body.appendText(concatenate('Program', ' is running!'));
}
+
+void printSomething() {
+ var local = 42;
+ print('Local is: $local'); // Breakpoint: printLocal
+ print(valueFromTestPackage); // Breakpoint: printGlobal
+}
diff --git a/frontend_server_common/CHANGELOG.md b/frontend_server_common/CHANGELOG.md
new file mode 100644
index 0000000..51d1510
--- /dev/null
+++ b/frontend_server_common/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+- Initial version
diff --git a/frontend_server_common/LICENSE b/frontend_server_common/LICENSE
new file mode 100644
index 0000000..18daf2b
--- /dev/null
+++ b/frontend_server_common/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2020, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/frontend_server_common/README.md b/frontend_server_common/README.md
new file mode 100644
index 0000000..ab54d27
--- /dev/null
+++ b/frontend_server_common/README.md
@@ -0,0 +1,14 @@
+Dart Web Developer Service
+
+__*Note: Under heavy development.*__
+
+This code is an edited copy of flutter code used for setting up frontend server
+and components that are needed to communicate to Chrome and dwds:
+
+- frontend server client
+- web runner
+- dev fs
+- asset server
+
+This eventually will transform into common code that both flutter and dwds use
+for better integration.
\ No newline at end of file
diff --git a/frontend_server_common/lib/src/asset.dart b/frontend_server_common/lib/src/asset.dart
new file mode 100644
index 0000000..28d8faf
--- /dev/null
+++ b/frontend_server_common/lib/src/asset.dart
@@ -0,0 +1,62 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests,
+// most functionality removed
+
+import 'dart:async';
+
+import 'devfs_content.dart';
+
+const AssetBundleFactory _manifestFactory = _MockManifestAssetBundleFactory();
+
+const String defaultManifestPath = 'pubspec.yaml';
+
+/// Injected factory class for spawning [AssetBundle] instances.
+abstract class AssetBundleFactory {
+ static AssetBundleFactory get defaultInstance => _manifestFactory;
+
+ /// Creates a new [AssetBundle].
+ AssetBundle createBundle();
+}
+
+abstract class AssetBundle {
+ Map<String, DevFSContent> get entries;
+
+ /// Returns 0 for success; non-zero for failure.
+ Future<int> build({
+ String manifestPath = defaultManifestPath,
+ String assetDirPath,
+ String packagesPath,
+ bool includeDefaultFonts = true,
+ bool reportLicensedPackages = false,
+ });
+}
+
+class _MockManifestAssetBundleFactory implements AssetBundleFactory {
+ const _MockManifestAssetBundleFactory();
+
+ @override
+ AssetBundle createBundle() => _MockManifestAssetBundle();
+}
+
+class _MockManifestAssetBundle implements AssetBundle {
+ /// Constructs an [_MockManifestAssetBundle] that gathers the set of assets from the
+ /// pubspec.yaml manifest.
+ _MockManifestAssetBundle();
+
+ @override
+ final Map<String, DevFSContent> entries = <String, DevFSContent>{};
+
+ @override
+ Future<int> build({
+ String manifestPath = defaultManifestPath,
+ String assetDirPath,
+ String packagesPath,
+ bool includeDefaultFonts = true,
+ bool reportLicensedPackages = false,
+ }) async {
+ return 0;
+ }
+}
diff --git a/frontend_server_common/lib/src/asset_server.dart b/frontend_server_common/lib/src/asset_server.dart
new file mode 100644
index 0000000..5fab1cc
--- /dev/null
+++ b/frontend_server_common/lib/src/asset_server.dart
@@ -0,0 +1,281 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:dwds/dwds.dart';
+import 'package:file/file.dart';
+import 'package:mime/mime.dart' as mime;
+// ignore: deprecated_member_use
+import 'package:package_config/discovery.dart';
+// ignore: deprecated_member_use
+import 'package:package_config/packages.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf;
+import 'package:logging/logging.dart';
+
+import 'utilities.dart';
+
+class TestAssetServer implements AssetReader {
+ TestAssetServer(this._root, this._httpServer, this._packages,
+ this.internetAddress, this._fileSystem, this._logWriter);
+
+ // Fallback to "application/octet-stream" on null which
+ // makes no claims as to the structure of the data.
+ static const String _defaultMimeType = 'application/octet-stream';
+ final FileSystem _fileSystem;
+
+ void _printTrace(String message) {
+ _logWriter(Level.INFO, message);
+ }
+
+ /// Start the web asset server on a [hostname] and [port].
+ ///
+ /// Unhandled exceptions will throw a exception with the error and stack
+ /// trace.
+ static Future<TestAssetServer> start(
+ FileSystem fileSystem,
+ String root,
+ String hostname,
+ int port,
+ UrlEncoder urlTunneller,
+ LogWriter logWriter) async {
+ var address = (await InternetAddress.lookup(hostname)).first;
+ var httpServer = await HttpServer.bind(address, port);
+ var packages = await loadPackagesFile(Uri.base.resolve('.packages'),
+ loader: (Uri uri) => fileSystem.file(uri).readAsBytes());
+ var server = TestAssetServer(
+ root, httpServer, packages, address, fileSystem, logWriter);
+
+ return server;
+ }
+
+ final String _root;
+ final HttpServer _httpServer;
+ // If holding these in memory is too much overhead, this can be switched to a
+ // RandomAccessFile and read on demand.
+ final Map<String, Uint8List> _files = <String, Uint8List>{};
+ final Map<String, Uint8List> _sourcemaps = <String, Uint8List>{};
+ // ignore: deprecated_member_use
+ final Packages _packages;
+ final InternetAddress internetAddress;
+ final LogWriter _logWriter;
+
+ Uint8List getFile(String path) => _files[path];
+
+ Uint8List getSourceMap(String path) => _sourcemaps[path];
+
+ // handle requests for JavaScript source, dart sources maps, or asset files.
+ Future<shelf.Response> handleRequest(shelf.Request request) async {
+ var headers = <String, String>{};
+
+ // Index file is serverd from the _root directory
+ if (request.url.path.endsWith('index.html')) {
+ final indexFile = _fileSystem.currentDirectory
+ .childDirectory(_root)
+ .childFile('index.html');
+ if (indexFile.existsSync()) {
+ headers[HttpHeaders.contentTypeHeader] = 'text/html';
+ headers[HttpHeaders.contentLengthHeader] =
+ indexFile.lengthSync().toString();
+ return shelf.Response.ok(indexFile.openRead(), headers: headers);
+ }
+ return shelf.Response.notFound('');
+ }
+
+ // NOTE: shelf removes leading `/` for some reason.
+ var requestPath = request.url.path.startsWith('/')
+ ? request.url.path
+ : '/${request.url.path}';
+
+ // If this is a JavaScript file, it must be in the in-memory cache.
+ // Attempt to look up the file by URI.
+ if (_files.containsKey(requestPath)) {
+ final List<int> bytes = getFile(requestPath);
+ headers[HttpHeaders.contentLengthHeader] = bytes.length.toString();
+ headers[HttpHeaders.contentTypeHeader] = 'application/javascript';
+ return shelf.Response.ok(bytes, headers: headers);
+ }
+ // If this is a sourcemap file, then it might be in the in-memory cache.
+ // Attempt to lookup the file by URI.
+ var sourceMapPath = _resolvePath(requestPath);
+ if (_sourcemaps.containsKey(sourceMapPath)) {
+ final List<int> bytes = getSourceMap(requestPath);
+ headers[HttpHeaders.contentLengthHeader] = bytes.length.toString();
+ headers[HttpHeaders.contentTypeHeader] = 'application/json';
+ return shelf.Response.ok(bytes, headers: headers);
+ }
+
+ var file = _resolveDartFile(requestPath);
+ if (!file.existsSync()) {
+ return shelf.Response.notFound('');
+ }
+
+ var length = file.lengthSync();
+ // Attempt to determine the file's mime type. if this is not provided some
+ // browsers will refuse to render images/show video et cetera. If the tool
+ // cannot determine a mime type, fall back to application/octet-stream.
+ String mimeType;
+ if (length >= 12) {
+ mimeType = mime.lookupMimeType(
+ file.path,
+ headerBytes: await file.openRead(0, 12).first,
+ );
+ }
+ mimeType ??= _defaultMimeType;
+ headers[HttpHeaders.contentLengthHeader] = length.toString();
+ headers[HttpHeaders.contentTypeHeader] = mimeType;
+ return shelf.Response.ok(file.openRead(), headers: headers);
+ }
+
+ /// Tear down the http server running.
+ Future<void> dispose() {
+ return _httpServer.close();
+ }
+
+ /// Write a single file into the in-memory cache.
+ void writeFile(String filePath, String contents) {
+ _files[filePath] = Uint8List.fromList(utf8.encode(contents));
+ }
+
+ /// Update the in-memory asset server with the provided source and manifest files.
+ ///
+ /// Returns a list of updated modules.
+ List<String> write(File codeFile, File manifestFile, File sourcemapFile) {
+ var modules = <String>[];
+ var codeBytes = codeFile.readAsBytesSync();
+ var sourcemapBytes = sourcemapFile.readAsBytesSync();
+ var manifest =
+ castStringKeyedMap(json.decode(manifestFile.readAsStringSync()));
+ for (var filePath in manifest.keys) {
+ if (filePath == null) {
+ _printTrace('Invalid manfiest file: $filePath');
+ continue;
+ }
+ var offsets = castStringKeyedMap(manifest[filePath]);
+ var codeOffsets = (offsets['code'] as List<dynamic>).cast<int>();
+ var sourcemapOffsets =
+ (offsets['sourcemap'] as List<dynamic>).cast<int>();
+ if (codeOffsets.length != 2 || sourcemapOffsets.length != 2) {
+ _printTrace('Invalid manifest byte offsets: $offsets');
+ continue;
+ }
+
+ var codeStart = codeOffsets[0];
+ var codeEnd = codeOffsets[1];
+ if (codeStart < 0 || codeEnd > codeBytes.lengthInBytes) {
+ _printTrace('Invalid byte index: [$codeStart, $codeEnd]');
+ continue;
+ }
+ var byteView = Uint8List.view(
+ codeBytes.buffer,
+ codeStart,
+ codeEnd - codeStart,
+ );
+ _files[filePath] = byteView;
+
+ var sourcemapStart = sourcemapOffsets[0];
+ var sourcemapEnd = sourcemapOffsets[1];
+ if (sourcemapStart < 0 || sourcemapEnd > sourcemapBytes.lengthInBytes) {
+ _printTrace('Invalid byte index: [$sourcemapStart, $sourcemapEnd]');
+ continue;
+ }
+ var sourcemapView = Uint8List.view(
+ sourcemapBytes.buffer,
+ sourcemapStart,
+ sourcemapEnd - sourcemapStart,
+ );
+ _sourcemaps['$filePath.map'] = sourcemapView;
+
+ modules.add(filePath);
+ }
+ return modules;
+ }
+
+ // Attempt to resolve `path` to a dart file.
+ File _resolveDartFile(String path) {
+ path = _resolvePath(path);
+
+ // If this is a dart file, it must be on the local file system and is
+ // likely coming from a source map request. The tool doesn't currently
+ // consider the case of Dart files as assets.
+ var dartFile =
+ _fileSystem.file(_fileSystem.currentDirectory.uri.resolve(path));
+ if (dartFile.existsSync()) {
+ return dartFile;
+ }
+
+ var segments = p.split(path);
+ if (segments.first.isEmpty) {
+ segments.removeAt(0);
+ }
+
+ // The file might have been a package file which is signaled by a
+ // `/packages/<package>/<path>` request.
+ if (segments.first == 'packages') {
+ var packageFile = _fileSystem.file(_packages
+ .resolve(Uri(scheme: 'package', pathSegments: segments.skip(1))));
+ if (packageFile.existsSync()) {
+ return packageFile;
+ }
+ }
+
+ // Otherwise it must be a Dart SDK source.
+ var dartSdkParent = _fileSystem.directory(dartSdkPath).parent;
+ var dartSdkFile = _fileSystem.file(
+ _fileSystem.path.joinAll(<String>[dartSdkParent.path, ...segments]));
+ return dartSdkFile;
+ }
+
+ // Mimick build_daemon by serving from the root (web)
+ // if the path does not belong to a package
+ // Note: This is a temporary workaround until we solve inconsistencies
+ // in different configurations by introducing module name and path
+ // translation interfaces between compiler, asset server, and the
+ // debugger.
+ // TODO(annagrin): module interface
+ // [issue #910](https://github.com/dart-lang/webdev/issues/910)
+ String _resolvePath(String path) {
+ var segments = p.split(path);
+ if (segments.first.isEmpty) {
+ segments.removeAt(0);
+ }
+
+ return path = segments.first == 'packages'
+ ? p.joinAll(segments)
+ : p.joinAll([_root, ...segments]);
+ }
+
+ @override
+ Future<String> dartSourceContents(String serverPath) {
+ var result = _resolveDartFile(serverPath);
+ if (result.existsSync()) {
+ return result.readAsString();
+ }
+ return null;
+ }
+
+ @override
+ Future<String> sourceMapContents(String serverPath) async {
+ var path = _resolvePath(serverPath);
+ path = '/$path';
+ if (_sourcemaps.containsKey(path)) {
+ return utf8.decode(_sourcemaps[path]);
+ }
+ return null;
+ }
+}
+
+/// Given a data structure which is a Map of String to dynamic values, return
+/// the same structure (`Map<String, dynamic>`) with the correct runtime types.
+Map<String, dynamic> castStringKeyedMap(dynamic untyped) {
+ var map = untyped as Map<dynamic, dynamic>;
+ return map?.cast<String, dynamic>();
+}
diff --git a/frontend_server_common/lib/src/bootstrap.dart b/frontend_server_common/lib/src/bootstrap.dart
new file mode 100644
index 0000000..ba2fc79
--- /dev/null
+++ b/frontend_server_common/lib/src/bootstrap.dart
@@ -0,0 +1,72 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests
+
+import 'package:meta/meta.dart';
+
+/// The JavaScript bootstrap script to support in-browser hot restart.
+///
+/// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl]
+/// loads the special Dart stack trace mapper. The [entrypoint] is the
+/// actual main.dart file.
+///
+/// This file is served when the browser requests "main.dart.js" in debug mode,
+/// and is responsible for bootstrapping the RequireJS modules and attaching
+/// the hot reload hooks.
+String generateBootstrapScript({
+ @required String requireUrl,
+ @required String mapperUrl,
+ @required String entrypoint,
+}) {
+ return '''
+"use strict";
+
+// Attach source mapping.
+var mapperEl = document.createElement("script");
+mapperEl.defer = true;
+mapperEl.async = false;
+mapperEl.src = "$mapperUrl";
+document.head.appendChild(mapperEl);
+
+// Attach require JS.
+var requireEl = document.createElement("script");
+requireEl.defer = true;
+requireEl.async = false;
+requireEl.src = "$requireUrl";
+// This attribute tells require JS what to load as main (defined below).
+requireEl.setAttribute("data-main", "main_module.bootstrap");
+document.head.appendChild(requireEl);
+''';
+}
+
+/// Generate a synthetic main module which captures the application's main
+/// method.
+///
+/// RE: Object.keys usage in app.main:
+/// 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
+/// code. The property name is based off of the entrypoint that is generated, for example
+/// the file `foo/bar/baz.dart` will generate a property named approximately
+/// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of
+/// this object is the module.
+String generateMainModule({@required String entrypoint}) {
+ return '''/* ENTRYPOINT_EXTENTION_MARKER */
+
+// Create the main module loaded below.
+define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
+ dart_sdk.dart.setStartAsyncSynchronously(true);
+ dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
+ dart_sdk._debugger.registerDevtoolsFormatter();
+ let voidToNull = () => (voidToNull = dart_sdk.dart.constFn(dart_sdk.dart.fnType(dart_sdk.core.Null, [dart_sdk.dart.void])))();
+
+ // See the generateMainModule doc comment.
+ var child = {};
+ child.main = app[Object.keys(app)[0]].main;
+
+ /* MAIN_EXTENSION_MARKER */
+ child.main();
+});
+''';
+}
diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart
new file mode 100644
index 0000000..707c72a
--- /dev/null
+++ b/frontend_server_common/lib/src/devfs.dart
@@ -0,0 +1,194 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests
+
+import 'dart:io';
+
+import 'package:dwds/dwds.dart';
+import 'package:file/file.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+import 'asset.dart';
+import 'asset_server.dart';
+import 'bootstrap.dart';
+import 'devfs_content.dart';
+import 'frontend_server_client.dart';
+import 'utilities.dart';
+
+final String dartWebSdkPath = p.join(dartSdkPath, 'lib', 'dev_compiler');
+
+class WebDevFS {
+ WebDevFS(
+ {this.fileSystem,
+ this.hostname,
+ this.port,
+ this.packagesFilePath,
+ this.packagesPath,
+ this.root,
+ this.urlTunneller,
+ this.logWriter});
+
+ final FileSystem fileSystem;
+ TestAssetServer assetServer;
+ final String hostname;
+ final int port;
+ final String packagesFilePath;
+ final String packagesPath;
+ final String root;
+ final UrlEncoder urlTunneller;
+ final LogWriter logWriter;
+ Directory _savedCurrentDirectory;
+ List<Uri> sources;
+
+ Future<Uri> create() async {
+ _savedCurrentDirectory = fileSystem.currentDirectory;
+ fileSystem.currentDirectory = packagesPath;
+ assetServer = await TestAssetServer.start(
+ fileSystem, root, hostname, port, urlTunneller, logWriter);
+ return Uri.parse('http://$hostname:$port');
+ }
+
+ Future<void> dispose() {
+ fileSystem.currentDirectory = _savedCurrentDirectory;
+ return assetServer.dispose();
+ }
+
+ Future<UpdateFSReport> update({
+ String mainPath,
+ AssetBundle bundle,
+ String dillOutputPath,
+ @required ResidentCompiler generator,
+ List<Uri> invalidatedFiles,
+ }) async {
+ assert(generator != null);
+ var outputDirectoryPath = fileSystem.file(mainPath).parent.path;
+ var entryPoint = mainPath;
+
+ assetServer.writeFile(
+ '/main.dart.js',
+ generateBootstrapScript(
+ requireUrl: _filePathToUriFragment(requireJS.path),
+ mapperUrl: _filePathToUriFragment(stackTraceMapper.path),
+ entrypoint: entryPoint,
+ ),
+ );
+ assetServer.writeFile(
+ '/main_module.bootstrap.js',
+ generateMainModule(
+ entrypoint: entryPoint,
+ ),
+ );
+
+ assetServer.writeFile('/main_module.digests', '{}');
+ assetServer.writeFile('/dart_sdk.js', dartSdk.readAsStringSync());
+ assetServer.writeFile(
+ '/dart_sdk.js.map', dartSdkSourcemap.readAsStringSync());
+ // TODO(jonahwilliams): refactor the asset code in this and the regular devfs to
+ // be shared.
+ if (bundle != null) {
+ await writeBundle(
+ fileSystem.directory(p.joinAll(['build', 'assets'])),
+ bundle.entries,
+ );
+ }
+
+ generator.reset();
+
+ var compilerOutput = await generator.recompile(
+ 'org-dartlang-app:///$mainPath',
+ invalidatedFiles,
+ outputPath: p.join(dillOutputPath, 'app.dill'),
+ packagesFilePath: packagesFilePath,
+ );
+ if (compilerOutput == null || compilerOutput.errorCount > 0) {
+ return UpdateFSReport(success: false);
+ }
+
+ // list of sources that needs to be monitored are in [compilerOutput.sources]
+ sources = compilerOutput.sources;
+ File codeFile;
+ File manifestFile;
+ File sourcemapFile;
+ List<String> modules;
+ try {
+ var parentDirectory = fileSystem.directory(outputDirectoryPath);
+ codeFile =
+ parentDirectory.childFile('${compilerOutput.outputFilename}.sources');
+ manifestFile =
+ parentDirectory.childFile('${compilerOutput.outputFilename}.json');
+ sourcemapFile =
+ parentDirectory.childFile('${compilerOutput.outputFilename}.map');
+ modules = assetServer.write(codeFile, manifestFile, sourcemapFile);
+ } on FileSystemException catch (err) {
+ throw Exception('Failed to load recompiled sources:\n$err');
+ }
+ return UpdateFSReport(
+ success: true,
+ syncedBytes: codeFile.lengthSync(),
+ invalidatedSourcesCount: invalidatedFiles.length,
+ )..invalidatedModules = modules;
+ }
+
+ File get requireJS => fileSystem.file(fileSystem.path.join(
+ dartWebSdkPath,
+ 'kernel',
+ 'amd',
+ 'require.js',
+ ));
+
+ File get dartSdk => fileSystem.file(fileSystem.path.join(
+ dartWebSdkPath,
+ 'kernel',
+ 'amd',
+ 'dart_sdk.js',
+ ));
+
+ File get dartSdkSourcemap => fileSystem.file(fileSystem.path.join(
+ dartWebSdkPath,
+ 'kernel',
+ 'amd',
+ 'dart_sdk.js.map',
+ ));
+
+ File get stackTraceMapper => fileSystem.file(fileSystem.path.join(
+ dartWebSdkPath,
+ 'web',
+ 'dart_stack_trace_mapper.js',
+ ));
+}
+
+String _filePathToUriFragment(String path) {
+ if (Platform.isWindows) {
+ var startWithSlash = path.startsWith('/');
+ var partial =
+ fileSystem.path.split(path).skip(startWithSlash ? 2 : 1).join('/');
+ if (partial.startsWith('/')) {
+ return partial;
+ }
+ return '/$partial';
+ }
+ return path;
+}
+
+Future<void> writeBundle(
+ Directory bundleDir, Map<String, DevFSContent> assetEntries) async {
+ if (bundleDir.existsSync()) {
+ try {
+ bundleDir.deleteSync(recursive: true);
+ } on FileSystemException catch (err) {
+ printError('Failed to clean up asset directory ${bundleDir.path}: $err\n'
+ 'To clean build artifacts, use the command "flutter clean".');
+ }
+ }
+ bundleDir.createSync(recursive: true);
+
+ await Future.wait<void>(assetEntries.entries
+ .map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
+ var file = fileSystem.file(fileSystem.path.join(bundleDir.path, entry.key));
+ file.parent.createSync(recursive: true);
+ await file.writeAsBytes(await entry.value.contentsAsBytes());
+ }));
+}
diff --git a/frontend_server_common/lib/src/devfs_content.dart b/frontend_server_common/lib/src/devfs_content.dart
new file mode 100644
index 0000000..596f1ed
--- /dev/null
+++ b/frontend_server_common/lib/src/devfs_content.dart
@@ -0,0 +1,225 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:file/file.dart';
+
+import 'utilities.dart';
+
+/// Common superclass for content copied to the device.
+abstract class DevFSContent {
+ /// Return true if this is the first time this method is called
+ /// or if the entry has been modified since this method was last called.
+ bool get isModified;
+
+ /// Return true if this is the first time this method is called
+ /// or if the entry has been modified after the given time
+ /// or if the given time is null.
+ bool isModifiedAfter(DateTime time);
+
+ int get size;
+
+ Future<List<int>> contentsAsBytes();
+
+ Stream<List<int>> contentsAsStream();
+
+ /// Return the list of files this content depends on.
+ List<String> get fileDependencies => <String>[];
+}
+
+// File content to be copied to the device.
+class DevFSFileContent extends DevFSContent {
+ DevFSFileContent(this.file);
+
+ final FileSystemEntity file;
+ File _linkTarget;
+ FileStat _fileStat;
+
+ File _getFile() {
+ if (_linkTarget != null) {
+ return _linkTarget;
+ }
+ if (file is Link) {
+ // The link target.
+ return fileSystem.file(file.resolveSymbolicLinksSync());
+ }
+ return file as File;
+ }
+
+ void _stat() {
+ if (_linkTarget != null) {
+ // Stat the cached symlink target.
+ var fileStat = _linkTarget.statSync();
+ if (fileStat.type == FileSystemEntityType.notFound) {
+ _linkTarget = null;
+ } else {
+ _fileStat = fileStat;
+ return;
+ }
+ }
+ var fileStat = file.statSync();
+ _fileStat =
+ fileStat.type == FileSystemEntityType.notFound ? null : fileStat;
+ if (_fileStat != null && _fileStat.type == FileSystemEntityType.link) {
+ // Resolve, stat the symlink target.
+ var resolved = file.resolveSymbolicLinksSync();
+ var linkTarget = fileSystem.file(resolved);
+ // Stat the link target.
+ var fileStat = linkTarget.statSync();
+ if (fileStat.type == FileSystemEntityType.notFound) {
+ _fileStat = null;
+ _linkTarget = null;
+ }
+ }
+ if (_fileStat == null) {
+ printError(
+ 'Unable to get status of file "${file.path}": file not found.');
+ }
+ }
+
+ @override
+ List<String> get fileDependencies => <String>[_getFile().path];
+
+ @override
+ bool get isModified {
+ var _oldFileStat = _fileStat;
+ _stat();
+ if (_oldFileStat == null && _fileStat == null) {
+ return false;
+ }
+ return _oldFileStat == null ||
+ _fileStat == null ||
+ _fileStat.modified.isAfter(_oldFileStat.modified);
+ }
+
+ @override
+ bool isModifiedAfter(DateTime time) {
+ var _oldFileStat = _fileStat;
+ _stat();
+ if (_oldFileStat == null && _fileStat == null) {
+ return false;
+ }
+ return time == null ||
+ _oldFileStat == null ||
+ _fileStat == null ||
+ _fileStat.modified.isAfter(time);
+ }
+
+ @override
+ int get size {
+ if (_fileStat == null) {
+ _stat();
+ }
+ // Can still be null if the file wasn't found.
+ return _fileStat?.size ?? 0;
+ }
+
+ @override
+ Future<List<int>> contentsAsBytes() => _getFile().readAsBytes();
+
+ @override
+ Stream<List<int>> contentsAsStream() => _getFile().openRead();
+}
+
+/// Byte content to be copied to the device.
+class DevFSByteContent extends DevFSContent {
+ DevFSByteContent(this._bytes);
+
+ List<int> _bytes;
+
+ bool _isModified = true;
+ DateTime _modificationTime = DateTime.now();
+
+ List<int> get bytes => _bytes;
+
+ set bytes(List<int> value) {
+ _bytes = value;
+ _isModified = true;
+ _modificationTime = DateTime.now();
+ }
+
+ /// Return true only once so that the content is written to the device only once.
+ @override
+ bool get isModified {
+ var modified = _isModified;
+ _isModified = false;
+ return modified;
+ }
+
+ @override
+ bool isModifiedAfter(DateTime time) {
+ return time == null || _modificationTime.isAfter(time);
+ }
+
+ @override
+ int get size => _bytes.length;
+
+ @override
+ Future<List<int>> contentsAsBytes() async => _bytes;
+
+ @override
+ Stream<List<int>> contentsAsStream() =>
+ Stream<List<int>>.fromIterable(<List<int>>[_bytes]);
+}
+
+/// String content to be copied to the device.
+class DevFSStringContent extends DevFSByteContent {
+ DevFSStringContent(String string)
+ : _string = string,
+ super(utf8.encode(string));
+
+ String _string;
+
+ String get string => _string;
+
+ set string(String value) {
+ _string = value;
+ super.bytes = utf8.encode(_string);
+ }
+
+ @override
+ set bytes(List<int> value) {
+ string = utf8.decode(value);
+ }
+}
+
+// Basic statistics for DevFS update operation.
+class UpdateFSReport {
+ UpdateFSReport({
+ bool success = false,
+ int invalidatedSourcesCount = 0,
+ int syncedBytes = 0,
+ }) {
+ _success = success;
+ _invalidatedSourcesCount = invalidatedSourcesCount;
+ _syncedBytes = syncedBytes;
+ }
+
+ bool get success => _success;
+ int get invalidatedSourcesCount => _invalidatedSourcesCount;
+ int get syncedBytes => _syncedBytes;
+
+ /// JavaScript modules produced by the incremental compiler in `dartdevc`
+ /// mode.
+ ///
+ /// Only used for JavaScript compilation.
+ List<String> invalidatedModules;
+
+ void incorporateResults(UpdateFSReport report) {
+ if (!report._success) {
+ _success = false;
+ }
+ _invalidatedSourcesCount += report._invalidatedSourcesCount;
+ _syncedBytes += report._syncedBytes;
+ invalidatedModules ??= report.invalidatedModules;
+ }
+
+ bool _success;
+ int _invalidatedSourcesCount;
+ int _syncedBytes;
+}
diff --git a/frontend_server_common/lib/src/frontend_server_client.dart b/frontend_server_common/lib/src/frontend_server_client.dart
new file mode 100644
index 0000000..cd2182e
--- /dev/null
+++ b/frontend_server_common/lib/src/frontend_server_client.dart
@@ -0,0 +1,797 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dwds/dwds.dart';
+import 'package:path/path.dart' as p;
+import 'package:meta/meta.dart';
+import 'package:pedantic/pedantic.dart';
+import 'package:usage/uuid/uuid.dart';
+import 'package:logging/logging.dart';
+
+import 'package_map.dart';
+import 'utilities.dart';
+
+/// True iff debug mode
+bool get debugFrontendServer =>
+ Platform.environment['DWDS_DEBUG_FRONTEND_SERVER'] == 'true';
+
+/// In debug mode, path to frontend_server_starter.dart
+/// Otherwise, path to precompiled snapshot.
+/// In debug mode, frontend_server prints an observatory Uri to stderr.
+String get frontendServerExecutable {
+ if (debugFrontendServer) {
+ final starter = Platform.environment['DWDS_FRONTEND_SERVER_STARTER'];
+ if (starter == null) {
+ throw Exception('Debug mode - define DWDS_FRONTEND_SERVER_STARTER '
+ 'environment variable to point to frontend_server_starter.dart');
+ }
+ return starter;
+ }
+ final snapshot =
+ p.join(dartSdkPath, 'bin', 'snapshots', 'frontend_server.dart.snapshot');
+ return snapshot;
+}
+
+typedef CompilerMessageConsumer = void Function(String message,
+ {StackTrace stackTrace});
+
+class CompilerOutput {
+ const CompilerOutput(this.outputFilename, this.errorCount, this.sources);
+
+ final String outputFilename;
+ final int errorCount;
+ final List<Uri> sources;
+}
+
+enum StdoutState { collectDiagnostic, collectDependencies }
+
+/// Handles stdin/stdout communication with the frontend server.
+class StdoutHandler {
+ final LogWriter _logWriter;
+
+ void _printTrace(String message) {
+ _logWriter(Level.INFO, message);
+ }
+
+ StdoutHandler(this._logWriter, {this.consumer = printError}) {
+ reset();
+ }
+
+ bool compilerMessageReceived = false;
+ final CompilerMessageConsumer consumer;
+ String boundaryKey;
+ StdoutState state = StdoutState.collectDiagnostic;
+ Completer<CompilerOutput> compilerOutput;
+ final List<Uri> sources = <Uri>[];
+
+ bool _suppressCompilerMessages;
+ bool _expectSources;
+ bool _badState = false;
+
+ void handler(String message) {
+ if (message.startsWith('Observatory listening')) {
+ stderr.writeln(message);
+ return;
+ }
+ if (message.startsWith('Observatory server failed')) {
+ throw Exception(message);
+ }
+ if (_badState) {
+ return;
+ }
+ var kResultPrefix = 'result ';
+ if (boundaryKey == null && message.startsWith(kResultPrefix)) {
+ boundaryKey = message.substring(kResultPrefix.length);
+ return;
+ }
+ // Invalid state, see commented issue below for more information.
+ // NB: both the completeError and _badState flags are required to avoid
+ // filling the console with exceptions.
+ if (boundaryKey == null) {
+ // Throwing a synchronous exception via throwToolExit will fail to cancel
+ // the stream. Instead use completeError so that the error is returned
+ // from the awaited future that the compiler consumers are expecting.
+ compilerOutput.completeError(
+ 'Frontend server tests encountered an internal problem'
+ 'This can be caused by printing to stdout into the stream that is '
+ 'used for communication between frontend server (in sdk) or '
+ 'frontend server client (in dwds tests).'
+ '\n\n'
+ 'Additional debugging information:\n'
+ ' StdoutState: $state\n'
+ ' compilerMessageReceived: $compilerMessageReceived\n'
+ ' message: $message\n'
+ ' _expectSources: $_expectSources\n'
+ ' sources: $sources\n');
+ // There are several event turns before the tool actually exits from a
+ // tool exception. Normally, the stream should be cancelled to prevent
+ // more events from entering the bad state, but because the error
+ // is coming from handler itself, there is no clean way to pipe this
+ // through. Instead, we set a flag to prevent more messages from
+ // registering.
+ _badState = true;
+ return;
+ }
+ if (message.startsWith(boundaryKey)) {
+ if (_expectSources) {
+ if (state == StdoutState.collectDiagnostic) {
+ state = StdoutState.collectDependencies;
+ return;
+ }
+ }
+ if (message.length <= boundaryKey.length) {
+ compilerOutput.complete(null);
+ return;
+ }
+ var spaceDelimiter = message.lastIndexOf(' ');
+ compilerOutput.complete(CompilerOutput(
+ message.substring(boundaryKey.length + 1, spaceDelimiter),
+ int.parse(message.substring(spaceDelimiter + 1).trim()),
+ sources));
+ return;
+ }
+ if (state == StdoutState.collectDiagnostic) {
+ if (!_suppressCompilerMessages) {
+ if (compilerMessageReceived == false) {
+ consumer('\nCompiler message:');
+ compilerMessageReceived = true;
+ }
+ consumer(message);
+ }
+ } else {
+ assert(state == StdoutState.collectDependencies);
+ switch (message[0]) {
+ case '+':
+ sources.add(Uri.parse(message.substring(1)));
+ break;
+ case '-':
+ sources.remove(Uri.parse(message.substring(1)));
+ break;
+ default:
+ _printTrace('Unexpected prefix for $message uri - ignoring');
+ }
+ }
+ }
+
+ // This is needed to get ready to process next compilation result output,
+ // with its own boundary key and new completer.
+ void reset(
+ {bool suppressCompilerMessages = false, bool expectSources = true}) {
+ boundaryKey = null;
+ compilerMessageReceived = false;
+ compilerOutput = Completer<CompilerOutput>();
+ _suppressCompilerMessages = suppressCompilerMessages;
+ _expectSources = expectSources;
+ state = StdoutState.collectDiagnostic;
+ }
+}
+
+/// Converts filesystem paths to package URIs.
+class PackageUriMapper {
+ PackageUriMapper(String scriptPath, String packagesFilePath,
+ String fileSystemScheme, List<String> fileSystemRoots) {
+ var packageMap = PackageMap(fileSystem.path.absolute(packagesFilePath)).map;
+ var isWindowsPath =
+ Platform.isWindows && !scriptPath.startsWith('org-dartlang-app');
+ var scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString();
+ for (var packageName in packageMap.keys) {
+ var prefix = packageMap[packageName].toString();
+ // Only perform a multi-root mapping if there are multiple roots.
+ if (fileSystemScheme != null &&
+ fileSystemRoots != null &&
+ fileSystemRoots.length > 1 &&
+ prefix.contains(fileSystemScheme)) {
+ _packageName = packageName;
+ _uriPrefixes = fileSystemRoots
+ .map((String name) =>
+ Uri.file(name, windows: Platform.isWindows).toString())
+ .toList();
+ return;
+ }
+ if (scriptUri.startsWith(prefix)) {
+ _packageName = packageName;
+ _uriPrefixes = <String>[prefix];
+ return;
+ }
+ }
+ }
+
+ String _packageName;
+ List<String> _uriPrefixes;
+
+ Uri map(String scriptPath) {
+ if (_packageName == null) {
+ return null;
+ }
+ var scriptUri =
+ Uri.file(scriptPath, windows: Platform.isWindows).toString();
+ for (var uriPrefix in _uriPrefixes) {
+ if (scriptUri.startsWith(uriPrefix)) {
+ return Uri.parse(
+ 'package:$_packageName/${scriptUri.substring(uriPrefix.length)}');
+ }
+ }
+ return null;
+ }
+
+ static Uri findUri(String scriptPath, String packagesFilePath,
+ String fileSystemScheme, List<String> fileSystemRoots) {
+ return PackageUriMapper(
+ scriptPath, packagesFilePath, fileSystemScheme, fileSystemRoots)
+ .map(scriptPath);
+ }
+}
+
+/// Class that allows to serialize compilation requests to the compiler.
+abstract class _CompilationRequest {
+ _CompilationRequest(this.completer);
+
+ Completer<CompilerOutput> completer;
+
+ Future<CompilerOutput> _run(DefaultResidentCompiler compiler);
+
+ Future<void> run(DefaultResidentCompiler compiler) async {
+ completer.complete(await _run(compiler));
+ }
+}
+
+class _RecompileRequest extends _CompilationRequest {
+ _RecompileRequest(
+ Completer<CompilerOutput> completer,
+ this.mainPath,
+ this.invalidatedFiles,
+ this.outputPath,
+ this.packagesFilePath,
+ ) : super(completer);
+
+ String mainPath;
+ List<Uri> invalidatedFiles;
+ String outputPath;
+ String packagesFilePath;
+
+ @override
+ Future<CompilerOutput> _run(DefaultResidentCompiler compiler) async =>
+ compiler._recompile(this);
+}
+
+class _CompileExpressionRequest extends _CompilationRequest {
+ _CompileExpressionRequest(
+ Completer<CompilerOutput> completer,
+ this.expression,
+ this.definitions,
+ this.typeDefinitions,
+ this.libraryUri,
+ this.klass,
+ this.isStatic,
+ ) : super(completer);
+
+ String expression;
+ List<String> definitions;
+ List<String> typeDefinitions;
+ String libraryUri;
+ String klass;
+ bool isStatic;
+
+ @override
+ Future<CompilerOutput> _run(DefaultResidentCompiler compiler) async =>
+ compiler._compileExpression(this);
+}
+
+class _CompileExpressionToJsRequest extends _CompilationRequest {
+ _CompileExpressionToJsRequest(
+ Completer<CompilerOutput> completer,
+ this.libraryUri,
+ this.line,
+ this.column,
+ this.jsModules,
+ this.jsFrameValues,
+ this.moduleName,
+ this.expression)
+ : super(completer);
+
+ String libraryUri;
+ int line;
+ int column;
+ Map<String, String> jsModules;
+ Map<String, String> jsFrameValues;
+ String moduleName;
+ String expression;
+
+ @override
+ Future<CompilerOutput> _run(DefaultResidentCompiler compiler) async =>
+ compiler._compileExpressionToJs(this);
+}
+
+class _RejectRequest extends _CompilationRequest {
+ _RejectRequest(Completer<CompilerOutput> completer) : super(completer);
+
+ @override
+ Future<CompilerOutput> _run(DefaultResidentCompiler compiler) async =>
+ compiler._reject();
+}
+
+/// Wrapper around incremental frontend server compiler, that communicates with
+/// server via stdin/stdout.
+///
+/// The wrapper is intended to stay resident in memory as user changes, reloads,
+/// restarts the Flutter app.
+abstract class ResidentCompiler {
+ factory ResidentCompiler(
+ String sdkRoot,
+ LogWriter logWriter, {
+ String packagesPath,
+ List<String> fileSystemRoots,
+ String fileSystemScheme,
+ String platformDill,
+ CompilerMessageConsumer compilerMessageConsumer,
+ }) = DefaultResidentCompiler;
+
+ // TODO(jonahwilliams): find a better way to configure additional file system
+ // roots from the runner.
+ // See: https://github.com/flutter/flutter/issues/50494
+ void addFileSystemRoot(String root);
+
+ /// If invoked for the first time, it compiles Dart script identified by
+ /// [mainPath], [invalidatedFiles] list is ignored.
+ /// On successive runs [invalidatedFiles] indicates which files need to be
+ /// recompiled. If [mainPath] is null, previously used [mainPath] entry
+ /// point that is used for recompilation.
+ /// Binary file name is returned if compilation was successful, otherwise
+ /// null is returned.
+ Future<CompilerOutput> recompile(
+ String mainPath,
+ List<Uri> invalidatedFiles, {
+ @required String outputPath,
+ String packagesFilePath,
+ });
+
+ Future<CompilerOutput> compileExpression(
+ String expression,
+ List<String> definitions,
+ List<String> typeDefinitions,
+ String libraryUri,
+ String klass,
+ bool isStatic,
+ );
+
+ Future<CompilerOutput> compileExpressionToJs(
+ String libraryUri,
+ int line,
+ int column,
+ Map<String, String> jsModules,
+ Map<String, String> jsFrameValues,
+ String moduleName,
+ String expression);
+
+ /// Should be invoked when results of compilation are accepted by the client.
+ ///
+ /// Either [accept] or [reject] should be called after every [recompile] call.
+ void accept();
+
+ /// Should be invoked when results of compilation are rejected by the client.
+ ///
+ /// Either [accept] or [reject] should be called after every [recompile] call.
+ Future<CompilerOutput> reject();
+
+ /// Should be invoked when frontend server compiler should forget what was
+ /// accepted previously so that next call to [recompile] produces complete
+ /// kernel file.
+ void reset();
+
+ /// stop the service normally
+ Future<dynamic> shutdown();
+
+ /// kill the service
+ Future<dynamic> kill();
+}
+
+@visibleForTesting
+class DefaultResidentCompiler implements ResidentCompiler {
+ DefaultResidentCompiler(
+ String sdkRoot,
+ this._logWriter, {
+ this.packagesPath,
+ this.fileSystemRoots,
+ this.fileSystemScheme,
+ this.platformDill,
+ CompilerMessageConsumer compilerMessageConsumer = printError,
+ }) : assert(sdkRoot != null),
+ _stdoutHandler =
+ StdoutHandler(_logWriter, consumer: compilerMessageConsumer),
+ // This is a URI, not a file path, so the forward slash is correct even on Windows.
+ sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/';
+
+ final LogWriter _logWriter;
+ final String packagesPath;
+ final List<String> fileSystemRoots;
+ final String fileSystemScheme;
+ final String platformDill;
+
+ void _printTrace(String message) {
+ _logWriter(Level.INFO, message);
+ }
+
+ @override
+ void addFileSystemRoot(String root) {
+ fileSystemRoots.add(root);
+ }
+
+ /// The path to the root of the Dart SDK used to compile.
+ final String sdkRoot;
+
+ Process _server;
+ final StdoutHandler _stdoutHandler;
+ bool _compileRequestNeedsConfirmation = false;
+
+ final StreamController<_CompilationRequest> _controller =
+ StreamController<_CompilationRequest>();
+
+ @override
+ Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles,
+ {@required String outputPath, String packagesFilePath}) async {
+ assert(outputPath != null);
+ if (!_controller.hasListener) {
+ _controller.stream.listen(_handleCompilationRequest);
+ }
+
+ var completer = Completer<CompilerOutput>();
+ _controller.add(_RecompileRequest(
+ completer, mainPath, invalidatedFiles, outputPath, packagesFilePath));
+ return completer.future;
+ }
+
+ Future<CompilerOutput> _recompile(_RecompileRequest request) async {
+ _stdoutHandler.reset();
+
+ // First time recompile is called we actually have to compile the app from
+ // scratch ignoring list of invalidated files.
+ PackageUriMapper packageUriMapper;
+ if (request.packagesFilePath != null) {
+ packageUriMapper = PackageUriMapper(
+ request.mainPath,
+ request.packagesFilePath,
+ fileSystemScheme,
+ fileSystemRoots,
+ );
+ }
+
+ _compileRequestNeedsConfirmation = true;
+
+ if (_server == null) {
+ return _compile(_mapFilename(request.mainPath, packageUriMapper),
+ request.outputPath, request.packagesFilePath);
+ }
+
+ var inputKey = Uuid().generateV4();
+ var mainUri = request.mainPath != null
+ ? '${_mapFilename(request.mainPath, packageUriMapper)} '
+ : '';
+ _server.stdin.writeln('recompile $mainUri$inputKey');
+ _printTrace('<- recompile $mainUri$inputKey');
+ for (var fileUri in request.invalidatedFiles) {
+ var message = _mapFileUri(fileUri.toString(), packageUriMapper);
+ _server.stdin.writeln(message);
+ _printTrace(message);
+ }
+ _server.stdin.writeln(inputKey);
+ _printTrace('<- $inputKey');
+
+ return _stdoutHandler.compilerOutput.future;
+ }
+
+ final List<_CompilationRequest> _compilationQueue = <_CompilationRequest>[];
+
+ Future<void> _handleCompilationRequest(_CompilationRequest request) async {
+ var isEmpty = _compilationQueue.isEmpty;
+ _compilationQueue.add(request);
+ // Only trigger processing if queue was empty - i.e. no other requests
+ // are currently being processed. This effectively enforces "one
+ // compilation request at a time".
+ if (isEmpty) {
+ while (_compilationQueue.isNotEmpty) {
+ var request = _compilationQueue.first;
+ await request.run(this);
+ _compilationQueue.removeAt(0);
+ }
+ }
+ }
+
+ Future<CompilerOutput> _compile(
+ String scriptUri, String outputFilePath, String packagesFilePath) async {
+ var debug = debugFrontendServer;
+ var frontendServer = frontendServerExecutable;
+ var args = <String>[
+ if (debug) '--observe',
+ frontendServer,
+ '--sdk-root',
+ sdkRoot,
+ '--incremental',
+ '--target=dartdevc',
+ '-Ddart.developer.causal_async_stacks=true',
+ '--output-dill',
+ outputFilePath,
+ if (packagesFilePath != null) ...<String>[
+ '--packages',
+ packagesFilePath,
+ ] else if (packagesPath != null) ...<String>[
+ '--packages',
+ packagesPath,
+ ],
+ if (fileSystemRoots != null)
+ for (final String root in fileSystemRoots) ...<String>[
+ '--filesystem-root',
+ root,
+ ],
+ if (fileSystemScheme != null) ...<String>[
+ '--filesystem-scheme',
+ fileSystemScheme,
+ ],
+ if (platformDill != null) ...<String>[
+ '--platform',
+ platformDill,
+ ],
+ '--debugger-module-names',
+ if (debug) '--verbose'
+ ];
+
+ _printTrace(args.join(' '));
+ _server = await Process.start(Platform.resolvedExecutable, args,
+ workingDirectory: packagesPath);
+ _server.stdout
+ .transform<String>(utf8.decoder)
+ .transform<String>(const LineSplitter())
+ .listen(_stdoutHandler.handler, onDone: () {
+ // when outputFilename future is not completed, but stdout is closed
+ // process has died unexpectedly.
+ if (!_stdoutHandler.compilerOutput.isCompleted) {
+ _stdoutHandler.compilerOutput.complete(null);
+ throw Exception('the Dart compiler exited unexpectedly.');
+ }
+ });
+
+ _server.stderr
+ .transform<String>(utf8.decoder)
+ .transform<String>(const LineSplitter())
+ .listen(printError);
+
+ unawaited(_server.exitCode.then((int code) {
+ if (code != 0) {
+ throw Exception('the Dart compiler exited unexpectedly.');
+ }
+ }));
+
+ _server.stdin.writeln('compile $scriptUri');
+ _printTrace('<- compile $scriptUri');
+
+ return _stdoutHandler.compilerOutput.future;
+ }
+
+ @override
+ Future<CompilerOutput> compileExpression(
+ String expression,
+ List<String> definitions,
+ List<String> typeDefinitions,
+ String libraryUri,
+ String klass,
+ bool isStatic,
+ ) {
+ if (!_controller.hasListener) {
+ _controller.stream.listen(_handleCompilationRequest);
+ }
+
+ var completer = Completer<CompilerOutput>();
+ _controller.add(_CompileExpressionRequest(completer, expression,
+ definitions, typeDefinitions, libraryUri, klass, isStatic));
+ return completer.future;
+ }
+
+ Future<CompilerOutput> _compileExpression(
+ _CompileExpressionRequest request) async {
+ _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
+
+ // 'compile-expression' should be invoked after compiler has been started,
+ // program was compiled.
+ if (_server == null) {
+ return null;
+ }
+
+ var inputKey = Uuid().generateV4();
+ _server.stdin.writeln('compile-expression $inputKey');
+ _server.stdin.writeln(request.expression);
+ request.definitions?.forEach(_server.stdin.writeln);
+ _server.stdin.writeln(inputKey);
+ request.typeDefinitions?.forEach(_server.stdin.writeln);
+ _server.stdin.writeln(inputKey);
+ _server.stdin.writeln(request.libraryUri ?? '');
+ _server.stdin.writeln(request.klass ?? '');
+ _server.stdin.writeln(request.isStatic ?? false);
+
+ return _stdoutHandler.compilerOutput.future;
+ }
+
+ @override
+ Future<CompilerOutput> compileExpressionToJs(
+ String libraryUri,
+ int line,
+ int column,
+ Map<String, String> jsModules,
+ Map<String, String> jsFrameValues,
+ String moduleName,
+ String expression) {
+ if (!_controller.hasListener) {
+ _controller.stream.listen(_handleCompilationRequest);
+ }
+
+ var completer = Completer<CompilerOutput>();
+ _controller.add(_CompileExpressionToJsRequest(completer, libraryUri, line,
+ column, jsModules, jsFrameValues, moduleName, expression));
+ return completer.future;
+ }
+
+ Future<CompilerOutput> _compileExpressionToJs(
+ _CompileExpressionToJsRequest request) async {
+ _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
+
+ // 'compile-expression-to-js' should be invoked after compiler has been started,
+ // program was compiled.
+ if (_server == null) {
+ return null;
+ }
+
+ var inputKey = Uuid().generateV4();
+ _server.stdin.writeln('compile-expression-to-js $inputKey');
+ _server.stdin.writeln(request.libraryUri ?? '');
+ _server.stdin.writeln(request.line);
+ _server.stdin.writeln(request.column);
+ request.jsModules?.forEach((k, v) {
+ _server.stdin.writeln('$k:$v');
+ });
+ _server.stdin.writeln(inputKey);
+ request.jsFrameValues?.forEach((k, v) {
+ _server.stdin.writeln('$k:$v');
+ });
+ _server.stdin.writeln(inputKey);
+ _server.stdin.writeln(request.moduleName ?? '');
+ _server.stdin.writeln(request.expression ?? '');
+
+ return _stdoutHandler.compilerOutput.future;
+ }
+
+ @override
+ void accept() {
+ if (_compileRequestNeedsConfirmation) {
+ _server.stdin.writeln('accept');
+ _printTrace('<- accept');
+ }
+ _compileRequestNeedsConfirmation = false;
+ }
+
+ @override
+ Future<CompilerOutput> reject() {
+ if (!_controller.hasListener) {
+ _controller.stream.listen(_handleCompilationRequest);
+ }
+
+ var completer = Completer<CompilerOutput>();
+ _controller.add(_RejectRequest(completer));
+ return completer.future;
+ }
+
+ Future<CompilerOutput> _reject() {
+ if (!_compileRequestNeedsConfirmation) {
+ return Future<CompilerOutput>.value(null);
+ }
+ _stdoutHandler.reset(expectSources: false);
+ _server.stdin.writeln('reject');
+ _printTrace('<- reject');
+ _compileRequestNeedsConfirmation = false;
+ return _stdoutHandler.compilerOutput.future;
+ }
+
+ @override
+ void reset() {
+ _server?.stdin?.writeln('reset');
+ _printTrace('<- reset');
+ }
+
+ Future<int> quit() async {
+ _server.stdin.writeln('quit');
+ _printTrace('<- quit');
+ return _server.exitCode;
+ }
+
+ String _mapFilename(String filename, PackageUriMapper packageUriMapper) {
+ return _doMapFilename(filename, packageUriMapper) ?? filename;
+ }
+
+ String _mapFileUri(String fileUri, PackageUriMapper packageUriMapper) {
+ String filename;
+ try {
+ filename = Uri.parse(fileUri).toFilePath();
+ } on UnsupportedError catch (_) {
+ return fileUri;
+ }
+ return _doMapFilename(filename, packageUriMapper) ?? fileUri;
+ }
+
+ String _doMapFilename(String filename, PackageUriMapper packageUriMapper) {
+ if (packageUriMapper != null) {
+ var packageUri = packageUriMapper.map(filename);
+ if (packageUri != null) {
+ return packageUri.toString();
+ }
+ }
+
+ if (fileSystemRoots != null) {
+ for (var root in fileSystemRoots) {
+ if (filename.startsWith(root)) {
+ return Uri(
+ scheme: fileSystemScheme,
+ path: filename.substring(root.length))
+ .toString();
+ }
+ }
+ }
+ if (Platform.isWindows &&
+ fileSystemRoots != null &&
+ fileSystemRoots.length > 1) {
+ return Uri.file(filename, windows: Platform.isWindows).toString();
+ }
+ return null;
+ }
+
+ @override
+ Future<dynamic> shutdown() async {
+ // Server was never successfully created.
+ if (_server == null) {
+ return 0;
+ }
+ return quit();
+ }
+
+ @override
+ Future<dynamic> kill() async {
+ if (_server == null) {
+ return 0;
+ }
+
+ _printTrace('killing pid ${_server.pid}');
+ _server.kill();
+ return _server.exitCode;
+ }
+}
+
+class TestExpressionCompiler implements ExpressionCompiler {
+ final ResidentCompiler _generator;
+ TestExpressionCompiler(this._generator);
+
+ @override
+ Future<ExpressionCompilationResult> compileExpressionToJs(
+ String isolateId,
+ String libraryUri,
+ int line,
+ int column,
+ Map<String, String> jsModules,
+ Map<String, String> jsFrameValues,
+ String moduleName,
+ String expression) async {
+ var compilerOutput = await _generator.compileExpressionToJs(libraryUri,
+ line, column, jsModules, jsFrameValues, moduleName, expression);
+
+ if (compilerOutput != null && compilerOutput.outputFilename != null) {
+ var content = utf8.decode(
+ fileSystem.file(compilerOutput.outputFilename).readAsBytesSync());
+ return ExpressionCompilationResult(
+ content, compilerOutput.errorCount > 0);
+ }
+
+ throw Exception('Failed to compile $expression');
+ }
+}
diff --git a/frontend_server_common/lib/src/package_map.dart b/frontend_server_common/lib/src/package_map.dart
new file mode 100644
index 0000000..bb6ee21
--- /dev/null
+++ b/frontend_server_common/lib/src/package_map.dart
@@ -0,0 +1,84 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools
+
+// TODO(annagrin): unify this code so both repos can use it
+// [issue #40835] (https://github.com/dart-lang/sdk/issues/40835)
+
+import 'dart:io';
+
+// ignore: deprecated_member_use
+import 'package:package_config/packages_file.dart' as packages_file;
+
+import 'utilities.dart';
+
+const String kPackagesFileName = '.packages';
+
+Map<String, Uri> _parse(String packagesPath) {
+ final List<int> source = fileSystem.file(packagesPath).readAsBytesSync();
+ return packages_file.parse(
+ source, Uri.file(packagesPath, windows: Platform.isWindows));
+}
+
+class PackageMap {
+ PackageMap(this.packagesPath);
+
+ static String get globalPackagesPath =>
+ _globalPackagesPath ?? kPackagesFileName;
+
+ static set globalPackagesPath(String value) {
+ _globalPackagesPath = value;
+ }
+
+ static bool get isUsingCustomPackagesPath => _globalPackagesPath != null;
+
+ static String _globalPackagesPath;
+
+ final String packagesPath;
+
+ /// Load and parses the .packages file.
+ void load() {
+ _map ??= _parse(packagesPath);
+ }
+
+ Map<String, Uri> get map {
+ load();
+ return _map;
+ }
+
+ Map<String, Uri> _map;
+
+ /// Returns the path to [packageUri].
+ String pathForPackage(Uri packageUri) => uriForPackage(packageUri).path;
+
+ /// Returns the path to [packageUri] as URL.
+ Uri uriForPackage(Uri packageUri) {
+ assert(packageUri.scheme == 'package');
+ var pathSegments = packageUri.pathSegments.toList();
+ var packageName = pathSegments.removeAt(0);
+ var packageBase = map[packageName];
+ if (packageBase == null) {
+ return null;
+ }
+ var packageRelativePath = fileSystem.path.joinAll(pathSegments);
+ return packageBase.resolveUri(fileSystem.path.toUri(packageRelativePath));
+ }
+
+ String checkValid() {
+ if (fileSystem.isFileSync(packagesPath)) {
+ return null;
+ }
+ var message = '$packagesPath does not exist.';
+ var pubspecPath = fileSystem.path
+ .absolute(fileSystem.path.dirname(packagesPath), 'pubspec.yaml');
+ if (fileSystem.isFileSync(pubspecPath)) {
+ message += '\nDid you run "flutter pub get" in this directory?';
+ } else {
+ message +=
+ '\nDid you run this command from the same directory as your pubspec.yaml file?';
+ }
+ return message;
+ }
+}
diff --git a/frontend_server_common/lib/src/resident_runner.dart b/frontend_server_common/lib/src/resident_runner.dart
new file mode 100644
index 0000000..979bb1e
--- /dev/null
+++ b/frontend_server_common/lib/src/resident_runner.dart
@@ -0,0 +1,102 @@
+// Copyright 2020 The Dart Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: this is a copy from flutter tools, updated to work with dwds tests,
+// and some functionality remioved (does not support hot reload yet)
+
+import 'dart:async';
+
+import 'package:dwds/dwds.dart';
+import 'package:path/path.dart' as p;
+
+import 'asset.dart';
+import 'devfs.dart';
+import 'devfs_content.dart';
+import 'frontend_server_client.dart';
+import 'utilities.dart';
+
+final String platformDill = p.join(dartSdkPath, '..', 'ddc_sdk.dill');
+
+class ResidentWebRunner {
+ ResidentWebRunner(
+ this.mainPath,
+ this.urlTunneller,
+ this.packagesPath,
+ this.packagesFilePath,
+ this.fileSystemRoots,
+ this.fileSystemScheme,
+ this.outputPath,
+ this.logWriter) {
+ generator = ResidentCompiler(dartSdkPath, logWriter,
+ packagesPath: packagesPath,
+ platformDill: platformDill,
+ fileSystemRoots: fileSystemRoots,
+ fileSystemScheme: fileSystemScheme);
+ expressionCompiler = TestExpressionCompiler(generator);
+ }
+
+ final LogWriter logWriter;
+ final UrlEncoder urlTunneller;
+ final String mainPath;
+ final String packagesPath;
+ final String packagesFilePath;
+ final String outputPath;
+ final List<String> fileSystemRoots;
+ final String fileSystemScheme;
+
+ ResidentCompiler generator;
+ ExpressionCompiler expressionCompiler;
+ AssetBundle assetBundle;
+ WebDevFS devFS;
+ Uri uri;
+ Iterable<String> modules;
+
+ Future<int> run(String hostname, int port, String root) async {
+ hostname ??= 'localhost';
+
+ assetBundle = AssetBundleFactory.defaultInstance.createBundle();
+
+ devFS = WebDevFS(
+ fileSystem: fileSystem,
+ hostname: hostname,
+ port: port,
+ packagesFilePath: packagesFilePath,
+ packagesPath: packagesPath,
+ root: root,
+ urlTunneller: urlTunneller,
+ logWriter: logWriter);
+ uri = await devFS.create();
+
+ var report = await _updateDevFS();
+ if (!report.success) {
+ printError('Failed to compile application.');
+ return 1;
+ }
+
+ modules = report.invalidatedModules;
+
+ generator.accept();
+ return 0;
+ }
+
+ Future<UpdateFSReport> _updateDevFS() async {
+ var result = await assetBundle.build();
+ if (result != 0) {
+ return UpdateFSReport(success: false);
+ }
+
+ var report = await devFS.update(
+ mainPath: mainPath,
+ bundle: assetBundle,
+ dillOutputPath: outputPath,
+ generator: generator,
+ invalidatedFiles: []);
+ return report;
+ }
+
+ Future<void> stop() async {
+ await generator.shutdown();
+ await devFS.dispose();
+ }
+}
diff --git a/frontend_server_common/lib/src/utilities.dart b/frontend_server_common/lib/src/utilities.dart
new file mode 100644
index 0000000..ab4e324
--- /dev/null
+++ b/frontend_server_common/lib/src/utilities.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, 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:io';
+
+import 'package:file/file.dart' as fs;
+import 'package:file/local.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+typedef LogWriter = void Function(Level, String);
+
+/// The path to the root directory of the SDK.
+final String _sdkDir = (() {
+ // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is
+ // "/path/to/sdk".
+ var aboveExecutable = p.dirname(p.dirname(Platform.resolvedExecutable));
+ assert(FileSystemEntity.isFileSync(p.join(aboveExecutable, 'version')));
+ return aboveExecutable;
+})();
+
+final String dartSdkPath = _sdkDir;
+final String dartPath = p.join(_sdkDir, 'bin', 'dart');
+final String pubSnapshot =
+ p.join(_sdkDir, 'bin', 'snapshots', 'pub.dart.snapshot');
+final String pubPath =
+ p.join(_sdkDir, 'bin', Platform.isWindows ? 'pub.bat' : 'pub');
+
+const fs.FileSystem fileSystem = LocalFileSystem();
+
+void printError(String message, {StackTrace stackTrace}) {
+ if (stackTrace != null) {
+ print('$message: $stackTrace');
+ }
+ print(message);
+}
diff --git a/frontend_server_common/mono_pkg.yaml b/frontend_server_common/mono_pkg.yaml
new file mode 100644
index 0000000..8e98c68
--- /dev/null
+++ b/frontend_server_common/mono_pkg.yaml
@@ -0,0 +1,14 @@
+# See https://github.com/dart-lang/mono_repo for details
+dart:
+ - 2.6.0
+ - dev
+
+stages:
+ - analyzer_and_format:
+ - group:
+ - dartfmt
+ - dartanalyzer: --fatal-infos --fatal-warnings .
+ dart: dev
+ - group:
+ - dartanalyzer: --fatal-warnings .
+ dart: [2.6.0]
diff --git a/frontend_server_common/pubspec.yaml b/frontend_server_common/pubspec.yaml
new file mode 100644
index 0000000..d3cf8ef
--- /dev/null
+++ b/frontend_server_common/pubspec.yaml
@@ -0,0 +1,13 @@
+name: frontend_server_common
+publish_to: none
+description: >-
+ Frontend server integration code to use for dwds tests. Mimicks flutter code.
+environment:
+ sdk: ">=2.6.0 <3.0.0"
+
+dependencies:
+ dwds:
+ path: ../dwds
+
+
+
diff --git a/tool/travis.sh b/tool/travis.sh
index 254fb61..9968b40 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -75,14 +75,10 @@
pub run test -x frontend-server || EXIT_CODE=$?
;;
test_2)
- echo 'pub run test'
- pub run test || EXIT_CODE=$?
- ;;
- test_3)
echo 'pub run test test/build/ensure_build_test.dart'
pub run test test/build/ensure_build_test.dart || EXIT_CODE=$?
;;
- test_4)
+ test_3)
echo 'pub run test -j 1'
pub run test -j 1 || EXIT_CODE=$?
;;