Home | History | Annotate | Download | only in common_lib
      1 #!/usr/bin/python
      2 
      3 # pylint: disable=missing-docstring
      4 
      5 import logging
      6 import os
      7 import shutil
      8 import stat
      9 import tempfile
     10 import unittest
     11 
     12 import common
     13 from autotest_lib.client.common_lib import base_job, error
     14 
     15 
     16 class stub_job_directory(object):
     17     """
     18     Stub job_directory class, for replacing the job._job_directory factory.
     19     Just creates a job_directory object without any of the actual directory
     20     checks. When given None it creates a temporary name (but not an actual
     21     temporary directory).
     22     """
     23     def __init__(self, path, is_writable=False):
     24         # path=None and is_writable=False is always an error
     25         assert path or is_writable
     26 
     27         if path is None and is_writable:
     28             self.path = tempfile.mktemp()
     29         else:
     30             self.path = path
     31 
     32 
     33 class stub_job_state(base_job.job_state):
     34     """
     35     Stub job state class, for replacing the job._job_state factory.
     36     Doesn't actually provide any persistence, just the state handling.
     37     """
     38     def __init__(self):
     39         self._state = {}
     40         self._backing_file_lock = None
     41 
     42     def read_from_file(self, file_path):
     43         pass
     44 
     45     def write_to_file(self, file_path):
     46         pass
     47 
     48     def set_backing_file(self, file_path):
     49         pass
     50 
     51     def _read_from_backing_file(self):
     52         pass
     53 
     54     def _write_to_backing_file(self):
     55         pass
     56 
     57     def _lock_backing_file(self):
     58         pass
     59 
     60     def _unlock_backing_file(self):
     61         pass
     62 
     63 
     64 class test_init(unittest.TestCase):
     65     class generic_tests(object):
     66         """
     67         Generic tests for any implementation of __init__.
     68 
     69         Expectations:
     70             A self.job attribute where self.job is a __new__'ed instance of
     71             the job class to be tested, but not yet __init__'ed.
     72 
     73             A self.call_init method that will config the appropriate mocks
     74             and then call job.__init__. It should undo any mocks it created
     75             afterwards.
     76         """
     77 
     78         PUBLIC_ATTRIBUTES = set([
     79             # standard directories
     80             'autodir', 'clientdir', 'serverdir', 'resultdir', 'pkgdir',
     81             'tmpdir', 'testdir', 'site_testdir', 'bindir',
     82             'profdir', 'toolsdir',
     83 
     84             # other special attributes
     85             'args', 'automatic_test_tag', 'control',
     86             'default_profile_only', 'drop_caches',
     87             'drop_caches_between_iterations', 'harness', 'hosts',
     88             'logging', 'machines', 'num_tests_failed', 'num_tests_run',
     89             'pkgmgr', 'profilers', 'resultdir', 'run_test_cleanup',
     90             'sysinfo', 'tag', 'user', 'use_sequence_number',
     91             'warning_loggers', 'warning_manager', 'label', 'test_retry',
     92             'parent_job_id', 'in_lab', 'machine_dict_list',
     93             'max_result_size_KB', 'fast'
     94             ])
     95 
     96         OPTIONAL_ATTRIBUTES = set([
     97             'serverdir',
     98 
     99             'automatic_test_tag', 'control', 'harness', 'num_tests_run',
    100             'num_tests_failed', 'tag', 'warning_manager',
    101             'warning_loggers', 'label', 'test_retry', 'parent_job_id',
    102             'max_result_size_KB', 'fast'
    103             ])
    104 
    105         OPTIONAL_ATTRIBUTES_DEVICE_ERROR = set(['failed_with_device_error'])
    106 
    107         def test_public_attributes_initialized(self):
    108             # only the known public attributes should be there after __init__
    109             self.call_init()
    110             public_attributes = set(attr for attr in dir(self.job)
    111                                     if not attr.startswith('_')
    112                                     and not callable(getattr(self.job, attr)))
    113             expected_attributes = self.PUBLIC_ATTRIBUTES
    114             missing_attributes = expected_attributes - public_attributes
    115             self.assertEqual(missing_attributes, set([]),
    116                              'Missing attributes: %s' %
    117                              ', '.join(sorted(missing_attributes)))
    118             extra_attributes = (public_attributes - expected_attributes -
    119                                 self.OPTIONAL_ATTRIBUTES_DEVICE_ERROR)
    120             self.assertEqual(extra_attributes, set([]),
    121                              'Extra public attributes found: %s' %
    122                              ', '.join(sorted(extra_attributes)))
    123 
    124 
    125         def test_required_attributes_not_none(self):
    126             required_attributes = (self.PUBLIC_ATTRIBUTES -
    127                                    self.OPTIONAL_ATTRIBUTES)
    128             self.call_init()
    129             for attribute in required_attributes:
    130                 self.assertNotEqual(getattr(self.job, attribute, None), None,
    131                                     'job.%s is None but is not optional'
    132                                     % attribute)
    133 
    134 
    135 class test_find_base_directories(unittest.TestCase):
    136     class generic_tests(object):
    137         """
    138         Generic tests for any implementation of _find_base_directories.
    139 
    140         Expectations:
    141             A self.job attribute where self.job is an instance of the job
    142             class to be tested.
    143         """
    144         def test_autodir_is_not_none(self):
    145             auto, client, server = self.job._find_base_directories()
    146             self.assertNotEqual(auto, None)
    147 
    148 
    149         def test_clientdir_is_not_none(self):
    150             auto, client, server = self.job._find_base_directories()
    151             self.assertNotEqual(client, None)
    152 
    153 
    154 class test_initialize_dir_properties(unittest.TestCase):
    155     def make_job(self, autodir, server):
    156         job = base_job.base_job.__new__(base_job.base_job)
    157         job._job_directory = stub_job_directory
    158         job._autodir = stub_job_directory(autodir)
    159         if server:
    160             job._clientdir = stub_job_directory(
    161                 os.path.join(autodir, 'client'))
    162             job._serverdir = stub_job_directory(
    163                 os.path.join(autodir, 'server'))
    164         else:
    165             job._clientdir = stub_job_directory(job.autodir)
    166             job._serverdir = None
    167         return job
    168 
    169 
    170     def setUp(self):
    171         self.cjob = self.make_job('/atest/client', False)
    172         self.sjob = self.make_job('/atest', True)
    173 
    174 
    175     def test_always_client_dirs(self):
    176         self.cjob._initialize_dir_properties()
    177         self.sjob._initialize_dir_properties()
    178 
    179         # check all the always-client dir properties
    180         self.assertEqual(self.cjob.bindir, self.sjob.bindir)
    181         self.assertEqual(self.cjob.profdir, self.sjob.profdir)
    182         self.assertEqual(self.cjob.pkgdir, self.sjob.pkgdir)
    183 
    184 
    185     def test_dynamic_dirs(self):
    186         self.cjob._initialize_dir_properties()
    187         self.sjob._initialize_dir_properties()
    188 
    189         # check all the context-specifc dir properties
    190         self.assert_(self.cjob.tmpdir.startswith('/atest/client'))
    191         self.assert_(self.cjob.testdir.startswith('/atest/client'))
    192         self.assert_(self.cjob.site_testdir.startswith('/atest/client'))
    193         self.assertEqual(self.sjob.tmpdir, tempfile.gettempdir())
    194         self.assert_(self.sjob.testdir.startswith('/atest/server'))
    195         self.assert_(self.sjob.site_testdir.startswith('/atest/server'))
    196 
    197 
    198 class test_execution_context(unittest.TestCase):
    199     def setUp(self):
    200         clientdir = os.path.abspath(os.path.join(__file__, '..', '..'))
    201         self.resultdir = tempfile.mkdtemp(suffix='unittest')
    202         self.job = base_job.base_job.__new__(base_job.base_job)
    203         self.job._find_base_directories = lambda: (clientdir, clientdir, None)
    204         self.job._find_resultdir = lambda *_: self.resultdir
    205         self.job.__init__()
    206 
    207 
    208     def tearDown(self):
    209         shutil.rmtree(self.resultdir, ignore_errors=True)
    210 
    211 
    212     def test_pop_fails_without_push(self):
    213         self.assertRaises(IndexError, self.job.pop_execution_context)
    214 
    215 
    216     def test_push_changes_to_subdir(self):
    217         sub1 = os.path.join(self.resultdir, 'sub1')
    218         os.mkdir(sub1)
    219         self.job.push_execution_context('sub1')
    220         self.assertEqual(self.job.resultdir, sub1)
    221 
    222 
    223     def test_push_creates_subdir(self):
    224         sub2 = os.path.join(self.resultdir, 'sub2')
    225         self.job.push_execution_context('sub2')
    226         self.assertEqual(self.job.resultdir, sub2)
    227         self.assert_(os.path.exists(sub2))
    228 
    229 
    230     def test_push_handles_absolute_paths(self):
    231         otherresults = tempfile.mkdtemp(suffix='unittest')
    232         try:
    233             self.job.push_execution_context(otherresults)
    234             self.assertEqual(self.job.resultdir, otherresults)
    235         finally:
    236             shutil.rmtree(otherresults, ignore_errors=True)
    237 
    238 
    239     def test_pop_restores_context(self):
    240         sub3 = os.path.join(self.resultdir, 'sub3')
    241         self.job.push_execution_context('sub3')
    242         self.assertEqual(self.job.resultdir, sub3)
    243         self.job.pop_execution_context()
    244         self.assertEqual(self.job.resultdir, self.resultdir)
    245 
    246 
    247     def test_push_and_pop_are_fifo(self):
    248         sub4 = os.path.join(self.resultdir, 'sub4')
    249         subsub = os.path.join(sub4, 'subsub')
    250         self.job.push_execution_context('sub4')
    251         self.assertEqual(self.job.resultdir, sub4)
    252         self.job.push_execution_context('subsub')
    253         self.assertEqual(self.job.resultdir, subsub)
    254         self.job.pop_execution_context()
    255         self.assertEqual(self.job.resultdir, sub4)
    256         self.job.pop_execution_context()
    257         self.assertEqual(self.job.resultdir, self.resultdir)
    258 
    259 
    260 class test_job_directory(unittest.TestCase):
    261     def setUp(self):
    262         self.testdir = tempfile.mkdtemp(suffix='unittest')
    263         self.original_wd = os.getcwd()
    264         os.chdir(self.testdir)
    265 
    266 
    267     def tearDown(self):
    268         os.chdir(self.original_wd)
    269         shutil.rmtree(self.testdir, ignore_errors=True)
    270 
    271 
    272     def test_passes_if_dir_exists(self):
    273         os.mkdir('testing')
    274         self.assert_(os.path.isdir('testing'))
    275         jd = base_job.job_directory('testing')
    276         self.assert_(os.path.isdir('testing'))
    277 
    278 
    279     def test_fails_if_not_writable_and_dir_doesnt_exist(self):
    280         self.assert_(not os.path.isdir('testing2'))
    281         self.assertRaises(base_job.job_directory.MissingDirectoryException,
    282                           base_job.job_directory, 'testing2')
    283 
    284 
    285     def test_fails_if_file_already_exists(self):
    286         open('testing3', 'w').close()
    287         self.assert_(os.path.isfile('testing3'))
    288         self.assertRaises(base_job.job_directory.MissingDirectoryException,
    289                           base_job.job_directory, 'testing3')
    290 
    291 
    292     def test_passes_if_writable_and_dir_exists(self):
    293         os.mkdir('testing4')
    294         self.assert_(os.path.isdir('testing4'))
    295         jd = base_job.job_directory('testing4', True)
    296         self.assert_(os.path.isdir('testing4'))
    297 
    298 
    299     def test_creates_dir_if_writable_and_dir_doesnt_exist(self):
    300         self.assert_(not os.path.isdir('testing5'))
    301         jd = base_job.job_directory('testing5', True)
    302         self.assert_(os.path.isdir('testing5'))
    303 
    304 
    305     def test_recursive_creates_dir_if_writable_and_dir_doesnt_exist(self):
    306         self.assert_(not os.path.isdir('testing6'))
    307         base_job.job_directory('testing6/subdir', True)
    308         self.assert_(os.path.isdir('testing6/subdir'))
    309 
    310 
    311     def test_fails_if_writable_and_file_exists(self):
    312         open('testing7', 'w').close()
    313         self.assert_(os.path.isfile('testing7'))
    314         self.assert_(not os.path.isdir('testing7'))
    315         self.assertRaises(base_job.job_directory.UncreatableDirectoryException,
    316                           base_job.job_directory, 'testing7', True)
    317 
    318 
    319     def test_fails_if_writable_and_no_permission_to_create(self):
    320         os.mkdir('testing8', 0555)
    321         self.assert_(os.path.isdir('testing8'))
    322         self.assertRaises(base_job.job_directory.UncreatableDirectoryException,
    323                           base_job.job_directory, 'testing8/subdir', True)
    324 
    325 
    326     def test_passes_if_not_is_writable_and_dir_not_writable(self):
    327         os.mkdir('testing9', 0555)
    328         self.assert_(os.path.isdir('testing9'))
    329         self.assert_(not os.access('testing9', os.W_OK))
    330         jd = base_job.job_directory('testing9')
    331 
    332 
    333     def test_fails_if_is_writable_but_dir_not_writable(self):
    334         os.mkdir('testing10', 0555)
    335         self.assert_(os.path.isdir('testing10'))
    336         self.assert_(not os.access('testing10', os.W_OK))
    337         self.assertRaises(base_job.job_directory.UnwritableDirectoryException,
    338                           base_job.job_directory, 'testing10', True)
    339 
    340 
    341     def test_fails_if_no_path_and_not_writable(self):
    342         self.assertRaises(base_job.job_directory.MissingDirectoryException,
    343                           base_job.job_directory, None)
    344 
    345 
    346     def test_no_path_and_and_not_writable_creates_tempdir(self):
    347         jd = base_job.job_directory(None, True)
    348         self.assert_(os.path.isdir(jd.path))
    349         self.assert_(os.access(jd.path, os.W_OK))
    350         temp_path = jd.path
    351         del jd
    352         self.assert_(not os.path.isdir(temp_path))
    353 
    354 
    355 class test_job_state(unittest.TestCase):
    356     def setUp(self):
    357         self.state = base_job.job_state()
    358 
    359 
    360     def test_undefined_name_returns_key_error(self):
    361         self.assertRaises(KeyError, self.state.get, 'ns1', 'missing_name')
    362 
    363 
    364     def test_undefined_name_returns_default(self):
    365         self.assertEqual(42, self.state.get('ns2', 'missing_name', default=42))
    366 
    367 
    368     def test_none_is_valid_default(self):
    369         self.assertEqual(None, self.state.get('ns3', 'missing_name',
    370                                               default=None))
    371 
    372 
    373     def test_get_returns_set_values(self):
    374         self.state.set('ns4', 'name1', 50)
    375         self.assertEqual(50, self.state.get('ns4', 'name1'))
    376 
    377 
    378     def test_get_ignores_default_when_value_is_defined(self):
    379         self.state.set('ns5', 'name2', 55)
    380         self.assertEqual(55, self.state.get('ns5', 'name2', default=45))
    381 
    382 
    383     def test_set_only_sets_one_value(self):
    384         self.state.set('ns6', 'name3', 50)
    385         self.assertEqual(50, self.state.get('ns6', 'name3'))
    386         self.assertRaises(KeyError, self.state.get, 'ns6', 'name4')
    387 
    388 
    389     def test_set_works_with_multiple_names(self):
    390         self.state.set('ns7', 'name5', 60)
    391         self.state.set('ns7', 'name6', 70)
    392         self.assertEquals(60, self.state.get('ns7', 'name5'))
    393         self.assertEquals(70, self.state.get('ns7', 'name6'))
    394 
    395 
    396     def test_multiple_sets_override_each_other(self):
    397         self.state.set('ns8', 'name7', 10)
    398         self.state.set('ns8', 'name7', 25)
    399         self.assertEquals(25, self.state.get('ns8', 'name7'))
    400 
    401 
    402     def test_get_with_default_does_not_set(self):
    403         self.assertEquals(100, self.state.get('ns9', 'name8', default=100))
    404         self.assertRaises(KeyError, self.state.get, 'ns9', 'name8')
    405 
    406 
    407     def test_set_in_one_namespace_ignores_other(self):
    408         self.state.set('ns10', 'name9', 200)
    409         self.assertEquals(200, self.state.get('ns10', 'name9'))
    410         self.assertRaises(KeyError, self.state.get, 'ns11', 'name9')
    411 
    412 
    413     def test_namespaces_do_not_collide(self):
    414         self.state.set('ns12', 'name10', 250)
    415         self.state.set('ns13', 'name10', -150)
    416         self.assertEquals(-150, self.state.get('ns13', 'name10'))
    417         self.assertEquals(250, self.state.get('ns12', 'name10'))
    418 
    419 
    420     def test_discard_does_nothing_on_undefined_namespace(self):
    421         self.state.discard('missing_ns', 'missing')
    422         self.assertRaises(KeyError, self.state.get, 'missing_ns', 'missing')
    423 
    424 
    425     def test_discard_does_nothing_on_missing_name(self):
    426         self.state.set('ns14', 'name20', 111)
    427         self.state.discard('ns14', 'missing')
    428         self.assertEqual(111, self.state.get('ns14', 'name20'))
    429         self.assertRaises(KeyError, self.state.get, 'ns14', 'missing')
    430 
    431 
    432     def test_discard_deletes_name(self):
    433         self.state.set('ns15', 'name21', 4567)
    434         self.assertEqual(4567, self.state.get('ns15', 'name21'))
    435         self.state.discard('ns15', 'name21')
    436         self.assertRaises(KeyError, self.state.get, 'ns15', 'name21')
    437 
    438 
    439     def test_discard_doesnt_touch_other_values(self):
    440         self.state.set('ns16_1', 'name22', 'val1')
    441         self.state.set('ns16_1', 'name23', 'val2')
    442         self.state.set('ns16_2', 'name23', 'val3')
    443         self.assertEqual('val1', self.state.get('ns16_1', 'name22'))
    444         self.assertEqual('val3', self.state.get('ns16_2', 'name23'))
    445         self.state.discard('ns16_1', 'name23')
    446         self.assertEqual('val1', self.state.get('ns16_1', 'name22'))
    447         self.assertEqual('val3', self.state.get('ns16_2', 'name23'))
    448 
    449 
    450     def test_has_is_true_for_all_set_values(self):
    451         self.state.set('ns17_1', 'name24', 1)
    452         self.state.set('ns17_1', 'name25', 2)
    453         self.state.set('ns17_2', 'name25', 3)
    454         self.assert_(self.state.has('ns17_1', 'name24'))
    455         self.assert_(self.state.has('ns17_1', 'name25'))
    456         self.assert_(self.state.has('ns17_2', 'name25'))
    457 
    458 
    459     def test_has_is_false_for_all_unset_values(self):
    460         self.state.set('ns18_1', 'name26', 1)
    461         self.state.set('ns18_1', 'name27', 2)
    462         self.state.set('ns18_2', 'name27', 3)
    463         self.assert_(not self.state.has('ns18_2', 'name26'))
    464 
    465 
    466     def test_discard_namespace_drops_all_values(self):
    467         self.state.set('ns19', 'var1', 10)
    468         self.state.set('ns19', 'var3', 100)
    469         self.state.discard_namespace('ns19')
    470         self.assertRaises(KeyError, self.state.get, 'ns19', 'var1')
    471         self.assertRaises(KeyError, self.state.get, 'ns19', 'var3')
    472 
    473 
    474     def test_discard_namespace_works_on_missing_namespace(self):
    475         self.state.discard_namespace('missing_ns')
    476 
    477 
    478     def test_discard_namespace_doesnt_touch_other_values(self):
    479         self.state.set('ns20', 'var1', 20)
    480         self.state.set('ns20', 'var2', 200)
    481         self.state.set('ns21', 'var2', 21)
    482         self.state.discard_namespace('ns20')
    483         self.assertEqual(21, self.state.get('ns21', 'var2'))
    484 
    485 
    486 # run the same tests as test_job_state, but with a backing file turned on
    487 # also adds some tests to check that each method is persistent
    488 class test_job_state_with_backing_file(test_job_state):
    489     def setUp(self):
    490         self.backing_file = tempfile.mktemp()
    491         self.state = base_job.job_state()
    492         self.state.set_backing_file(self.backing_file)
    493 
    494 
    495     def tearDown(self):
    496         if os.path.exists(self.backing_file):
    497             os.remove(self.backing_file)
    498 
    499 
    500     def test_set_is_persistent(self):
    501         self.state.set('persist', 'var', 'value')
    502         written_state = base_job.job_state()
    503         written_state.read_from_file(self.backing_file)
    504         self.assertEqual('value', written_state.get('persist', 'var'))
    505 
    506 
    507     def test_discard_is_persistent(self):
    508         self.state.set('persist', 'var', 'value')
    509         self.state.discard('persist', 'var')
    510         written_state = base_job.job_state()
    511         written_state.read_from_file(self.backing_file)
    512         self.assertRaises(KeyError, written_state.get, 'persist', 'var')
    513 
    514 
    515     def test_discard_namespace_is_persistent(self):
    516         self.state.set('persist', 'var', 'value')
    517         self.state.discard_namespace('persist')
    518         written_state = base_job.job_state()
    519         written_state.read_from_file(self.backing_file)
    520         self.assertRaises(KeyError, written_state.get, 'persist', 'var')
    521 
    522 
    523 class test_job_state_read_write_file(unittest.TestCase):
    524     def setUp(self):
    525         self.testdir = tempfile.mkdtemp(suffix='unittest')
    526         self.original_wd = os.getcwd()
    527         os.chdir(self.testdir)
    528 
    529 
    530     def tearDown(self):
    531         os.chdir(self.original_wd)
    532         shutil.rmtree(self.testdir, ignore_errors=True)
    533 
    534 
    535     def test_write_read_transfers_all_state(self):
    536         state1 = base_job.job_state()
    537         state1.set('ns1', 'var0', 50)
    538         state1.set('ns2', 'var10', 100)
    539         state1.write_to_file('transfer_file')
    540         state2 = base_job.job_state()
    541         self.assertRaises(KeyError, state2.get, 'ns1', 'var0')
    542         self.assertRaises(KeyError, state2.get, 'ns2', 'var10')
    543         state2.read_from_file('transfer_file')
    544         self.assertEqual(50, state2.get('ns1', 'var0'))
    545         self.assertEqual(100, state2.get('ns2', 'var10'))
    546 
    547 
    548     def test_read_overwrites_in_memory(self):
    549         state = base_job.job_state()
    550         state.set('ns', 'myvar', 'hello')
    551         state.write_to_file('backup')
    552         state.set('ns', 'myvar', 'goodbye')
    553         self.assertEqual('goodbye', state.get('ns', 'myvar'))
    554         state.read_from_file('backup')
    555         self.assertEqual('hello', state.get('ns', 'myvar'))
    556 
    557 
    558     def test_read_updates_persistent_file(self):
    559         state1 = base_job.job_state()
    560         state1.set('ns', 'var1', 'value1')
    561         state1.write_to_file('to_be_read')
    562         state2 = base_job.job_state()
    563         state2.set_backing_file('backing_file')
    564         state2.set('ns', 'var2', 'value2')
    565         state2.read_from_file('to_be_read')
    566         state2.set_backing_file(None)
    567         state3 = base_job.job_state()
    568         state3.read_from_file('backing_file')
    569         self.assertEqual('value1', state3.get('ns', 'var1'))
    570         self.assertEqual('value2', state3.get('ns', 'var2'))
    571 
    572 
    573     def test_read_without_merge(self):
    574         state = base_job.job_state()
    575         state.set('ns', 'myvar1', 'hello')
    576         state.write_to_file('backup')
    577         state.discard('ns', 'myvar1')
    578         state.set('ns', 'myvar2', 'goodbye')
    579         self.assertFalse(state.has('ns', 'myvar1'))
    580         self.assertEqual('goodbye', state.get('ns', 'myvar2'))
    581         state.read_from_file('backup', merge=False)
    582         self.assertEqual('hello', state.get('ns', 'myvar1'))
    583         self.assertFalse(state.has('ns', 'myvar2'))
    584 
    585 
    586 class test_job_state_set_backing_file(unittest.TestCase):
    587     def setUp(self):
    588         self.testdir = tempfile.mkdtemp(suffix='unittest')
    589         self.original_wd = os.getcwd()
    590         os.chdir(self.testdir)
    591 
    592 
    593     def tearDown(self):
    594         os.chdir(self.original_wd)
    595         shutil.rmtree(self.testdir, ignore_errors=True)
    596 
    597 
    598     def test_writes_to_file(self):
    599         state = base_job.job_state()
    600         state.set_backing_file('outfile1')
    601         self.assert_(os.path.exists('outfile1'))
    602 
    603 
    604     def test_set_backing_file_updates_existing_file(self):
    605         state1 = base_job.job_state()
    606         state1.set_backing_file('second_file')
    607         state1.set('ns0', 'var1x', 100)
    608         state1.set_backing_file(None)
    609         state2 = base_job.job_state()
    610         state2.set_backing_file('first_file')
    611         state2.set('ns0', 'var0x', 0)
    612         state2.set_backing_file('second_file')
    613         state2.set_backing_file(None)
    614         state3 = base_job.job_state()
    615         state3.read_from_file('second_file')
    616         self.assertEqual(0, state3.get('ns0', 'var0x'))
    617         self.assertEqual(100, state3.get('ns0', 'var1x'))
    618 
    619 
    620     def test_set_backing_file_does_not_overwrite_previous_backing_file(self):
    621         state1 = base_job.job_state()
    622         state1.set_backing_file('second_file')
    623         state1.set('ns0', 'var1y', 10)
    624         state1.set_backing_file(None)
    625         state2 = base_job.job_state()
    626         state2.set_backing_file('first_file')
    627         state2.set('ns0', 'var0y', -10)
    628         state2.set_backing_file('second_file')
    629         state2.set_backing_file(None)
    630         state3 = base_job.job_state()
    631         state3.read_from_file('first_file')
    632         self.assertEqual(-10, state3.get('ns0', 'var0y'))
    633         self.assertRaises(KeyError, state3.get, 'ns0', 'var1y')
    634 
    635 
    636     def test_writes_stop_after_backing_file_removed(self):
    637         state = base_job.job_state()
    638         state.set('ns', 'var1', 'value1')
    639         state.set_backing_file('outfile2')
    640         state.set_backing_file(None)
    641         os.remove('outfile2')
    642         state.set('n2', 'var2', 'value2')
    643         self.assert_(not os.path.exists('outfile2'))
    644 
    645 
    646     def test_written_files_can_be_reloaded(self):
    647         state1 = base_job.job_state()
    648         state1.set_backing_file('outfile3')
    649         state1.set('n3', 'var1', 67)
    650         state1.set_backing_file(None)
    651         state2 = base_job.job_state()
    652         self.assertRaises(KeyError, state2.get, 'n3', 'var1')
    653         state2.set_backing_file('outfile3')
    654         self.assertEqual(67, state2.get('n3', 'var1'))
    655 
    656 
    657     def test_backing_file_overrides_in_memory_values(self):
    658         state1 = base_job.job_state()
    659         state1.set_backing_file('outfile4')
    660         state1.set('n4', 'var1', 42)
    661         state1.set_backing_file(None)
    662         state2 = base_job.job_state()
    663         state2.set('n4', 'var1', 430)
    664         self.assertEqual(430, state2.get('n4', 'var1'))
    665         state2.set_backing_file('outfile4')
    666         self.assertEqual(42, state2.get('n4', 'var1'))
    667 
    668 
    669     def test_backing_file_only_overrides_values_it_defines(self):
    670         state1 = base_job.job_state()
    671         state1.set_backing_file('outfile5')
    672         state1.set('n5', 'var1', 123)
    673         state1.set_backing_file(None)
    674         state2 = base_job.job_state()
    675         state2.set('n5', 'var2', 456)
    676         state2.set_backing_file('outfile5')
    677         self.assertEqual(123, state2.get('n5', 'var1'))
    678         self.assertEqual(456, state2.get('n5', 'var2'))
    679 
    680 
    681     def test_shared_backing_file_propagates_state_to_get(self):
    682         state1 = base_job.job_state()
    683         state1.set_backing_file('outfile6')
    684         state2 = base_job.job_state()
    685         state2.set_backing_file('outfile6')
    686         self.assertRaises(KeyError, state1.get, 'n6', 'shared1')
    687         self.assertRaises(KeyError, state2.get, 'n6', 'shared1')
    688         state1.set('n6', 'shared1', 345)
    689         self.assertEqual(345, state1.get('n6', 'shared1'))
    690         self.assertEqual(345, state2.get('n6', 'shared1'))
    691 
    692 
    693     def test_shared_backing_file_propagates_state_to_has(self):
    694         state1 = base_job.job_state()
    695         state1.set_backing_file('outfile7')
    696         state2 = base_job.job_state()
    697         state2.set_backing_file('outfile7')
    698         self.assertFalse(state1.has('n6', 'shared2'))
    699         self.assertFalse(state2.has('n6', 'shared2'))
    700         state1.set('n6', 'shared2', 'hello')
    701         self.assertTrue(state1.has('n6', 'shared2'))
    702         self.assertTrue(state2.has('n6', 'shared2'))
    703 
    704 
    705     def test_shared_backing_file_propagates_state_from_discard(self):
    706         state1 = base_job.job_state()
    707         state1.set_backing_file('outfile8')
    708         state1.set('n6', 'shared3', 10000)
    709         state2 = base_job.job_state()
    710         state2.set_backing_file('outfile8')
    711         self.assertEqual(10000, state1.get('n6', 'shared3'))
    712         self.assertEqual(10000, state2.get('n6', 'shared3'))
    713         state1.discard('n6', 'shared3')
    714         self.assertRaises(KeyError, state1.get, 'n6', 'shared3')
    715         self.assertRaises(KeyError, state2.get, 'n6', 'shared3')
    716 
    717 
    718     def test_shared_backing_file_propagates_state_from_discard_namespace(self):
    719         state1 = base_job.job_state()
    720         state1.set_backing_file('outfile9')
    721         state1.set('n7', 'shared4', -1)
    722         state1.set('n7', 'shared5', -2)
    723         state2 = base_job.job_state()
    724         state2.set_backing_file('outfile9')
    725         self.assertEqual(-1, state1.get('n7', 'shared4'))
    726         self.assertEqual(-1, state2.get('n7', 'shared4'))
    727         self.assertEqual(-2, state1.get('n7', 'shared5'))
    728         self.assertEqual(-2, state2.get('n7', 'shared5'))
    729         state1.discard_namespace('n7')
    730         self.assertRaises(KeyError, state1.get, 'n7', 'shared4')
    731         self.assertRaises(KeyError, state2.get, 'n7', 'shared4')
    732         self.assertRaises(KeyError, state1.get, 'n7', 'shared5')
    733         self.assertRaises(KeyError, state2.get, 'n7', 'shared5')
    734 
    735 
    736 class test_job_state_backing_file_locking(unittest.TestCase):
    737     def setUp(self):
    738         self.testdir = tempfile.mkdtemp(suffix='unittest')
    739         self.original_wd = os.getcwd()
    740         os.chdir(self.testdir)
    741 
    742         # create a job_state object with stub read_* and write_* methods
    743         # to check that a lock is always held during a call to them
    744         ut_self = self
    745         class mocked_job_state(base_job.job_state):
    746             def read_from_file(self, file_path, merge=True):
    747                 if self._backing_file and file_path == self._backing_file:
    748                     ut_self.assertNotEqual(None, self._backing_file_lock)
    749                 return super(mocked_job_state, self).read_from_file(
    750                     file_path, merge=True)
    751             def write_to_file(self, file_path):
    752                 if self._backing_file and file_path == self._backing_file:
    753                     ut_self.assertNotEqual(None, self._backing_file_lock)
    754                 return super(mocked_job_state, self).write_to_file(file_path)
    755         self.state = mocked_job_state()
    756         self.state.set_backing_file('backing_file')
    757 
    758 
    759     def tearDown(self):
    760         os.chdir(self.original_wd)
    761         shutil.rmtree(self.testdir, ignore_errors=True)
    762 
    763 
    764     def test_set(self):
    765         self.state.set('ns1', 'var1', 100)
    766 
    767 
    768     def test_get_missing(self):
    769         self.assertRaises(KeyError, self.state.get, 'ns2', 'var2')
    770 
    771 
    772     def test_get_present(self):
    773         self.state.set('ns3', 'var3', 333)
    774         self.assertEqual(333, self.state.get('ns3', 'var3'))
    775 
    776 
    777     def test_set_backing_file(self):
    778         self.state.set_backing_file('some_other_file')
    779 
    780 
    781     def test_has_missing(self):
    782         self.assertFalse(self.state.has('ns4', 'var4'))
    783 
    784 
    785     def test_has_present(self):
    786         self.state.set('ns5', 'var5', 55555)
    787         self.assertTrue(self.state.has('ns5', 'var5'))
    788 
    789 
    790     def test_discard_missing(self):
    791         self.state.discard('ns6', 'var6')
    792 
    793 
    794     def test_discard_present(self):
    795         self.state.set('ns7', 'var7', -777)
    796         self.state.discard('ns7', 'var7')
    797 
    798 
    799     def test_discard_missing_namespace(self):
    800         self.state.discard_namespace('ns8')
    801 
    802 
    803     def test_discard_present_namespace(self):
    804         self.state.set('ns8', 'var8', 80)
    805         self.state.set('ns8', 'var8.1', 81)
    806         self.state.discard_namespace('ns8')
    807 
    808 
    809     def test_disable_backing_file(self):
    810         self.state.set_backing_file(None)
    811 
    812 
    813     def test_change_backing_file(self):
    814         self.state.set_backing_file('another_backing_file')
    815 
    816 
    817     def test_read_from_a_non_backing_file(self):
    818         state = base_job.job_state()
    819         state.set('ns9', 'var9', 9999)
    820         state.write_to_file('non_backing_file')
    821         self.state.read_from_file('non_backing_file')
    822 
    823 
    824     def test_write_to_a_non_backing_file(self):
    825         self.state.write_to_file('non_backing_file')
    826 
    827 
    828 class test_job_state_property_factory(unittest.TestCase):
    829     def setUp(self):
    830         class job_stub(object):
    831             pass
    832         self.job_class = job_stub
    833         self.job = job_stub()
    834         self.state = base_job.job_state()
    835         self.job.stateobj = self.state
    836 
    837 
    838     def test_properties_are_readwrite(self):
    839         self.job_class.testprop1 = base_job.job_state.property_factory(
    840             'stateobj', 'testprop1', 1)
    841         self.job.testprop1 = 'testvalue'
    842         self.assertEqual('testvalue', self.job.testprop1)
    843 
    844 
    845     def test_properties_use_default_if_not_initialized(self):
    846         self.job_class.testprop2 = base_job.job_state.property_factory(
    847             'stateobj', 'testprop2', 'abc123')
    848         self.assertEqual('abc123', self.job.testprop2)
    849 
    850 
    851     def test_properties_do_not_collisde(self):
    852         self.job_class.testprop3 = base_job.job_state.property_factory(
    853             'stateobj', 'testprop3', 2)
    854         self.job_class.testprop4 = base_job.job_state.property_factory(
    855             'stateobj', 'testprop4', 3)
    856         self.job.testprop3 = 500
    857         self.job.testprop4 = '1000'
    858         self.assertEqual(500, self.job.testprop3)
    859         self.assertEqual('1000', self.job.testprop4)
    860 
    861 
    862     def test_properties_do_not_collide_across_different_state_objects(self):
    863         self.job_class.testprop5 = base_job.job_state.property_factory(
    864             'stateobj', 'testprop5', 55)
    865         self.job.auxstateobj = base_job.job_state()
    866         self.job_class.auxtestprop5 = base_job.job_state.property_factory(
    867             'auxstateobj', 'testprop5', 600)
    868         self.job.auxtestprop5 = 700
    869         self.assertEqual(55, self.job.testprop5)
    870         self.assertEqual(700, self.job.auxtestprop5)
    871 
    872 
    873     def test_properties_do_not_collide_across_different_job_objects(self):
    874         self.job_class.testprop6 = base_job.job_state.property_factory(
    875             'stateobj', 'testprop6', 'defaultval')
    876         job1 = self.job
    877         job2 = self.job_class()
    878         job2.stateobj = base_job.job_state()
    879         job1.testprop6 = 'notdefaultval'
    880         self.assertEqual('notdefaultval', job1.testprop6)
    881         self.assertEqual('defaultval', job2.testprop6)
    882         job2.testprop6 = 'job2val'
    883         self.assertEqual('notdefaultval', job1.testprop6)
    884         self.assertEqual('job2val', job2.testprop6)
    885 
    886     def test_properties_in_different_namespaces_do_not_collide(self):
    887         self.job_class.ns1 = base_job.job_state.property_factory(
    888             'stateobj', 'attribute', 'default1', namespace='ns1')
    889         self.job_class.ns2 = base_job.job_state.property_factory(
    890             'stateobj', 'attribute', 'default2', namespace='ns2')
    891         self.assertEqual('default1', self.job.ns1)
    892         self.assertEqual('default2', self.job.ns2)
    893         self.job.ns1 = 'notdefault'
    894         self.job.ns2 = 'alsonotdefault'
    895         self.assertEqual('notdefault', self.job.ns1)
    896         self.assertEqual('alsonotdefault', self.job.ns2)
    897 
    898 
    899 class test_status_log_entry(unittest.TestCase):
    900     def test_accepts_valid_status_code(self):
    901         base_job.status_log_entry('GOOD', None, None, '', None)
    902         base_job.status_log_entry('FAIL', None, None, '', None)
    903         base_job.status_log_entry('ABORT', None, None, '', None)
    904 
    905 
    906     def test_accepts_valid_start_status_code(self):
    907         base_job.status_log_entry('START', None, None, '', None)
    908 
    909 
    910     def test_accepts_valid_end_status_code(self):
    911         base_job.status_log_entry('END GOOD', None, None, '', None)
    912         base_job.status_log_entry('END FAIL', None, None, '', None)
    913         base_job.status_log_entry('END ABORT', None, None, '', None)
    914 
    915 
    916     def test_rejects_invalid_status_code(self):
    917         self.assertRaises(ValueError, base_job.status_log_entry,
    918                           'FAKE', None, None, '', None)
    919 
    920 
    921     def test_rejects_invalid_start_status_code(self):
    922         self.assertRaises(ValueError, base_job.status_log_entry,
    923                           'START GOOD', None, None, '', None)
    924         self.assertRaises(ValueError, base_job.status_log_entry,
    925                           'START FAIL', None, None, '', None)
    926         self.assertRaises(ValueError, base_job.status_log_entry,
    927                           'START ABORT', None, None, '', None)
    928         self.assertRaises(ValueError, base_job.status_log_entry,
    929                           'START FAKE', None, None, '', None)
    930 
    931 
    932     def test_rejects_invalid_end_status_code(self):
    933         self.assertRaises(ValueError, base_job.status_log_entry,
    934                           'END FAKE', None, None, '', None)
    935 
    936 
    937     def test_accepts_valid_subdir(self):
    938         base_job.status_log_entry('GOOD', 'subdir', None, '', None)
    939         base_job.status_log_entry('FAIL', 'good.subdir', None, '', None)
    940 
    941 
    942     def test_rejects_bad_subdir(self):
    943         self.assertRaises(ValueError, base_job.status_log_entry,
    944                           'GOOD', 'bad.subdir\t', None, '', None)
    945         self.assertRaises(ValueError, base_job.status_log_entry,
    946                           'GOOD', 'bad.subdir\t', None, '', None)
    947         self.assertRaises(ValueError, base_job.status_log_entry,
    948                           'GOOD', 'bad.subdir\t', None, '', None)
    949         self.assertRaises(ValueError, base_job.status_log_entry,
    950                           'GOOD', 'bad.subdir\t', None, '', None)
    951         self.assertRaises(ValueError, base_job.status_log_entry,
    952                           'GOOD', 'bad.subdir\t', None, '', None)
    953 
    954 
    955     def test_accepts_valid_operation(self):
    956         base_job.status_log_entry('GOOD', None, 'build', '', None)
    957         base_job.status_log_entry('FAIL', None, 'clean', '', None)
    958 
    959 
    960     def test_rejects_bad_operation(self):
    961         self.assertRaises(ValueError, base_job.status_log_entry,
    962                           'GOOD', None, 'bad.operation\n', '', None)
    963         self.assertRaises(ValueError, base_job.status_log_entry,
    964                           'GOOD', None, 'bad.\voperation', '', None)
    965         self.assertRaises(ValueError, base_job.status_log_entry,
    966                           'GOOD', None, 'bad.\foperation', '', None)
    967         self.assertRaises(ValueError, base_job.status_log_entry,
    968                           'GOOD', None, 'bad\r.operation', '', None)
    969         self.assertRaises(ValueError, base_job.status_log_entry,
    970                           'GOOD', None, '\tbad.operation', '', None)
    971 
    972 
    973     def test_simple_message(self):
    974         base_job.status_log_entry('ERROR', None, None, 'simple error message',
    975                                   None)
    976 
    977 
    978     def test_message_split_into_multiple_lines(self):
    979         def make_entry(msg):
    980             return base_job.status_log_entry('GOOD', None, None, msg, None)
    981         base_job.status_log_entry('ABORT', None, None, 'first line\nsecond',
    982                                   None)
    983 
    984 
    985     def test_message_with_tabs(self):
    986         base_job.status_log_entry('GOOD', None, None, '\tindent\tagain', None)
    987 
    988 
    989     def test_message_with_custom_fields(self):
    990         base_job.status_log_entry('GOOD', None, None, 'my message',
    991                                   {'key1': 'blah', 'key2': 'blahblah'})
    992 
    993 
    994     def assertRendered(self, rendered, status, subdir, operation, msg,
    995                        extra_fields, timestamp):
    996         parts = rendered.split('\t')
    997         self.assertEqual(parts[0], status)
    998         self.assertEqual(parts[1], subdir)
    999         self.assertEqual(parts[2], operation)
   1000         self.assertEqual(parts[-1], msg)
   1001         fields = dict(f.split('=', 1) for f in parts[3:-1])
   1002         self.assertEqual(int(fields['timestamp']), timestamp)
   1003         self.assert_('localtime' in fields)  # too flaky to do an exact check
   1004         del fields['timestamp']
   1005         del fields['localtime']
   1006         self.assertEqual(fields, extra_fields)
   1007 
   1008 
   1009     def test_base_render(self):
   1010         entry = base_job.status_log_entry('GOOD', None, None, 'message1', None,
   1011                                           timestamp=1)
   1012         self.assertRendered(entry.render(), 'GOOD', '----', '----', 'message1',
   1013                             {}, 1)
   1014 
   1015 
   1016     def test_subdir_render(self):
   1017         entry = base_job.status_log_entry('FAIL', 'sub', None, 'message2', None,
   1018                                           timestamp=2)
   1019         self.assertRendered(entry.render(), 'FAIL', 'sub', '----', 'message2',
   1020                             {}, 2)
   1021 
   1022 
   1023     def test_operation_render(self):
   1024         entry = base_job.status_log_entry('ABORT', None, 'myop', 'message3',
   1025                                           None, timestamp=4)
   1026         self.assertRendered(entry.render(), 'ABORT', '----', 'myop', 'message3',
   1027                             {}, 4)
   1028 
   1029 
   1030     def test_fields_render(self):
   1031         custom_fields = {'custom1': 'foo', 'custom2': 'bar'}
   1032         entry = base_job.status_log_entry('WARN', None, None, 'message4',
   1033                                           custom_fields, timestamp=8)
   1034         self.assertRendered(entry.render(), 'WARN', '----', '----', 'message4',
   1035                             custom_fields, 8)
   1036 
   1037 
   1038     def assertEntryEqual(self, lhs, rhs):
   1039         self.assertEqual(
   1040           (lhs.status_code, lhs.subdir, lhs.operation, lhs.fields, lhs.message),
   1041           (rhs.status_code, rhs.subdir, rhs.operation, rhs.fields, rhs.message))
   1042 
   1043 
   1044     def test_base_parse(self):
   1045         entry = base_job.status_log_entry(
   1046             'GOOD', None, None, 'message', {'field1': 'x', 'field2': 'y'},
   1047             timestamp=16)
   1048         parsed_entry = base_job.status_log_entry.parse(
   1049             'GOOD\t----\t----\tfield1=x\tfield2=y\ttimestamp=16\tmessage\n')
   1050         self.assertEntryEqual(entry, parsed_entry)
   1051 
   1052 
   1053     def test_subdir_parse(self):
   1054         entry = base_job.status_log_entry(
   1055             'FAIL', 'sub', None, 'message', {'field1': 'x', 'field2': 'y'},
   1056             timestamp=32)
   1057         parsed_entry = base_job.status_log_entry.parse(
   1058             'FAIL\tsub\t----\tfield1=x\tfield2=y\ttimestamp=32\tmessage\n')
   1059         self.assertEntryEqual(entry, parsed_entry)
   1060 
   1061 
   1062     def test_operation_parse(self):
   1063         entry = base_job.status_log_entry(
   1064             'ABORT', None, 'myop', 'message', {'field1': 'x', 'field2': 'y'},
   1065             timestamp=64)
   1066         parsed_entry = base_job.status_log_entry.parse(
   1067             'ABORT\t----\tmyop\tfield1=x\tfield2=y\ttimestamp=64\tmessage\n')
   1068         self.assertEntryEqual(entry, parsed_entry)
   1069 
   1070 
   1071     def test_extra_lines_parse(self):
   1072         parsed_entry = base_job.status_log_entry.parse(
   1073             '  This is a non-status line, line in a traceback\n')
   1074         self.assertEqual(None, parsed_entry)
   1075 
   1076 
   1077 class test_status_logger(unittest.TestCase):
   1078     def setUp(self):
   1079         self.testdir = tempfile.mkdtemp(suffix='unittest')
   1080         self.original_wd = os.getcwd()
   1081         os.chdir(self.testdir)
   1082 
   1083         class stub_job(object):
   1084             resultdir = self.testdir
   1085         self.job = stub_job()  # need to hold a reference to the job
   1086         class stub_indenter(object):
   1087             def __init__(self):
   1088                 self.indent = 0
   1089             def increment(self):
   1090                 self.indent += 1
   1091             def decrement(self):
   1092                 self.indent -= 1
   1093         self.indenter = stub_indenter()
   1094         self.logger = base_job.status_logger(self.job, self.indenter)
   1095 
   1096 
   1097     def make_dummy_entry(self, rendered_text, start=False, end=False,
   1098                          subdir=None):
   1099         """Helper to make a dummy status log entry with custom rendered text.
   1100 
   1101         Helpful when validating the logging since it lets the test control
   1102         the rendered text and so it doesn't depend on the exact formatting
   1103         of a "real" status log entry.
   1104 
   1105         @param rendred_text: The value to return when rendering the entry.
   1106         @param start: An optional value indicating if this should be the start
   1107             of a nested group.
   1108         @param end: An optional value indicating if this should be the end
   1109             of a nested group.
   1110         @param subdir: An optional value to use for the entry subdir field.
   1111 
   1112         @return: A dummy status log entry object with the given subdir field
   1113             and a render implementation that returns rendered_text.
   1114         """
   1115         assert not start or not end  # real entries would never be both
   1116         class dummy_entry(object):
   1117             def is_start(self):
   1118                 return start
   1119             def is_end(self):
   1120                 return end
   1121             def render(self):
   1122                 return rendered_text
   1123         entry = dummy_entry()
   1124         entry.subdir = subdir
   1125         return entry
   1126 
   1127 
   1128     def test_render_includes_indent(self):
   1129         entry = self.make_dummy_entry('LINE0')
   1130         self.assertEqual('LINE0', self.logger.render_entry(entry))
   1131         self.indenter.increment()
   1132         self.indenter.increment()
   1133         self.assertEqual('\t\tLINE0', self.logger.render_entry(entry))
   1134 
   1135 
   1136     def test_render_handles_start(self):
   1137         entry = self.make_dummy_entry('LINE10', start=True)
   1138         self.indenter.increment()
   1139         self.assertEqual('\tLINE10', self.logger.render_entry(entry))
   1140 
   1141 
   1142     def test_render_handles_end(self):
   1143         entry = self.make_dummy_entry('LINE20', end=True)
   1144         self.indenter.increment()
   1145         self.indenter.increment()
   1146         self.indenter.increment()
   1147         self.assertEqual('\t\tLINE20', self.logger.render_entry(entry))
   1148 
   1149 
   1150     def test_writes_toplevel_log(self):
   1151         entries = [self.make_dummy_entry('LINE%d' % x) for x in xrange(3)]
   1152         for entry in entries:
   1153             self.logger.record_entry(entry)
   1154         self.assertEqual('LINE0\nLINE1\nLINE2\n', open('status').read())
   1155 
   1156 
   1157     def test_uses_given_filenames(self):
   1158         os.mkdir('sub')
   1159         self.logger = base_job.status_logger(self.job, self.indenter,
   1160                                              global_filename='global.log',
   1161                                              subdir_filename='subdir.log')
   1162         self.logger.record_entry(self.make_dummy_entry('LINE1', subdir='sub'))
   1163         self.logger.record_entry(self.make_dummy_entry('LINE2', subdir='sub'))
   1164         self.logger.record_entry(self.make_dummy_entry('LINE3'))
   1165 
   1166         self.assertEqual('LINE1\nLINE2\nLINE3\n', open('global.log').read())
   1167         self.assertEqual('LINE1\nLINE2\n', open('sub/subdir.log').read())
   1168 
   1169         self.assertFalse(os.path.exists('status'))
   1170         self.assertFalse(os.path.exists('sub/status'))
   1171         self.assertFalse(os.path.exists('subdir.log'))
   1172         self.assertFalse(os.path.exists('sub/global.log'))
   1173 
   1174 
   1175     def test_filenames_are_mutable(self):
   1176         os.mkdir('sub2')
   1177         self.logger = base_job.status_logger(self.job, self.indenter,
   1178                                              global_filename='global.log',
   1179                                              subdir_filename='subdir.log')
   1180         self.logger.record_entry(self.make_dummy_entry('LINE1', subdir='sub2'))
   1181         self.logger.record_entry(self.make_dummy_entry('LINE2'))
   1182         self.logger.global_filename = 'global.log2'
   1183         self.logger.subdir_filename = 'subdir.log2'
   1184         self.logger.record_entry(self.make_dummy_entry('LINE3', subdir='sub2'))
   1185         self.logger.record_entry(self.make_dummy_entry('LINE4'))
   1186 
   1187         self.assertEqual('LINE1\nLINE2\n', open('global.log').read())
   1188         self.assertEqual('LINE1\n', open('sub2/subdir.log').read())
   1189         self.assertEqual('LINE3\nLINE4\n', open('global.log2').read())
   1190         self.assertEqual('LINE3\n', open('sub2/subdir.log2').read())
   1191 
   1192 
   1193     def test_writes_subdir_logs(self):
   1194         os.mkdir('abc')
   1195         os.mkdir('123')
   1196         self.logger.record_entry(self.make_dummy_entry('LINE1'))
   1197         self.logger.record_entry(self.make_dummy_entry('LINE2', subdir='abc'))
   1198         self.logger.record_entry(self.make_dummy_entry('LINE3', subdir='abc'))
   1199         self.logger.record_entry(self.make_dummy_entry('LINE4', subdir='123'))
   1200 
   1201         self.assertEqual('LINE1\nLINE2\nLINE3\nLINE4\n', open('status').read())
   1202         self.assertEqual('LINE2\nLINE3\n', open('abc/status').read())
   1203         self.assertEqual('LINE4\n', open('123/status').read())
   1204 
   1205 
   1206     def test_writes_no_subdir_when_disabled(self):
   1207         os.mkdir('sub')
   1208         self.logger.record_entry(self.make_dummy_entry('LINE1'))
   1209         self.logger.record_entry(self.make_dummy_entry('LINE2', subdir='sub'))
   1210         self.logger.record_entry(self.make_dummy_entry(
   1211             'LINE3', subdir='sub_nowrite'), log_in_subdir=False)
   1212         self.logger.record_entry(self.make_dummy_entry('LINE4', subdir='sub'))
   1213 
   1214         self.assertEqual('LINE1\nLINE2\nLINE3\nLINE4\n', open('status').read())
   1215         self.assertEqual('LINE2\nLINE4\n', open('sub/status').read())
   1216         self.assert_(not os.path.exists('sub_nowrite/status'))
   1217 
   1218 
   1219     def test_indentation(self):
   1220         self.logger.record_entry(self.make_dummy_entry('LINE1', start=True))
   1221         self.logger.record_entry(self.make_dummy_entry('LINE2'))
   1222         self.logger.record_entry(self.make_dummy_entry('LINE3', start=True))
   1223         self.logger.record_entry(self.make_dummy_entry('LINE4'))
   1224         self.logger.record_entry(self.make_dummy_entry('LINE5'))
   1225         self.logger.record_entry(self.make_dummy_entry('LINE6', end=True))
   1226         self.logger.record_entry(self.make_dummy_entry('LINE7', end=True))
   1227         self.logger.record_entry(self.make_dummy_entry('LINE8'))
   1228 
   1229         expected_log = ('LINE1\n\tLINE2\n\tLINE3\n\t\tLINE4\n\t\tLINE5\n'
   1230                         '\tLINE6\nLINE7\nLINE8\n')
   1231         self.assertEqual(expected_log, open('status').read())
   1232 
   1233 
   1234     def test_multiline_indent(self):
   1235         self.logger.record_entry(self.make_dummy_entry('LINE1\n  blah\n'))
   1236         self.logger.record_entry(self.make_dummy_entry('LINE2', start=True))
   1237         self.logger.record_entry(
   1238             self.make_dummy_entry('LINE3\n  blah\n  two\n'))
   1239         self.logger.record_entry(self.make_dummy_entry('LINE4', end=True))
   1240 
   1241         expected_log = ('LINE1\n  blah\nLINE2\n'
   1242                         '\tLINE3\n  blah\n  two\nLINE4\n')
   1243         self.assertEqual(expected_log, open('status').read())
   1244 
   1245 
   1246     def test_hook_is_called(self):
   1247         entries = [self.make_dummy_entry('LINE%d' % x) for x in xrange(5)]
   1248         recorded_entries = []
   1249         def hook(entry):
   1250             recorded_entries.append(entry)
   1251         self.logger = base_job.status_logger(self.job, self.indenter,
   1252                                              record_hook=hook)
   1253         for entry in entries:
   1254             self.logger.record_entry(entry)
   1255         self.assertEqual(entries, recorded_entries)
   1256 
   1257 
   1258     def tearDown(self):
   1259         os.chdir(self.original_wd)
   1260         shutil.rmtree(self.testdir, ignore_errors=True)
   1261 
   1262 
   1263 class test_job_tags(unittest.TestCase):
   1264     def setUp(self):
   1265         class stub_job(base_job.base_job):
   1266             _job_directory = stub_job_directory
   1267             @classmethod
   1268             def _find_base_directories(cls):
   1269                 return '/autodir', '/autodir/client', '/autodir/server'
   1270             def _find_resultdir(self):
   1271                 return '/autodir/results'
   1272         self.job = stub_job()
   1273 
   1274 
   1275     def test_default_with_no_args_means_no_tags(self):
   1276         self.assertEqual(('testname', 'testname', ''),
   1277                          self.job._build_tagged_test_name('testname', {}))
   1278         self.assertEqual(('othername', 'othername', ''),
   1279                          self.job._build_tagged_test_name('othername', {}))
   1280 
   1281 
   1282     def test_tag_argument_appended(self):
   1283         self.assertEqual(
   1284             ('test1.mytag', 'test1.mytag', 'mytag'),
   1285             self.job._build_tagged_test_name('test1', {'tag': 'mytag'}))
   1286 
   1287 
   1288     def test_turning_on_use_sequence_adds_sequence_tags(self):
   1289         self.job.use_sequence_number = True
   1290         self.assertEqual(
   1291             ('test2._01_', 'test2._01_', '_01_'),
   1292             self.job._build_tagged_test_name('test2', {}))
   1293         self.assertEqual(
   1294             ('test2._02_', 'test2._02_', '_02_'),
   1295             self.job._build_tagged_test_name('test2', {}))
   1296         self.assertEqual(
   1297             ('test3._03_', 'test3._03_', '_03_'),
   1298             self.job._build_tagged_test_name('test3', {}))
   1299 
   1300 
   1301     def test_adding_automatic_test_tag_automatically_tags(self):
   1302         self.job.automatic_test_tag = 'autotag'
   1303         self.assertEqual(
   1304             ('test4.autotag', 'test4.autotag', 'autotag'),
   1305             self.job._build_tagged_test_name('test4', {}))
   1306 
   1307 
   1308     def test_none_automatic_test_tag_turns_off_tagging(self):
   1309         self.job.automatic_test_tag = 'autotag'
   1310         self.assertEqual(
   1311             ('test5.autotag', 'test5.autotag', 'autotag'),
   1312             self.job._build_tagged_test_name('test5', {}))
   1313         self.job.automatic_test_tag = None
   1314         self.assertEqual(
   1315             ('test5', 'test5', ''),
   1316             self.job._build_tagged_test_name('test5', {}))
   1317 
   1318 
   1319     def test_empty_automatic_test_tag_turns_off_tagging(self):
   1320         self.job.automatic_test_tag = 'autotag'
   1321         self.assertEqual(
   1322             ('test6.autotag', 'test6.autotag', 'autotag'),
   1323             self.job._build_tagged_test_name('test6', {}))
   1324         self.job.automatic_test_tag = ''
   1325         self.assertEqual(
   1326             ('test6', 'test6', ''),
   1327             self.job._build_tagged_test_name('test6', {}))
   1328 
   1329 
   1330     def test_subdir_tag_modifies_subdir_and_tag_only(self):
   1331         self.assertEqual(
   1332             ('test7', 'test7.subdirtag', 'subdirtag'),
   1333             self.job._build_tagged_test_name('test7',
   1334                                              {'subdir_tag': 'subdirtag'}))
   1335 
   1336 
   1337     def test_all_tag_components_together(self):
   1338         self.job.use_sequence_number = True
   1339         self.job.automatic_test_tag = 'auto'
   1340         expected = ('test8.tag._01_.auto',
   1341                     'test8.tag._01_.auto.subdir',
   1342                     'tag._01_.auto.subdir')
   1343         actual = self.job._build_tagged_test_name(
   1344             'test8', {'tag': 'tag', 'subdir_tag': 'subdir'})
   1345         self.assertEqual(expected, actual)
   1346 
   1347 
   1348     def test_subtest_with_master_test_path_and_subdir(self):
   1349         self.assertEqual(
   1350             ('test9', 'subtestdir/test9.subdirtag', 'subdirtag'),
   1351             self.job._build_tagged_test_name('test9',
   1352                                              {'master_testpath': 'subtestdir',
   1353                                               'subdir_tag': 'subdirtag'}))
   1354 
   1355 
   1356     def test_subtest_all_tag_components_together_subdir(self):
   1357         self.job.use_sequence_number = True
   1358         self.job.automatic_test_tag = 'auto'
   1359         expected = ('test10.tag._01_.auto',
   1360                     'subtestdir/test10.tag._01_.auto.subdir',
   1361                     'tag._01_.auto.subdir')
   1362         actual = self.job._build_tagged_test_name(
   1363             'test10', {'tag': 'tag', 'subdir_tag': 'subdir',
   1364                        'master_testpath': 'subtestdir'})
   1365         self.assertEqual(expected, actual)
   1366 
   1367 
   1368 class test_make_outputdir(unittest.TestCase):
   1369     def setUp(self):
   1370         self.resultdir = tempfile.mkdtemp(suffix='unittest')
   1371         class stub_job(base_job.base_job):
   1372             @classmethod
   1373             def _find_base_directories(cls):
   1374                 return '/autodir', '/autodir/client', '/autodir/server'
   1375             @classmethod
   1376             def _find_resultdir(cls):
   1377                 return self.resultdir
   1378 
   1379         # stub out _job_directory for creation only
   1380         stub_job._job_directory = stub_job_directory
   1381         self.job = stub_job()
   1382         del stub_job._job_directory
   1383 
   1384         # stub out logging.exception
   1385         self.original_exception = logging.exception
   1386         logging.exception = lambda *args, **dargs: None
   1387 
   1388         self.original_wd = os.getcwd()
   1389         os.chdir(self.resultdir)
   1390 
   1391 
   1392     def tearDown(self):
   1393         logging.exception = self.original_exception
   1394         os.chdir(self.original_wd)
   1395         shutil.rmtree(self.resultdir, ignore_errors=True)
   1396 
   1397 
   1398     def test_raises_test_error_if_outputdir_exists(self):
   1399         os.mkdir('subdir1')
   1400         self.assert_(os.path.exists('subdir1'))
   1401         self.assertRaises(error.TestError, self.job._make_test_outputdir,
   1402                           'subdir1')
   1403 
   1404 
   1405     def test_raises_test_error_if_outputdir_uncreatable(self):
   1406         os.chmod(self.resultdir, stat.S_IRUSR | stat.S_IXUSR)
   1407         self.assert_(not os.path.exists('subdir2'))
   1408         self.assertRaises(OSError, os.mkdir, 'subdir2')
   1409         self.assertRaises(error.TestError, self.job._make_test_outputdir,
   1410                           'subdir2')
   1411         self.assert_(not os.path.exists('subdir2'))
   1412 
   1413 
   1414     def test_creates_writable_directory(self):
   1415         self.assert_(not os.path.exists('subdir3'))
   1416         self.job._make_test_outputdir('subdir3')
   1417         self.assert_(os.path.isdir('subdir3'))
   1418 
   1419         # we can write to the directory afterwards
   1420         self.assert_(not os.path.exists('subdir3/testfile'))
   1421         open('subdir3/testfile', 'w').close()
   1422         self.assert_(os.path.isfile('subdir3/testfile'))
   1423 
   1424 
   1425 if __name__ == "__main__":
   1426     unittest.main()
   1427