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