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