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