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