Version 1.2.0-dev.2.4

svn merge -c 32111 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 32117 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

R=dgrove@google.com

Review URL: https://codereview.chromium.org//150433003

git-svn-id: http://dart.googlecode.com/svn/trunk@32164 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/tools/VERSION b/tools/VERSION
index d31a8d9..8e5dd3b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 2
 PATCH 0
 PRERELEASE 2
-PRERELEASE_PATCH 3
+PRERELEASE_PATCH 4
diff --git a/tools/archive_crash.py b/tools/archive_crash.py
new file mode 100755
index 0000000..ce2fe02
--- /dev/null
+++ b/tools/archive_crash.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2014, 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.
+#
+
+# A script that copies a core file and binary to GCS
+# We expect the dumps to be located in /tmp/coredump_PID directory
+# After we copy out the core files we delete the dumps localy
+
+import os
+import shutil
+import sys
+import subprocess
+import tarfile
+import utils
+import uuid
+
+GCS_FOLDER = 'dart-crashes'
+
+def CreateTarball(dir, tarname):
+  print 'Creating tar file: %s' % (tarname)
+  tar = tarfile.open(tarname, mode='w:gz')
+  tar.add(dir)
+  tar.close()
+
+def CopyToGCS(filename):
+  gs_location = 'gs://%s/%s/' % (GCS_FOLDER, uuid.uuid4())
+  cmd = ['gsutil', 'cp', filename, gs_location]
+  print 'Running command: %s' % (cmd)
+  subprocess.check_call(cmd)
+  archived_filename = '%s%s' % (gs_location, filename.split('/').pop())
+  print 'Dump now available in %s' % (archived_filename) 
+
+def Main():
+  if utils.GuessOS() != 'linux':
+    print 'Currently only archiving crash dumps on linux'
+    return 0
+  print 'Looking for crash dumps'
+  num_dumps = 0
+  for v in os.listdir('/tmp'):
+    if v.startswith('coredump'):
+      fullpath = '/tmp/%s' % (v)
+      if os.path.isdir(fullpath):
+        num_dumps += 1
+        tarname = '%s.tar.gz' % fullpath
+        CreateTarball(fullpath, tarname)
+        CopyToGCS(tarname)
+        os.unlink(tarname)
+        shutil.rmtree(fullpath)
+  print 'Found %s core dumps' % (num_dumps)
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/test.dart b/tools/test.dart
index 278dc74..27d0370 100755
--- a/tools/test.dart
+++ b/tools/test.dart
@@ -261,6 +261,10 @@
   if (firstConf['write_test_outcome_log']) {
     eventListener.add(new TestOutcomeLogWriter());
   }
+  if (firstConf['copy_coredumps']) {
+    eventListener.add(new UnexpectedCrashDumpArchiver());
+  }
+
   eventListener.add(new ExitCodeSetter());
 
   void startProcessQueue() {
diff --git a/tools/testing/dart/test_options.dart b/tools/testing/dart/test_options.dart
index fa28151..3274d57 100644
--- a/tools/testing/dart/test_options.dart
+++ b/tools/testing/dart/test_options.dart
@@ -335,6 +335,15 @@
               'bool'
               ),
           new _TestOptionSpecification(
+              'copy_coredumps',
+              'If we see a crash that we did not expect, copy the core dumps. '
+              'to /tmp',
+              ['--copy-coredumps'],
+              [],
+              false,
+              'bool'
+              ),
+          new _TestOptionSpecification(
               'local_ip',
               'IP address the http servers should listen on.'
               'This address is also used for browsers to connect.',
@@ -616,6 +625,7 @@
       print("Cannot have both --use-repository-packages and "
             "--use-public-packages");
     }
+
     return isValid;
   }
 
diff --git a/tools/testing/dart/test_progress.dart b/tools/testing/dart/test_progress.dart
index cf4a808..a2056ab 100644
--- a/tools/testing/dart/test_progress.dart
+++ b/tools/testing/dart/test_progress.dart
@@ -276,6 +276,32 @@
   }
 }
 
+
+class UnexpectedCrashDumpArchiver extends EventListener {
+  void done(TestCase test) {
+    if (test.unexpectedOutput && test.result == Expectation.CRASH) {
+      var name = "core.dart.${test.lastCommandOutput.pid}";
+      var file = new File(name);
+      if (file.existsSync()) {
+        // Find the binary - we assume this is the first part of the command
+        var binName = test.lastCommandExecuted.toString().split(' ').first;
+        var binFile = new File(binName);
+        var binBaseName = new Path(binName).filename;
+        if (binFile.existsSync()) {
+          var tmpPath = new Path(Directory.systemTemp.path);
+          var dir = new Path(TestUtils.mkdirRecursive(tmpPath,
+              new Path('coredump_${test.lastCommandOutput.pid}')).path);
+          TestUtils.copyFile(new Path(name), dir.append(name));
+          TestUtils.copyFile(new Path(binName), dir.append(binBaseName));
+          print("\nCopied core dump and binary for unexpected crash to: "
+                "$dir");
+        }
+      }
+    }
+  }
+}
+
+
 class SummaryPrinter extends EventListener {
   void allTestsKnown() {
     if (SummaryReport.total > 0) {
@@ -284,6 +310,7 @@
   }
 }
 
+
 class TimingPrinter extends EventListener {
   final _command2testCases = new Map<Command, List<TestCase>>();
   final _commandOutputs = new Set<CommandOutput>();
@@ -456,6 +483,7 @@
   }
 }
 
+
 class TestFailurePrinter extends EventListener {
   bool _printSummary;
   var _formatter;
diff --git a/tools/testing/dart/test_runner.dart b/tools/testing/dart/test_runner.dart
index 9765d463..33382c3 100644
--- a/tools/testing/dart/test_runner.dart
+++ b/tools/testing/dart/test_runner.dart
@@ -744,6 +744,15 @@
     return commandOutputs[commands[commandOutputs.length - 1]];
   }
 
+  Command get lastCommandExecuted {
+    if (commandOutputs.length == 0) {
+      throw new Exception("CommandOutputs is empty, maybe no command was run? ("
+                          "displayName: '$displayName', "
+                          "configurationString: '$configurationString')");
+    }
+    return commands[commandOutputs.length - 1];
+  }
+
   int get timeout {
     if (expectedOutcomes.contains(Expectation.SLOW)) {
       return configuration['timeout'] * SLOW_TIMEOUT_MULTIPLIER;
@@ -871,6 +880,7 @@
   Duration time;
   List<String> diagnostics;
   bool compilationSkipped;
+  int pid;
 
   /**
    * A flag to indicate we have already printed a warning about ignoring the VM
@@ -885,7 +895,8 @@
                     List<int> this.stdout,
                     List<int> this.stderr,
                     Duration this.time,
-                    bool this.compilationSkipped) {
+                    bool this.compilationSkipped,
+                    int this.pid) {
     diagnostics = [];
   }
 
@@ -971,7 +982,8 @@
           stdout,
           stderr,
           time,
-          compilationSkipped) {
+          compilationSkipped,
+          0) {
     _failedBecauseOfMissingXDisplay = _didFailBecauseOfMissingXDisplay();
     if (_failedBecauseOfMissingXDisplay) {
       DebugLogger.warning("Warning: Test failure because of missing XDisplay");
@@ -1321,7 +1333,7 @@
       Command command, BrowserTestOutput result, this._rawOutcome,
       List<int> stdout, List<int> stderr)
       : super(command, 0, result.didTimeout, stdout, stderr, result.duration,
-              false) {
+              false, 0) {
     _result = result;
   }
 
@@ -1363,7 +1375,8 @@
           stdout,
           stderr,
           time,
-          compilationSkipped);
+          compilationSkipped,
+          0);
 
   Expectation result(TestCase testCase) {
     // TODO(kustermann): If we run the analyzer not in batch mode, make sure
@@ -1456,8 +1469,9 @@
   static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255;
 
   VmCommandOutputImpl(Command command, int exitCode, bool timedOut,
-      List<int> stdout, List<int> stderr, Duration time)
-      : super(command, exitCode, timedOut, stdout, stderr, time, false);
+                      List<int> stdout, List<int> stderr, Duration time,
+                      int pid)
+      : super(command, exitCode, timedOut, stdout, stderr, time, false, pid);
 
   Expectation result(TestCase testCase) {
     // Handle crashes and timeouts first
@@ -1507,7 +1521,7 @@
       List<int> stdout, List<int> stderr, Duration time,
       bool compilationSkipped)
       : super(command, exitCode, timedOut, stdout, stderr, time,
-              compilationSkipped);
+              compilationSkipped, 0);
 
   Expectation result(TestCase testCase) {
     // Handle general crash/timeout detection.
@@ -1551,7 +1565,7 @@
                               with UnittestSuiteMessagesMixin {
   JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut,
       List<int> stdout, List<int> stderr, Duration time)
-      : super(command, exitCode, timedOut, stdout, stderr, time, false);
+      : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
 
   Expectation result(TestCase testCase) {
     // Handle crashes and timeouts first
@@ -1572,7 +1586,7 @@
 class PubCommandOutputImpl extends CommandOutputImpl {
   PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut,
       List<int> stdout, List<int> stderr, Duration time)
-      : super(command, exitCode, timedOut, stdout, stderr, time, false);
+  : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
 
   Expectation result(TestCase testCase) {
     // Handle crashes and timeouts first
@@ -1594,7 +1608,7 @@
 
   ScriptCommandOutputImpl(ScriptCommand command, this._result,
                           String scriptExecutionInformation, Duration time)
-      : super(command, 0, false, [], [], time, false) {
+  : super(command, 0, false, [], [], time, false, 0) {
     var lines = scriptExecutionInformation.split("\n");
     diagnostics.addAll(lines);
   }
@@ -1613,7 +1627,8 @@
                                   List<int> stdout,
                                   List<int> stderr,
                                   Duration time,
-                                  bool compilationSkipped) {
+                                  bool compilationSkipped,
+                                  [int pid = 0]) {
   if (command is ContentShellCommand) {
     return new BrowserCommandOutputImpl(
         command, exitCode, timedOut, stdout, stderr,
@@ -1628,7 +1643,7 @@
         time, compilationSkipped);
   } else if (command is VmCommand) {
     return new VmCommandOutputImpl(
-        command, exitCode, timedOut, stdout, stderr, time);
+        command, exitCode, timedOut, stdout, stderr, time, pid);
   } else if (command is CompilationCommand) {
     return new CompilationCommandOutputImpl(
         command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
@@ -1642,7 +1657,7 @@
 
   return new CommandOutputImpl(
       command, exitCode, timedOut, stdout, stderr,
-      time, compilationSkipped);
+      time, compilationSkipped, pid);
 }
 
 
@@ -1662,6 +1677,7 @@
   bool timedOut = false;
   DateTime startTime;
   Timer timeoutTimer;
+  int pid;
   List<int> stdout = <int>[];
   List<int> stderr = <int>[];
   bool compilationSkipped = false;
@@ -1699,6 +1715,7 @@
 
           bool stdoutDone = false;
           bool stderrDone = false;
+          pid = process.pid;
 
           // This timer is used to close stdio to the subprocess once we got
           // the exitCode. Sometimes descendants of the subprocess keep stdio
@@ -1709,7 +1726,6 @@
             if (!stdoutDone) {
               stdoutCompleter.complete();
               stdoutDone = true;
-
               if (stderrDone && watchdogTimer != null) {
                 watchdogTimer.cancel();
               }
@@ -1789,7 +1805,8 @@
         stdout,
         stderr,
         new DateTime.now().difference(startTime),
-        compilationSkipped);
+        compilationSkipped,
+        pid);
     return commandOutput;
   }