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