• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2
3# pylint: disable=missing-docstring
4
5import logging
6import os
7import shutil
8import stat
9import tempfile
10import unittest
11
12import common
13from autotest_lib.client.common_lib import base_job, error
14
15
16class 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
33class 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
64class 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
134class 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
153class 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
197class 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
259class 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
354class 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
487class 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
522class 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
585class 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
735class 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
827class 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
898class 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
1076class 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
1262class 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
1367class 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
1424if __name__ == "__main__":
1425    unittest.main()
1426