Allow stepping when paused at isolate start.
Fix tests.
BUG=
R=johnmccutchan@google.com
Review URL: https://codereview.chromium.org//1285643004 .
diff --git a/runtime/observatory/lib/src/elements/debugger.dart b/runtime/observatory/lib/src/elements/debugger.dart
index 8a98d1b..2034b95 100644
--- a/runtime/observatory/lib/src/elements/debugger.dart
+++ b/runtime/observatory/lib/src/elements/debugger.dart
@@ -477,6 +477,16 @@
Future run(List<String> args) {
if (debugger.isolatePaused()) {
+ var event = debugger.isolate.pauseEvent;
+ if (event.kind == ServiceEvent.kPauseStart) {
+ debugger.console.print(
+ "Type 'continue' [F7] or 'step' [F10] to start the isolate");
+ return new Future.value(null);
+ }
+ if (event.kind == ServiceEvent.kPauseExit) {
+ debugger.console.print("Type 'continue' [F7] to exit the isolate");
+ return new Future.value(null);
+ }
return debugger.isolate.stepOut();
} else {
debugger.console.print('The program is already running');
@@ -1339,7 +1349,7 @@
void _reportPause(ServiceEvent event) {
if (event.kind == ServiceEvent.kPauseStart) {
console.print(
- "Paused at isolate start (type 'continue' or [F7] to start the isolate')");
+ "Paused at isolate start (type 'continue' [F7] or 'step' [F10] to start the isolate')");
} else if (event.kind == ServiceEvent.kPauseExit) {
console.print(
"Paused at isolate exit (type 'continue' or [F7] to exit the isolate')");
@@ -1612,11 +1622,11 @@
if (isolatePaused()) {
var event = isolate.pauseEvent;
if (event.kind == ServiceEvent.kPauseStart) {
- console.print("Type 'continue' or [F7] to start the isolate");
+ console.print("Type 'continue' [F7] or 'step' [F10] to start the isolate");
return new Future.value(null);
}
if (event.kind == ServiceEvent.kPauseExit) {
- console.print("Type 'continue' or [F7] to exit the isolate");
+ console.print("Type 'continue' [F7] to exit the isolate");
return new Future.value(null);
}
return isolate.stepOver();
@@ -1629,12 +1639,8 @@
Future step() {
if (isolatePaused()) {
var event = isolate.pauseEvent;
- if (event.kind == ServiceEvent.kPauseStart) {
- console.print("Type 'continue' or [F7] to start the isolate");
- return new Future.value(null);
- }
if (event.kind == ServiceEvent.kPauseExit) {
- console.print("Type 'continue' or [F7] to exit the isolate");
+ console.print("Type 'continue' [F7] to exit the isolate");
return new Future.value(null);
}
return isolate.stepInto();
diff --git a/runtime/observatory/tests/service/isolate_lifecycle_test.dart b/runtime/observatory/tests/service/isolate_lifecycle_test.dart
index dbacef8..2160d0c 100644
--- a/runtime/observatory/tests/service/isolate_lifecycle_test.dart
+++ b/runtime/observatory/tests/service/isolate_lifecycle_test.dart
@@ -40,29 +40,21 @@
return paused;
}
-int numRunning(vm) {
- int running = 0;
- for (var isolate in vm.isolates) {
- if (!isolate.paused) {
- running++;
- }
- }
- return running;
-}
-
var tests = [
(VM vm) async {
- // Wait for the testee to start all of the isolates.
- if (vm.isolates.length != spawnCount + 1) {
- await processServiceEvents(vm, VM.kIsolateStream,
- (event, sub, completer) {
+ Completer completer = new Completer();
+ var stream = await vm.getEventStream(VM.kIsolateStream);
+ if (vm.isolates.length < spawnCount + 1) {
+ var subscription;
+ subscription = stream.listen((ServiceEvent event) {
if (event.kind == ServiceEvent.kIsolateStart) {
- if (vm.isolates.length == spawnCount + 1) {
- sub.cancel();
+ if (vm.isolates.length == (spawnCount + 1)) {
+ subscription.cancel();
completer.complete(null);
}
}
});
+ await completer.future;
}
expect(vm.isolates.length, spawnCount + 1);
},
@@ -75,35 +67,40 @@
},
(VM vm) async {
- // Wait for all spawned isolates to hit pause-at-exit.
- if (numPaused(vm) != spawnCount) {
- await processServiceEvents(vm, VM.kDebugStream,
- (event, sub, completer) {
+ Completer completer = new Completer();
+ var stream = await vm.getEventStream(VM.kDebugStream);
+ if (numPaused(vm) < spawnCount) {
+ var subscription;
+ subscription = stream.listen((ServiceEvent event) {
if (event.kind == ServiceEvent.kPauseExit) {
- if (numPaused(vm) == spawnCount) {
- sub.cancel();
+ if (numPaused(vm) == (spawnCount + 1)) {
+ subscription.cancel();
completer.complete(null);
}
}
});
+ await completer.future;
}
- expect(numPaused(vm), spawnCount);
- expect(numRunning(vm), 1);
+ expect(numPaused(vm), spawnCount + 1);
},
(VM vm) async {
var resumedReceived = 0;
- var eventsDone = processServiceEvents(vm, VM.kIsolateStream,
- (event, sub, completer) {
+ Completer completer = new Completer();
+ var stream = await vm.getEventStream(VM.kIsolateStream);
+ var subscription;
+ subscription = stream.listen((ServiceEvent event) {
if (event.kind == ServiceEvent.kIsolateExit) {
resumedReceived++;
- if (resumedReceived == resumeCount) {
- sub.cancel();
+ if (resumedReceived >= resumeCount) {
+ subscription.cancel();
completer.complete(null);
}
}
});
+
+ // Resume a subset of the isolates.
var resumesIssued = 0;
var isolateList = vm.isolates.toList();
for (var isolate in isolateList) {
@@ -118,12 +115,11 @@
break;
}
}
- return eventsDone;
+ await completer.future;
},
(VM vm) async {
- expect(numPaused(vm), spawnCount - resumeCount);
- expect(numRunning(vm), 1);
+ expect(numPaused(vm), spawnCount + 1 - resumeCount);
},
];
diff --git a/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart b/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart
new file mode 100644
index 0000000..012a68f
--- /dev/null
+++ b/runtime/observatory/tests/service/pause_on_start_and_exit_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2015, 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.
+// VMOptions=--compile_all --error_on_bad_type --error_on_bad_override
+
+import 'package:observatory/service_io.dart';
+import 'package:unittest/unittest.dart';
+import 'test_helper.dart';
+import 'dart:async';
+
+void testMain() {
+ print('Hello');
+}
+
+var tests = [
+
+(Isolate isolate) async {
+ Completer completer = new Completer();
+ var stream = await isolate.vm.getEventStream(VM.kDebugStream);
+ var subscription;
+ subscription = stream.listen((ServiceEvent event) {
+ if (event.kind == ServiceEvent.kPauseStart) {
+ print('Received PauseStart');
+ subscription.cancel();
+ completer.complete();
+ }
+ });
+
+ if (isolate.pauseEvent != null &&
+ isolate.pauseEvent.kind == ServiceEvent.kPauseStart) {
+ // Wait for the isolate to hit PauseStart.
+ subscription.cancel();
+ } else {
+ await completer.future;
+ }
+
+ completer = new Completer();
+ stream = await isolate.vm.getEventStream(VM.kDebugStream);
+ subscription = stream.listen((ServiceEvent event) {
+ if (event.kind == ServiceEvent.kPauseExit) {
+ print('Received PauseExit');
+ subscription.cancel();
+ completer.complete();
+ }
+ });
+
+ print('Resuming...');
+ isolate.resume();
+
+ // Wait for the isolate to hit PauseExit.
+ await completer.future;
+},
+
+];
+
+main(args) => runIsolateTests(args, tests,
+ testeeConcurrent: testMain,
+ pause_on_start: true, pause_on_exit: true);
diff --git a/runtime/observatory/tests/service/pause_on_start_then_step_test.dart b/runtime/observatory/tests/service/pause_on_start_then_step_test.dart
new file mode 100644
index 0000000..2c7b3b6
--- /dev/null
+++ b/runtime/observatory/tests/service/pause_on_start_then_step_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2015, 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.
+// VMOptions=--compile_all --error_on_bad_type --error_on_bad_override
+
+import 'package:observatory/service_io.dart';
+import 'package:unittest/unittest.dart';
+import 'test_helper.dart';
+import 'dart:async';
+
+void testMain() {
+ print('Hello');
+}
+
+var tests = [
+
+(Isolate isolate) async {
+ Completer completer = new Completer();
+ var stream = await isolate.vm.getEventStream(VM.kDebugStream);
+ var subscription;
+ subscription = stream.listen((ServiceEvent event) {
+ if (event.kind == ServiceEvent.kPauseStart) {
+ print('Received PauseStart');
+ subscription.cancel();
+ completer.complete();
+ }
+ });
+
+ if (isolate.pauseEvent != null &&
+ isolate.pauseEvent.kind == ServiceEvent.kPauseStart) {
+ // Wait for the isolate to hit PauseStart.
+ subscription.cancel();
+ } else {
+ await completer.future;
+ }
+
+ completer = new Completer();
+ stream = await isolate.vm.getEventStream(VM.kDebugStream);
+ subscription = stream.listen((ServiceEvent event) {
+ if (event.kind == ServiceEvent.kPauseBreakpoint) {
+ print('Received PauseBreakpoint');
+ subscription.cancel();
+ completer.complete();
+ }
+ });
+
+ print('Stepping...');
+ isolate.stepInto();
+
+ // Wait for the isolate to hit PauseBreakpoint.
+ await completer.future;
+},
+
+];
+
+main(args) => runIsolateTests(args, tests,
+ testeeConcurrent: testMain,
+ pause_on_start: true, pause_on_exit: true);
diff --git a/runtime/observatory/tests/service/test_helper.dart b/runtime/observatory/tests/service/test_helper.dart
index 4c492ed..d640dc5 100644
--- a/runtime/observatory/tests/service/test_helper.dart
+++ b/runtime/observatory/tests/service/test_helper.dart
@@ -26,9 +26,12 @@
Platform.script.toFilePath(),
_TESTEE_MODE_FLAG] {}
- Future<int> launch(bool pause_on_exit) {
+ Future<int> launch(bool pause_on_start, bool pause_on_exit) {
String dartExecutable = Platform.executable;
var fullArgs = [];
+ if (pause_on_start == true) {
+ fullArgs.add('--pause-isolates-on-start');
+ }
if (pause_on_exit == true) {
fullArgs.add('--pause-isolates-on-exit');
}
@@ -49,7 +52,7 @@
var port = portExp.firstMatch(line).group(1);
portNumber = int.parse(port);
}
- if (line == '') {
+ if (pause_on_start || line == '') {
// Received blank line.
blank = true;
}
@@ -98,20 +101,26 @@
List<IsolateTest> tests,
{void testeeBefore(),
void testeeConcurrent(),
- bool pause_on_exit}) {
+ bool pause_on_start: false,
+ bool pause_on_exit: false}) {
+ assert(!pause_on_start || testeeBefore == null);
if (mainArgs.contains(_TESTEE_MODE_FLAG)) {
- if (testeeBefore != null) {
- testeeBefore();
+ if (!pause_on_start) {
+ if (testeeBefore != null) {
+ testeeBefore();
+ }
+ print(''); // Print blank line to signal that we are ready.
}
- print(''); // Print blank line to signal that we are ready.
if (testeeConcurrent != null) {
testeeConcurrent();
}
- // Wait around for the process to be killed.
- stdin.first.then((_) => exit(0));
+ if (!pause_on_exit) {
+ // Wait around for the process to be killed.
+ stdin.first.then((_) => exit(0));
+ }
} else {
var process = new _TestLauncher();
- process.launch(pause_on_exit).then((port) {
+ process.launch(pause_on_start, pause_on_exit).then((port) {
if (mainArgs.contains("--gdb")) {
port = 8181;
}
@@ -140,26 +149,6 @@
}
-// Cancel the subscription and complete the completer when finished processing
-// events.
-typedef void ServiceEventHandler(ServiceEvent event,
- StreamSubscription subscription,
- Completer completer);
-
-Future processServiceEvents(VM vm,
- String streamId,
- ServiceEventHandler handler) {
- Completer completer = new Completer();
- vm.getEventStream(streamId).then((stream) {
- var subscription;
- subscription = stream.listen((ServiceEvent event) {
- handler(event, subscription, completer);
- });
- });
- return completer.future;
-}
-
-
Future<Isolate> hasStoppedAtBreakpoint(Isolate isolate) {
// Set up a listener to wait for breakpoint events.
Completer completer = new Completer();
@@ -286,20 +275,25 @@
List<VMTest> tests,
{Future testeeBefore(),
Future testeeConcurrent(),
- bool pause_on_exit}) async {
+ bool pause_on_start: false,
+ bool pause_on_exit: false}) async {
if (mainArgs.contains(_TESTEE_MODE_FLAG)) {
- if (testeeBefore != null) {
- await testeeBefore();
+ if (!pause_on_start) {
+ if (testeeBefore != null) {
+ await testeeBefore();
+ }
+ print(''); // Print blank line to signal that we are ready.
}
- print(''); // Print blank line to signal that we are ready.
if (testeeConcurrent != null) {
await testeeConcurrent();
}
- // Wait around for the process to be killed.
- stdin.first.then((_) => exit(0));
+ if (!pause_on_exit) {
+ // Wait around for the process to be killed.
+ stdin.first.then((_) => exit(0));
+ }
} else {
var process = new _TestLauncher();
- process.launch(pause_on_exit).then((port) async {
+ process.launch(pause_on_start, pause_on_exit).then((port) async {
if (mainArgs.contains("--gdb")) {
port = 8181;
}
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index c7d549c..5573f4b 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -1270,6 +1270,7 @@
resume_action_ = kStepOut;
}
+
RawFunction* Debugger::ResolveFunction(const Library& library,
const String& class_name,
const String& function_name) {
@@ -2322,6 +2323,13 @@
}
+void Debugger::EnterSingleStepMode() {
+ stepping_fp_ = 0;
+ DeoptimizeWorld();
+ isolate_->set_single_step(true);
+}
+
+
void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace) {
stepping_fp_ = 0;
if (resume_action_ == kSingleStep) {
diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h
index faaf558..79a620c 100644
--- a/runtime/vm/debugger.h
+++ b/runtime/vm/debugger.h
@@ -459,6 +459,12 @@
bool IsPaused() const { return pause_event_ != NULL; }
+ // Put the isolate into single stepping mode when Dart code next runs.
+ //
+ // This is used by the vm service to allow the user to step while
+ // paused at isolate start.
+ void EnterSingleStepMode();
+
// Indicates why the debugger is currently paused. If the debugger
// is not paused, this returns NULL. Note that the debugger can be
// paused for breakpoints, isolate interruption, and (sometimes)
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 6f4fd03..b40b477 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -2258,6 +2258,11 @@
static bool Resume(Isolate* isolate, JSONStream* js) {
const char* step_param = js->LookupParam("step");
if (isolate->message_handler()->paused_on_start()) {
+ // If the user is issuing a 'Over' or an 'Out' step, that is the
+ // same as a regular resume request.
+ if ((step_param != NULL) && (strcmp(step_param, "Into") == 0)) {
+ isolate->debugger()->EnterSingleStepMode();
+ }
isolate->message_handler()->set_pause_on_start(false);
if (Service::debug_stream.enabled()) {
ServiceEvent event(isolate, ServiceEvent::kResume);