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