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