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