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);