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