job_unittest.py revision be583c18a66815a3d93fae411c5fd58788aff4c0
1#!/usr/bin/python
2
3import logging, os, shutil, sys, time, StringIO
4import common
5
6from autotest_lib.client.bin import job, boottool, config, sysinfo, harness
7from autotest_lib.client.bin import test, xen, kernel, utils
8from autotest_lib.client.common_lib import packages, error, log
9from autotest_lib.client.common_lib import logging_manager, logging_config
10from autotest_lib.client.common_lib import base_job_unittest
11from autotest_lib.client.common_lib.test_utils import mock, unittest
12
13
14class job_test_case(unittest.TestCase):
15    """Generic job TestCase class that defines a standard job setUp and
16    tearDown, with some standard stubs."""
17
18    job_class = job.base_client_job
19
20    def setUp(self):
21        self.god = mock.mock_god()
22        self.god.stub_with(job.base_client_job, '_get_environ_autodir',
23                           classmethod(lambda cls: '/adir'))
24        self.job = self.job_class.__new__(self.job_class)
25        self.job._job_directory = base_job_unittest.stub_job_directory
26
27
28    def tearDown(self):
29        self.god.unstub_all()
30
31
32class test_find_base_directories(
33        base_job_unittest.test_find_base_directories.generic_tests,
34        job_test_case):
35
36    def test_autodir_equals_clientdir(self):
37        autodir, clientdir, _ = self.job._find_base_directories()
38        self.assertEqual(autodir, '/adir')
39        self.assertEqual(clientdir, '/adir')
40
41
42    def test_serverdir_is_none(self):
43        _, _, serverdir = self.job._find_base_directories()
44        self.assertEqual(serverdir, None)
45
46
47class abstract_test_init(base_job_unittest.test_init.generic_tests):
48    """Generic client job mixin used when defining variations on the
49    job.__init__ generic tests."""
50    OPTIONAL_ATTRIBUTES = (
51        base_job_unittest.test_init.generic_tests.OPTIONAL_ATTRIBUTES
52        - set(['control', 'bootloader', 'harness']))
53
54
55class test_init_minimal_options(abstract_test_init, job_test_case):
56    def call_init(self):
57        # TODO(jadmanski): refactor more of the __init__ code to not need to
58        # stub out countless random APIs
59        self.god.stub_function_to_return(job.os, 'mkdir', None)
60        self.god.stub_function_to_return(job.os.path, 'exists', True)
61        self.god.stub_function_to_return(self.job, '_load_state', None)
62        self.god.stub_function_to_return(self.job, 'record', None)
63        self.god.stub_function_to_return(job.shutil, 'copyfile', None)
64        self.god.stub_function_to_return(job.logging_manager,
65                                         'configure_logging', None)
66        class manager:
67            def start_logging(self):
68                return None
69        self.god.stub_function_to_return(job.logging_manager,
70                                         'get_logging_manager', manager())
71        class stub_sysinfo:
72            def log_per_reboot_data(self):
73                return None
74        self.god.stub_function_to_return(job.sysinfo, 'sysinfo',
75                                         stub_sysinfo())
76        class stub_harness:
77            run_start = lambda self: None
78        self.god.stub_function_to_return(job.harness, 'select', stub_harness())
79        self.god.stub_function_to_return(job.boottool, 'boottool', object())
80        class options:
81            tag = ''
82            verbose = False
83            cont = False
84            harness = 'stub'
85            hostname = None
86            user = None
87            log = False
88            args = ''
89        self.god.stub_function_to_return(job.utils, 'drop_caches', None)
90
91        self.job._job_state = base_job_unittest.stub_job_state
92        self.job.__init__('/control', options)
93
94
95class dummy(object):
96    """A simple placeholder for attributes"""
97    pass
98
99
100class first_line_comparator(mock.argument_comparator):
101    def __init__(self, first_line):
102        self.first_line = first_line
103
104
105    def is_satisfied_by(self, parameter):
106        return self.first_line == parameter.splitlines()[0]
107
108
109class test_base_job(unittest.TestCase):
110    def setUp(self):
111        # make god
112        self.god = mock.mock_god()
113
114        # need to set some environ variables
115        self.autodir = "autodir"
116        os.environ['AUTODIR'] = self.autodir
117
118        # set up some variables
119        self.control = "control"
120        self.jobtag = "jobtag"
121
122        # get rid of stdout and logging
123        sys.stdout = StringIO.StringIO()
124        logging_manager.configure_logging(logging_config.TestingConfig())
125        logging.disable(logging.CRITICAL)
126        def dummy_configure_logging(*args, **kwargs):
127            pass
128        self.god.stub_with(logging_manager, 'configure_logging',
129                           dummy_configure_logging)
130        real_get_logging_manager = logging_manager.get_logging_manager
131        def get_logging_manager_no_fds(manage_stdout_and_stderr=False,
132                                       redirect_fds=False):
133            return real_get_logging_manager(manage_stdout_and_stderr, False)
134        self.god.stub_with(logging_manager, 'get_logging_manager',
135                           get_logging_manager_no_fds)
136
137        # stub out some stuff
138        self.god.stub_function(os.path, 'exists')
139        self.god.stub_function(os.path, 'isdir')
140        self.god.stub_function(os, 'makedirs')
141        self.god.stub_function(os, 'mkdir')
142        self.god.stub_function(os, 'remove')
143        self.god.stub_function(shutil, 'rmtree')
144        self.god.stub_function(shutil, 'copyfile')
145        self.god.stub_function(job, 'open')
146        self.god.stub_function(utils, 'system')
147        self.god.stub_function(utils, 'drop_caches')
148        self.god.stub_function(harness, 'select')
149        self.god.stub_function(sysinfo, 'log_per_reboot_data')
150
151        self.god.stub_class(config, 'config')
152        self.god.stub_class(job.local_host, 'LocalHost')
153        self.god.stub_class(boottool, 'boottool')
154        self.god.stub_class(sysinfo, 'sysinfo')
155
156        self.god.stub_class_method(job.base_client_job,
157                                   '_cleanup_debugdir_files')
158        self.god.stub_class_method(job.base_client_job, '_cleanup_results_dir')
159
160        self.god.stub_with(job.base_job.job_directory, '_ensure_valid',
161                           lambda *_: None)
162
163
164    def tearDown(self):
165        sys.stdout = sys.__stdout__
166        self.god.unstub_all()
167
168
169    def _setup_pre_record_init(self, cont):
170        self.god.stub_function(self.job, '_load_state')
171
172        resultdir = os.path.join(self.autodir, 'results', self.jobtag)
173        tmpdir = os.path.join(self.autodir, 'tmp')
174        if not cont:
175            job.base_client_job._cleanup_debugdir_files.expect_call()
176            job.base_client_job._cleanup_results_dir.expect_call()
177
178        self.job._load_state.expect_call()
179
180        my_harness = self.god.create_mock_class(harness.harness,
181                                                'my_harness')
182        harness.select.expect_call(None,
183                                   self.job).and_return(my_harness)
184
185        return resultdir, my_harness
186
187
188    def _setup_post_record_init(self, cont, resultdir, my_harness):
189        # now some specific stubs
190        self.god.stub_function(self.job, 'config_get')
191        self.god.stub_function(self.job, 'config_set')
192        self.god.stub_function(self.job, 'record')
193
194        # other setup
195        results = os.path.join(self.autodir, 'results')
196        download = os.path.join(self.autodir, 'tests', 'download')
197        pkgdir = os.path.join(self.autodir, 'packages')
198
199        utils.drop_caches.expect_call()
200        job_sysinfo = sysinfo.sysinfo.expect_new(resultdir)
201        if not cont:
202            os.path.exists.expect_call(download).and_return(False)
203            os.mkdir.expect_call(download)
204            shutil.copyfile.expect_call(mock.is_string_comparator(),
205                                 os.path.join(resultdir, 'control'))
206
207        self.config = config.config.expect_new(self.job)
208        self.job.config_get.expect_call(
209                'boottool.executable').and_return(None)
210        bootloader = boottool.boottool.expect_new(None)
211        job.local_host.LocalHost.expect_new(hostname='localhost',
212                                            bootloader=bootloader)
213        job_sysinfo.log_per_reboot_data.expect_call()
214        if not cont:
215            self.job.record.expect_call('START', None, None)
216
217        my_harness.run_start.expect_call()
218
219        self.god.stub_function(utils, 'read_one_line')
220        utils.read_one_line.expect_call('/proc/cmdline').and_return(
221            'blah more-blah root=lala IDENT=81234567 blah-again console=tty1')
222        self.job.config_set.expect_call('boot.default_args',
223                                        'more-blah console=tty1')
224
225
226    def construct_job(self, cont):
227        # will construct class instance using __new__
228        self.job = job.base_client_job.__new__(job.base_client_job)
229
230        # record
231        resultdir, my_harness = self._setup_pre_record_init(cont)
232        self._setup_post_record_init(cont, resultdir, my_harness)
233
234        # finish constructor
235        options = dummy()
236        options.tag = self.jobtag
237        options.cont = cont
238        options.harness = None
239        options.log = False
240        options.verbose = False
241        options.hostname = 'localhost'
242        options.user = 'my_user'
243        options.args = ''
244        self.job.__init__(self.control, options,
245                          extra_copy_cmdline=['more-blah'])
246
247        # check
248        self.god.check_playback()
249
250
251    def get_partition_mock(self, devname):
252        """
253        Create a mock of a partition object and return it.
254        """
255        class mock(object):
256            device = devname
257            get_mountpoint = self.god.create_mock_function('get_mountpoint')
258        return mock
259
260
261    def test_constructor_first_run(self):
262        self.construct_job(False)
263
264
265    def test_constructor_continuation(self):
266        self.construct_job(True)
267
268
269    def test_constructor_post_record_failure(self):
270        """
271        Test post record initialization failure.
272        """
273        self.job = job.base_client_job.__new__(job.base_client_job)
274        options = dummy()
275        options.tag = self.jobtag
276        options.cont = False
277        options.harness = None
278        options.log = False
279        options.verbose = False
280        options.hostname = 'localhost'
281        options.user = 'my_user'
282        options.args = ''
283        error = Exception('fail')
284
285        self.god.stub_function(self.job, '_post_record_init')
286        self.god.stub_function(self.job, 'record')
287
288        self._setup_pre_record_init(False)
289        self.job._post_record_init.expect_call(
290                self.control, options, True, ['more-blah']).and_raises(error)
291        self.job.record.expect_call(
292                'ABORT', None, None,'client.bin.job.__init__ failed: %s' %
293                str(error))
294
295        self.assertRaises(
296                Exception, self.job.__init__, self.control, options,
297                drop_caches=True, extra_copy_cmdline=['more-blah'])
298
299        # check
300        self.god.check_playback()
301
302
303    def test_relative_path(self):
304        self.construct_job(True)
305        dummy = "asdf"
306        ret = self.job.relative_path(os.path.join(self.job.resultdir, dummy))
307        self.assertEquals(ret, dummy)
308
309
310    def test_control_functions(self):
311        self.construct_job(True)
312        control_file = "blah"
313        self.job.control_set(control_file)
314        self.assertEquals(self.job.control_get(), os.path.abspath(control_file))
315
316
317    def test_harness_select(self):
318        self.construct_job(True)
319
320        # record
321        which = "which"
322        harness.select.expect_call(which, self.job).and_return(None)
323
324        # run and test
325        self.job.harness_select(which)
326        self.god.check_playback()
327
328
329    def test_config_set(self):
330        self.construct_job(True)
331
332        # unstub config_set
333        self.god.unstub(self.job, 'config_set')
334        # record
335        name = "foo"
336        val = 10
337        self.config.set.expect_call(name, val)
338
339        # run and test
340        self.job.config_set(name, val)
341        self.god.check_playback()
342
343
344    def test_config_get(self):
345        self.construct_job(True)
346
347        # unstub config_get
348        self.god.unstub(self.job, 'config_get')
349        # record
350        name = "foo"
351        val = 10
352        self.config.get.expect_call(name).and_return(val)
353
354        # run and test
355        self.job.config_get(name)
356        self.god.check_playback()
357
358
359    def test_setup_dirs_raise(self):
360        self.construct_job(True)
361
362        # setup
363        results_dir = 'foo'
364        tmp_dir = 'bar'
365
366        # record
367        os.path.exists.expect_call(tmp_dir).and_return(True)
368        os.path.isdir.expect_call(tmp_dir).and_return(False)
369
370        # test
371        self.assertRaises(ValueError, self.job.setup_dirs, results_dir, tmp_dir)
372        self.god.check_playback()
373
374
375    def test_setup_dirs(self):
376        self.construct_job(True)
377
378        # setup
379        results_dir1 = os.path.join(self.job.resultdir, 'build')
380        results_dir2 = os.path.join(self.job.resultdir, 'build.2')
381        results_dir3 = os.path.join(self.job.resultdir, 'build.3')
382        tmp_dir = 'bar'
383
384        # record
385        os.path.exists.expect_call(tmp_dir).and_return(False)
386        os.mkdir.expect_call(tmp_dir)
387        os.path.isdir.expect_call(tmp_dir).and_return(True)
388        os.path.exists.expect_call(results_dir1).and_return(True)
389        os.path.exists.expect_call(results_dir2).and_return(True)
390        os.path.exists.expect_call(results_dir3).and_return(False)
391        os.path.exists.expect_call(results_dir3).and_return(False)
392        os.mkdir.expect_call(results_dir3)
393
394        # test
395        self.assertEqual(self.job.setup_dirs(None, tmp_dir),
396                         (results_dir3, tmp_dir))
397        self.god.check_playback()
398
399
400    def test_xen(self):
401        self.construct_job(True)
402
403        # setup
404        self.god.stub_function(self.job, "setup_dirs")
405        self.god.stub_class(xen, "xen")
406        results = 'results_dir'
407        tmp = 'tmp'
408        build = 'xen'
409        base_tree = object()
410
411        # record
412        self.job.setup_dirs.expect_call(results,
413                                        tmp).and_return((results, tmp))
414        myxen = xen.xen.expect_new(self.job, base_tree, results, tmp, build,
415                                   False, None)
416
417        # run job and check
418        axen = self.job.xen(base_tree, results, tmp)
419        self.god.check_playback()
420        self.assertEquals(myxen, axen)
421
422
423    def test_kernel_rpm(self):
424        self.construct_job(True)
425
426        # setup
427        self.god.stub_function(self.job, "setup_dirs")
428        self.god.stub_class(kernel, "rpm_kernel")
429        self.god.stub_function(kernel, "preprocess_path")
430        self.god.stub_function(self.job.pkgmgr, "fetch_pkg")
431        self.god.stub_function(utils, "get_os_vendor")
432        results = 'results_dir'
433        tmp = 'tmp'
434        build = 'xen'
435        path = "somepath.rpm"
436        packages_dir = os.path.join("autodir/packages", path)
437
438        # record
439        self.job.setup_dirs.expect_call(results,
440                                        tmp).and_return((results, tmp))
441        kernel.preprocess_path.expect_call(path).and_return(path)
442        os.path.exists.expect_call(path).and_return(False)
443        self.job.pkgmgr.fetch_pkg.expect_call(path, packages_dir, repo_url='')
444        utils.get_os_vendor.expect_call()
445        mykernel = kernel.rpm_kernel.expect_new(self.job, [packages_dir],
446                                                results)
447
448        # check
449        akernel = self.job.kernel(path, results, tmp)
450        self.god.check_playback()
451        self.assertEquals(mykernel, akernel)
452
453
454    def test_kernel(self):
455        self.construct_job(True)
456
457        # setup
458        self.god.stub_function(self.job, "setup_dirs")
459        self.god.stub_class(kernel, "kernel")
460        self.god.stub_function(kernel, "preprocess_path")
461        results = 'results_dir'
462        tmp = 'tmp'
463        build = 'linux'
464        path = "somepath.deb"
465
466        # record
467        self.job.setup_dirs.expect_call(results,
468                                        tmp).and_return((results, tmp))
469        kernel.preprocess_path.expect_call(path).and_return(path)
470        mykernel = kernel.kernel.expect_new(self.job, path, results, tmp,
471                                            build, False)
472
473        # check
474        akernel = self.job.kernel(path, results, tmp)
475        self.god.check_playback()
476        self.assertEquals(mykernel, akernel)
477
478
479    def test_run_test_logs_test_error_from_unhandled_error(self):
480        self.construct_job(True)
481
482        # set up stubs
483        self.god.stub_function(self.job.pkgmgr, 'get_package_name')
484        self.god.stub_function(self.job, "_runtest")
485
486        # create an unhandled error object
487        class MyError(error.TestError):
488            pass
489        real_error = MyError("this is the real error message")
490        unhandled_error = error.UnhandledTestError(real_error)
491
492        # set up the recording
493        testname = "error_test"
494        outputdir = os.path.join(self.job.resultdir, testname)
495        self.job.pkgmgr.get_package_name.expect_call(
496            testname, 'test').and_return(("", testname))
497        os.path.exists.expect_call(outputdir).and_return(False)
498        self.job.record.expect_call("START", testname, testname)
499        self.job._runtest.expect_call(testname, "", (), {}).and_raises(
500            unhandled_error)
501        self.job.record.expect_call("ERROR", testname, testname,
502                                    first_line_comparator(str(real_error)))
503        self.job.record.expect_call("END ERROR", testname, testname)
504        self.job.harness.run_test_complete.expect_call()
505        utils.drop_caches.expect_call()
506
507        # run and check
508        self.job.run_test(testname)
509        self.god.check_playback()
510
511
512    def test_run_test_logs_non_test_error_from_unhandled_error(self):
513        self.construct_job(True)
514
515        # set up stubs
516        self.god.stub_function(self.job.pkgmgr, 'get_package_name')
517        self.god.stub_function(self.job, "_runtest")
518
519        # create an unhandled error object
520        class MyError(Exception):
521            pass
522        real_error = MyError("this is the real error message")
523        unhandled_error = error.UnhandledTestError(real_error)
524        reason = first_line_comparator("Unhandled MyError: %s" % real_error)
525
526        # set up the recording
527        testname = "error_test"
528        outputdir = os.path.join(self.job.resultdir, testname)
529        self.job.pkgmgr.get_package_name.expect_call(
530            testname, 'test').and_return(("", testname))
531        os.path.exists.expect_call(outputdir).and_return(False)
532        self.job.record.expect_call("START", testname, testname)
533        self.job._runtest.expect_call(testname, "", (), {}).and_raises(
534            unhandled_error)
535        self.job.record.expect_call("ERROR", testname, testname, reason)
536        self.job.record.expect_call("END ERROR", testname, testname)
537        self.job.harness.run_test_complete.expect_call()
538        utils.drop_caches.expect_call()
539
540        # run and check
541        self.job.run_test(testname)
542        self.god.check_playback()
543
544
545    def test_report_reboot_failure(self):
546        self.construct_job(True)
547
548        # record
549        self.job.record.expect_call("ABORT", "sub", "reboot.verify",
550                                    "boot failure")
551        self.job.record.expect_call("END ABORT", "sub", "reboot",
552                                    optional_fields={"kernel": "2.6.15-smp"})
553
554        # playback
555        self.job._record_reboot_failure("sub", "reboot.verify", "boot failure",
556                                        running_id="2.6.15-smp")
557        self.god.check_playback()
558
559
560    def _setup_check_post_reboot(self, mount_info, cpu_count):
561        # setup
562        self.god.stub_function(job.partition_lib, "get_partition_list")
563        self.god.stub_function(utils, "count_cpus")
564
565        part_list = [self.get_partition_mock("/dev/hda1"),
566                     self.get_partition_mock("/dev/hdb1")]
567        mount_list = ["/mnt/hda1", "/mnt/hdb1"]
568
569        # record
570        job.partition_lib.get_partition_list.expect_call(
571                self.job, exclude_swap=False).and_return(part_list)
572        for i in xrange(len(part_list)):
573            part_list[i].get_mountpoint.expect_call().and_return(mount_list[i])
574        if cpu_count is not None:
575            utils.count_cpus.expect_call().and_return(cpu_count)
576        self.job._state.set('client', 'mount_info', mount_info)
577        self.job._state.set('client', 'cpu_count', 8)
578
579
580    def test_check_post_reboot_success(self):
581        self.construct_job(True)
582
583        mount_info = set([("/dev/hda1", "/mnt/hda1"),
584                          ("/dev/hdb1", "/mnt/hdb1")])
585        self._setup_check_post_reboot(mount_info, 8)
586
587        # playback
588        self.job._check_post_reboot("sub")
589        self.god.check_playback()
590
591
592    def test_check_post_reboot_mounts_failure(self):
593        self.construct_job(True)
594
595        mount_info = set([("/dev/hda1", "/mnt/hda1")])
596        self._setup_check_post_reboot(mount_info, None)
597
598        self.god.stub_function(self.job, "_record_reboot_failure")
599        self.job._record_reboot_failure.expect_call("sub",
600                "reboot.verify_config", "mounted partitions are different after"
601                " reboot (old entries: set([]), new entries: set([('/dev/hdb1',"
602                " '/mnt/hdb1')]))", running_id=None)
603
604        # playback
605        self.assertRaises(error.JobError, self.job._check_post_reboot, "sub")
606        self.god.check_playback()
607
608
609    def test_check_post_reboot_cpu_failure(self):
610        self.construct_job(True)
611
612        mount_info = set([("/dev/hda1", "/mnt/hda1"),
613                          ("/dev/hdb1", "/mnt/hdb1")])
614        self._setup_check_post_reboot(mount_info, 4)
615
616        self.god.stub_function(self.job, "_record_reboot_failure")
617        self.job._record_reboot_failure.expect_call(
618            'sub', 'reboot.verify_config',
619            'Number of CPUs changed after reboot (old count: 8, new count: 4)',
620            running_id=None)
621
622        # playback
623        self.assertRaises(error.JobError, self.job._check_post_reboot, "sub")
624        self.god.check_playback()
625
626
627    def test_end_boot(self):
628        self.construct_job(True)
629        self.god.stub_function(self.job, "_check_post_reboot")
630
631        # set up the job class
632        self.job._record_prefix = '\t\t'
633
634        self.job._check_post_reboot.expect_call("sub", running_id=None)
635        self.job.record.expect_call("END GOOD", "sub", "reboot",
636                                    optional_fields={"kernel": "2.6.15-smp",
637                                                     "patch0": "patchname"})
638
639        # run test
640        self.job.end_reboot("sub", "2.6.15-smp", ["patchname"])
641        self.god.check_playback()
642
643
644    def test_end_boot_and_verify_success(self):
645        self.construct_job(True)
646        self.god.stub_function(self.job, "_check_post_reboot")
647
648        # set up the job class
649        self.job._record_prefix = '\t\t'
650
651        self.god.stub_function(utils, "running_os_ident")
652        utils.running_os_ident.expect_call().and_return("2.6.15-smp")
653
654        utils.read_one_line.expect_call("/proc/cmdline").and_return(
655            "blah more-blah root=lala IDENT=81234567 blah-again")
656
657        self.god.stub_function(utils, "running_os_full_version")
658        running_id = "2.6.15-smp"
659        utils.running_os_full_version.expect_call().and_return(running_id)
660
661        self.job.record.expect_call("GOOD", "sub", "reboot.verify",
662                                    running_id)
663        self.job._check_post_reboot.expect_call("sub", running_id=running_id)
664        self.job.record.expect_call("END GOOD", "sub", "reboot",
665                                    optional_fields={"kernel": running_id})
666
667        # run test
668        self.job.end_reboot_and_verify(81234567, "2.6.15-smp", "sub")
669        self.god.check_playback()
670
671
672    def test_end_boot_and_verify_failure(self):
673        self.construct_job(True)
674        self.god.stub_function(self.job, "_record_reboot_failure")
675
676        # set up the job class
677        self.job._record_prefix = '\t\t'
678
679        self.god.stub_function(utils, "running_os_ident")
680        utils.running_os_ident.expect_call().and_return("2.6.15-smp")
681
682        utils.read_one_line.expect_call("/proc/cmdline").and_return(
683            "blah more-blah root=lala IDENT=81234567 blah-again")
684
685        self.job._record_reboot_failure.expect_call("sub", "reboot.verify",
686                "boot failure", running_id="2.6.15-smp")
687
688        # run test
689        self.assertRaises(error.JobError, self.job.end_reboot_and_verify,
690                          91234567, "2.6.16-smp", "sub")
691        self.god.check_playback()
692
693
694if __name__ == "__main__":
695    unittest.main()
696