mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 1 | # Shell class for a test, inherited by all individual tests |
| 2 | # |
| 3 | # Methods: |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 4 | # __init__ initialise |
| 5 | # initialize run once for each job |
| 6 | # setup run once for each new version of the test installed |
| 7 | # run run the test (wrapped by job.run_test()) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 8 | # |
| 9 | # Data: |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 10 | # job backreference to the job this test instance is part of |
| 11 | # outputdir eg. results/<job>/<testname.tag> |
| 12 | # resultsdir eg. results/<job>/<testname.tag>/results |
| 13 | # profdir eg. results/<job>/<testname.tag>/profiling |
| 14 | # debugdir eg. results/<job>/<testname.tag>/debug |
| 15 | # bindir eg. tests/<test> |
| 16 | # src eg. tests/<test>/src |
jadmanski | 825e24c | 2008-08-27 20:54:31 +0000 | [diff] [blame] | 17 | # tmpdir eg. tmp/<tempname>_<testname.tag> |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 18 | |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 19 | #pylint: disable=C0111 |
Aviv Keshet | 39164ca | 2013-03-27 15:08:33 -0700 | [diff] [blame] | 20 | |
Xixuan Wu | 5019d69 | 2017-11-10 15:58:33 -0800 | [diff] [blame] | 21 | import fcntl |
| 22 | import json |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 23 | import logging |
Xixuan Wu | 5019d69 | 2017-11-10 15:58:33 -0800 | [diff] [blame] | 24 | import os |
| 25 | import re |
| 26 | import shutil |
| 27 | import stat |
| 28 | import sys |
| 29 | import tempfile |
| 30 | import time |
| 31 | import traceback |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 32 | |
mbligh | 53da18e | 2009-01-05 21:13:26 +0000 | [diff] [blame] | 33 | from autotest_lib.client.bin import utils |
Ilja H. Friedel | 8753d83 | 2014-10-07 22:30:06 -0700 | [diff] [blame] | 34 | from autotest_lib.client.common_lib import error |
Xixuan Wu | e8ce654 | 2017-11-07 17:22:10 -0800 | [diff] [blame] | 35 | from autotest_lib.client.common_lib import utils as client_utils |
| 36 | |
| 37 | try: |
| 38 | from chromite.lib import metrics |
| 39 | except ImportError: |
| 40 | metrics = client_utils.metrics_mock |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 41 | |
| 42 | |
jadmanski | aafbf2a | 2010-06-25 17:07:24 +0000 | [diff] [blame] | 43 | class base_test(object): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 44 | preserve_srcdir = False |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 45 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 46 | def __init__(self, job, bindir, outputdir): |
| 47 | self.job = job |
mbligh | 21e3358 | 2009-02-04 18:18:31 +0000 | [diff] [blame] | 48 | self.pkgmgr = job.pkgmgr |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 49 | self.autodir = job.autodir |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 50 | self.outputdir = outputdir |
showard | b18134f | 2009-03-20 20:52:18 +0000 | [diff] [blame] | 51 | self.tagged_testname = os.path.basename(self.outputdir) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 52 | self.resultsdir = os.path.join(self.outputdir, 'results') |
| 53 | os.mkdir(self.resultsdir) |
| 54 | self.profdir = os.path.join(self.outputdir, 'profiling') |
| 55 | os.mkdir(self.profdir) |
| 56 | self.debugdir = os.path.join(self.outputdir, 'debug') |
| 57 | os.mkdir(self.debugdir) |
Eric Li | 8b2954a | 2010-07-12 21:42:34 -0700 | [diff] [blame] | 58 | # TODO(ericli): figure out how autotest crash handler work with cros |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 59 | # Once this is re-enabled import getpass. crosbug.com/31232 |
Eric Li | 8b2954a | 2010-07-12 21:42:34 -0700 | [diff] [blame] | 60 | # crash handler, we should restore it in near term. |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 61 | # if getpass.getuser() == 'root': |
| 62 | # self.configure_crash_handler() |
| 63 | # else: |
Eric Li | 8b2954a | 2010-07-12 21:42:34 -0700 | [diff] [blame] | 64 | self.crash_handling_enabled = False |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 65 | self.bindir = bindir |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 66 | self.srcdir = os.path.join(self.bindir, 'src') |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 67 | self.tmpdir = tempfile.mkdtemp("_" + self.tagged_testname, |
showard | b18134f | 2009-03-20 20:52:18 +0000 | [diff] [blame] | 68 | dir=job.tmpdir) |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 69 | self._keyvals = [] |
| 70 | self._new_keyval = False |
mbligh | 32cb5b4 | 2009-05-01 23:05:09 +0000 | [diff] [blame] | 71 | self.failed_constraints = [] |
mbligh | 5e703a2 | 2009-06-15 22:00:12 +0000 | [diff] [blame] | 72 | self.iteration = 0 |
mbligh | 742ae42 | 2009-05-13 20:46:41 +0000 | [diff] [blame] | 73 | self.before_iteration_hooks = [] |
| 74 | self.after_iteration_hooks = [] |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 75 | |
Dan Shi | 2ca9777 | 2013-10-21 17:17:27 -0700 | [diff] [blame] | 76 | # Flag to indicate if the test has succeeded or failed. |
| 77 | self.success = False |
| 78 | |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 79 | |
mbligh | 6894ce2 | 2009-09-18 19:56:30 +0000 | [diff] [blame] | 80 | def configure_crash_handler(self): |
lmr | fb11887 | 2009-10-13 19:41:48 +0000 | [diff] [blame] | 81 | pass |
mbligh | 6894ce2 | 2009-09-18 19:56:30 +0000 | [diff] [blame] | 82 | |
| 83 | |
| 84 | def crash_handler_report(self): |
lmr | fb11887 | 2009-10-13 19:41:48 +0000 | [diff] [blame] | 85 | pass |
mbligh | 6894ce2 | 2009-09-18 19:56:30 +0000 | [diff] [blame] | 86 | |
| 87 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 88 | def assert_(self, expr, msg='Assertion failed.'): |
| 89 | if not expr: |
| 90 | raise error.TestError(msg) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 91 | |
| 92 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 93 | def write_test_keyval(self, attr_dict): |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 94 | utils.write_keyval(self.outputdir, attr_dict) |
jadmanski | cc54917 | 2008-05-21 18:11:51 +0000 | [diff] [blame] | 95 | |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 96 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 97 | @staticmethod |
| 98 | def _append_type_to_keys(dictionary, typename): |
| 99 | new_dict = {} |
| 100 | for key, value in dictionary.iteritems(): |
| 101 | new_key = "%s{%s}" % (key, typename) |
| 102 | new_dict[new_key] = value |
| 103 | return new_dict |
jadmanski | cc54917 | 2008-05-21 18:11:51 +0000 | [diff] [blame] | 104 | |
| 105 | |
Fang Deng | e689e71 | 2013-11-13 18:27:06 -0800 | [diff] [blame] | 106 | def output_perf_value(self, description, value, units=None, |
Po-Hsien Wang | 3c4fc6f | 2017-05-20 12:11:58 -0700 | [diff] [blame] | 107 | higher_is_better=None, graph=None, |
| 108 | replacement='_', replace_existing_values=False): |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 109 | """ |
| 110 | Records a measured performance value in an output file. |
| 111 | |
| 112 | The output file will subsequently be parsed by the TKO parser to have |
| 113 | the information inserted into the results database. |
| 114 | |
| 115 | @param description: A string describing the measured perf value. Must |
| 116 | be maximum length 256, and may only contain letters, numbers, |
| 117 | periods, dashes, and underscores. For example: |
| 118 | "page_load_time", "scrolling-frame-rate". |
| 119 | @param value: A number representing the measured perf value, or a list |
| 120 | of measured values if a test takes multiple measurements. |
| 121 | Measured perf values can be either ints or floats. |
| 122 | @param units: A string describing the units associated with the |
| 123 | measured perf value. Must be maximum length 32, and may only |
| 124 | contain letters, numbers, periods, dashes, and underscores. |
| 125 | For example: "msec", "fps", "score", "runs_per_second". |
| 126 | @param higher_is_better: A boolean indicating whether or not a "higher" |
| 127 | measured perf value is considered to be better. If False, it is |
| 128 | assumed that a "lower" measured value is considered to be |
Ilja H. Friedel | d06a7a5 | 2014-11-06 21:34:20 -0800 | [diff] [blame] | 129 | better. This impacts dashboard plotting and email notification. |
| 130 | Pure autotests are expected to specify either True or False! |
| 131 | This value can be set to "None" to indicate that the perf |
| 132 | dashboard should apply the rules encoded via Chromium |
| 133 | unit-info.json. This is only used for tracking Chromium based |
| 134 | tests (in particular telemetry). |
Fang Deng | 7f24f0b | 2013-11-12 11:22:16 -0800 | [diff] [blame] | 135 | @param graph: A string indicating the name of the graph on which |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 136 | the perf value will be subsequently displayed on the chrome perf |
| 137 | dashboard. This allows multiple metrics be grouped together on |
| 138 | the same graphs. Defaults to None, indicating that the perf |
| 139 | value should be displayed individually on a separate graph. |
| 140 | @param replacement: string to replace illegal characters in |
| 141 | |description| and |units| with. |
Po-Hsien Wang | 3c4fc6f | 2017-05-20 12:11:58 -0700 | [diff] [blame] | 142 | @param replace_existing_values: A boolean indicating whether or not a |
| 143 | new added perf value should replace existing perf. |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 144 | """ |
| 145 | if len(description) > 256: |
| 146 | raise ValueError('The description must be at most 256 characters.') |
David Sharp | 78a16a5 | 2015-12-16 11:58:31 -0800 | [diff] [blame] | 147 | if units and len(units) > 32: |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 148 | raise ValueError('The units must be at most 32 characters.') |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 149 | |
| 150 | # If |replacement| is legal replace illegal characters with it. |
| 151 | string_regex = re.compile(r'[^-\.\w]') |
| 152 | if replacement is None or re.search(string_regex, replacement): |
| 153 | raise ValueError('Invalid replacement string to mask illegal ' |
| 154 | 'characters. May only contain letters, numbers, ' |
| 155 | 'periods, dashes, and underscores. ' |
| 156 | 'replacement: %s' % replacement) |
| 157 | description = re.sub(string_regex, replacement, description) |
| 158 | units = re.sub(string_regex, replacement, units) if units else None |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 159 | |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 160 | charts = {} |
| 161 | output_file = os.path.join(self.resultsdir, 'results-chart.json') |
| 162 | if os.path.isfile(output_file): |
| 163 | with open(output_file, 'r') as fp: |
| 164 | contents = fp.read() |
| 165 | if contents: |
| 166 | charts = json.loads(contents) |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 167 | |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 168 | if graph: |
Keith Haddow | df2730b | 2016-03-28 21:31:18 -0700 | [diff] [blame] | 169 | first_level = graph |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 170 | second_level = description |
| 171 | else: |
| 172 | first_level = description |
| 173 | second_level = 'summary' |
| 174 | |
| 175 | direction = 'up' if higher_is_better else 'down' |
| 176 | |
Keith Haddow | 4fc2465 | 2016-03-16 10:08:17 -0700 | [diff] [blame] | 177 | # All input should be a number - but at times there are strings |
| 178 | # representing numbers logged, attempt to convert them to numbers. |
| 179 | # If a non number string is logged an exception will be thrown. |
| 180 | if isinstance(value, list): |
| 181 | value = map(float, value) |
| 182 | else: |
| 183 | value = float(value) |
| 184 | |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 185 | result_type = 'scalar' |
| 186 | value_key = 'value' |
| 187 | result_value = value |
| 188 | |
| 189 | # The chart json spec go/telemetry-json differenciates between a single |
| 190 | # value vs a list of values. Lists of values get extra processing in |
| 191 | # the chromeperf dashboard ( mean, standard deviation etc) |
| 192 | # Tests can log one or more values for the same metric, to adhere stricly |
| 193 | # to the specification the first value logged is a scalar but if another |
| 194 | # value is logged the results become a list of scalar. |
| 195 | # TODO Figure out if there would be any difference of always using list |
| 196 | # of scalar even if there is just one item in the list. |
Keith Haddow | d804bc8 | 2016-03-15 17:16:03 -0700 | [diff] [blame] | 197 | if isinstance(value, list): |
| 198 | result_type = 'list_of_scalar_values' |
| 199 | value_key = 'values' |
| 200 | if first_level in charts and second_level in charts[first_level]: |
| 201 | if 'values' in charts[first_level][second_level]: |
| 202 | result_value = charts[first_level][second_level]['values'] |
Keith Haddow | d804bc8 | 2016-03-15 17:16:03 -0700 | [diff] [blame] | 203 | elif 'value' in charts[first_level][second_level]: |
| 204 | result_value = [charts[first_level][second_level]['value']] |
Po-Hsien Wang | 3c4fc6f | 2017-05-20 12:11:58 -0700 | [diff] [blame] | 205 | if replace_existing_values: |
| 206 | result_value = value |
| 207 | else: |
Keith Haddow | d804bc8 | 2016-03-15 17:16:03 -0700 | [diff] [blame] | 208 | result_value.extend(value) |
| 209 | else: |
| 210 | result_value = value |
Po-Hsien Wang | 3c4fc6f | 2017-05-20 12:11:58 -0700 | [diff] [blame] | 211 | elif (first_level in charts and second_level in charts[first_level] and |
| 212 | not replace_existing_values): |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 213 | result_type = 'list_of_scalar_values' |
| 214 | value_key = 'values' |
| 215 | if 'values' in charts[first_level][second_level]: |
| 216 | result_value = charts[first_level][second_level]['values'] |
| 217 | result_value.append(value) |
| 218 | else: |
| 219 | result_value = [charts[first_level][second_level]['value'], value] |
| 220 | |
Keith Haddow | df2730b | 2016-03-28 21:31:18 -0700 | [diff] [blame] | 221 | test_data = { |
| 222 | second_level: { |
| 223 | 'type': result_type, |
| 224 | 'units': units, |
| 225 | value_key: result_value, |
| 226 | 'improvement_direction': direction |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | if first_level in charts: |
| 231 | charts[first_level].update(test_data) |
| 232 | else: |
| 233 | charts.update({first_level: test_data}) |
| 234 | |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 235 | with open(output_file, 'w') as fp: |
| 236 | fp.write(json.dumps(charts, indent=2)) |
Dennis Jeffrey | 918863f | 2013-06-19 16:49:48 -0700 | [diff] [blame] | 237 | |
| 238 | |
mbligh | 0b3dd5f | 2008-07-16 20:37:13 +0000 | [diff] [blame] | 239 | def write_perf_keyval(self, perf_dict): |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 240 | self.write_iteration_keyval({}, perf_dict) |
jadmanski | cc54917 | 2008-05-21 18:11:51 +0000 | [diff] [blame] | 241 | |
mbligh | 0b3dd5f | 2008-07-16 20:37:13 +0000 | [diff] [blame] | 242 | |
| 243 | def write_attr_keyval(self, attr_dict): |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 244 | self.write_iteration_keyval(attr_dict, {}) |
mbligh | 0b3dd5f | 2008-07-16 20:37:13 +0000 | [diff] [blame] | 245 | |
| 246 | |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 247 | def write_iteration_keyval(self, attr_dict, perf_dict): |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 248 | # append the dictionaries before they have the {perf} and {attr} added |
| 249 | self._keyvals.append({'attr':attr_dict, 'perf':perf_dict}) |
| 250 | self._new_keyval = True |
| 251 | |
mbligh | 0b3dd5f | 2008-07-16 20:37:13 +0000 | [diff] [blame] | 252 | if attr_dict: |
| 253 | attr_dict = self._append_type_to_keys(attr_dict, "attr") |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 254 | utils.write_keyval(self.resultsdir, attr_dict, type_tag="attr") |
mbligh | 0b3dd5f | 2008-07-16 20:37:13 +0000 | [diff] [blame] | 255 | |
| 256 | if perf_dict: |
| 257 | perf_dict = self._append_type_to_keys(perf_dict, "perf") |
Aviv Keshet | f0c8224 | 2017-05-18 22:06:40 -0700 | [diff] [blame] | 258 | utils.write_keyval(self.resultsdir, perf_dict, type_tag="perf") |
jadmanski | cc54917 | 2008-05-21 18:11:51 +0000 | [diff] [blame] | 259 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 260 | keyval_path = os.path.join(self.resultsdir, "keyval") |
| 261 | print >> open(keyval_path, "a"), "" |
jadmanski | cc54917 | 2008-05-21 18:11:51 +0000 | [diff] [blame] | 262 | |
| 263 | |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 264 | def analyze_perf_constraints(self, constraints): |
| 265 | if not self._new_keyval: |
| 266 | return |
| 267 | |
jadmanski | 0d9ea77 | 2009-10-08 22:30:56 +0000 | [diff] [blame] | 268 | # create a dict from the keyvals suitable as an environment for eval |
| 269 | keyval_env = self._keyvals[-1]['perf'].copy() |
| 270 | keyval_env['__builtins__'] = None |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 271 | self._new_keyval = False |
mbligh | 32cb5b4 | 2009-05-01 23:05:09 +0000 | [diff] [blame] | 272 | failures = [] |
jadmanski | 0d9ea77 | 2009-10-08 22:30:56 +0000 | [diff] [blame] | 273 | |
| 274 | # evaluate each constraint using the current keyvals |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 275 | for constraint in constraints: |
jadmanski | 0d9ea77 | 2009-10-08 22:30:56 +0000 | [diff] [blame] | 276 | logging.info('___________________ constraint = %s', constraint) |
| 277 | logging.info('___________________ keyvals = %s', keyval_env) |
| 278 | |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 279 | try: |
jadmanski | 0d9ea77 | 2009-10-08 22:30:56 +0000 | [diff] [blame] | 280 | if not eval(constraint, keyval_env): |
mbligh | 8beabca | 2009-05-21 01:33:15 +0000 | [diff] [blame] | 281 | failures.append('%s: constraint was not met' % constraint) |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 282 | except: |
mbligh | 32cb5b4 | 2009-05-01 23:05:09 +0000 | [diff] [blame] | 283 | failures.append('could not evaluate constraint: %s' |
| 284 | % constraint) |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 285 | |
mbligh | 32cb5b4 | 2009-05-01 23:05:09 +0000 | [diff] [blame] | 286 | # keep track of the errors for each iteration |
| 287 | self.failed_constraints.append(failures) |
| 288 | |
| 289 | |
| 290 | def process_failed_constraints(self): |
| 291 | msg = '' |
| 292 | for i, failures in enumerate(self.failed_constraints): |
| 293 | if failures: |
| 294 | msg += 'iteration %d:%s ' % (i, ','.join(failures)) |
| 295 | |
| 296 | if msg: |
| 297 | raise error.TestFail(msg) |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 298 | |
| 299 | |
mbligh | 742ae42 | 2009-05-13 20:46:41 +0000 | [diff] [blame] | 300 | def register_before_iteration_hook(self, iteration_hook): |
| 301 | """ |
| 302 | This is how we expect test writers to register a before_iteration_hook. |
| 303 | This adds the method to the list of hooks which are executed |
| 304 | before each iteration. |
| 305 | |
| 306 | @param iteration_hook: Method to run before each iteration. A valid |
| 307 | hook accepts a single argument which is the |
| 308 | test object. |
| 309 | """ |
| 310 | self.before_iteration_hooks.append(iteration_hook) |
| 311 | |
| 312 | |
| 313 | def register_after_iteration_hook(self, iteration_hook): |
| 314 | """ |
| 315 | This is how we expect test writers to register an after_iteration_hook. |
| 316 | This adds the method to the list of hooks which are executed |
Christopher Grant | b16491a | 2015-06-11 15:46:19 -0400 | [diff] [blame] | 317 | after each iteration. Hooks are executed starting with the most- |
| 318 | recently registered, in stack fashion. |
mbligh | 742ae42 | 2009-05-13 20:46:41 +0000 | [diff] [blame] | 319 | |
| 320 | @param iteration_hook: Method to run after each iteration. A valid |
| 321 | hook accepts a single argument which is the |
| 322 | test object. |
| 323 | """ |
| 324 | self.after_iteration_hooks.append(iteration_hook) |
| 325 | |
| 326 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 327 | def initialize(self): |
| 328 | pass |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 329 | |
| 330 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 331 | def setup(self): |
| 332 | pass |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 333 | |
| 334 | |
mbligh | 14f9856 | 2008-07-29 21:16:27 +0000 | [diff] [blame] | 335 | def warmup(self, *args, **dargs): |
mbligh | 4205d89 | 2008-07-14 16:23:20 +0000 | [diff] [blame] | 336 | pass |
mbligh | b53a347 | 2008-07-11 21:27:58 +0000 | [diff] [blame] | 337 | |
| 338 | |
mbligh | b5dac43 | 2008-11-27 00:38:44 +0000 | [diff] [blame] | 339 | def drop_caches_between_iterations(self): |
| 340 | if self.job.drop_caches_between_iterations: |
mbligh | 53da18e | 2009-01-05 21:13:26 +0000 | [diff] [blame] | 341 | utils.drop_caches() |
mbligh | b5dac43 | 2008-11-27 00:38:44 +0000 | [diff] [blame] | 342 | |
| 343 | |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 344 | def _call_run_once_with_retry(self, constraints, profile_only, |
| 345 | postprocess_profiled_run, args, dargs): |
| 346 | """Thin wrapper around _call_run_once that retries unsuccessful tests. |
| 347 | |
| 348 | If the job object's attribute test_retry is > 0 retry any tests that |
| 349 | ran unsuccessfully X times. |
| 350 | *Note this does not competely re-initialize the test, it only |
| 351 | re-executes code once all the initial job set up (packages, |
| 352 | sysinfo, etc) is complete. |
| 353 | """ |
| 354 | if self.job.test_retry != 0: |
| 355 | logging.info('Test will be retried a maximum of %d times', |
| 356 | self.job.test_retry) |
| 357 | |
| 358 | max_runs = self.job.test_retry |
| 359 | for retry_run in xrange(0, max_runs+1): |
| 360 | try: |
| 361 | self._call_run_once(constraints, profile_only, |
| 362 | postprocess_profiled_run, args, dargs) |
| 363 | break |
Aviv Keshet | 39164ca | 2013-03-27 15:08:33 -0700 | [diff] [blame] | 364 | except error.TestFailRetry as err: |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 365 | if retry_run == max_runs: |
| 366 | raise |
| 367 | self.job.record('INFO', None, None, 'Run %s failed with %s' % ( |
| 368 | retry_run, err)) |
| 369 | if retry_run > 0: |
| 370 | self.write_test_keyval({'test_retries_before_success': retry_run}) |
| 371 | |
| 372 | |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 373 | def _call_run_once(self, constraints, profile_only, |
| 374 | postprocess_profiled_run, args, dargs): |
mbligh | 6b97f79 | 2009-03-23 21:23:12 +0000 | [diff] [blame] | 375 | self.drop_caches_between_iterations() |
mbligh | 4395bbd | 2009-03-25 19:34:17 +0000 | [diff] [blame] | 376 | # execute iteration hooks |
Xixuan Wu | f4fb11b | 2017-11-13 19:54:38 -0800 | [diff] [blame] | 377 | if not self.job.fast: |
| 378 | logging.debug('Starting before_iteration_hooks for %s', |
| 379 | self.tagged_testname) |
| 380 | with metrics.SecondsTimer( |
| 381 | 'chromeos/autotest/job/before_iteration_hook_duration'): |
| 382 | for hook in self.before_iteration_hooks: |
| 383 | hook(self) |
| 384 | logging.debug('before_iteration_hooks completed') |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 385 | |
Xixuan Wu | f4fb11b | 2017-11-13 19:54:38 -0800 | [diff] [blame] | 386 | finished = False |
Eric Li | daf6ff0 | 2011-03-01 15:31:31 -0800 | [diff] [blame] | 387 | try: |
| 388 | if profile_only: |
| 389 | if not self.job.profilers.present(): |
| 390 | self.job.record('WARN', None, None, |
| 391 | 'No profilers have been added but ' |
| 392 | 'profile_only is set - nothing ' |
| 393 | 'will be run') |
| 394 | self.run_once_profiling(postprocess_profiled_run, |
| 395 | *args, **dargs) |
| 396 | else: |
| 397 | self.before_run_once() |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 398 | logging.debug('starting test(run_once()), test details follow' |
| 399 | '\n%r', args) |
Eric Li | daf6ff0 | 2011-03-01 15:31:31 -0800 | [diff] [blame] | 400 | self.run_once(*args, **dargs) |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 401 | logging.debug('The test has completed successfully') |
Eric Li | daf6ff0 | 2011-03-01 15:31:31 -0800 | [diff] [blame] | 402 | self.after_run_once() |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 403 | |
Eric Li | daf6ff0 | 2011-03-01 15:31:31 -0800 | [diff] [blame] | 404 | self.postprocess_iteration() |
| 405 | self.analyze_perf_constraints(constraints) |
Xixuan Wu | f4fb11b | 2017-11-13 19:54:38 -0800 | [diff] [blame] | 406 | finished = True |
Christopher Grant | 4beca02 | 2015-06-16 15:14:47 -0400 | [diff] [blame] | 407 | # Catch and re-raise to let after_iteration_hooks see the exception. |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 408 | except Exception as e: |
| 409 | logging.debug('Test failed due to %s. Exception log follows the ' |
| 410 | 'after_iteration_hooks.', str(e)) |
Christopher Grant | 4beca02 | 2015-06-16 15:14:47 -0400 | [diff] [blame] | 411 | raise |
Eric Li | daf6ff0 | 2011-03-01 15:31:31 -0800 | [diff] [blame] | 412 | finally: |
Xixuan Wu | f4fb11b | 2017-11-13 19:54:38 -0800 | [diff] [blame] | 413 | if not finished or not self.job.fast: |
| 414 | logging.debug('Starting after_iteration_hooks for %s', |
| 415 | self.tagged_testname) |
| 416 | with metrics.SecondsTimer( |
| 417 | 'chromeos/autotest/job/after_iteration_hook_duration'): |
| 418 | for hook in reversed(self.after_iteration_hooks): |
| 419 | hook(self) |
| 420 | logging.debug('after_iteration_hooks completed') |
mbligh | 6b97f79 | 2009-03-23 21:23:12 +0000 | [diff] [blame] | 421 | |
| 422 | |
showard | a6082ef | 2009-10-12 20:25:44 +0000 | [diff] [blame] | 423 | def execute(self, iterations=None, test_length=None, profile_only=None, |
mbligh | a49c5cb | 2009-02-26 01:01:09 +0000 | [diff] [blame] | 424 | _get_time=time.time, postprocess_profiled_run=None, |
mbligh | 7af0997 | 2009-04-17 22:17:08 +0000 | [diff] [blame] | 425 | constraints=(), *args, **dargs): |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 426 | """ |
| 427 | This is the basic execute method for the tests inherited from base_test. |
| 428 | If you want to implement a benchmark test, it's better to implement |
| 429 | the run_once function, to cope with the profiling infrastructure. For |
| 430 | other tests, you can just override the default implementation. |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 431 | |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 432 | @param test_length: The minimum test length in seconds. We'll run the |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 433 | run_once function for a number of times large enough to cover the |
| 434 | minimum test length. |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 435 | |
| 436 | @param iterations: A number of iterations that we'll run the run_once |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 437 | function. This parameter is incompatible with test_length and will |
| 438 | be silently ignored if you specify both. |
| 439 | |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 440 | @param profile_only: If true run X iterations with profilers enabled. |
showard | a6082ef | 2009-10-12 20:25:44 +0000 | [diff] [blame] | 441 | If false run X iterations and one with profiling if profiles are |
| 442 | enabled. If None, default to the value of job.default_profile_only. |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 443 | |
| 444 | @param _get_time: [time.time] Used for unit test time injection. |
mbligh | c931408 | 2009-02-26 00:48:18 +0000 | [diff] [blame] | 445 | |
mbligh | a49c5cb | 2009-02-26 01:01:09 +0000 | [diff] [blame] | 446 | @param postprocess_profiled_run: Run the postprocessing for the |
| 447 | profiled run. |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 448 | """ |
| 449 | |
| 450 | # For our special class of tests, the benchmarks, we don't want |
| 451 | # profilers to run during the test iterations. Let's reserve only |
| 452 | # the last iteration for profiling, if needed. So let's stop |
| 453 | # all profilers if they are present and active. |
mbligh | b3c0c91 | 2008-11-27 00:32:45 +0000 | [diff] [blame] | 454 | profilers = self.job.profilers |
| 455 | if profilers.active(): |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 456 | profilers.stop(self) |
showard | a6082ef | 2009-10-12 20:25:44 +0000 | [diff] [blame] | 457 | if profile_only is None: |
| 458 | profile_only = self.job.default_profile_only |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 459 | # If the user called this test in an odd way (specified both iterations |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 460 | # and test_length), let's warn them. |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 461 | if iterations and test_length: |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 462 | logging.debug('Iterations parameter ignored (timed execution)') |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 463 | if test_length: |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 464 | test_start = _get_time() |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 465 | time_elapsed = 0 |
| 466 | timed_counter = 0 |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 467 | logging.debug('Test started. Specified %d s as the minimum test ' |
| 468 | 'length', test_length) |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 469 | while time_elapsed < test_length: |
| 470 | timed_counter = timed_counter + 1 |
| 471 | if time_elapsed == 0: |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 472 | logging.debug('Executing iteration %d', timed_counter) |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 473 | elif time_elapsed > 0: |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 474 | logging.debug('Executing iteration %d, time_elapsed %d s', |
| 475 | timed_counter, time_elapsed) |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 476 | self._call_run_once_with_retry(constraints, profile_only, |
| 477 | postprocess_profiled_run, args, |
| 478 | dargs) |
mbligh | 4b835b8 | 2009-02-11 01:26:13 +0000 | [diff] [blame] | 479 | test_iteration_finish = _get_time() |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 480 | time_elapsed = test_iteration_finish - test_start |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 481 | logging.debug('Test finished after %d iterations, ' |
| 482 | 'time elapsed: %d s', timed_counter, time_elapsed) |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 483 | else: |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 484 | if iterations is None: |
mbligh | 777d96e | 2008-09-03 16:34:38 +0000 | [diff] [blame] | 485 | iterations = 1 |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 486 | if iterations > 1: |
| 487 | logging.debug('Test started. Specified %d iterations', |
| 488 | iterations) |
| 489 | for self.iteration in xrange(1, iterations + 1): |
| 490 | if iterations > 1: |
| 491 | logging.debug('Executing iteration %d of %d', |
| 492 | self.iteration, iterations) |
Scott Zawalski | 91493c8 | 2013-01-25 16:15:20 -0500 | [diff] [blame] | 493 | self._call_run_once_with_retry(constraints, profile_only, |
| 494 | postprocess_profiled_run, args, |
| 495 | dargs) |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 496 | |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 497 | if not profile_only: |
mbligh | 5e703a2 | 2009-06-15 22:00:12 +0000 | [diff] [blame] | 498 | self.iteration += 1 |
mbligh | f58865f | 2009-05-13 21:32:42 +0000 | [diff] [blame] | 499 | self.run_once_profiling(postprocess_profiled_run, *args, **dargs) |
mbligh | d27604e | 2009-02-03 02:06:08 +0000 | [diff] [blame] | 500 | |
| 501 | # Do any postprocessing, normally extracting performance keyvals, etc |
| 502 | self.postprocess() |
mbligh | 32cb5b4 | 2009-05-01 23:05:09 +0000 | [diff] [blame] | 503 | self.process_failed_constraints() |
mbligh | d27604e | 2009-02-03 02:06:08 +0000 | [diff] [blame] | 504 | |
| 505 | |
mbligh | a49c5cb | 2009-02-26 01:01:09 +0000 | [diff] [blame] | 506 | def run_once_profiling(self, postprocess_profiled_run, *args, **dargs): |
mbligh | d27604e | 2009-02-03 02:06:08 +0000 | [diff] [blame] | 507 | profilers = self.job.profilers |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 508 | # Do a profiling run if necessary |
mbligh | b3c0c91 | 2008-11-27 00:32:45 +0000 | [diff] [blame] | 509 | if profilers.present(): |
mbligh | c931408 | 2009-02-26 00:48:18 +0000 | [diff] [blame] | 510 | self.drop_caches_between_iterations() |
mbligh | 1b0faf9 | 2009-12-19 05:26:13 +0000 | [diff] [blame] | 511 | profilers.before_start(self) |
| 512 | |
| 513 | self.before_run_once() |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 514 | profilers.start(self) |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 515 | logging.debug('Profilers present. Profiling run started') |
mbligh | 1b0faf9 | 2009-12-19 05:26:13 +0000 | [diff] [blame] | 516 | |
jadmanski | 0d39007 | 2008-11-19 21:19:56 +0000 | [diff] [blame] | 517 | try: |
| 518 | self.run_once(*args, **dargs) |
mbligh | a49c5cb | 2009-02-26 01:01:09 +0000 | [diff] [blame] | 519 | |
| 520 | # Priority to the run_once() argument over the attribute. |
| 521 | postprocess_attribute = getattr(self, |
| 522 | 'postprocess_profiled_run', |
| 523 | False) |
| 524 | |
| 525 | if (postprocess_profiled_run or |
| 526 | (postprocess_profiled_run is None and |
| 527 | postprocess_attribute)): |
| 528 | self.postprocess_iteration() |
| 529 | |
jadmanski | 0d39007 | 2008-11-19 21:19:56 +0000 | [diff] [blame] | 530 | finally: |
| 531 | profilers.stop(self) |
| 532 | profilers.report(self) |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 533 | |
mbligh | 1b0faf9 | 2009-12-19 05:26:13 +0000 | [diff] [blame] | 534 | self.after_run_once() |
| 535 | |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 536 | |
| 537 | def postprocess(self): |
| 538 | pass |
| 539 | |
| 540 | |
mbligh | 34b297b | 2009-02-03 17:49:48 +0000 | [diff] [blame] | 541 | def postprocess_iteration(self): |
| 542 | pass |
| 543 | |
| 544 | |
mbligh | 6043471 | 2008-07-16 16:35:10 +0000 | [diff] [blame] | 545 | def cleanup(self): |
| 546 | pass |
mbligh | cd8a516 | 2008-07-16 16:32:12 +0000 | [diff] [blame] | 547 | |
| 548 | |
mbligh | 1b0faf9 | 2009-12-19 05:26:13 +0000 | [diff] [blame] | 549 | def before_run_once(self): |
| 550 | """ |
| 551 | Override in tests that need it, will be called before any run_once() |
| 552 | call including the profiling run (when it's called before starting |
| 553 | the profilers). |
| 554 | """ |
| 555 | pass |
| 556 | |
| 557 | |
| 558 | def after_run_once(self): |
| 559 | """ |
| 560 | Called after every run_once (including from a profiled run when it's |
| 561 | called after stopping the profilers). |
| 562 | """ |
| 563 | pass |
| 564 | |
| 565 | |
Owen Lin | 9f85240 | 2014-04-15 16:35:05 +0800 | [diff] [blame] | 566 | @staticmethod |
| 567 | def _make_writable_to_others(directory): |
| 568 | mode = os.stat(directory).st_mode |
| 569 | mode = mode | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH |
| 570 | os.chmod(directory, mode) |
| 571 | |
| 572 | |
mbligh | 742ae42 | 2009-05-13 20:46:41 +0000 | [diff] [blame] | 573 | def _exec(self, args, dargs): |
showard | ee36bc7 | 2009-06-18 23:13:53 +0000 | [diff] [blame] | 574 | self.job.logging.tee_redirect_debug_dir(self.debugdir, |
| 575 | log_name=self.tagged_testname) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 576 | try: |
jadmanski | 6265578 | 2008-07-28 21:27:46 +0000 | [diff] [blame] | 577 | # write out the test attributes into a keyval |
| 578 | dargs = dargs.copy() |
jadmanski | 23afbec | 2008-09-17 18:12:07 +0000 | [diff] [blame] | 579 | run_cleanup = dargs.pop('run_cleanup', self.job.run_test_cleanup) |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 580 | keyvals = dargs.pop('test_attributes', {}).copy() |
jadmanski | 6265578 | 2008-07-28 21:27:46 +0000 | [diff] [blame] | 581 | keyvals['version'] = self.version |
jadmanski | 2ae0e05 | 2008-09-04 16:37:28 +0000 | [diff] [blame] | 582 | for i, arg in enumerate(args): |
| 583 | keyvals['param-%d' % i] = repr(arg) |
| 584 | for name, arg in dargs.iteritems(): |
| 585 | keyvals['param-%s' % name] = repr(arg) |
jadmanski | 6265578 | 2008-07-28 21:27:46 +0000 | [diff] [blame] | 586 | self.write_test_keyval(keyvals) |
| 587 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 588 | _validate_args(args, dargs, self.initialize, self.setup, |
| 589 | self.execute, self.cleanup) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 590 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 591 | try: |
Owen Lin | 9f85240 | 2014-04-15 16:35:05 +0800 | [diff] [blame] | 592 | # Make resultsdir and tmpdir accessible to everyone. We may |
| 593 | # output data to these directories as others, e.g., chronos. |
| 594 | self._make_writable_to_others(self.tmpdir) |
| 595 | self._make_writable_to_others(self.resultsdir) |
| 596 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 597 | # Initialize: |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 598 | _cherry_pick_call(self.initialize, *args, **dargs) |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 599 | |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 600 | lockfile = open(os.path.join(self.job.tmpdir, '.testlock'), 'w') |
| 601 | try: |
| 602 | fcntl.flock(lockfile, fcntl.LOCK_EX) |
| 603 | # Setup: (compile and install the test, if needed) |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 604 | p_args, p_dargs = _cherry_pick_args(self.setup, args, dargs) |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 605 | utils.update_version(self.srcdir, self.preserve_srcdir, |
| 606 | self.version, self.setup, |
| 607 | *p_args, **p_dargs) |
| 608 | finally: |
| 609 | fcntl.flock(lockfile, fcntl.LOCK_UN) |
| 610 | lockfile.close() |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 611 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 612 | # Execute: |
jadmanski | 6265578 | 2008-07-28 21:27:46 +0000 | [diff] [blame] | 613 | os.chdir(self.outputdir) |
mbligh | 4395bbd | 2009-03-25 19:34:17 +0000 | [diff] [blame] | 614 | |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 615 | # call self.warmup cherry picking the arguments it accepts and |
| 616 | # translate exceptions if needed |
| 617 | _call_test_function(_cherry_pick_call, self.warmup, |
| 618 | *args, **dargs) |
| 619 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 620 | if hasattr(self, 'run_once'): |
| 621 | p_args, p_dargs = _cherry_pick_args(self.run_once, |
| 622 | args, dargs) |
jadmanski | 886c81f | 2009-02-19 12:54:03 +0000 | [diff] [blame] | 623 | # pull in any non-* and non-** args from self.execute |
| 624 | for param in _get_nonstar_args(self.execute): |
| 625 | if param in dargs: |
| 626 | p_dargs[param] = dargs[param] |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 627 | else: |
| 628 | p_args, p_dargs = _cherry_pick_args(self.execute, |
| 629 | args, dargs) |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 630 | |
| 631 | _call_test_function(self.execute, *p_args, **p_dargs) |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 632 | except Exception: |
| 633 | # Save the exception while we run our cleanup() before |
Prathmesh Prabhu | ac1a4c5 | 2014-08-20 17:26:54 -0700 | [diff] [blame] | 634 | # reraising it, but log it to so actual time of error is known. |
jadmanski | d625c7f | 2008-08-27 14:08:52 +0000 | [diff] [blame] | 635 | exc_info = sys.exc_info() |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 636 | logging.warning('The test failed with the following exception', |
Prathmesh Prabhu | ac1a4c5 | 2014-08-20 17:26:54 -0700 | [diff] [blame] | 637 | exc_info=True) |
| 638 | |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 639 | try: |
| 640 | try: |
| 641 | if run_cleanup: |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 642 | logging.debug('Running cleanup for test.') |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 643 | _cherry_pick_call(self.cleanup, *args, **dargs) |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 644 | except Exception: |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 645 | logging.error('Ignoring exception during cleanup() ' |
| 646 | 'phase:') |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 647 | traceback.print_exc() |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 648 | logging.error('Now raising the earlier %s error', |
| 649 | exc_info[0]) |
mbligh | 6894ce2 | 2009-09-18 19:56:30 +0000 | [diff] [blame] | 650 | self.crash_handler_report() |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 651 | finally: |
xixuan | 02b6fee | 2017-02-01 18:35:20 -0800 | [diff] [blame] | 652 | # Raise exception after running cleanup, reporting crash, |
| 653 | # and restoring job's logging, even if the first two |
| 654 | # actions fail. |
showard | 75cdfee | 2009-06-10 17:40:41 +0000 | [diff] [blame] | 655 | self.job.logging.restore() |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 656 | try: |
| 657 | raise exc_info[0], exc_info[1], exc_info[2] |
| 658 | finally: |
| 659 | # http://docs.python.org/library/sys.html#sys.exc_info |
| 660 | # Be nice and prevent a circular reference. |
| 661 | del exc_info |
jadmanski | d625c7f | 2008-08-27 14:08:52 +0000 | [diff] [blame] | 662 | else: |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 663 | try: |
| 664 | if run_cleanup: |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 665 | _cherry_pick_call(self.cleanup, *args, **dargs) |
mbligh | 6894ce2 | 2009-09-18 19:56:30 +0000 | [diff] [blame] | 666 | self.crash_handler_report() |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 667 | finally: |
showard | 75cdfee | 2009-06-10 17:40:41 +0000 | [diff] [blame] | 668 | self.job.logging.restore() |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 669 | except error.AutotestError: |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 670 | # Pass already-categorized errors on up. |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 671 | raise |
| 672 | except Exception, e: |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 673 | # Anything else is an ERROR in our own code, not execute(). |
mbligh | c218083 | 2008-07-25 03:26:12 +0000 | [diff] [blame] | 674 | raise error.UnhandledTestError(e) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 675 | |
Dale Curtis | 74a314b | 2011-06-23 14:55:46 -0700 | [diff] [blame] | 676 | def runsubtest(self, url, *args, **dargs): |
| 677 | """ |
| 678 | Execute another autotest test from inside the current test's scope. |
| 679 | |
| 680 | @param test: Parent test. |
| 681 | @param url: Url of new test. |
| 682 | @param tag: Tag added to test name. |
| 683 | @param args: Args for subtest. |
| 684 | @param dargs: Dictionary with args for subtest. |
| 685 | @iterations: Number of subtest iterations. |
| 686 | @profile_only: If true execute one profiled run. |
| 687 | """ |
| 688 | dargs["profile_only"] = dargs.get("profile_only", False) |
| 689 | test_basepath = self.outputdir[len(self.job.resultdir + "/"):] |
| 690 | return self.job.run_test(url, master_testpath=test_basepath, |
| 691 | *args, **dargs) |
| 692 | |
| 693 | |
jadmanski | 886c81f | 2009-02-19 12:54:03 +0000 | [diff] [blame] | 694 | def _get_nonstar_args(func): |
| 695 | """Extract all the (normal) function parameter names. |
| 696 | |
| 697 | Given a function, returns a tuple of parameter names, specifically |
| 698 | excluding the * and ** parameters, if the function accepts them. |
| 699 | |
| 700 | @param func: A callable that we want to chose arguments for. |
| 701 | |
| 702 | @return: A tuple of parameters accepted by the function. |
| 703 | """ |
| 704 | return func.func_code.co_varnames[:func.func_code.co_argcount] |
| 705 | |
| 706 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 707 | def _cherry_pick_args(func, args, dargs): |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 708 | """Sanitize positional and keyword arguments before calling a function. |
| 709 | |
| 710 | Given a callable (func), an argument tuple and a dictionary of keyword |
| 711 | arguments, pick only those arguments which the function is prepared to |
| 712 | accept and return a new argument tuple and keyword argument dictionary. |
| 713 | |
| 714 | Args: |
| 715 | func: A callable that we want to choose arguments for. |
| 716 | args: A tuple of positional arguments to consider passing to func. |
| 717 | dargs: A dictionary of keyword arguments to consider passing to func. |
| 718 | Returns: |
| 719 | A tuple of: (args tuple, keyword arguments dictionary) |
| 720 | """ |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 721 | # Cherry pick args: |
| 722 | if func.func_code.co_flags & 0x04: |
| 723 | # func accepts *args, so return the entire args. |
| 724 | p_args = args |
| 725 | else: |
| 726 | p_args = () |
| 727 | |
| 728 | # Cherry pick dargs: |
| 729 | if func.func_code.co_flags & 0x08: |
| 730 | # func accepts **dargs, so return the entire dargs. |
| 731 | p_dargs = dargs |
| 732 | else: |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 733 | # Only return the keyword arguments that func accepts. |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 734 | p_dargs = {} |
jadmanski | 886c81f | 2009-02-19 12:54:03 +0000 | [diff] [blame] | 735 | for param in _get_nonstar_args(func): |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 736 | if param in dargs: |
| 737 | p_dargs[param] = dargs[param] |
| 738 | |
| 739 | return p_args, p_dargs |
| 740 | |
| 741 | |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 742 | def _cherry_pick_call(func, *args, **dargs): |
| 743 | """Cherry picks arguments from args/dargs based on what "func" accepts |
| 744 | and calls the function with the picked arguments.""" |
| 745 | p_args, p_dargs = _cherry_pick_args(func, args, dargs) |
| 746 | return func(*p_args, **p_dargs) |
| 747 | |
| 748 | |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 749 | def _validate_args(args, dargs, *funcs): |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 750 | """Verify that arguments are appropriate for at least one callable. |
| 751 | |
| 752 | Given a list of callables as additional parameters, verify that |
| 753 | the proposed keyword arguments in dargs will each be accepted by at least |
| 754 | one of the callables. |
| 755 | |
| 756 | NOTE: args is currently not supported and must be empty. |
| 757 | |
| 758 | Args: |
| 759 | args: A tuple of proposed positional arguments. |
| 760 | dargs: A dictionary of proposed keyword arguments. |
| 761 | *funcs: Callables to be searched for acceptance of args and dargs. |
| 762 | Raises: |
| 763 | error.AutotestError: if an arg won't be accepted by any of *funcs. |
| 764 | """ |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 765 | all_co_flags = 0 |
| 766 | all_varnames = () |
| 767 | for func in funcs: |
| 768 | all_co_flags |= func.func_code.co_flags |
| 769 | all_varnames += func.func_code.co_varnames[:func.func_code.co_argcount] |
| 770 | |
| 771 | # Check if given args belongs to at least one of the methods below. |
| 772 | if len(args) > 0: |
| 773 | # Current implementation doesn't allow the use of args. |
mbligh | 234a84f | 2008-11-20 19:57:43 +0000 | [diff] [blame] | 774 | raise error.TestError('Unnamed arguments not accepted. Please ' |
| 775 | 'call job.run_test with named args only') |
mbligh | cf23819 | 2008-07-17 01:18:44 +0000 | [diff] [blame] | 776 | |
| 777 | # Check if given dargs belongs to at least one of the methods below. |
| 778 | if len(dargs) > 0: |
| 779 | if not all_co_flags & 0x08: |
| 780 | # no func accepts *dargs, so: |
| 781 | for param in dargs: |
| 782 | if not param in all_varnames: |
| 783 | raise error.AutotestError('Unknown parameter: %s' % param) |
| 784 | |
| 785 | |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 786 | def _installtest(job, url): |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 787 | (group, name) = job.pkgmgr.get_package_name(url, 'test') |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 788 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 789 | # Bail if the test is already installed |
| 790 | group_dir = os.path.join(job.testdir, "download", group) |
| 791 | if os.path.exists(os.path.join(group_dir, name)): |
| 792 | return (group, name) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 793 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 794 | # If the group directory is missing create it and add |
| 795 | # an empty __init__.py so that sub-directories are |
| 796 | # considered for import. |
| 797 | if not os.path.exists(group_dir): |
lmr | 2342172 | 2010-06-17 17:51:07 +0000 | [diff] [blame] | 798 | os.makedirs(group_dir) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 799 | f = file(os.path.join(group_dir, '__init__.py'), 'w+') |
| 800 | f.close() |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 801 | |
Dale Curtis | 456d3c1 | 2011-07-19 11:42:51 -0700 | [diff] [blame] | 802 | logging.debug("%s: installing test url=%s", name, url) |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 803 | tarball = os.path.basename(url) |
| 804 | tarball_path = os.path.join(group_dir, tarball) |
| 805 | test_dir = os.path.join(group_dir, name) |
| 806 | job.pkgmgr.fetch_pkg(tarball, tarball_path, |
| 807 | repo_url = os.path.dirname(url)) |
| 808 | |
| 809 | # Create the directory for the test |
| 810 | if not os.path.exists(test_dir): |
| 811 | os.mkdir(os.path.join(group_dir, name)) |
| 812 | |
| 813 | job.pkgmgr.untar_pkg(tarball_path, test_dir) |
| 814 | |
| 815 | os.remove(tarball_path) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 816 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 817 | # For this 'sub-object' to be importable via the name |
| 818 | # 'group.name' we need to provide an __init__.py, |
| 819 | # so link the main entry point to this. |
| 820 | os.symlink(name + '.py', os.path.join(group_dir, name, |
| 821 | '__init__.py')) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 822 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 823 | # The test is now installed. |
| 824 | return (group, name) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 825 | |
| 826 | |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 827 | def _call_test_function(func, *args, **dargs): |
| 828 | """Calls a test function and translates exceptions so that errors |
| 829 | inside test code are considered test failures.""" |
| 830 | try: |
| 831 | return func(*args, **dargs) |
| 832 | except error.AutotestError: |
mbligh | 5c1bb25 | 2009-03-25 22:06:49 +0000 | [diff] [blame] | 833 | raise |
| 834 | except Exception, e: |
| 835 | # Other exceptions must be treated as a FAIL when |
| 836 | # raised during the test functions |
| 837 | raise error.UnhandledTestFail(e) |
| 838 | |
| 839 | |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 840 | def runtest(job, url, tag, args, dargs, |
jadmanski | 30e9b59 | 2008-09-25 19:51:57 +0000 | [diff] [blame] | 841 | local_namespace={}, global_namespace={}, |
mbligh | 4395bbd | 2009-03-25 19:34:17 +0000 | [diff] [blame] | 842 | before_test_hook=None, after_test_hook=None, |
| 843 | before_iteration_hook=None, after_iteration_hook=None): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 844 | local_namespace = local_namespace.copy() |
| 845 | global_namespace = global_namespace.copy() |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 846 | # if this is not a plain test name then download and install the |
| 847 | # specified test |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 848 | if url.endswith('.tar.bz2'): |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 849 | (testgroup, testname) = _installtest(job, url) |
| 850 | bindir = os.path.join(job.testdir, 'download', testgroup, testname) |
lmr | 2342172 | 2010-06-17 17:51:07 +0000 | [diff] [blame] | 851 | importdir = os.path.join(job.testdir, 'download') |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 852 | modulename = '%s.%s' % (re.sub('/', '.', testgroup), testname) |
| 853 | classname = '%s.%s' % (modulename, testname) |
| 854 | path = testname |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 855 | else: |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 856 | # If the test is local, it may be under either testdir or site_testdir. |
| 857 | # Tests in site_testdir override tests defined in testdir |
| 858 | testname = path = url |
| 859 | testgroup = '' |
jadmanski | 0676704 | 2010-03-29 18:28:33 +0000 | [diff] [blame] | 860 | path = re.sub(':', '/', testname) |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 861 | modulename = os.path.basename(path) |
| 862 | classname = '%s.%s' % (modulename, modulename) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 863 | |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 864 | # Try installing the test package |
| 865 | # The job object may be either a server side job or a client side job. |
| 866 | # 'install_pkg' method will be present only if it's a client side job. |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 867 | if hasattr(job, 'install_pkg'): |
| 868 | try: |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 869 | bindir = os.path.join(job.testdir, testname) |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 870 | job.install_pkg(testname, 'test', bindir) |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 871 | except error.PackageInstallError: |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 872 | # continue as a fall back mechanism and see if the test code |
| 873 | # already exists on the machine |
| 874 | pass |
| 875 | |
Ilja H. Friedel | 21bf562 | 2014-09-08 18:59:04 -0700 | [diff] [blame] | 876 | bindir = None |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 877 | for dir in [job.testdir, getattr(job, 'site_testdir', None)]: |
| 878 | if dir is not None and os.path.exists(os.path.join(dir, path)): |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 879 | importdir = bindir = os.path.join(dir, path) |
| 880 | if not bindir: |
| 881 | raise error.TestError(testname + ': test does not exist') |
| 882 | |
Dale Curtis | 74a314b | 2011-06-23 14:55:46 -0700 | [diff] [blame] | 883 | subdir = os.path.join(dargs.pop('master_testpath', ""), testname) |
| 884 | outputdir = os.path.join(job.resultdir, subdir) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 885 | if tag: |
| 886 | outputdir += '.' + tag |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 887 | |
mbligh | c5ddfd1 | 2008-08-04 17:15:00 +0000 | [diff] [blame] | 888 | local_namespace['job'] = job |
| 889 | local_namespace['bindir'] = bindir |
| 890 | local_namespace['outputdir'] = outputdir |
| 891 | |
lmr | 2342172 | 2010-06-17 17:51:07 +0000 | [diff] [blame] | 892 | sys.path.insert(0, importdir) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 893 | try: |
mbligh | 620ccf0 | 2010-03-26 17:44:29 +0000 | [diff] [blame] | 894 | exec ('import %s' % modulename, local_namespace, global_namespace) |
| 895 | exec ("mytest = %s(job, bindir, outputdir)" % classname, |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 896 | local_namespace, global_namespace) |
| 897 | finally: |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 898 | sys.path.pop(0) |
mbligh | 6231cd6 | 2008-02-02 19:18:33 +0000 | [diff] [blame] | 899 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 900 | pwd = os.getcwd() |
| 901 | os.chdir(outputdir) |
mbligh | 4395bbd | 2009-03-25 19:34:17 +0000 | [diff] [blame] | 902 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 903 | try: |
| 904 | mytest = global_namespace['mytest'] |
Dan Shi | 2ca9777 | 2013-10-21 17:17:27 -0700 | [diff] [blame] | 905 | mytest.success = False |
Xixuan Wu | 5019d69 | 2017-11-10 15:58:33 -0800 | [diff] [blame] | 906 | if not job.fast and before_test_hook: |
Xixuan Wu | e8ce654 | 2017-11-07 17:22:10 -0800 | [diff] [blame] | 907 | logging.info('Starting before_hook for %s', mytest.tagged_testname) |
| 908 | with metrics.SecondsTimer( |
| 909 | 'chromeos/autotest/job/before_hook_duration'): |
| 910 | before_test_hook(mytest) |
| 911 | logging.info('before_hook completed') |
mbligh | 742ae42 | 2009-05-13 20:46:41 +0000 | [diff] [blame] | 912 | |
| 913 | # we use the register iteration hooks methods to register the passed |
| 914 | # in hooks |
| 915 | if before_iteration_hook: |
| 916 | mytest.register_before_iteration_hook(before_iteration_hook) |
| 917 | if after_iteration_hook: |
| 918 | mytest.register_after_iteration_hook(after_iteration_hook) |
| 919 | mytest._exec(args, dargs) |
Dan Shi | 2ca9777 | 2013-10-21 17:17:27 -0700 | [diff] [blame] | 920 | mytest.success = True |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 921 | finally: |
jadmanski | 213b02b | 2008-08-26 20:51:58 +0000 | [diff] [blame] | 922 | os.chdir(pwd) |
Xixuan Wu | 5019d69 | 2017-11-10 15:58:33 -0800 | [diff] [blame] | 923 | if after_test_hook and (not mytest.success or not job.fast): |
Xixuan Wu | e8ce654 | 2017-11-07 17:22:10 -0800 | [diff] [blame] | 924 | logging.info('Starting after_hook for %s', mytest.tagged_testname) |
| 925 | with metrics.SecondsTimer( |
| 926 | 'chromeos/autotest/job/after_hook_duration'): |
| 927 | after_test_hook(mytest) |
| 928 | logging.info('after_hook completed') |
Keith Haddow | 1e5c701 | 2016-03-09 16:05:37 -0800 | [diff] [blame] | 929 | |
Xixuan Wu | 5019d69 | 2017-11-10 15:58:33 -0800 | [diff] [blame] | 930 | shutil.rmtree(mytest.tmpdir, ignore_errors=True) |