diff --git a/CHANGELOG.md b/CHANGELOG.md index 9881978d3f8d87f849e762ca5cacb7b7d214aed2..ea2bd48da9d1fc11278de77413b5be4243efc814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.19 + +* Add support for debugging VM tests. + ## 0.12.18+1 * Fix the deprecated `expectAsync()` function. The deprecation caused it to diff --git a/README.md b/README.md index 7c585371ca90d0eb59abf372ee30144058072429..1e6e2e2f39ea02fd3a08f1dc13d5baaa59ed2d92 100644 --- a/README.md +++ b/README.md @@ -581,24 +581,28 @@ A configuration file can do much more than just set global defaults. See ## Debugging -Tests can be debugged interactively using browsers' built-in development tools, -including Observatory when you're using Dartium. Currently there's no support -for interactively debugging command-line VM tests, but it will be added -[in the future][issue 50]. +Tests can be debugged interactively using platforms' built-in development tools. +Tests running on browsers can use those browsers' development consoles to +inspect the document, set breakpoints, and step through code. Those running on +the Dart VM or Dartium can also use [the Dart Observatory][observatory]'s +debugger. -[issue 50]: https://github.com/dart-lang/test/issues/50 +[observatory]: https://dart-lang.github.io/observatory/ The first step when debugging is to pass the `--pause-after-load` flag to the -test runner. This pauses the browser after each test suite has loaded, so that -you have time to open the development tools and set breakpoints. For Dartium, -the test runner will print the Observatory URL for you. For PhantomJS, it will -print the remote debugger URL. For content shell, it'll print both! - -Once you've set breakpoints, either click the big arrow in the middle of the web -page or press Enter in your terminal to start the tests running. When you hit a -breakpoint, the runner will open its own debugging console in the terminal that -controls how tests are run. You can type "restart" there to re-run your test as -many times as you need to figure out what's going on. +test runner. This pauses the runner after each test suite has loaded, so that +you have time to open the development tools and set breakpoints. For the Dart VM +and Dartium, the test runner will print the Observatory URL for you. For +PhantomJS, it will print the remote debugger URL. For content shell, it'll print +both! + +Once you've set breakpoints, you can press Enter in your terminal to start the +tests running. Some platforms also have shortcuts for this: you can start +browser tests by clicking on the "play" button in the middle of the window, and +you can start VM tests by unpausing the Observatory. When you hit a breakpoint, +the runner will open its own debugging console in the terminal that controls how +tests are run. You can type "restart" there to re-run your test as many times as +you need to figure out what's going on. Normally, browser tests are run in hidden iframes. However, when debugging, the iframe for the current test suite is expanded to fill the browser window so you diff --git a/lib/src/backend/test_platform.dart b/lib/src/backend/test_platform.dart index 0af1a66785077cee628eda98418354bb7fc9dd59..a5f5c69d8fe6f4a4ea565b1163a2ef7cc410365d 100644 --- a/lib/src/backend/test_platform.dart +++ b/lib/src/backend/test_platform.dart @@ -10,7 +10,7 @@ class TestPlatform { /// The command-line Dart VM. static const TestPlatform vm = - const TestPlatform._("VM", "vm", isDartVM: true); + const TestPlatform._("VM", "vm", isDartVM: true, isHeadless: true); /// Dartium. static const TestPlatform dartium = const TestPlatform._("Dartium", "dartium", diff --git a/lib/src/runner.dart b/lib/src/runner.dart index 8424bb11cd8cfbfb407245818d43bb4cca1086d8..4dd0faf30aeb3cdc91f02376c3a093ea81985484 100644 --- a/lib/src/runner.dart +++ b/lib/src/runner.dart @@ -371,11 +371,6 @@ class Runner { /// Loads each suite in [suites] in order, pausing after load for platforms /// that support debugging. Future<bool> _loadThenPause(Stream<LoadSuite> suites) async { - if (_config.suiteDefaults.platforms.contains(TestPlatform.vm)) { - warn("Debugging is currently unsupported on the Dart VM.", - color: _config.color); - } - _suiteSubscription = suites.asyncMap((loadSuite) async { _debugOperation = debug(_engine, _reporter, loadSuite); await _debugOperation.valueOrCancellation(); diff --git a/lib/src/runner/debugger.dart b/lib/src/runner/debugger.dart index 5a3f6f0c0fafee48c8939c75ac7ea5e6b2c5f384..11b424f695d1ac30471aed69391b30f683b482dc 100644 --- a/lib/src/runner/debugger.dart +++ b/lib/src/runner/debugger.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:async/async.dart'; +import '../backend/test_platform.dart'; import '../util/io.dart'; import '../utils.dart'; import 'configuration.dart'; @@ -72,6 +73,10 @@ class _Debugger { /// overlap with the reporter's reporting. final Console _console; + /// A completer that's used to manually unpause the test if the debugger is + /// closed. + final _pauseCompleter = new CancelableCompleter(); + /// The subscription to [_suite.onDebugging]. StreamSubscription<bool> _onDebuggingSubscription; @@ -89,14 +94,6 @@ class _Debugger { "restart", "Restart the current test after it finishes running.", _restartTest); - _onDebuggingSubscription = _suite.onDebugging.listen((debugging) { - if (debugging) { - _onDebugging(); - } else { - _onNotDebugging(); - } - }); - _onRestartSubscription = _suite.environment.onRestart.listen((_) { _restartTest(); }); @@ -111,6 +108,16 @@ class _Debugger { await _pause(); if (_closed) return; + // Wait to subscribe to the debugging stream because we don't want to open + // up the debugging console before any tests are run. + _onDebuggingSubscription = _suite.onDebugging.listen((debugging) { + if (debugging) { + _onDebugging(); + } else { + _onNotDebugging(); + } + }); + _engine.resume(); await _engine.onIdle.first; } finally { @@ -143,7 +150,7 @@ class _Debugger { } } - if (_suite.platform.isHeadless) { + if (_suite.platform.isHeadless && _suite.platform != TestPlatform.vm) { var url = _suite.environment.remoteDebuggerUrl; if (url == null) { print("${yellow}Remote debugger URL not found.$noColor"); @@ -154,12 +161,16 @@ class _Debugger { var buffer = new StringBuffer( "${bold}The test runner is paused.${noColor} "); - if (!_suite.platform.isHeadless) { - buffer.write("Open the dev console in ${_suite.platform} "); + if (_suite.platform == TestPlatform.vm) { + buffer.write("Open the Observatory "); } else { - buffer.write("Open the remote debugger "); + 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 "); } - 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."); @@ -169,7 +180,8 @@ class _Debugger { await inCompletionOrder([ _suite.environment.displayPause(), - cancelableNext(stdinLines) + cancelableNext(stdinLines), + _pauseCompleter.operation, ]).first; } finally { if (!_json) _reporter.resume(); @@ -209,8 +221,9 @@ class _Debugger { /// Closes the debugger and releases its resources. void close() { + _pauseCompleter.complete(); _closed = true; - _onDebuggingSubscription.cancel(); + _onDebuggingSubscription?.cancel(); _onRestartSubscription.cancel(); _console.stop(); } diff --git a/lib/src/runner/vm/environment.dart b/lib/src/runner/vm/environment.dart new file mode 100644 index 0000000000000000000000000000000000000000..2f860900fcde9f27d70ec89f13cf548dbe332a5d --- /dev/null +++ b/lib/src/runner/vm/environment.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:vm_service_client/vm_service_client.dart'; + +import '../environment.dart'; + +/// The environment in which VM tests are loaded. +class VMEnvironment implements Environment { + final supportsDebugging = true; + final Uri observatoryUrl; + + /// The VM service isolate object used to control this isolate. + final VMIsolate _isolate; + + VMEnvironment(this.observatoryUrl, this._isolate); + + Uri get remoteDebuggerUrl => null; + + Stream get onRestart => new StreamController.broadcast().stream; + + CancelableOperation displayPause() { + var completer = new CancelableCompleter(onCancel: () => _isolate.resume()); + + completer.complete(_isolate.pause().then((_) => + _isolate.onPauseOrResume.firstWhere((event) => event is VMResumeEvent))); + + return completer.operation; + } +} diff --git a/lib/src/runner/vm/platform.dart b/lib/src/runner/vm/platform.dart index 27d01c3bb74ab8d8f77f63868e7b6339faa806e5..cb67e1d634423e0798d13b29e25177fd65b20c9e 100644 --- a/lib/src/runner/vm/platform.dart +++ b/lib/src/runner/vm/platform.dart @@ -3,16 +3,25 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:developer'; +import 'dart:io'; import 'dart:isolate'; import 'package:path/path.dart' as p; import 'package:stream_channel/stream_channel.dart'; +import 'package:vm_service_client/vm_service_client.dart'; import '../../backend/test_platform.dart'; import '../../util/dart.dart' as dart; import '../configuration.dart'; +import '../configuration/suite.dart'; +import '../environment.dart'; import '../load_exception.dart'; +import '../plugin/environment.dart'; import '../plugin/platform.dart'; +import '../plugin/platform_helpers.dart'; +import '../runner_suite.dart'; +import 'environment.dart'; /// A platform that loads tests in isolates spawned within this Dart process. class VMPlatform extends PlatformPlugin { @@ -21,29 +30,64 @@ class VMPlatform extends PlatformPlugin { VMPlatform(); - StreamChannel loadChannel(String path, TestPlatform platform) { + StreamChannel loadChannel(String path, TestPlatform platform) => + throw new UnimplementedError(); + + Future<RunnerSuite> load(String path, TestPlatform platform, + SuiteConfiguration suiteConfig) async { assert(platform == TestPlatform.vm); - var isolate; - var channel = StreamChannelCompleter.fromFuture(() async { - var receivePort = new ReceivePort(); + var receivePort = new ReceivePort(); + Isolate isolate; + try { + isolate = await _spawnIsolate(path, receivePort.sendPort); + } catch (error) { + receivePort.close(); + rethrow; + } - try { - isolate = await _spawnIsolate(path, receivePort.sendPort); - } catch (error) { - receivePort.close(); - rethrow; - } + VMServiceClient client; + var channel = new IsolateChannel.connectReceive(receivePort) + .transformStream(new StreamTransformer.fromHandlers(handleDone: (sink) { + // Once the connection is closed by either end, kill the isolate (and + // the VM service client if we have one). + isolate.kill(); + client?.close(); + sink.close(); + })); + + if (!_config.pauseAfterLoad) { + var controller = await deserializeSuite( + path, platform, suiteConfig, new PluginEnvironment(), channel); + return controller.suite; + } + + // Print an empty line because the VM prints an "Observatory listening on" + // line and we don't want that to end up on the same line as the reporter + // info. + if (_config.reporter == 'compact') stdout.writeln(); + + var info = await Service.controlWebServer(enable: true); + var isolateID = Service.getIsolateID(isolate); + + client = new VMServiceClient.connect(info.serverUri); + var isolateNumber = int.parse(isolateID.split("/").last); + var vmIsolate = (await client.getVM()).isolates + .firstWhere((isolate) => isolate.number == isolateNumber); + await vmIsolate.setName(path); + + var library = (await vmIsolate.loadRunnable()) + .libraries[p.toUri(p.absolute(path))]; + var url = info.serverUri.resolveUri(library.observatoryUrl); + var environment = new VMEnvironment(url, vmIsolate); + var controller = await deserializeSuite( + path, platform, suiteConfig, environment, channel); - return new IsolateChannel.connectReceive(receivePort); - }()); + vmIsolate.onPauseOrResume.listen((event) { + controller.setDebugging(event is! VMResumeEvent); + }); - // Once the connection is closed by either end, kill the isolate. - return channel.transformStream( - new StreamTransformer.fromHandlers(handleDone: (sink) { - if (isolate != null) isolate.kill(); - sink.close(); - })); + return controller.suite; } /// Spawns an isolate and passes it [message]. diff --git a/pubspec.yaml b/pubspec.yaml index 150ab3df4e33bb1fbf328d727d30f767ca87bbc5..afb18dd956753447331219f433e6f502583c6144 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: test -version: 0.12.18+1 +version: 0.12.19-dev author: Dart Team <misc@dartlang.org> description: A library for writing dart unit tests. homepage: https://github.com/dart-lang/test environment: - sdk: '>=1.14.0 <2.0.0' + sdk: '>=1.21.0 <2.0.0' dependencies: analyzer: '>=0.23.0 <0.30.0' args: '^0.13.1' @@ -28,6 +28,7 @@ dependencies: stack_trace: '^1.2.1' stream_channel: '^1.3.1' string_scanner: '>=0.1.1 <2.0.0' + vm_service_client: '^0.2.3' web_socket_channel: '^1.0.0' yaml: '^2.0.0' @@ -39,3 +40,9 @@ dev_dependencies: http: '^0.11.0' js: '^0.6.0' scheduled_test: '^0.12.5' + +dependency_overrides: + vm_service_client: + git: + url: git://github.com/dart-lang/vm_service_client + ref: observatory-url diff --git a/test/runner/pause_after_load_test.dart b/test/runner/pause_after_load_test.dart index 1103a7094ea1b1c55aa2195862cafef5f54949b0..6b1baab61aae7a82a510831a6e4c8da8e63a1bfd 100644 --- a/test/runner/pause_after_load_test.dart +++ b/test/runner/pause_after_load_test.dart @@ -18,33 +18,32 @@ void main() { test("pauses the test runner for each file until the user presses enter", () { d.file("test1.dart", """ -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test 1!'); + void main() { + print('loaded test 1!'); - test("success", () {}); -} -""").create(); + test("success", () {}); + } + """).create(); d.file("test2.dart", """ -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test 2!'); + void main() { + print('loaded test 2!'); - test("success", () {}); -} -""").create(); + test("success", () {}); + } + """).create(); - var test = runTest( - ["--pause-after-load", "-p", "dartium", "test1.dart", "test2.dart"]); + var test = runTest(["--pause-after-load", "test1.dart", "test2.dart"]); test.stdout.expect(consumeThrough("loaded test 1!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); schedule(() async { @@ -65,9 +64,9 @@ void main() { test.stdout.expect(consumeThrough("loaded test 2!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); schedule(() async { @@ -86,34 +85,34 @@ void main() { test.writeLine(''); test.stdout.expect(consumeThrough(contains("+2: All tests passed!"))); test.shouldExit(0); - }, tags: 'dartium'); + }); test("pauses the test runner for each platform until the user presses enter", () { d.file("test.dart", """ -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test!'); + void main() { + print('loaded test!'); - test("success", () {}); -} -""").create(); + test("success", () {}); + } + """).create(); var test = runTest( - ["--pause-after-load", "-p", "dartium", "-p", "chrome", "test.dart"]); + ["--pause-after-load", "-p", "vm", "-p", "dartium", "test.dart"]); test.stdout.expect(consumeThrough("loaded test!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); schedule(() async { var nextLineFired = false; test.stdout.next().then(expectAsync1((line) { - expect(line, contains("+0: [Dartium] success")); + expect(line, contains("+0: [VM] success")); nextLineFired = true; })); @@ -127,8 +126,8 @@ void main() { test.stdout.expect(consumeThrough("loaded test!")); test.stdout.expect(consumeThrough(inOrder([ - "The test runner is paused. Open the dev console in Chrome and set " - "breakpoints. Once you're finished,", + "The test runner is paused. Open the remote debugger or the Observatory " + "and set breakpoints. Once you're finished,", "return to this terminal and press Enter." ]))); @@ -148,120 +147,55 @@ void main() { test.writeLine(''); test.stdout.expect(consumeThrough(contains("+2: All tests passed!"))); test.shouldExit(0); - }, tags: ['dartium', 'chrome']); - - test("prints a warning and doesn't pause for unsupported platforms", () { - d.file("test.dart", """ -import 'package:test/test.dart'; - -void main() { - test("success", () {}); -} -""").create(); - - var test = runTest( - ["--pause-after-load", "-p", "vm", "test.dart"]); - test.stderr.expect( - "Warning: Debugging is currently unsupported on the Dart VM."); - test.stdout.expect(consumeThrough(contains("+1: All tests passed!"))); - test.shouldExit(0); - }); - - test("can mix supported and unsupported platforms", () { - d.file("test.dart", """ -import 'package:test/test.dart'; - -void main() { - print('loaded test!'); - - test("success", () {}); -} -""").create(); - - var test = runTest( - ["--pause-after-load", "-p", "dartium", "-p", "vm", "test.dart"]); - test.stderr.expect( - "Warning: Debugging is currently unsupported on the Dart VM."); - - test.stdout.expect(consumeThrough("loaded test!")); - test.stdout.expect(consumeThrough(inOrder([ - startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." - ]))); - - schedule(() async { - var nextLineFired = false; - test.stdout.next().then(expectAsync1((line) { - expect(line, contains("+0: [Dartium] success")); - nextLineFired = true; - })); - - // Wait a little bit to be sure that the tests don't start running without - // our input. - await new Future.delayed(new Duration(seconds: 2)); - expect(nextLineFired, isFalse); - }); - - test.writeLine(''); - - test.stdout.expect(containsInOrder([ - "loaded test!", - "+1: [VM] success", - "+2: All tests passed!" - ])); - test.shouldExit(0); }, tags: 'dartium'); test("stops immediately if killed while paused", () { d.file("test.dart", """ -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test!'); + void main() { + print('loaded test!'); - test("success", () {}); -} -""").create(); + test("success", () {}); + } + """).create(); - var test = runTest(["--pause-after-load", "-p", "dartium", "test.dart"]); + var test = runTest(["--pause-after-load", "test.dart"]); test.stdout.expect(consumeThrough("loaded test!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); test.signal(ProcessSignal.SIGTERM); test.shouldExit(); test.stderr.expect(isDone); - }, tags: 'dartium', testOn: "!windows"); + }, testOn: "!windows"); test("disables timeouts", () { d.file("test.dart", """ -import 'dart:async'; + import 'dart:async'; -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test 1!'); + void main() { + print('loaded test 1!'); - test("success", () async { - await new Future.delayed(Duration.ZERO); - }, timeout: new Timeout(Duration.ZERO)); -} -""").create(); + test("success", () async { + await new Future.delayed(Duration.ZERO); + }, timeout: new Timeout(Duration.ZERO)); + } + """).create(); - var test = runTest( - ["--pause-after-load", "-p", "dartium", "-n", "success", "test.dart"]); + var test = runTest(["--pause-after-load", "-n", "success", "test.dart"]); test.stdout.expect(consumeThrough("loaded test 1!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); schedule(() async { @@ -280,30 +214,29 @@ void main() { test.writeLine(''); test.stdout.expect(consumeThrough(contains("+1: All tests passed!"))); test.shouldExit(0); - }, tags: 'dartium'); + }); // Regression test for #304. test("supports test name patterns", () { d.file("test.dart", """ -import 'package:test/test.dart'; + import 'package:test/test.dart'; -void main() { - print('loaded test 1!'); + void main() { + print('loaded test 1!'); - test("failure 1", () {}); - test("success", () {}); - test("failure 2", () {}); -} -""").create(); + test("failure 1", () {}); + test("success", () {}); + test("failure 2", () {}); + } + """).create(); - var test = runTest( - ["--pause-after-load", "-p", "dartium", "-n", "success", "test.dart"]); + var test = runTest(["--pause-after-load", "-n", "success", "test.dart"]); test.stdout.expect(consumeThrough("loaded test 1!")); test.stdout.expect(consumeThrough(inOrder([ startsWith("Observatory URL: "), - "The test runner is paused. Open the dev console in Dartium or the " - "Observatory and set breakpoints.", - "Once you're finished, return to this terminal and press Enter." + "The test runner is paused. Open the Observatory and set breakpoints. " + "Once you're finished, return to", + "this terminal and press Enter." ]))); schedule(() async { @@ -322,5 +255,104 @@ void main() { test.writeLine(''); test.stdout.expect(consumeThrough(contains("+1: All tests passed!"))); test.shouldExit(0); - }, tags: 'dartium'); + }); + + group("for a browser", () { + test("pauses the test runner for each file until the user presses enter", () { + d.file("test1.dart", """ + import 'package:test/test.dart'; + + void main() { + print('loaded test 1!'); + + test("success", () {}); + } + """).create(); + + d.file("test2.dart", """ + import 'package:test/test.dart'; + + void main() { + print('loaded test 2!'); + + test("success", () {}); + } + """).create(); + + var test = runTest( + ["--pause-after-load", "-p", "dartium", "test1.dart", "test2.dart"]); + test.stdout.expect(consumeThrough("loaded test 1!")); + test.stdout.expect(consumeThrough(inOrder([ + startsWith("Observatory URL: "), + "The test runner is paused. Open the dev console in Dartium or the " + "Observatory and set breakpoints.", + "Once you're finished, return to this terminal and press Enter." + ]))); + + schedule(() async { + var nextLineFired = false; + test.stdout.next().then(expectAsync1((line) { + expect(line, contains("+0: test1.dart: success")); + nextLineFired = true; + })); + + // Wait a little bit to be sure that the tests don't start running without + // our input. + await new Future.delayed(new Duration(seconds: 2)); + expect(nextLineFired, isFalse); + }); + + test.writeLine(''); + + test.stdout.expect(consumeThrough("loaded test 2!")); + test.stdout.expect(consumeThrough(inOrder([ + startsWith("Observatory URL: "), + "The test runner is paused. Open the dev console in Dartium or the " + "Observatory and set breakpoints.", + "Once you're finished, return to this terminal and press Enter." + ]))); + + schedule(() async { + var nextLineFired = false; + test.stdout.next().then(expectAsync1((line) { + expect(line, contains("+1: test2.dart: success")); + nextLineFired = true; + })); + + // Wait a little bit to be sure that the tests don't start running without + // our input. + await new Future.delayed(new Duration(seconds: 2)); + expect(nextLineFired, isFalse); + }); + + test.writeLine(''); + test.stdout.expect(consumeThrough(contains("+2: All tests passed!"))); + test.shouldExit(0); + }, tags: 'dartium'); + + test("stops immediately if killed while paused", () { + d.file("test.dart", """ + import 'package:test/test.dart'; + + void main() { + print('loaded test!'); + + test("success", () {}); + } + """).create(); + + var test = runTest(["--pause-after-load", "-p", "dartium", "test.dart"]); + test.stdout.expect(consumeThrough("loaded test!")); + test.stdout.expect(consumeThrough(inOrder([ + startsWith("Observatory URL: "), + "The test runner is paused. Open the dev console in Dartium or the " + "Observatory and set breakpoints.", + "Once you're finished, return to this terminal and press Enter." + ]))); + + test.signal(ProcessSignal.SIGTERM); + test.shouldExit(); + test.stderr.expect(isDone); + }, tags: 'dartium', testOn: "!windows"); + }); }