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