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