Put name of in-progress test in crash metadata.
When we start uploading crashes from test results, it will be useful to
know during which test a crash occurred (if any).
Also, run git cl format.
This is a reland of https://crrev.com/c/2429928, fixed to work for
server tests and cheets tests, and to be more robust.
BUG=chromium:1119443
TEST=test_that $DUT logging_UserCrash logging_GenerateCrashFiles \
cheets_CTS_P.9.0_r13.x86.wm-presubmit
Change-Id: I7fab8f4567a7318e9a9aea64b34df5d669d49ea3
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2481972
Tested-by: Miriam Zimmerman <mutexlox@chromium.org>
Reviewed-by: Greg Edelston <gredelston@google.com>
Reviewed-by: Ian Barkley-Yeung <iby@chromium.org>
Commit-Queue: Miriam Zimmerman <mutexlox@chromium.org>
diff --git a/client/common_lib/test.py b/client/common_lib/test.py
index addb69a..fe4a5dc 100644
--- a/client/common_lib/test.py
+++ b/client/common_lib/test.py
@@ -18,6 +18,7 @@
#pylint: disable=C0111
+import errno
import fcntl
import json
import logging
@@ -66,6 +67,9 @@
self.srcdir = os.path.join(self.bindir, 'src')
self.tmpdir = tempfile.mkdtemp("_" + self.tagged_testname,
dir=job.tmpdir)
+ # The crash_reporter uses this file to determine which test is in
+ # progress.
+ self.test_in_prog_file = '/run/crash_reporter/test-in-prog'
self._keyvals = []
self._new_keyval = False
self.failed_constraints = []
@@ -169,7 +173,7 @@
with open(output_file, 'r') as fp:
contents = fp.read()
if contents:
- charts = json.loads(contents)
+ charts = json.loads(contents)
if graph:
first_level = graph
@@ -184,9 +188,9 @@
# representing numbers logged, attempt to convert them to numbers.
# If a non number string is logged an exception will be thrown.
if isinstance(value, list):
- value = map(float, value)
+ value = map(float, value)
else:
- value = float(value)
+ value = float(value)
result_type = 'scalar'
value_key = 'value'
@@ -361,6 +365,43 @@
logging.debug('before_iteration_hooks completed')
finished = False
+
+ # Mark the current test in progress so that crash_reporter can report
+ # it in uploaded crashes.
+ # if the file already exists, truncate and overwrite.
+ # TODO(mutexlox): Determine what to do if the file already exists, which
+ # could happen for a few reasons:
+ # * An earlier tast or autotest run crashed before removing the
+ # test-in-prog file. In this case, we would ideally overwrite the
+ # existing test name and _not_ restore it.
+ # * An autotest ran another autotest (e.g. logging_GenerateCrashFiles
+ # runs desktopui_SimpleLogin). In this case, arguably it makes sense
+ # to attribute the crash to logging_GenerateCrashFiles and not to
+ # desktopui_SimpleLogin (or to attribute it to both), since the
+ # context in which it's running is different than if it were run on
+ # its own.
+ # * Every tast test is kicked off by the 'tast' autotest (see
+ # server/site_tests/tast/tast.py), so the file will always be
+ # populated when the tast suite starts running. In this case, we
+ # want to attribute crashes that happen during a specific test to
+ # that test, but if the tast infra causes a crash we should
+ # attribute the crash to it (e.g. to the 'tast.critical-system'
+ # "autotest"). For this case, we should save the contents of the
+ # file before a test and restore it after.
+ if 'host' in dargs:
+ dargs['host'].run('echo %s > %s' %
+ (self.tagged_testname, self.test_in_prog_file),
+ ignore_status=True)
+ else:
+ crash_run_dir = os.path.dirname(self.test_in_prog_file)
+ try:
+ # Only try to create the file if the directory already exists
+ # (otherwise, we may not be on a CrOS device)
+ if os.path.exists(crash_run_dir):
+ with open(self.test_in_prog_file, 'w') as f:
+ f.write(self.tagged_testname)
+ except: # Broad 'except' because we don't want this to block tests
+ logging.warning('failed to write in progress test name')
try:
if profile_only:
if not self.job.profilers.present():
@@ -387,6 +428,20 @@
'after_iteration_hooks.', str(e))
raise
finally:
+ if 'host' in dargs:
+ dargs['host'].run('rm -f %s' % self.test_in_prog_file)
+ else:
+ try:
+ # Unmark the test as running.
+ os.remove(self.test_in_prog_file)
+ except OSError as e:
+ # If something removed it, do nothing--we're in the desired
+ # state (the file is gone). Otherwise, log.
+ if e.errno != errno.ENOENT:
+ logging.warning(
+ "Couldn't remove test-in-prog file: %s",
+ traceback.format_exc())
+
if not finished or not self.job.fast:
logging.debug('Starting after_iteration_hooks for %s',
self.tagged_testname)
@@ -757,10 +812,18 @@
raise error.UnhandledTestFail(e)
-def runtest(job, url, tag, args, dargs,
- local_namespace={}, global_namespace={},
- before_test_hook=None, after_test_hook=None,
- before_iteration_hook=None, after_iteration_hook=None):
+def runtest(job,
+ url,
+ tag,
+ args,
+ dargs,
+ local_namespace={},
+ global_namespace={},
+ before_test_hook=None,
+ after_test_hook=None,
+ before_iteration_hook=None,
+ after_iteration_hook=None,
+ override_test_in_prog_file=None):
local_namespace = local_namespace.copy()
global_namespace = global_namespace.copy()
# if this is not a plain test name then download and install the
@@ -822,6 +885,8 @@
try:
mytest = global_namespace['mytest']
+ if override_test_in_prog_file:
+ mytest.test_in_prog_file = override_test_in_prog_file
mytest.success = False
if not job.fast and before_test_hook:
logging.info('Starting before_hook for %s', mytest.tagged_testname)
diff --git a/client/common_lib/test_unittest.py b/client/common_lib/test_unittest.py
index 22c3e24..3241347 100755
--- a/client/common_lib/test_unittest.py
+++ b/client/common_lib/test_unittest.py
@@ -36,6 +36,10 @@
self.before_iteration_hooks = []
self.after_iteration_hooks = []
+ self.crash_reporter_dir = tempfile.mkdtemp()
+ # Make a temp dir for the test-in-prog file to be created.
+ self.test_in_prog_file = os.path.join(self.crash_reporter_dir,
+ "test-in-prog")
def setUp(self):
self.god = mock.mock_god()
@@ -44,6 +48,7 @@
def tearDown(self):
self.god.unstub_all()
+ shutil.rmtree(self.test.crash_reporter_dir)
@@ -422,10 +427,11 @@
for (config_tag, ap_config_tag, bt_tag, drop) in test_data:
- self.test.output_perf_value(config_tag + '_' + bt_tag + '_drop',
- drop, units='percent_drop',
- higher_is_better=False,
- graph=ap_config_tag + '_drop')
+ self.test.output_perf_value(config_tag + '_' + bt_tag + '_drop',
+ drop,
+ units='percent_drop',
+ higher_is_better=False,
+ graph=ap_config_tag + '_drop')
f = open(self.test.resultsdir + "/results-chart.json")
expected_result = {
"ch006_mode11B_none_drop": {
@@ -534,6 +540,8 @@
resultdir = os.path.join(self.workdir, 'results')
tmpdir = os.path.join(self.workdir, 'tmp')
+ self.test_in_prog_file = os.path.join(tmpdir, "test-in-prog")
+
os.makedirs(os.path.join(testdir, self.testname))
os.makedirs(os.path.join(resultdir, self.testname))
os.makedirs(tmpdir)
@@ -550,7 +558,11 @@
def test_runtest(self):
all_args = {'host': 'hostvalue', 'arg1': 'value1', 'arg2': 'value2'}
- test.runtest(self.job, self.testname, '', (), all_args)
+ test.runtest(self.job,
+ self.testname,
+ '', (),
+ all_args,
+ override_test_in_prog_file=self.test_in_prog_file)
self.job.initialize_mock.assert_called_with('hostvalue', 'value1')
self.job.warmup_mock.assert_called_with('hostvalue')
self.job.run_once_mock.assert_called_with('value2')
diff --git a/client/cros/crash/user_crash_test.py b/client/cros/crash/user_crash_test.py
index 097ad3a..480e999 100644
--- a/client/cros/crash/user_crash_test.py
+++ b/client/cros/crash/user_crash_test.py
@@ -104,8 +104,7 @@
first_line = symbols.split('\n')[0]
tokens = first_line.split()
if tokens[0] != 'MODULE' or tokens[1] != 'Linux':
- raise error.TestError('Unexpected symbols format: %s',
- first_line)
+ raise error.TestError('Unexpected symbols format: %s', first_line)
file_id = tokens[3]
target_dir = os.path.join(self._symbol_dir, basename, file_id)
os.makedirs(target_dir)
@@ -419,8 +418,8 @@
crash_contents = os.listdir(crash_dir)
basename = os.path.basename(crasher_path or self._crasher_path)
if expect_crash_reporter_fail:
- old_basename = basename
- basename = "crash_reporter_failure"
+ old_basename = basename
+ basename = "crash_reporter_failure"
# A dict tracking files for each crash report.
crash_report_files = {}
@@ -496,10 +495,15 @@
raise error.TestFail('crash_reporter did not catch crash')
- def _check_crashing_process(self, username, consent=True,
- crasher_path=None, run_crasher=None,
- expected_uid=None, expected_gid=None,
- expected_exit_code=None):
+ def _check_crashing_process(self,
+ username,
+ consent=True,
+ crasher_path=None,
+ run_crasher=None,
+ expected_uid=None,
+ expected_gid=None,
+ expected_exit_code=None,
+ extra_meta_contents=None):
result = self._run_crasher_process_and_analyze(
username, consent=consent,
crasher_path=crasher_path,
@@ -513,6 +517,12 @@
if not consent:
return
+ if extra_meta_contents:
+ with open(result['meta'], 'r') as f:
+ if extra_meta_contents not in f.read():
+ raise error.TestFail('metadata did not contain "%s"' %
+ extra_meta_contents)
+
if not result['minidump']:
raise error.TestFail('crash reporter did not generate minidump')
diff --git a/client/site_tests/logging_UserCrash/logging_UserCrash.py b/client/site_tests/logging_UserCrash/logging_UserCrash.py
index 6654620..59446ef 100644
--- a/client/site_tests/logging_UserCrash/logging_UserCrash.py
+++ b/client/site_tests/logging_UserCrash/logging_UserCrash.py
@@ -65,7 +65,10 @@
def _test_chronos_crasher(self):
"""Test a user space crash when running as chronos is handled."""
- self._check_crashing_process('chronos')
+ self._check_crashing_process(
+ 'chronos',
+ extra_meta_contents='upload_var_in_progress_integration_test='
+ 'logging_UserCrash')
def _test_chronos_crasher_no_consent(self):