1#!/usr/bin/python
2
3import os, stat, tempfile, shutil, logging, tempfile
4
5import common
6from autotest_lib.client.common_lib import base_job, error
7from autotest_lib.client.common_lib.test_utils import unittest
8
9
10class 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
27class 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
58class 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
128class 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
147class 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
191class 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
253class 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
348class 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
481class 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
516class 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
579class 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
729class 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
821class 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
892class 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
1070class 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
1256class 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
1361class 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
1418if __name__ == "__main__":
1419    unittest.main()
1420