1 #!/usr/bin/python 2 # pylint: disable=missing-docstring 3 4 import logging 5 import os 6 import shutil 7 import StringIO 8 import sys 9 import unittest 10 11 import common 12 from autotest_lib.client.bin import job, sysinfo, harness 13 from autotest_lib.client.bin import utils 14 from autotest_lib.client.common_lib import error 15 from autotest_lib.client.common_lib import logging_manager, logging_config 16 from autotest_lib.client.common_lib import base_job_unittest 17 from autotest_lib.client.common_lib.test_utils import mock 18 19 20 class job_test_case(unittest.TestCase): 21 """Generic job TestCase class that defines a standard job setUp and 22 tearDown, with some standard stubs.""" 23 24 job_class = job.base_client_job 25 26 def setUp(self): 27 self.god = mock.mock_god(ut=self) 28 self.god.stub_with(job.base_client_job, '_get_environ_autodir', 29 classmethod(lambda cls: '/adir')) 30 self.job = self.job_class.__new__(self.job_class) 31 self.job._job_directory = base_job_unittest.stub_job_directory 32 33 34 def tearDown(self): 35 self.god.unstub_all() 36 37 38 class test_find_base_directories( 39 base_job_unittest.test_find_base_directories.generic_tests, 40 job_test_case): 41 42 def test_autodir_equals_clientdir(self): 43 autodir, clientdir, _ = self.job._find_base_directories() 44 self.assertEqual(autodir, '/adir') 45 self.assertEqual(clientdir, '/adir') 46 47 48 def test_serverdir_is_none(self): 49 _, _, serverdir = self.job._find_base_directories() 50 self.assertEqual(serverdir, None) 51 52 53 class abstract_test_init(base_job_unittest.test_init.generic_tests): 54 """Generic client job mixin used when defining variations on the 55 job.__init__ generic tests.""" 56 OPTIONAL_ATTRIBUTES = ( 57 base_job_unittest.test_init.generic_tests.OPTIONAL_ATTRIBUTES 58 - set(['control', 'harness'])) 59 60 61 class test_init_minimal_options(abstract_test_init, job_test_case): 62 def call_init(self): 63 # TODO(jadmanski): refactor more of the __init__ code to not need to 64 # stub out countless random APIs 65 self.god.stub_function_to_return(job.os, 'mkdir', None) 66 self.god.stub_function_to_return(job.os.path, 'exists', True) 67 self.god.stub_function_to_return(self.job, '_load_state', None) 68 self.god.stub_function_to_return(self.job, 'record', None) 69 self.god.stub_function_to_return(job.shutil, 'copyfile', None) 70 self.god.stub_function_to_return(job.logging_manager, 71 'configure_logging', None) 72 class manager: 73 def start_logging(self): 74 return None 75 self.god.stub_function_to_return(job.logging_manager, 76 'get_logging_manager', manager()) 77 class stub_sysinfo: 78 def log_per_reboot_data(self): 79 return None 80 self.god.stub_function_to_return(job.sysinfo, 'sysinfo', 81 stub_sysinfo()) 82 class stub_harness: 83 run_start = lambda self: None 84 self.god.stub_function_to_return(job.harness, 'select', stub_harness()) 85 class options: 86 tag = '' 87 verbose = False 88 cont = False 89 harness = 'stub' 90 harness_args = None 91 hostname = None 92 user = None 93 log = False 94 args = '' 95 output_dir = '' 96 tap_report = None 97 self.god.stub_function_to_return(job.utils, 'drop_caches', None) 98 99 self.job._job_state = base_job_unittest.stub_job_state 100 self.job.__init__('/control', options) 101 102 103 class dummy(object): 104 """A simple placeholder for attributes""" 105 pass 106 107 108 class first_line_comparator(mock.argument_comparator): 109 def __init__(self, first_line): 110 self.first_line = first_line 111 112 113 def is_satisfied_by(self, parameter): 114 return self.first_line == parameter.splitlines()[0] 115 116 117 class test_base_job(unittest.TestCase): 118 def setUp(self): 119 # make god 120 self.god = mock.mock_god(ut=self) 121 122 # need to set some environ variables 123 self.autodir = "autodir" 124 os.environ['AUTODIR'] = self.autodir 125 126 # set up some variables 127 self.control = "control" 128 self.jobtag = "jobtag" 129 130 # get rid of stdout and logging 131 sys.stdout = StringIO.StringIO() 132 logging_manager.configure_logging(logging_config.TestingConfig()) 133 logging.disable(logging.CRITICAL) 134 def dummy_configure_logging(*args, **kwargs): 135 pass 136 self.god.stub_with(logging_manager, 'configure_logging', 137 dummy_configure_logging) 138 real_get_logging_manager = logging_manager.get_logging_manager 139 def get_logging_manager_no_fds(manage_stdout_and_stderr=False, 140 redirect_fds=False): 141 return real_get_logging_manager(manage_stdout_and_stderr, False) 142 self.god.stub_with(logging_manager, 'get_logging_manager', 143 get_logging_manager_no_fds) 144 145 # stub out some stuff 146 self.god.stub_function(os.path, 'exists') 147 self.god.stub_function(os.path, 'isdir') 148 self.god.stub_function(os, 'makedirs') 149 self.god.stub_function(os, 'mkdir') 150 self.god.stub_function(os, 'remove') 151 self.god.stub_function(shutil, 'rmtree') 152 self.god.stub_function(shutil, 'copyfile') 153 self.god.stub_function(job, 'open') 154 self.god.stub_function(utils, 'system') 155 self.god.stub_function(utils, 'drop_caches') 156 self.god.stub_function(harness, 'select') 157 self.god.stub_function(sysinfo, 'log_per_reboot_data') 158 159 self.god.stub_class(job.local_host, 'LocalHost') 160 self.god.stub_class(sysinfo, 'sysinfo') 161 162 self.god.stub_class_method(job.base_client_job, 163 '_cleanup_debugdir_files') 164 self.god.stub_class_method(job.base_client_job, '_cleanup_results_dir') 165 166 self.god.stub_with(job.base_job.job_directory, '_ensure_valid', 167 lambda *_: None) 168 169 170 def tearDown(self): 171 sys.stdout = sys.__stdout__ 172 self.god.unstub_all() 173 174 175 def _setup_pre_record_init(self, cont): 176 self.god.stub_function(self.job, '_load_state') 177 178 resultdir = os.path.join(self.autodir, 'results', self.jobtag) 179 tmpdir = os.path.join(self.autodir, 'tmp') 180 if not cont: 181 job.base_client_job._cleanup_debugdir_files.expect_call() 182 job.base_client_job._cleanup_results_dir.expect_call() 183 184 self.job._load_state.expect_call() 185 186 my_harness = self.god.create_mock_class(harness.harness, 187 'my_harness') 188 harness.select.expect_call(None, 189 self.job, 190 None).and_return(my_harness) 191 192 return resultdir, my_harness 193 194 195 def _setup_post_record_init(self, cont, resultdir, my_harness): 196 # now some specific stubs 197 self.god.stub_function(self.job, 'config_get') 198 self.god.stub_function(self.job, 'config_set') 199 self.god.stub_function(self.job, 'record') 200 201 # other setup 202 results = os.path.join(self.autodir, 'results') 203 download = os.path.join(self.autodir, 'tests', 'download') 204 pkgdir = os.path.join(self.autodir, 'packages') 205 206 utils.drop_caches.expect_call() 207 job_sysinfo = sysinfo.sysinfo.expect_new(resultdir) 208 if not cont: 209 os.path.exists.expect_call(download).and_return(False) 210 os.mkdir.expect_call(download) 211 shutil.copyfile.expect_call(mock.is_string_comparator(), 212 os.path.join(resultdir, 'control')) 213 214 job.local_host.LocalHost.expect_new(hostname='localhost') 215 job_sysinfo.log_per_reboot_data.expect_call() 216 if not cont: 217 self.job.record.expect_call('START', None, None) 218 219 my_harness.run_start.expect_call() 220 221 222 def construct_job(self, cont): 223 # will construct class instance using __new__ 224 self.job = job.base_client_job.__new__(job.base_client_job) 225 226 # record 227 resultdir, my_harness = self._setup_pre_record_init(cont) 228 self._setup_post_record_init(cont, resultdir, my_harness) 229 230 # finish constructor 231 options = dummy() 232 options.tag = self.jobtag 233 options.cont = cont 234 options.harness = None 235 options.harness_args = None 236 options.log = False 237 options.verbose = False 238 options.hostname = 'localhost' 239 options.user = 'my_user' 240 options.args = '' 241 options.output_dir = '' 242 options.tap_report = None 243 self.job.__init__(self.control, options) 244 245 # check 246 self.god.check_playback() 247 248 249 def get_partition_mock(self, devname): 250 """ 251 Create a mock of a partition object and return it. 252 """ 253 class mock(object): 254 device = devname 255 get_mountpoint = self.god.create_mock_function('get_mountpoint') 256 return mock 257 258 259 def test_constructor_first_run(self): 260 self.construct_job(False) 261 262 263 def test_constructor_continuation(self): 264 self.construct_job(True) 265 266 267 def test_constructor_post_record_failure(self): 268 """ 269 Test post record initialization failure. 270 """ 271 self.job = job.base_client_job.__new__(job.base_client_job) 272 options = dummy() 273 options.tag = self.jobtag 274 options.cont = False 275 options.harness = None 276 options.harness_args = None 277 options.log = False 278 options.verbose = False 279 options.hostname = 'localhost' 280 options.user = 'my_user' 281 options.args = '' 282 options.output_dir = '' 283 options.tap_report = None 284 error = Exception('fail') 285 286 self.god.stub_function(self.job, '_post_record_init') 287 self.god.stub_function(self.job, 'record') 288 289 self._setup_pre_record_init(False) 290 self.job._post_record_init.expect_call( 291 self.control, options, True).and_raises(error) 292 self.job.record.expect_call( 293 'ABORT', None, None,'client.bin.job.__init__ failed: %s' % 294 str(error)) 295 296 self.assertRaises( 297 Exception, self.job.__init__, self.control, options, 298 drop_caches=True) 299 300 # check 301 self.god.check_playback() 302 303 304 def test_control_functions(self): 305 self.construct_job(True) 306 control_file = "blah" 307 self.job.control_set(control_file) 308 self.assertEquals(self.job.control_get(), os.path.abspath(control_file)) 309 310 311 def test_harness_select(self): 312 self.construct_job(True) 313 314 # record 315 which = "which" 316 harness_args = '' 317 harness.select.expect_call(which, self.job, 318 harness_args).and_return(None) 319 320 # run and test 321 self.job.harness_select(which, harness_args) 322 self.god.check_playback() 323 324 325 def test_setup_dirs_raise(self): 326 self.construct_job(True) 327 328 # setup 329 results_dir = 'foo' 330 tmp_dir = 'bar' 331 332 # record 333 os.path.exists.expect_call(tmp_dir).and_return(True) 334 os.path.isdir.expect_call(tmp_dir).and_return(False) 335 336 # test 337 self.assertRaises(ValueError, self.job.setup_dirs, results_dir, tmp_dir) 338 self.god.check_playback() 339 340 341 def test_setup_dirs(self): 342 self.construct_job(True) 343 344 # setup 345 results_dir1 = os.path.join(self.job.resultdir, 'build') 346 results_dir2 = os.path.join(self.job.resultdir, 'build.2') 347 results_dir3 = os.path.join(self.job.resultdir, 'build.3') 348 tmp_dir = 'bar' 349 350 # record 351 os.path.exists.expect_call(tmp_dir).and_return(False) 352 os.mkdir.expect_call(tmp_dir) 353 os.path.isdir.expect_call(tmp_dir).and_return(True) 354 os.path.exists.expect_call(results_dir1).and_return(True) 355 os.path.exists.expect_call(results_dir2).and_return(True) 356 os.path.exists.expect_call(results_dir3).and_return(False) 357 os.path.exists.expect_call(results_dir3).and_return(False) 358 os.mkdir.expect_call(results_dir3) 359 360 # test 361 self.assertEqual(self.job.setup_dirs(None, tmp_dir), 362 (results_dir3, tmp_dir)) 363 self.god.check_playback() 364 365 366 def test_run_test_logs_test_error_from_unhandled_error(self): 367 self.construct_job(True) 368 369 # set up stubs 370 self.god.stub_function(self.job.pkgmgr, 'get_package_name') 371 self.god.stub_function(self.job, "_runtest") 372 373 # create an unhandled error object 374 class MyError(error.TestError): 375 pass 376 real_error = MyError("this is the real error message") 377 unhandled_error = error.UnhandledTestError(real_error) 378 379 # set up the recording 380 testname = "error_test" 381 outputdir = os.path.join(self.job.resultdir, testname) 382 self.job.pkgmgr.get_package_name.expect_call( 383 testname, 'test').and_return(("", testname)) 384 os.path.exists.expect_call(outputdir).and_return(False) 385 self.job.record.expect_call("START", testname, testname, 386 optional_fields=None) 387 self.job._runtest.expect_call(testname, "", None, (), {}).and_raises( 388 unhandled_error) 389 self.job.record.expect_call("ERROR", testname, testname, 390 first_line_comparator(str(real_error))) 391 self.job.record.expect_call("END ERROR", testname, testname) 392 self.job.harness.run_test_complete.expect_call() 393 utils.drop_caches.expect_call() 394 395 # run and check 396 self.job.run_test(testname) 397 self.god.check_playback() 398 399 400 def test_run_test_logs_non_test_error_from_unhandled_error(self): 401 self.construct_job(True) 402 403 # set up stubs 404 self.god.stub_function(self.job.pkgmgr, 'get_package_name') 405 self.god.stub_function(self.job, "_runtest") 406 407 # create an unhandled error object 408 class MyError(Exception): 409 pass 410 real_error = MyError("this is the real error message") 411 unhandled_error = error.UnhandledTestError(real_error) 412 reason = first_line_comparator("Unhandled MyError: %s" % real_error) 413 414 # set up the recording 415 testname = "error_test" 416 outputdir = os.path.join(self.job.resultdir, testname) 417 self.job.pkgmgr.get_package_name.expect_call( 418 testname, 'test').and_return(("", testname)) 419 os.path.exists.expect_call(outputdir).and_return(False) 420 self.job.record.expect_call("START", testname, testname, 421 optional_fields=None) 422 self.job._runtest.expect_call(testname, "", None, (), {}).and_raises( 423 unhandled_error) 424 self.job.record.expect_call("ERROR", testname, testname, reason) 425 self.job.record.expect_call("END ERROR", testname, testname) 426 self.job.harness.run_test_complete.expect_call() 427 utils.drop_caches.expect_call() 428 429 # run and check 430 self.job.run_test(testname) 431 self.god.check_playback() 432 433 434 def test_report_reboot_failure(self): 435 self.construct_job(True) 436 437 # record 438 self.job.record.expect_call("ABORT", "sub", "reboot.verify", 439 "boot failure") 440 self.job.record.expect_call("END ABORT", "sub", "reboot", 441 optional_fields={"kernel": "2.6.15-smp"}) 442 443 # playback 444 self.job._record_reboot_failure("sub", "reboot.verify", "boot failure", 445 running_id="2.6.15-smp") 446 self.god.check_playback() 447 448 449 def _setup_check_post_reboot(self, mount_info, cpu_count): 450 # setup 451 self.god.stub_function(job.partition_lib, "get_partition_list") 452 self.god.stub_function(utils, "count_cpus") 453 454 part_list = [self.get_partition_mock("/dev/hda1"), 455 self.get_partition_mock("/dev/hdb1")] 456 mount_list = ["/mnt/hda1", "/mnt/hdb1"] 457 458 # record 459 job.partition_lib.get_partition_list.expect_call( 460 self.job, exclude_swap=False).and_return(part_list) 461 for i in xrange(len(part_list)): 462 part_list[i].get_mountpoint.expect_call().and_return(mount_list[i]) 463 if cpu_count is not None: 464 utils.count_cpus.expect_call().and_return(cpu_count) 465 self.job._state.set('client', 'mount_info', mount_info) 466 self.job._state.set('client', 'cpu_count', 8) 467 468 469 def test_check_post_reboot_success(self): 470 self.construct_job(True) 471 472 mount_info = set([("/dev/hda1", "/mnt/hda1"), 473 ("/dev/hdb1", "/mnt/hdb1")]) 474 self._setup_check_post_reboot(mount_info, 8) 475 476 # playback 477 self.job._check_post_reboot("sub") 478 self.god.check_playback() 479 480 481 def test_check_post_reboot_mounts_failure(self): 482 self.construct_job(True) 483 484 mount_info = set([("/dev/hda1", "/mnt/hda1")]) 485 self._setup_check_post_reboot(mount_info, None) 486 487 self.god.stub_function(self.job, "_record_reboot_failure") 488 self.job._record_reboot_failure.expect_call("sub", 489 "reboot.verify_config", "mounted partitions are different after" 490 " reboot (old entries: set([]), new entries: set([('/dev/hdb1'," 491 " '/mnt/hdb1')]))", running_id=None) 492 493 # playback 494 self.assertRaises(error.JobError, self.job._check_post_reboot, "sub") 495 self.god.check_playback() 496 497 498 def test_check_post_reboot_cpu_failure(self): 499 self.construct_job(True) 500 501 mount_info = set([("/dev/hda1", "/mnt/hda1"), 502 ("/dev/hdb1", "/mnt/hdb1")]) 503 self._setup_check_post_reboot(mount_info, 4) 504 505 self.god.stub_function(self.job, "_record_reboot_failure") 506 self.job._record_reboot_failure.expect_call( 507 'sub', 'reboot.verify_config', 508 'Number of CPUs changed after reboot (old count: 8, new count: 4)', 509 running_id=None) 510 511 # playback 512 self.assertRaises(error.JobError, self.job._check_post_reboot, "sub") 513 self.god.check_playback() 514 515 516 def test_parse_args(self): 517 test_set = {"a='foo bar baz' b='moo apt'": 518 ["a='foo bar baz'", "b='moo apt'"], 519 "a='foo bar baz' only=gah": 520 ["a='foo bar baz'", "only=gah"], 521 "a='b c d' no=argh": 522 ["a='b c d'", "no=argh"]} 523 for t in test_set: 524 parsed_args = job.base_client_job._parse_args(t) 525 expected_args = test_set[t] 526 self.assertEqual(parsed_args, expected_args) 527 528 529 def test_run_test_timeout_parameter_is_propagated(self): 530 self.construct_job(True) 531 532 # set up stubs 533 self.god.stub_function(self.job.pkgmgr, 'get_package_name') 534 self.god.stub_function(self.job, "_runtest") 535 536 # create an unhandled error object 537 #class MyError(error.TestError): 538 # pass 539 #real_error = MyError("this is the real error message") 540 #unhandled_error = error.UnhandledTestError(real_error) 541 542 # set up the recording 543 testname = "test" 544 outputdir = os.path.join(self.job.resultdir, testname) 545 self.job.pkgmgr.get_package_name.expect_call( 546 testname, 'test').and_return(("", testname)) 547 os.path.exists.expect_call(outputdir).and_return(False) 548 timeout = 60 549 optional_fields = {} 550 optional_fields['timeout'] = timeout 551 self.job.record.expect_call("START", testname, testname, 552 optional_fields=optional_fields) 553 self.job._runtest.expect_call(testname, "", timeout, (), {}) 554 self.job.record.expect_call("GOOD", testname, testname, 555 "completed successfully") 556 self.job.record.expect_call("END GOOD", testname, testname) 557 self.job.harness.run_test_complete.expect_call() 558 utils.drop_caches.expect_call() 559 560 # run and check 561 self.job.run_test(testname, timeout=timeout) 562 self.god.check_playback() 563 564 565 if __name__ == "__main__": 566 unittest.main() 567