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