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