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