[autotest] Don't process crashdumps for passing tests

More accurately, if no tests during a given job failed, skip
symbolication of crashdumps.

BUG=chromium:623739
TEST=Run autotest jobs locally.

Change-Id: I8dda3f3714d3047e0398aef36915adaa5b4cf0ea
Reviewed-on: https://chromium-review.googlesource.com/387110
Commit-Ready: Allen Li <[email protected]>
Tested-by: Allen Li <[email protected]>
Reviewed-by: Aviv Keshet <[email protected]>
diff --git a/server/server_job.py b/server/server_job.py
index 86cfda3..3d3618c 100644
--- a/server/server_job.py
+++ b/server/server_job.py
@@ -11,17 +11,41 @@
 Copyright Martin J. Bligh, Andy Whitcroft 2007
 """
 
-import getpass, os, sys, re, tempfile, time, select, platform
-import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
+import errno
+import fcntl
+import getpass
+import itertools
+import logging
+import os
+import pickle
+import platform
+import re
+import select
+import shutil
+import sys
+import tempfile
+import time
+import traceback
+import warnings
+
 from autotest_lib.client.bin import sysinfo
-from autotest_lib.client.common_lib import base_job, global_config
-from autotest_lib.client.common_lib import error, utils, packages
+from autotest_lib.client.common_lib import base_job
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import global_config
 from autotest_lib.client.common_lib import logging_manager
-from autotest_lib.server import test, subcommand, profilers
+from autotest_lib.client.common_lib import packages
+from autotest_lib.client.common_lib import utils
+from autotest_lib.server import profilers
+from autotest_lib.server import subcommand
+from autotest_lib.server import test
 from autotest_lib.server import utils as server_utils
 from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
-from autotest_lib.server.hosts import abstract_ssh, factory as host_factory
-from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
+from autotest_lib.server.hosts import abstract_ssh
+from autotest_lib.server.hosts import factory as host_factory
+from autotest_lib.tko import db as tko_db
+from autotest_lib.tko import models as tko_models
+from autotest_lib.tko import status_lib
+from autotest_lib.tko import utils as tko_utils
 
 
 INCREMENTAL_TKO_PARSING = global_config.global_config.get_config_value(
@@ -598,6 +622,70 @@
         return success_machines
 
 
+    def _has_failed_tests(self):
+        """Parse status log for failed tests.
+
+        This checks the current working directory and is intended only for use
+        by the run() method.
+
+        @return boolean
+        """
+        path = os.getcwd()
+
+        # TODO(ayatane): Copied from tko/parse.py.  Needs extensive refactor to
+        # make code reuse plausible.
+        job_keyval = tko_models.job.read_keyval(path)
+        status_version = job_keyval.get("status_version", 0)
+
+        # parse out the job
+        parser = status_lib.parser(status_version)
+        job = parser.make_job(path)
+        status_log = os.path.join(path, "status.log")
+        if not os.path.exists(status_log):
+            status_log = os.path.join(path, "status")
+        if not os.path.exists(status_log):
+            logging.warning("! Unable to parse job, no status file")
+            return True
+
+        # parse the status logs
+        status_lines = open(status_log).readlines()
+        parser.start(job)
+        tests = parser.end(status_lines)
+
+        # parser.end can return the same object multiple times, so filter out
+        # dups
+        job.tests = []
+        already_added = set()
+        for test in tests:
+            if test not in already_added:
+                already_added.add(test)
+                job.tests.append(test)
+
+        failed = False
+        for test in job.tests:
+            # The current job is still running and shouldn't count as failed.
+            # The parser will fail to parse the exit status of the job since it
+            # hasn't exited yet (this running right now is the job).
+            failed = failed or (test.status != 'GOOD'
+                                and not _is_current_server_job(test))
+        return failed
+
+
+    def _collect_crashes(self, namespace, collect_crashinfo):
+        """Collect crashes.
+
+        @param namespace: namespace dict.
+        @param collect_crashinfo: whether to collect crashinfo in addition to
+                dumps
+        """
+        if collect_crashinfo:
+            # includes crashdumps
+            crash_control_file = CRASHINFO_CONTROL_FILE
+        else:
+            crash_control_file = CRASHDUMPS_CONTROL_FILE
+        self._execute_code(crash_control_file, namespace)
+
+
     _USE_TEMP_DIR = object()
     def run(self, install_before=False, install_after=False,
             collect_crashdumps=True, namespace={}, control=None,
@@ -715,15 +803,16 @@
                                  temp_control_file_dir, e)
 
             if machines and (collect_crashdumps or collect_crashinfo):
-                namespace['test_start_time'] = test_start_time
                 if skip_crash_collection:
                     logging.info('Skipping crash dump/info collection '
                                  'as requested.')
-                elif collect_crashinfo:
-                    # includes crashdumps
-                    self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
                 else:
-                    self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
+                    namespace['test_start_time'] = test_start_time
+                    # Remove crash files for passing tests.
+                    # TODO(ayatane): Tests that create crash files should be
+                    # reported.
+                    namespace['has_failed_tests'] = self._has_failed_tests()
+                    self._collect_crashes(namespace, collect_crashinfo)
             self.disable_external_logging()
             if self._uncollected_log_file and created_uncollected_logs:
                 os.remove(self._uncollected_log_file)
@@ -1308,6 +1397,14 @@
             intervals[-1] = (intervals[-1][0], int(current_time_func()))
 
 
+def _is_current_server_job(test):
+    """Return True if parsed test is the currently running job.
+
+    @param test: test instance from tko parser.
+    """
+    return test.testname == 'SERVER_JOB'
+
+
 # load up site-specific code for generating site-specific job data
 get_site_job_data = utils.import_site_function(__file__,
     "autotest_lib.server.site_server_job", "get_site_job_data",