Permalink
Browse files

Expose debug URLs to the JSON reporter.

  • Loading branch information...
nex3 committed Oct 4, 2016
1 parent c4370e3 commit 48cb47003f602c06707c9258e0ff3e50f4a27cc3
View
@@ -132,6 +132,36 @@ A suite event is emitted before any `GroupEvent`s for groups in a given test
suite. This is the only event that contains the full metadata about a suite;
future events will refer to the suite by its opaque ID.
+### DebugEvent
+
+```
+class DebugEvent extends Event {
+ String type = "debug";
+
+ /// The suite for which debug information is reported.
+ int suiteID;
+
+ /// The HTTP URL for the Dart Observatory, or `null` if the Observatory isn't
+ /// available for this suite.
+ String observatory;
+
+ /// The HTTP URL for the remote debugger for this suite's host page, or `null`
+ /// if no remote debugger is available for this suite.
+ String remoteDebugger;
+}
+```
+
+A debug event is emitted after (although not necessarily directly after) a
+`SuiteEvent`, and includes information about how to debug that suite. It's only
+emitted if the `--pause-after-load` flag is passed to the test runner.
+
+Note that the `remoteDebugger` URL refers to a remote debugger whose protocol
+may differ based on the browser the suite is running on. You can tell which
+protocol is in use by the `Suite.platform` field for the suite with the given
+ID. Since the same browser instance is used for multiple suites, different
+suites may have the same `host` URL, although only one suite at a time will be
+active when `--pause-after-load` is passed.
+
### GroupEvent
```
View
@@ -128,7 +128,22 @@
"required": ["suite"],
"properties": {
"type": {"enum": ["suite"]},
- "group": {"$ref": "#/definitions/Suite"}
+ "suite": {"$ref": "#/definitions/Suite"}
+ }
+ },
+
+ {
+ "title": "DebugEvent",
+ "required": ["suiteID"],
+ "properties": {
+ "type": {"enum": ["debug"]},
+ "suiteID": {"type": "integer", "minimum": 0},
+ "observatory": {
+ "oneOf": [{"type": "string", "format": "uri"}, {"type": "null"}]
+ },
+ "remoteDebugger": {
+ "oneOf": [{"type": "string", "format": "uri"}, {"type": "null"}]
+ }
}
},
@@ -194,7 +209,7 @@
"not": {
"enum": [
"start", "testStart", "allSuites", "suite", "group", "print",
- "error", "testDone", "done"
+ "error", "testDone", "done", "debug"
]
}
}
@@ -31,7 +31,10 @@ CancelableOperation debug(Engine engine, Reporter reporter,
return new CancelableOperation.fromFuture(() async {
// Make the underlying suite null so that the engine doesn't start running
// it immediately.
- engine.suiteSink.add(loadSuite.changeSuite((_) => null));
+ engine.suiteSink.add(loadSuite.changeSuite((runnerSuite) {
+ engine.pause();
+ return runnerSuite;
+ }));
var suite = await loadSuite.suite;
if (canceled || suite == null) return;
@@ -73,6 +76,8 @@ class _Debugger {
/// Whether [close] has been called.
bool _closed = false;
+ bool get _json => _config.reporter == 'json';
+
_Debugger(this._engine, this._reporter, this._suite)
: _console = new Console(color: Configuration.current.color) {
_console.registerCommand(
@@ -97,7 +102,7 @@ class _Debugger {
await _pause();
if (_closed) return;
- _engine.suiteSink.add(_suite);
+ _engine.resume();
await _engine.onIdle.first;
} finally {
close();
@@ -111,79 +116,85 @@ class _Debugger {
if (!_suite.environment.supportsDebugging) return;
try {
- _reporter.pause();
-
- var bold = _config.color ? '\u001b[1m' : '';
- var yellow = _config.color ? '\u001b[33m' : '';
- var noColor = _config.color ? '\u001b[0m' : '';
- print('');
-
- if (_suite.platform.isDartVM) {
- var url = _suite.environment.observatoryUrl;
- if (url == null) {
- print("${yellow}Observatory URL not found. Make sure you're using "
- "${_suite.platform.name} 1.11 or later.$noColor");
- } else {
- print("Observatory URL: $bold$url$noColor");
+ if (!_json) {
+ _reporter.pause();
+
+ var bold = _config.color ? '\u001b[1m' : '';
+ var yellow = _config.color ? '\u001b[33m' : '';
+ var noColor = _config.color ? '\u001b[0m' : '';
+ print('');
+
+ if (_suite.platform.isDartVM) {
+ var url = _suite.environment.observatoryUrl;
+ if (url == null) {
+ print("${yellow}Observatory URL not found. Make sure you're using "
+ "${_suite.platform.name} 1.11 or later.$noColor");
+ } else {
+ print("Observatory URL: $bold$url$noColor");
+ }
}
- }
- if (_suite.platform.isHeadless) {
- var url = _suite.environment.remoteDebuggerUrl;
- if (url == null) {
- print("${yellow}Remote debugger URL not found.$noColor");
- } else {
- print("Remote debugger URL: $bold$url$noColor");
+ if (_suite.platform.isHeadless) {
+ var url = _suite.environment.remoteDebuggerUrl;
+ if (url == null) {
+ print("${yellow}Remote debugger URL not found.$noColor");
+ } else {
+ print("Remote debugger URL: $bold$url$noColor");
+ }
}
- }
- var buffer = new StringBuffer(
- "${bold}The test runner is paused.${noColor} ");
- if (!_suite.platform.isHeadless) {
- buffer.write("Open the dev console in ${_suite.platform} ");
- } else {
- buffer.write("Open the remote debugger ");
- }
- if (_suite.platform.isDartVM) buffer.write("or the Observatory ");
+ var buffer = new StringBuffer(
+ "${bold}The test runner is paused.${noColor} ");
+ if (!_suite.platform.isHeadless) {
+ buffer.write("Open the dev console in ${_suite.platform} ");
+ } else {
+ buffer.write("Open the remote debugger ");
+ }
+ if (_suite.platform.isDartVM) buffer.write("or the Observatory ");
- buffer.write("and set breakpoints. Once you're finished, return to this "
- "terminal and press Enter.");
+ buffer.write("and set breakpoints. Once you're finished, return to "
+ "this terminal and press Enter.");
- print(wordWrap(buffer.toString()));
+ print(wordWrap(buffer.toString()));
+ }
await inCompletionOrder([
_suite.environment.displayPause(),
cancelableNext(stdinLines)
]).first;
} finally {
- _reporter.resume();
+ if (!_json) _reporter.resume();
}
}
/// Handles the environment pausing to debug.
///
/// This starts the interactive console.
void _onDebugging() {
- _reporter.pause();
+ if (!_json) _reporter.pause();
- print('\nEntering debugging console. Type "help" for help.');
+ if (!_json) {
+ print('\nEntering debugging console. Type "help" for help.');
+ }
_console.start();
}
/// Handles the environment starting up again.
///
/// This closes the interactive console.
void _onNotDebugging() {
- _reporter.resume();
+ if (!_json) _reporter.resume();
_console.stop();
}
/// Restarts the current test.
void _restartTest() {
var liveTest = _engine.active.single;
_engine.restartTest(liveTest);
- print(wordWrap(
- 'Will restart "${liveTest.test.name}" once it finishes running.'));
+ if (!_json) {
+ print(wordWrap(
+ 'Will restart "${liveTest.test.name}" once it finishes running.'));
+ }
}
/// Closes the debugger and releases its resources.
View
@@ -73,6 +73,18 @@ class Engine {
/// previous suites to be unloaded in the order they completed.
final Pool _loadPool;
+ /// A completer that will complete when [this] is unpaused.
+ ///
+ /// If [this] isn't paused, [_pauseCompleter] is `null`.
+ Completer _pauseCompleter;
+
+ /// A future that completes once [this] is unpaused.
+ ///
+ /// If [this] isn't paused, this completes immediately.
+ Future get _onUnpaused => _pauseCompleter == null
+ ? new Future.value()
+ : _pauseCompleter.future;
+
/// Whether all tests passed or were skipped.
///
/// This fires once all tests have completed and [suiteSink] has been closed.
@@ -87,6 +99,9 @@ class Engine {
/// A group of futures for each test suite.
final _group = new FutureGroup();
+ /// All of the engine's stream subscriptions.
+ final _subscriptions = new Set<StreamSubscription>();
+
/// A sink used to pass [RunnerSuite]s in to the engine to run.
///
/// Suites may be added as quickly as they're available; the Engine will only
@@ -232,7 +247,8 @@ class Engine {
}
_runCalled = true;
- _suiteController.stream.listen((suite) {
+ StreamSubscription subscription;
+ subscription = _suiteController.stream.listen((suite) {
_addedSuites.add(suite);
_onSuiteAddedController.add(suite);
@@ -241,6 +257,7 @@ class Engine {
var controller;
if (suite is LoadSuite) {
+ await _onUnpaused;
controller = await _addLoadSuite(suite);
if (controller == null) {
loadResource.release();
@@ -260,9 +277,11 @@ class Engine {
});
}));
}, onDone: () {
+ _subscriptions.remove(subscription);
_onSuiteAddedController.close();
_group.close();
});
+ _subscriptions.add(subscription);
return success;
}
@@ -323,22 +342,27 @@ class Engine {
/// if it succeeds. Otherwise, it's removed from [liveTests] entirely.
Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest,
{bool countSuccess: true}) async {
+ await _onUnpaused;
_active.add(liveTest);
// If there were no active non-load tests, the current active test would
// have been a load test. In that case, remove it, since now we have a
// non-load test to add.
if (_active.first.suite is LoadSuite) _active.removeFirst();
- liveTest.onStateChange.listen((state) {
+ StreamSubscription subscription;
+ subscription = liveTest.onStateChange.listen((state) {
if (state.status != Status.complete) return;
_active.remove(liveTest);
// If we're out of non-load tests, surface a load test.
if (_active.isEmpty && _activeLoadTests.isNotEmpty) {
_active.add(_activeLoadTests.first);
}
+ }, onDone: () {
+ _subscriptions.remove(subscription);
});
+ _subscriptions.add(subscription);
suiteController.reportLiveTest(liveTest, countSuccess: countSuccess);
@@ -361,7 +385,8 @@ class Engine {
/// [suiteController] is the controller for the suite that contains [test].
/// [parents] is a list of groups that contain [test].
Future _runSkippedTest(LiveSuiteController suiteController, Test test,
- List<Group> parents) {
+ List<Group> parents) async {
+ await _onUnpaused;
var skipped = new LocalTest(test.name, test.metadata, () {},
trace: test.trace);
@@ -380,7 +405,7 @@ class Engine {
controller.completer.complete();
}, () {}, groups: parents);
- return _runLiveTest(suiteController, controller.liveTest);
+ return await _runLiveTest(suiteController, controller.liveTest);
}
/// Closes [liveTest] and tells the engine to re-run it once it's done
@@ -415,7 +440,8 @@ class Engine {
// Only surface the load test if there are no other tests currently running.
if (_active.isEmpty) _active.add(liveTest);
- liveTest.onStateChange.listen((state) {
+ StreamSubscription subscription;
+ subscription = liveTest.onStateChange.listen((state) {
if (state.status != Status.complete) return;
_activeLoadTests.remove(liveTest);
@@ -426,7 +452,10 @@ class Engine {
_active.remove(liveTest);
if (_activeLoadTests.isNotEmpty) _active.add(_activeLoadTests.last);
}
+ }, onDone: () {
+ _subscriptions.remove(subscription);
});
+ _subscriptions.add(subscription);
controller.reportLiveTest(liveTest, countSuccess: false);
controller.noMoreLiveTests();
@@ -463,6 +492,30 @@ class Engine {
_failedGroup.add(liveSuite.failed);
}
+ /// Pauses the engine.
+ ///
+ /// This pauses all streams and keeps any new suites from being loaded or
+ /// tests from being run until [resume] is called.
+ ///
+ /// This does nothing if the engine is already paused. Pauses are *not*
+ /// cumulative.
+ void pause() {
+ if (_pauseCompleter != null) return;
+ _pauseCompleter = new Completer();
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ void resume() {
+ if (_pauseCompleter == null) return;
+ _pauseCompleter.complete();
+ _pauseCompleter = null;
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
/// Signals that the caller is done paying attention to test results and the
/// engine should release any resources it has allocated.
///
@@ -12,11 +12,9 @@ class PluginEnvironment implements Environment {
const PluginEnvironment();
- Uri get observatoryUrl => throw new UnsupportedError(
- "PluginEnvironment.observatoryUrl is not supported.");
+ Uri get observatoryUrl => null;
- Uri get remoteDebuggerUrl => throw new UnsupportedError(
- "PluginEnvironment.remoteDebuggerUrl is not supported.");
+ Uri get remoteDebuggerUrl => null;
CancelableOperation displayPause() =>
throw new UnsupportedError(
Oops, something went wrong.

0 comments on commit 48cb470

Please sign in to comment.