1 # Copyright Martin J. Bligh, Andy Whitcroft, 2007 2 # 3 # Define the server-side test class 4 # 5 # pylint: disable=missing-docstring 6 7 import logging 8 import os 9 import tempfile 10 11 from autotest_lib.client.common_lib import log 12 from autotest_lib.client.common_lib import test as common_test 13 from autotest_lib.client.common_lib import utils 14 15 16 class test(common_test.base_test): 17 disable_sysinfo_install_cache = False 18 host_parameter = None 19 20 21 _sysinfo_before_test_script = """\ 22 import pickle 23 from autotest_lib.client.bin import test 24 mytest = test.test(job, '', %r) 25 job.sysinfo.log_before_each_test(mytest) 26 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 27 pickle.dump(job.sysinfo, open(sysinfo_pickle, 'w')) 28 job.record('GOOD', '', 'sysinfo.before') 29 """ 30 31 _sysinfo_after_test_script = """\ 32 import pickle 33 from autotest_lib.client.bin import test 34 mytest = test.test(job, '', %r) 35 # success is passed in so diffable_logdir can decide if or not to collect 36 # full log content. 37 mytest.success = %s 38 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 39 if os.path.exists(sysinfo_pickle): 40 job.sysinfo = pickle.load(open(sysinfo_pickle)) 41 job.sysinfo.__init__(job.resultdir) 42 job.sysinfo.log_after_each_test(mytest) 43 job.record('GOOD', '', 'sysinfo.after') 44 """ 45 46 # this script is ran after _sysinfo_before_test_script and before 47 # _sysinfo_after_test_script which means the pickle file exists 48 # already and should be dumped with updated state for 49 # _sysinfo_after_test_script to pick it up later 50 _sysinfo_iteration_script = """\ 51 import pickle 52 from autotest_lib.client.bin import test 53 mytest = test.test(job, '', %r) 54 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 55 if os.path.exists(sysinfo_pickle): 56 job.sysinfo = pickle.load(open(sysinfo_pickle)) 57 job.sysinfo.__init__(job.resultdir) 58 job.sysinfo.%s(mytest, iteration=%d) 59 pickle.dump(job.sysinfo, open(sysinfo_pickle, 'w')) 60 job.record('GOOD', '', 'sysinfo.iteration.%s') 61 """ 62 63 64 def install_autotest_and_run(func): 65 def wrapper(self, mytest): 66 host, at, outputdir = self._install() 67 # TODO(kevcheng): remove when host client install is supported for 68 # ADBHost. crbug.com/543702 69 if not host.is_client_install_supported: 70 logging.debug('host client install not supported, skipping %s:', 71 func.__name__) 72 return 73 74 try: 75 host.erase_dir_contents(outputdir) 76 func(self, mytest, host, at, outputdir) 77 finally: 78 # the test class can define this flag to make us remove the 79 # sysinfo install files and outputdir contents after each run 80 if mytest.disable_sysinfo_install_cache: 81 self.cleanup(host_close=False) 82 83 return wrapper 84 85 86 class _sysinfo_logger(object): 87 def __init__(self, job): 88 self.job = job 89 self.pickle = None 90 91 # for now support a single host 92 self.host = None 93 self.autotest = None 94 self.outputdir = None 95 96 if len(job.machines) != 1: 97 # disable logging on multi-machine tests 98 self.before_hook = self.after_hook = None 99 self.before_iteration_hook = self.after_iteration_hook = None 100 101 102 def _install(self): 103 if not self.host: 104 from autotest_lib.server import hosts, autotest 105 self.host = hosts.create_target_machine( 106 self.job.machine_dict_list[0]) 107 # TODO(kevcheng): remove when host client install is supported for 108 # ADBHost. crbug.com/543702 109 if not self.host.is_client_install_supported: 110 return self.host, None, None 111 try: 112 tmp_dir = self.host.get_tmp_dir(parent="/tmp/sysinfo") 113 self.autotest = autotest.Autotest(self.host) 114 self.autotest.install(autodir=tmp_dir) 115 self.outputdir = self.host.get_tmp_dir() 116 except: 117 # if installation fails roll back the host 118 try: 119 self.host.close() 120 except: 121 logging.exception("Unable to close host %s", 122 self.host.hostname) 123 self.host = None 124 self.autotest = None 125 raise 126 else: 127 # TODO(kevcheng): remove when host client install is supported for 128 # ADBHost. crbug.com/543702 129 if not self.host.is_client_install_supported: 130 return self.host, None, None 131 132 # if autotest client dir does not exist, reinstall (it may have 133 # been removed by the test code) 134 autodir = self.host.get_autodir() 135 if not autodir or not self.host.path_exists(autodir): 136 self.autotest.install(autodir=autodir) 137 138 # if the output dir does not exist, recreate it 139 if not self.host.path_exists(self.outputdir): 140 self.host.run('mkdir -p %s' % self.outputdir) 141 142 return self.host, self.autotest, self.outputdir 143 144 145 def _pull_pickle(self, host, outputdir): 146 """Pulls from the client the pickle file with the saved sysinfo state. 147 """ 148 fd, path = tempfile.mkstemp(dir=self.job.tmpdir) 149 os.close(fd) 150 host.get_file(os.path.join(outputdir, "sysinfo.pickle"), path) 151 self.pickle = path 152 153 154 def _push_pickle(self, host, outputdir): 155 """Pushes the server saved sysinfo pickle file to the client. 156 """ 157 if self.pickle: 158 host.send_file(self.pickle, 159 os.path.join(outputdir, "sysinfo.pickle")) 160 os.remove(self.pickle) 161 self.pickle = None 162 163 164 def _pull_sysinfo_keyval(self, host, outputdir, mytest): 165 """Pulls sysinfo and keyval data from the client. 166 """ 167 # pull the sysinfo data back on to the server 168 host.get_file(os.path.join(outputdir, "sysinfo"), mytest.outputdir) 169 170 # pull the keyval data back into the local one 171 fd, path = tempfile.mkstemp(dir=self.job.tmpdir) 172 os.close(fd) 173 host.get_file(os.path.join(outputdir, "keyval"), path) 174 keyval = utils.read_keyval(path) 175 os.remove(path) 176 mytest.write_test_keyval(keyval) 177 178 179 @log.log_and_ignore_errors("pre-test server sysinfo error:") 180 @install_autotest_and_run 181 def before_hook(self, mytest, host, at, outputdir): 182 # run the pre-test sysinfo script 183 at.run(_sysinfo_before_test_script % outputdir, 184 results_dir=self.job.resultdir) 185 186 self._pull_pickle(host, outputdir) 187 188 189 @log.log_and_ignore_errors("pre-test iteration server sysinfo error:") 190 @install_autotest_and_run 191 def before_iteration_hook(self, mytest, host, at, outputdir): 192 # this function is called after before_hook() se we have sysinfo state 193 # to push to the server 194 self._push_pickle(host, outputdir); 195 # run the pre-test iteration sysinfo script 196 at.run(_sysinfo_iteration_script % 197 (outputdir, 'log_before_each_iteration', mytest.iteration, 198 'before'), 199 results_dir=self.job.resultdir) 200 201 # get the new sysinfo state from the client 202 self._pull_pickle(host, outputdir) 203 204 205 @log.log_and_ignore_errors("post-test iteration server sysinfo error:") 206 @install_autotest_and_run 207 def after_iteration_hook(self, mytest, host, at, outputdir): 208 # push latest sysinfo state to the client 209 self._push_pickle(host, outputdir); 210 # run the post-test iteration sysinfo script 211 at.run(_sysinfo_iteration_script % 212 (outputdir, 'log_after_each_iteration', mytest.iteration, 213 'after'), 214 results_dir=self.job.resultdir) 215 216 # get the new sysinfo state from the client 217 self._pull_pickle(host, outputdir) 218 219 220 @log.log_and_ignore_errors("post-test server sysinfo error:") 221 @install_autotest_and_run 222 def after_hook(self, mytest, host, at, outputdir): 223 self._push_pickle(host, outputdir); 224 # run the post-test sysinfo script 225 at.run(_sysinfo_after_test_script % (outputdir, mytest.success), 226 results_dir=self.job.resultdir) 227 228 self._pull_sysinfo_keyval(host, outputdir, mytest) 229 230 231 def cleanup(self, host_close=True): 232 if self.host and self.autotest: 233 try: 234 try: 235 self.autotest.uninstall() 236 finally: 237 if host_close: 238 self.host.close() 239 else: 240 self.host.erase_dir_contents(self.outputdir) 241 242 except Exception: 243 # ignoring exceptions here so that we don't hide the true 244 # reason of failure from runtest 245 logging.exception('Error cleaning up the sysinfo autotest/host ' 246 'objects, ignoring it') 247 248 249 def runtest(job, url, tag, args, dargs): 250 """Server-side runtest. 251 252 @param job: A server_job instance. 253 @param url: URL to the test. 254 @param tag: Test tag that will be appended to the test name. 255 See client/common_lib/test.py:runtest 256 @param args: args to pass to the test. 257 @param dargs: key-val based args to pass to the test. 258 """ 259 260 disable_before_test_hook = dargs.pop('disable_before_test_sysinfo', False) 261 disable_after_test_hook = dargs.pop('disable_after_test_sysinfo', False) 262 disable_before_iteration_hook = dargs.pop( 263 'disable_before_iteration_sysinfo', False) 264 disable_after_iteration_hook = dargs.pop( 265 'disable_after_iteration_sysinfo', False) 266 267 disable_sysinfo = dargs.pop('disable_sysinfo', False) 268 if job.fast and not disable_sysinfo: 269 # Server job will be executed in fast mode, which means 270 # 1) if job succeeds, no hook will be executed. 271 # 2) if job failed, after_hook will be executed. 272 logger = _sysinfo_logger(job) 273 logging_args = [None, logger.after_hook, None, 274 logger.after_iteration_hook] 275 elif not disable_sysinfo: 276 logger = _sysinfo_logger(job) 277 logging_args = [ 278 logger.before_hook if not disable_before_test_hook else None, 279 logger.after_hook if not disable_after_test_hook else None, 280 (logger.before_iteration_hook 281 if not disable_before_iteration_hook else None), 282 (logger.after_iteration_hook 283 if not disable_after_iteration_hook else None), 284 ] 285 else: 286 logger = None 287 logging_args = [None, None, None, None] 288 289 # add in a hook that calls host.log_kernel if we can 290 def log_kernel_hook(mytest, existing_hook=logging_args[0]): 291 if mytest.host_parameter: 292 host = dargs[mytest.host_parameter] 293 if host: 294 host.log_kernel() 295 # chain this call with any existing hook 296 if existing_hook: 297 existing_hook(mytest) 298 logging_args[0] = log_kernel_hook 299 300 try: 301 common_test.runtest(job, url, tag, args, dargs, locals(), globals(), 302 *logging_args) 303 finally: 304 if logger: 305 logger.cleanup() 306