Added more analytics (#1420)
* Added more analytics
Helps: https://github.com/dart-lang/webdev/issues/1419
* Update HTTP_REQUEST_EXCEPTION event to match other events
* Addressed CR comments, added event on fullReload as well
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 9447f17..f960e1b 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -6,7 +6,11 @@
- Use default constant port for debug service.
- If we fail binding to the port, fall back to previous strategy
of finding unbound ports.
-- Add metrics measuring DevTools Initial Page Load time.
+- Add metrics measuring
+ - DevTools Initial Page Load time
+ - Various VM API
+ - Hot restart
+ - Http request handling exceptions
- Add `ext.dwds.sendEvent` service extension to dwds so other tools
can send events to the debugger.
Event format:
diff --git a/dwds/lib/dwds.dart b/dwds/lib/dwds.dart
index 0a0c3fd..f3c3d58 100644
--- a/dwds/lib/dwds.dart
+++ b/dwds/lib/dwds.dart
@@ -86,7 +86,9 @@
Future<DebugConnection> debugConnection(AppConnection appConnection) async {
if (!_enableDebugging) throw StateError('Debugging is not enabled.');
- var appDebugServices = await _devHandler.loadAppServices(appConnection);
+ final dwdsStats = DwdsStats(DateTime.now());
+ var appDebugServices =
+ await _devHandler.loadAppServices(appConnection, dwdsStats);
await appDebugServices.chromeProxyService.isInitialized;
return DebugConnection(appDebugServices);
}
diff --git a/dwds/lib/src/dwds_vm_client.dart b/dwds/lib/src/dwds_vm_client.dart
index 1c82609..8aaa165 100644
--- a/dwds/lib/src/dwds_vm_client.dart
+++ b/dwds/lib/src/dwds_vm_client.dart
@@ -80,71 +80,19 @@
}
};
});
- await client.registerService('_flutter.listViews', 'DWDS listViews');
+ await client.registerService('_flutter.listViews', 'DWDS');
- client.registerServiceCallback('hotRestart', (request) async {
- _logger.info('Attempting a hot restart');
+ client.registerServiceCallback(
+ 'hotRestart',
+ (request) => captureElapsedTime(
+ () => _hotRestart(chromeProxyService, client),
+ (_) => DwdsEvent.hotRestart()));
+ await client.registerService('hotRestart', 'DWDS');
- chromeProxyService.terminatingIsolates = true;
- await _disableBreakpointsAndResume(client, chromeProxyService);
- int context;
- try {
- _logger.info('Attempting to get execution context ID.');
- context = await chromeProxyService.executionContext.id;
- _logger.info('Got execution context ID.');
- } on StateError catch (e) {
- // We couldn't find the execution context. `hotRestart` may have been
- // triggered in the middle of a full reload.
- return {
- 'error': {
- 'code': RPCError.kInternalError,
- 'message': e.message,
- }
- };
- }
- // Start listening for isolate create events before issuing a hot
- // restart. Only return success after the isolate has fully started.
- var stream = chromeProxyService.onEvent('Isolate');
- try {
- _logger.info('Issuing \$dartHotRestart request.');
- await chromeProxyService.remoteDebugger
- .sendCommand('Runtime.evaluate', params: {
- 'expression': r'$dartHotRestart();',
- 'awaitPromise': true,
- 'contextId': context,
- });
- _logger.info('\$dartHotRestart request complete.');
- } on WipError catch (exception) {
- var code = exception.error['code'];
- // This corresponds to `Execution context was destroyed` which can
- // occur during a hot restart that must fall back to a full reload.
- if (code != RPCError.kServerError) {
- return {
- 'error': {
- 'code': exception.error['code'],
- 'message': exception.error['message'],
- 'data': exception,
- }
- };
- }
- }
-
- _logger.info('Waiting for Isolate Start event.');
- await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
- chromeProxyService.terminatingIsolates = false;
-
- _logger.info('Successful hot restart');
- return {'result': Success().toJson()};
- });
- await client.registerService('hotRestart', 'DWDS fullReload');
-
- client.registerServiceCallback('fullReload', (_) async {
- _logger.info('Attempting a full reload');
- await chromeProxyService.remoteDebugger.enablePage();
- await chromeProxyService.remoteDebugger.pageReload();
- _logger.info('Successful full reload');
- return {'result': Success().toJson()};
- });
+ client.registerServiceCallback(
+ 'fullReload',
+ (request) => captureElapsedTime(() => _fullReload(chromeProxyService),
+ (_) => DwdsEvent.fullReload()));
await client.registerService('fullReload', 'DWDS');
client.registerServiceCallback('ext.dwds.screenshot', (_) async {
@@ -205,6 +153,9 @@
var action = payload == null ? null : payload['action'];
if (screen == 'debugger' && action == 'pageReady') {
if (dwdsStats.isFirstDebuggerReady()) {
+ emitEvent(DwdsEvent.devToolsLoad(DateTime.now()
+ .difference(dwdsStats.devToolsStart)
+ .inMilliseconds));
emitEvent(DwdsEvent.debuggerReady(DateTime.now()
.difference(dwdsStats.debuggerStart)
.inMilliseconds));
@@ -218,6 +169,71 @@
}
}
+Future<Map<String, dynamic>> _hotRestart(
+ ChromeProxyService chromeProxyService, VmService client) async {
+ _logger.info('Attempting a hot restart');
+
+ chromeProxyService.terminatingIsolates = true;
+ await _disableBreakpointsAndResume(client, chromeProxyService);
+ int context;
+ try {
+ _logger.info('Attempting to get execution context ID.');
+ context = await chromeProxyService.executionContext.id;
+ _logger.info('Got execution context ID.');
+ } on StateError catch (e) {
+ // We couldn't find the execution context. `hotRestart` may have been
+ // triggered in the middle of a full reload.
+ return {
+ 'error': {
+ 'code': RPCError.kInternalError,
+ 'message': e.message,
+ }
+ };
+ }
+ // Start listening for isolate create events before issuing a hot
+ // restart. Only return success after the isolate has fully started.
+ var stream = chromeProxyService.onEvent('Isolate');
+ try {
+ _logger.info('Issuing \$dartHotRestart request.');
+ await chromeProxyService.remoteDebugger
+ .sendCommand('Runtime.evaluate', params: {
+ 'expression': r'$dartHotRestart();',
+ 'awaitPromise': true,
+ 'contextId': context,
+ });
+ _logger.info('\$dartHotRestart request complete.');
+ } on WipError catch (exception) {
+ var code = exception.error['code'];
+ // This corresponds to `Execution context was destroyed` which can
+ // occur during a hot restart that must fall back to a full reload.
+ if (code != RPCError.kServerError) {
+ return {
+ 'error': {
+ 'code': exception.error['code'],
+ 'message': exception.error['message'],
+ 'data': exception,
+ }
+ };
+ }
+ }
+
+ _logger.info('Waiting for Isolate Start event.');
+ await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
+ chromeProxyService.terminatingIsolates = false;
+
+ _logger.info('Successful hot restart');
+ return {'result': Success().toJson()};
+}
+
+Future<Map<String, dynamic>> _fullReload(
+ ChromeProxyService chromeProxyService) async {
+ _logger.info('Attempting a full reload');
+ await chromeProxyService.remoteDebugger.enablePage();
+ await chromeProxyService.remoteDebugger.pageReload();
+ _logger.info('Successful full reload');
+ return {'result': Success().toJson()};
+}
+
Future<void> _disableBreakpointsAndResume(
VmService client, ChromeProxyService chromeProxyService) async {
_logger.info('Attempting to disable breakpoints and resume the isolate');
diff --git a/dwds/lib/src/events.dart b/dwds/lib/src/events.dart
index 36e5ad6..f7bb86d 100644
--- a/dwds/lib/src/events.dart
+++ b/dwds/lib/src/events.dart
@@ -12,6 +12,9 @@
/// The time when the user starts the debugger.
final DateTime debuggerStart;
+ /// The time when dwds launches DevTools.
+ DateTime devToolsStart;
+
var _isDebuggerReady = false;
/// Records and returns whether the debugger became ready.
@@ -28,13 +31,17 @@
static const String compilerUpdateDependencies =
'COMPILER_UPDATE_DEPENDENCIES';
static const String devtoolsLaunch = 'DEVTOOLS_LAUNCH';
+ static const String devToolsLoad = 'DEVTOOLS_LOAD';
+ static const String debuggerReady = 'DEBUGGER_READY';
static const String evaluate = 'EVALUATE';
static const String evaluateInFrame = 'EVALUATE_IN_FRAME';
+ static const String fullReload = 'FULL_RELOAD';
static const String getIsolate = 'GET_ISOLATE';
static const String getScripts = 'GET_SCRIPTS';
static const String getSourceReport = 'GET_SOURCE_REPORT';
- static const String debuggerReady = 'DEBUGGER_READY';
static const String getVM = 'GET_VM';
+ static const String hotRestart = 'HOT_RESTART';
+ static const String httpRequestException = 'HTTP_REQUEST_EXCEPTION';
static const String resume = 'RESUME';
DwdsEventKind._();
@@ -77,11 +84,26 @@
DwdsEvent.getSourceReport() : this(DwdsEventKind.getSourceReport, {});
+ DwdsEvent.hotRestart() : this(DwdsEventKind.hotRestart, {});
+
+ DwdsEvent.fullReload() : this(DwdsEventKind.fullReload, {});
+
DwdsEvent.debuggerReady(int elapsedMilliseconds)
: this(DwdsEventKind.debuggerReady, {
'elapsedMilliseconds': elapsedMilliseconds,
});
+ DwdsEvent.devToolsLoad(int elapsedMilliseconds)
+ : this(DwdsEventKind.devToolsLoad, {
+ 'elapsedMilliseconds': elapsedMilliseconds,
+ });
+
+ DwdsEvent.httpRequestException(String server, String exception)
+ : this(DwdsEventKind.httpRequestException, {
+ 'server': server,
+ 'exception': exception,
+ });
+
void addException(dynamic exception) {
payload['exception'] = exception;
}
@@ -103,3 +125,24 @@
/// A global stream of [DwdsEvent]s.
Stream<DwdsEvent> get eventStream => _eventController.stream;
+
+/// Call [function] and record its execution time.
+///
+/// Calls [event] to create the event to be recorded,
+/// and appends time and exception details to it if
+/// available.
+Future<T> captureElapsedTime<T>(
+ Future<T> Function() function, DwdsEvent Function(T result) event) async {
+ var stopwatch = Stopwatch()..start();
+ T result;
+ try {
+ return result = await function();
+ } catch (e) {
+ emitEvent(event(result)
+ ..addException(e)
+ ..addElapsedTime(stopwatch.elapsedMilliseconds));
+ rethrow;
+ } finally {
+ emitEvent(event(result)..addElapsedTime(stopwatch.elapsedMilliseconds));
+ }
+}
diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart
index 39324bf..922bf23 100644
--- a/dwds/lib/src/handlers/dev_handler.dart
+++ b/dwds/lib/src/handlers/dev_handler.dart
@@ -221,8 +221,8 @@
);
}
- Future<AppDebugServices> loadAppServices(AppConnection appConnection) async {
- var dwdsStats = DwdsStats(DateTime.now());
+ Future<AppDebugServices> loadAppServices(
+ AppConnection appConnection, DwdsStats dwdsStats) async {
var appId = appConnection.request.appId;
if (_servicesByAppId[appId] == null) {
var debugService = await _startLocalDebugService(
@@ -321,9 +321,10 @@
return;
}
+ var dwdsStats = DwdsStats(DateTime.now());
AppDebugServices appServices;
try {
- appServices = await loadAppServices(appConnection);
+ appServices = await loadAppServices(appConnection, dwdsStats);
} catch (_) {
var error = 'Unable to connect debug services to your '
'application. Most likely this means you are trying to '
@@ -364,6 +365,7 @@
..promptExtension = false))));
appServices.connectedInstanceId = appConnection.request.instanceId;
+ dwdsStats.devToolsStart = DateTime.now();
await _launchDevTools(appServices.chromeProxyService.remoteDebugger,
appServices.debugService.uri);
}
@@ -511,6 +513,7 @@
extensionDebugConnections.add(DebugConnection(appServices));
_servicesByAppId[appId] = appServices;
}
+ dwdsStats.devToolsStart = DateTime.now();
await _launchDevTools(extensionDebugger,
await _servicesByAppId[appId].debugService.encodedUri);
});
diff --git a/dwds/lib/src/servers/extension_backend.dart b/dwds/lib/src/servers/extension_backend.dart
index b6aa03a..85aba76 100644
--- a/dwds/lib/src/servers/extension_backend.dart
+++ b/dwds/lib/src/servers/extension_backend.dart
@@ -8,12 +8,12 @@
import 'dart:io';
import 'package:async/async.dart';
-
import 'package:http_multi_server/http_multi_server.dart';
import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
import '../../data/extension_request.dart';
+import '../events.dart';
import '../handlers/socket_connections.dart';
import '../utilities/shared.dart';
import 'extension_debugger.dart';
@@ -21,7 +21,7 @@
const authenticationResponse = 'Dart Debug Authentication Success!\n\n'
'You can close this tab and launch the Dart Debug Extension again.';
-Logger _logger = Logger('ExtensiobBackend');
+Logger _logger = Logger('ExtensionBackend');
/// A backend for the Dart Debug Extension.
///
@@ -56,7 +56,8 @@
}).add(_socketHandler.handler);
var server = await HttpMultiServer.bind(hostname, 0);
serveHttpRequests(server, cascade.handler, (e, s) {
- _logger.warning('Error serving requests', e, s);
+ _logger.warning('Error serving requests', e);
+ emitEvent(DwdsEvent.httpRequestException('ExtensionBackend', '$e:$s'));
});
return ExtensionBackend._(
_socketHandler, server.address.host, server.port, server);
diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart
index 3a71651..7f2231a 100644
--- a/dwds/lib/src/services/chrome_proxy_service.dart
+++ b/dwds/lib/src/services/chrome_proxy_service.dart
@@ -184,7 +184,7 @@
moduleFormat: moduleFormat, soundNullSafety: soundNullSafety);
var dependencies =
await globalLoadStrategy.moduleInfoForEntrypoint(entrypoint);
- await _captureElapsedTime(() async {
+ await captureElapsedTime(() async {
var result = await _compiler.updateDependencies(dependencies);
// Expression evaluation is ready after dependencies are updated.
if (!_compilerCompleter.isCompleted) _compilerCompleter.complete();
@@ -422,7 +422,7 @@
bool disableBreakpoints,
}) async {
// TODO(798) - respect disableBreakpoints.
- return _captureElapsedTime(() async {
+ return captureElapsedTime(() async {
await isInitialized;
if (_expressionEvaluator != null) {
await isCompilerInitialized;
@@ -447,7 +447,7 @@
{Map<String, String> scope, bool disableBreakpoints}) async {
// TODO(798) - respect disableBreakpoints.
- return _captureElapsedTime(() async {
+ return captureElapsedTime(() async {
await isInitialized;
if (_expressionEvaluator != null) {
await isCompilerInitialized;
@@ -509,7 +509,7 @@
@override
Future<Isolate> getIsolate(String isolateId) async {
- return _captureElapsedTime(() async {
+ return captureElapsedTime(() async {
await isInitialized;
return _getIsolate(isolateId);
}, (result) => DwdsEvent.getIsolate());
@@ -531,7 +531,7 @@
@override
Future<ScriptList> getScripts(String isolateId) async {
- return await _captureElapsedTime(() async {
+ return await captureElapsedTime(() async {
await isInitialized;
return _inspector?.getScripts(isolateId);
}, (result) => DwdsEvent.getScripts());
@@ -544,7 +544,7 @@
int endTokenPos,
bool forceCompile,
bool reportLines}) async {
- return await _captureElapsedTime(() async {
+ return await captureElapsedTime(() async {
await isInitialized;
return await _inspector?.getSourceReport(isolateId, reports,
scriptId: scriptId,
@@ -568,7 +568,7 @@
@override
Future<VM> getVM() async {
- return _captureElapsedTime(() async {
+ return captureElapsedTime(() async {
await isInitialized;
return _vm;
}, (result) => DwdsEvent.getVM());
@@ -685,7 +685,7 @@
{String step, int frameIndex}) async {
if (_inspector == null) throw StateError('No running isolate.');
if (_inspector.appConnection.isStarted) {
- return _captureElapsedTime(() async {
+ return captureElapsedTime(() async {
await isInitialized;
return await (await _debugger)
.resume(isolateId, step: step, frameIndex: frameIndex);
@@ -1037,27 +1037,6 @@
Future<Breakpoint> setBreakpointState(
String isolateId, String breakpointId, bool enable) =>
throw UnimplementedError();
-
- /// Call [function] and record its execution time.
- ///
- /// Calls [event] to create the event to be recorded,
- /// and appends time and exception details to it if
- /// available.
- Future<T> _captureElapsedTime<T>(
- Future<T> Function() function, DwdsEvent Function(T result) event) async {
- var stopwatch = Stopwatch()..start();
- T result;
- try {
- return result = await function();
- } catch (e) {
- emitEvent(event(result)
- ..addException(e)
- ..addElapsedTime(stopwatch.elapsedMilliseconds));
- rethrow;
- } finally {
- emitEvent(event(result)..addElapsedTime(stopwatch.elapsedMilliseconds));
- }
- }
}
/// The `type`s of [ConsoleAPIEvent]s that are treated as `stderr` logs.
diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart
index 3b06d6e..9987c5e 100644
--- a/dwds/lib/src/services/debug_service.dart
+++ b/dwds/lib/src/services/debug_service.dart
@@ -23,6 +23,7 @@
import '../../dwds.dart';
import '../debugging/execution_context.dart';
import '../debugging/remote_debugger.dart';
+import '../events.dart';
import '../utilities/shared.dart';
import 'chrome_proxy_service.dart';
@@ -256,7 +257,8 @@
}
var server = await startHttpServer(hostname, port: 44456);
serveHttpRequests(server, handler, (e, s) {
- _logger.warning('Error serving requests', e, s);
+ _logger.warning('Error serving requests', e);
+ emitEvent(DwdsEvent.httpRequestException('DebugService', '$e:$s'));
});
return DebugService._(
chromeProxyService,
diff --git a/dwds/test/events_test.dart b/dwds/test/events_test.dart
index 61bcce6..7ad33dd 100644
--- a/dwds/test/events_test.dart
+++ b/dwds/test/events_test.dart
@@ -5,10 +5,13 @@
// @dart = 2.9
import 'dart:async';
+import 'dart:io';
import 'package:dwds/src/connections/debug_connection.dart';
import 'package:dwds/src/events.dart';
import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:dwds/src/utilities/shared.dart';
+import 'package:http_multi_server/http_multi_server.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webdriver/async_core.dart';
@@ -25,323 +28,417 @@
final context = TestContext();
void main() {
- Future initialEvents;
- VmService vmService;
- Keyboard keyboard;
- Stream<DwdsEvent> events;
-
- /// Runs [action] and waits for an event matching [eventMatcher].
- Future<T> expectEventDuring<T>(
- Matcher eventMatcher, Future<T> Function() action,
- {Timeout timeout}) async {
- // The events stream is a broadcast stream so start listening
- // before the action.
- final events = expectLater(
- pipe(context.testServer.dwds.events, timeout: timeout),
- emitsThrough(eventMatcher));
- final result = await action();
- await events;
- return result;
- }
-
- setUpAll(() async {
- setCurrentLogWriter();
- initialEvents = expectLater(
- pipe(eventStream, timeout: const Timeout.factor(5)),
- emitsThrough(matchesEvent(DwdsEventKind.compilerUpdateDependencies, {
- 'entrypoint': 'hello_world/main.dart.bootstrap.js',
- 'elapsedMilliseconds': isNotNull
- })));
- await context.setUp(
- serveDevTools: true,
- enableExpressionEvaluation: true,
- );
- vmService = context.debugConnection.vmService;
- keyboard = context.webDriver.driver.keyboard;
- events = context.testServer.dwds.events;
- });
-
- tearDownAll(() async {
- await context.tearDown();
- });
-
- test('emits DEVTOOLS_LAUNCH event', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.devtoolsLaunch, {}),
- () => keyboard.sendChord([Keyboard.alt, 'd']),
- );
- });
-
- test('emits DEBUGGER_READY event', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.debuggerReady, {
- 'elapsedMilliseconds': isNotNull,
- }),
- () => keyboard.sendChord([Keyboard.alt, 'd']),
- );
- },
- skip: 'Enable after publishing of '
- 'https://github.com/flutter/devtools/pull/3346');
-
- test('events can be listened to multiple times', () async {
- events.listen((_) {});
- events.listen((_) {});
- });
-
- test('can emit event through service extension', () async {
- final response = await expectEventDuring(
- matchesEvent('foo-event', {'data': 1234}),
- () => vmService.callServiceExtension('ext.dwds.emitEvent', args: {
- 'type': 'foo-event',
- 'payload': {'data': 1234},
- }));
- expect(response.type, 'Success');
- });
-
- group('evaluate', () {
- Isolate isolate;
- LibraryRef bootstrap;
-
- setUpAll(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
- isolate = await service.getIsolate(vm.isolates.first.id);
- bootstrap = isolate.rootLib;
- });
+ group('serve requests', () {
+ HttpServer server;
setUp(() async {
setCurrentLogWriter();
- });
-
- test('emits EVALUATE events on evaluation success', () async {
- final expression = "helloString('world')";
- await expectEventDuring(
- matchesEvent(DwdsEventKind.evaluate, {
- 'expression': expression,
- 'success': isTrue,
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.evaluate(isolate.id, bootstrap.id, expression));
- });
-
- test('emits COMPILER_UPDATE_DEPENDENCIES event', () async {
- await initialEvents;
- });
-
- test('emits EVALUATE events on evaluation failure', () async {
- final expression = 'some-bad-expression';
- await expectEventDuring(
- matchesEvent(DwdsEventKind.evaluate, {
- 'expression': expression,
- 'success': isFalse,
- 'error': isA<ErrorRef>(),
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.evaluate(isolate.id, bootstrap.id, expression));
- });
- });
-
- group('evaluateInFrame', () {
- String isolateId;
- Stream<Event> stream;
- ScriptList scripts;
- ScriptRef mainScript;
-
- setUpAll(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
-
- isolateId = vm.isolates.first.id;
- scripts = await service.getScripts(isolateId);
- await service.streamListen('Debug');
- stream = service.onEvent('Debug');
- mainScript = scripts.scripts
- .firstWhere((script) => script.uri.contains('main.dart'));
- });
-
- setUp(() async {
- setCurrentLogWriter();
- });
-
- test('emits EVALUATE_IN_FRAME events on RPC error', () async {
- final expression = 'some-bad-expression';
- await expectEventDuring(
- matchesEvent(DwdsEventKind.evaluateInFrame, {
- 'expression': expression,
- 'success': isFalse,
- 'exception': isA<RPCError>().having(
- (e) => e.message, 'message', contains('program is not paused')),
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service
- .evaluateInFrame(isolateId, 0, expression)
- .catchError((_) {}));
- });
-
- test('emits EVALUATE_IN_FRAME events on evaluation error', () async {
- final line = await context.findBreakpointLine(
- 'callPrintCount', isolateId, mainScript);
- final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
- // Wait for breakpoint to trigger.
- await stream
- .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
- // Evaluation succeeds and return ErrorRef containing compilation error,
- // so event is marked as success.
- final expression = 'some-bad-expression';
- await expectEventDuring(
- matchesEvent(DwdsEventKind.evaluateInFrame, {
- 'expression': expression,
- 'success': isFalse,
- 'error': isA<ErrorRef>(),
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service
- .evaluateInFrame(isolateId, 0, expression)
- .catchError((_) {}));
-
- await service.removeBreakpoint(isolateId, bp.id);
- await service.resume(isolateId);
- });
-
- test('emits EVALUATE_IN_FRAME events on evaluation success', () async {
- final line = await context.findBreakpointLine(
- 'callPrintCount', isolateId, mainScript);
- final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
- // Wait for breakpoint to trigger.
- await stream
- .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
- // Evaluation succeeds and return InstanceRef,
- // so event is marked as success.
- final expression = 'true';
- await expectEventDuring(
- matchesEvent(DwdsEventKind.evaluateInFrame, {
- 'expression': expression,
- 'success': isTrue,
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service
- .evaluateInFrame(isolateId, 0, expression)
- .catchError((_) {}));
-
- await service.removeBreakpoint(isolateId, bp.id);
- await service.resume(isolateId);
- });
- });
-
- group('getSourceReport', () {
- String isolateId;
- ScriptList scripts;
- ScriptRef mainScript;
-
- setUp(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
- isolateId = vm.isolates.first.id;
- scripts = await service.getScripts(isolateId);
-
- mainScript = scripts.scripts
- .firstWhere((script) => script.uri.contains('main.dart'));
- });
-
- test('emits GET_SOURCE_REPORT events', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.getSourceReport, {
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.getSourceReport(
- isolateId, [SourceReportKind.kPossibleBreakpoints],
- scriptId: mainScript.id));
- });
- });
-
- group('getSripts', () {
- String isolateId;
-
- setUp(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
- isolateId = vm.isolates.first.id;
- });
-
- test('emits GET_SCRIPTS events', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.getScripts, {
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.getScripts(isolateId));
- });
- });
-
- group('getIsolate', () {
- String isolateId;
-
- setUp(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
- isolateId = vm.isolates.first.id;
- });
-
- test('emits GET_ISOLATE events', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.getIsolate, {
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.getIsolate(isolateId));
- });
- });
-
- group('getVM', () {
- setUp(() async {
- setCurrentLogWriter();
- });
-
- test('emits GET_VM events', () async {
- await expectEventDuring(
- matchesEvent(DwdsEventKind.getVM, {
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.getVM());
- });
- });
-
- group('resume', () {
- String isolateId;
- Stream<Event> stream;
- ScriptList scripts;
- ScriptRef mainScript;
-
- setUp(() async {
- setCurrentLogWriter();
- final vm = await service.getVM();
- isolateId = vm.isolates.first.id;
- scripts = await service.getScripts(isolateId);
- await service.streamListen('Debug');
- stream = service.onEvent('Debug');
- mainScript = scripts.scripts
- .firstWhere((script) => script.uri.contains('main.dart'));
- final line = await context.findBreakpointLine(
- 'callPrintCount', isolateId, mainScript);
- final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
- // Wait for breakpoint to trigger.
- await stream
- .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
- await service.removeBreakpoint(isolateId, bp.id);
+ server = await HttpMultiServer.bind('localhost', 0);
});
tearDown(() async {
- // Resume execution to not impact other tests.
- await service.resume(isolateId);
+ await server?.close();
});
- test('emits RESUME events', () async {
+ test('emits HTTP_REQUEST_EXCEPTION event', () async {
+ final throwAsyncException = () async {
+ await Future.delayed(const Duration(milliseconds: 100));
+ throw Exception('async error');
+ };
+
+ // The events stream is a broadcast stream so start listening
+ // before the action.
+ final events = expectLater(
+ pipe(eventStream),
+ emitsThrough(matchesEvent(DwdsEventKind.httpRequestException, {
+ 'server': 'FakeServer',
+ 'exception': startsWith('Exception: async error'),
+ })));
+
+ // Start serving requests with a failing handler in an error zone.
+ serveHttpRequests(server, (request) async {
+ unawaited(throwAsyncException());
+ return null;
+ }, (e, s) {
+ emitEvent(DwdsEvent.httpRequestException('FakeServer', '$e:$s'));
+ });
+
+ // Send a request.
+ final client = HttpClient();
+ var request =
+ await client.getUrl(Uri.parse('http://localhost:${server.port}/foo'));
+
+ // Ignore the response.
+ var response = await request.close();
+ await response.drain();
+
+ // Wait for expected events.
+ await events;
+ });
+ });
+
+ group('with dwds', () {
+ Future initialEvents;
+ VmService vmService;
+ Keyboard keyboard;
+ Stream<DwdsEvent> events;
+
+ /// Runs [action] and waits for an event matching [eventMatcher].
+ Future<T> expectEventDuring<T>(
+ Matcher eventMatcher, Future<T> Function() action,
+ {Timeout timeout}) async {
+ // The events stream is a broadcast stream so start listening
+ // before the action.
+ final events = expectLater(
+ pipe(context.testServer.dwds.events, timeout: timeout),
+ emitsThrough(eventMatcher));
+ final result = await action();
+ await events;
+ return result;
+ }
+
+ setUpAll(() async {
+ setCurrentLogWriter();
+ initialEvents = expectLater(
+ pipe(eventStream, timeout: const Timeout.factor(5)),
+ emitsThrough(matchesEvent(DwdsEventKind.compilerUpdateDependencies, {
+ 'entrypoint': 'hello_world/main.dart.bootstrap.js',
+ 'elapsedMilliseconds': isNotNull
+ })));
+ await context.setUp(
+ serveDevTools: true,
+ enableExpressionEvaluation: true,
+ );
+ vmService = context.debugConnection.vmService;
+ keyboard = context.webDriver.driver.keyboard;
+ events = context.testServer.dwds.events;
+ });
+
+ tearDownAll(() async {
+ await context.tearDown();
+ });
+
+ test('emits DEVTOOLS_LAUNCH event', () async {
await expectEventDuring(
- matchesEvent(DwdsEventKind.resume, {
- 'step': 'Into',
- 'elapsedMilliseconds': isNotNull,
- }),
- () => service.resume(isolateId, step: 'Into'));
+ matchesEvent(DwdsEventKind.devtoolsLaunch, {}),
+ () => keyboard.sendChord([Keyboard.alt, 'd']),
+ );
+ });
+
+ test('emits DEBUGGER_READY event', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.debuggerReady, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => keyboard.sendChord([Keyboard.alt, 'd']),
+ );
+ },
+ skip: 'Enable after publishing of '
+ 'https://github.com/flutter/devtools/pull/3346');
+
+ test('emits DEVTOOLS_LOAD events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.devToolsLoad, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => keyboard.sendChord([Keyboard.alt, 'd']),
+ );
+ },
+ skip: 'Enable after publishing of '
+ 'https://github.com/flutter/devtools/pull/3346');
+
+ test('events can be listened to multiple times', () async {
+ events.listen((_) {});
+ events.listen((_) {});
+ });
+
+ test('can emit event through service extension', () async {
+ final response = await expectEventDuring(
+ matchesEvent('foo-event', {'data': 1234}),
+ () => vmService.callServiceExtension('ext.dwds.emitEvent', args: {
+ 'type': 'foo-event',
+ 'payload': {'data': 1234},
+ }));
+ expect(response.type, 'Success');
+ });
+
+ group('evaluate', () {
+ Isolate isolate;
+ LibraryRef bootstrap;
+
+ setUpAll(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+ isolate = await service.getIsolate(vm.isolates.first.id);
+ bootstrap = isolate.rootLib;
+ });
+
+ setUp(() async {
+ setCurrentLogWriter();
+ });
+
+ test('emits EVALUATE events on evaluation success', () async {
+ final expression = "helloString('world')";
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.evaluate, {
+ 'expression': expression,
+ 'success': isTrue,
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.evaluate(isolate.id, bootstrap.id, expression));
+ });
+
+ test('emits COMPILER_UPDATE_DEPENDENCIES event', () async {
+ await initialEvents;
+ });
+
+ test('emits EVALUATE events on evaluation failure', () async {
+ final expression = 'some-bad-expression';
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.evaluate, {
+ 'expression': expression,
+ 'success': isFalse,
+ 'error': isA<ErrorRef>(),
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.evaluate(isolate.id, bootstrap.id, expression));
+ });
+ });
+
+ group('evaluateInFrame', () {
+ String isolateId;
+ Stream<Event> stream;
+ ScriptList scripts;
+ ScriptRef mainScript;
+
+ setUpAll(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+
+ isolateId = vm.isolates.first.id;
+ scripts = await service.getScripts(isolateId);
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+ mainScript = scripts.scripts
+ .firstWhere((script) => script.uri.contains('main.dart'));
+ });
+
+ setUp(() async {
+ setCurrentLogWriter();
+ });
+
+ test('emits EVALUATE_IN_FRAME events on RPC error', () async {
+ final expression = 'some-bad-expression';
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.evaluateInFrame, {
+ 'expression': expression,
+ 'success': isFalse,
+ 'exception': isA<RPCError>().having((e) => e.message, 'message',
+ contains('program is not paused')),
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service
+ .evaluateInFrame(isolateId, 0, expression)
+ .catchError((_) {}));
+ });
+
+ test('emits EVALUATE_IN_FRAME events on evaluation error', () async {
+ final line = await context.findBreakpointLine(
+ 'callPrintCount', isolateId, mainScript);
+ final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
+ // Wait for breakpoint to trigger.
+ await stream
+ .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
+ // Evaluation succeeds and return ErrorRef containing compilation error,
+ // so event is marked as success.
+ final expression = 'some-bad-expression';
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.evaluateInFrame, {
+ 'expression': expression,
+ 'success': isFalse,
+ 'error': isA<ErrorRef>(),
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service
+ .evaluateInFrame(isolateId, 0, expression)
+ .catchError((_) {}));
+
+ await service.removeBreakpoint(isolateId, bp.id);
+ await service.resume(isolateId);
+ });
+
+ test('emits EVALUATE_IN_FRAME events on evaluation success', () async {
+ final line = await context.findBreakpointLine(
+ 'callPrintCount', isolateId, mainScript);
+ final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
+ // Wait for breakpoint to trigger.
+ await stream
+ .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
+ // Evaluation succeeds and return InstanceRef,
+ // so event is marked as success.
+ final expression = 'true';
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.evaluateInFrame, {
+ 'expression': expression,
+ 'success': isTrue,
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service
+ .evaluateInFrame(isolateId, 0, expression)
+ .catchError((_) {}));
+
+ await service.removeBreakpoint(isolateId, bp.id);
+ await service.resume(isolateId);
+ });
+ });
+
+ group('getSourceReport', () {
+ String isolateId;
+ ScriptList scripts;
+ ScriptRef mainScript;
+
+ setUp(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+ isolateId = vm.isolates.first.id;
+ scripts = await service.getScripts(isolateId);
+
+ mainScript = scripts.scripts
+ .firstWhere((script) => script.uri.contains('main.dart'));
+ });
+
+ test('emits GET_SOURCE_REPORT events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.getSourceReport, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.getSourceReport(
+ isolateId, [SourceReportKind.kPossibleBreakpoints],
+ scriptId: mainScript.id));
+ });
+ });
+
+ group('getSripts', () {
+ String isolateId;
+
+ setUp(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+ isolateId = vm.isolates.first.id;
+ });
+
+ test('emits GET_SCRIPTS events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.getScripts, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.getScripts(isolateId));
+ });
+ });
+
+ group('getIsolate', () {
+ String isolateId;
+
+ setUp(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+ isolateId = vm.isolates.first.id;
+ });
+
+ test('emits GET_ISOLATE events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.getIsolate, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.getIsolate(isolateId));
+ });
+ });
+
+ group('getVM', () {
+ setUp(() async {
+ setCurrentLogWriter();
+ });
+
+ test('emits GET_VM events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.getVM, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.getVM());
+ });
+ });
+
+ group('hotRestart', () {
+ setUp(() async {
+ setCurrentLogWriter();
+ });
+
+ test('emits HOT_RESTART event', () async {
+ var client = context.debugConnection.vmService;
+
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.hotRestart, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => client.callServiceExtension('hotRestart'));
+ });
+ });
+
+ group('resume', () {
+ String isolateId;
+ Stream<Event> stream;
+ ScriptList scripts;
+ ScriptRef mainScript;
+
+ setUp(() async {
+ setCurrentLogWriter();
+ final vm = await service.getVM();
+ isolateId = vm.isolates.first.id;
+ scripts = await service.getScripts(isolateId);
+ await service.streamListen('Debug');
+ stream = service.onEvent('Debug');
+ mainScript = scripts.scripts
+ .firstWhere((script) => script.uri.contains('main.dart'));
+ final line = await context.findBreakpointLine(
+ 'callPrintCount', isolateId, mainScript);
+ final bp = await service.addBreakpoint(isolateId, mainScript.id, line);
+ // Wait for breakpoint to trigger.
+ await stream
+ .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+ await service.removeBreakpoint(isolateId, bp.id);
+ });
+
+ tearDown(() async {
+ // Resume execution to not impact other tests.
+ await service.resume(isolateId);
+ });
+
+ test('emits RESUME events', () async {
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.resume, {
+ 'step': 'Into',
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => service.resume(isolateId, step: 'Into'));
+ });
+ });
+
+ group('fullReload', () {
+ setUp(() async {
+ setCurrentLogWriter();
+ });
+
+ test('emits FULL_RELOAD event', () async {
+ var client = context.debugConnection.vmService;
+
+ await expectEventDuring(
+ matchesEvent(DwdsEventKind.fullReload, {
+ 'elapsedMilliseconds': isNotNull,
+ }),
+ () => client.callServiceExtension('fullReload'));
+ });
});
});
}