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