1 # 2 # Copyright (C) 2016 The Android Open Source Project 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 # 16 17 import logging 18 import os 19 import re 20 21 from vts.proto import VtsReportMessage_pb2 as ReportMsg 22 from vts.runners.host import asserts 23 from vts.runners.host import const 24 from vts.runners.host import errors 25 from vts.runners.host import keys 26 from vts.runners.host import logger 27 from vts.runners.host import records 28 from vts.runners.host import signals 29 from vts.runners.host import utils 30 from vts.utils.python.controllers import android_device 31 from vts.utils.python.common import filter_utils 32 from vts.utils.python.common import list_utils 33 from vts.utils.python.coverage import coverage_utils 34 from vts.utils.python.profiling import profiling_utils 35 from vts.utils.python.reporting import log_uploading_utils 36 from vts.utils.python.systrace import systrace_utils 37 from vts.utils.python.web import feature_utils 38 from vts.utils.python.web import web_utils 39 40 # Macro strings for test result reporting 41 TEST_CASE_TOKEN = "[Test Case]" 42 RESULT_LINE_TEMPLATE = TEST_CASE_TOKEN + " %s %s" 43 STR_TEST = "test" 44 STR_GENERATE = "generate" 45 _REPORT_MESSAGE_FILE_NAME = "report_proto.msg" 46 _BUG_REPORT_FILE_PREFIX = "bugreport" 47 _BUG_REPORT_FILE_EXTENSION = ".zip" 48 _ANDROID_DEVICES = '_android_devices' 49 50 51 class BaseTestClass(object): 52 """Base class for all test classes to inherit from. 53 54 This class gets all the controller objects from test_runner and executes 55 the test cases requested within itself. 56 57 Most attributes of this class are set at runtime based on the configuration 58 provided. 59 60 Attributes: 61 android_devices: A list of AndroidDevice object, representing android 62 devices. 63 tests: A list of strings, each representing a test case name. 64 TAG: A string used to refer to a test class. Default is the test class 65 name. 66 results: A records.TestResult object for aggregating test results from 67 the execution of test cases. 68 _current_record: A records.TestResultRecord object for the test case 69 currently being executed. If no test is running, this 70 should be None. 71 include_filer: A list of string, each representing a test case name to 72 include. 73 exclude_filer: A list of string, each representing a test case name to 74 exclude. Has no effect if include_filer is not empty. 75 abi_name: String, name of abi in use 76 abi_bitness: String, bitness of abi in use 77 web: WebFeature, object storing web feature util for test run 78 coverage: CoverageFeature, object storing coverage feature util for test run 79 profiling: ProfilingFeature, object storing profiling feature util for test run 80 _skip_all_testcases: A boolean, can be set by a subclass in 81 setUpClass() to skip all test cases. 82 _bug_report_on_failure: bool, whether to catch bug report at the end 83 of failed test cases. 84 test_filter: Filter object to filter test names. 85 """ 86 TAG = None 87 88 def __init__(self, configs): 89 self.tests = [] 90 if not self.TAG: 91 self.TAG = self.__class__.__name__ 92 # Set all the controller objects and params. 93 for name, value in configs.items(): 94 setattr(self, name, value) 95 self.results = records.TestResult() 96 self._current_record = None 97 98 # Setup test filters 99 self.include_filter = self.getUserParam( 100 [ 101 keys.ConfigKeys.KEY_TEST_SUITE, 102 keys.ConfigKeys.KEY_INCLUDE_FILTER 103 ], 104 default_value=[]) 105 self.exclude_filter = self.getUserParam( 106 [ 107 keys.ConfigKeys.KEY_TEST_SUITE, 108 keys.ConfigKeys.KEY_EXCLUDE_FILTER 109 ], 110 default_value=[]) 111 112 # TODO(yuexima): remove include_filter and exclude_filter from class attributes 113 # after confirming all modules no longer have reference to them 114 self.include_filter = list_utils.ExpandItemDelimiters( 115 list_utils.ItemsToStr(self.include_filter), ',') 116 self.exclude_filter = list_utils.ExpandItemDelimiters( 117 list_utils.ItemsToStr(self.exclude_filter), ',') 118 exclude_over_include = self.getUserParam( 119 keys.ConfigKeys.KEY_EXCLUDE_OVER_INCLUDE, default_value=None) 120 self.test_module_name = self.getUserParam(keys.ConfigKeys.KEY_TESTBED_NAME, 121 default_value=None) 122 self.test_filter = filter_utils.Filter( 123 self.include_filter, 124 self.exclude_filter, 125 enable_regex=True, 126 exclude_over_include=exclude_over_include, 127 enable_negative_pattern=True, 128 enable_module_name_prefix_matching=True, 129 module_name=self.test_module_name) 130 self.test_filter.ExpandBitness() 131 logging.info('Test filter: %s' % self.test_filter) 132 133 # TODO: get abi information differently for multi-device support. 134 # Set other optional parameters 135 self.abi_name = self.getUserParam( 136 keys.ConfigKeys.IKEY_ABI_NAME, default_value=None) 137 self.abi_bitness = self.getUserParam( 138 keys.ConfigKeys.IKEY_ABI_BITNESS, default_value=None) 139 self.skip_on_32bit_abi = self.getUserParam( 140 keys.ConfigKeys.IKEY_SKIP_ON_32BIT_ABI, default_value=False) 141 self.skip_on_64bit_abi = self.getUserParam( 142 keys.ConfigKeys.IKEY_SKIP_ON_64BIT_ABI, default_value=False) 143 self.run_32bit_on_64bit_abi = self.getUserParam( 144 keys.ConfigKeys.IKEY_RUN_32BIT_ON_64BIT_ABI, default_value=False) 145 self.web = web_utils.WebFeature(self.user_params) 146 self.coverage = coverage_utils.CoverageFeature( 147 self.user_params, web=self.web) 148 self.profiling = profiling_utils.ProfilingFeature( 149 self.user_params, web=self.web) 150 self.systrace = systrace_utils.SystraceFeature( 151 self.user_params, web=self.web) 152 self.log_uploading = log_uploading_utils.LogUploadingFeature( 153 self.user_params, web=self.web) 154 self._skip_all_testcases = False 155 self._bug_report_on_failure = self.getUserParam( 156 keys.ConfigKeys.IKEY_BUG_REPORT_ON_FAILURE, default_value=False) 157 158 @property 159 def android_devices(self): 160 """Returns a list of AndroidDevice objects""" 161 if not hasattr(self, _ANDROID_DEVICES): 162 setattr(self, _ANDROID_DEVICES, 163 self.registerController(android_device)) 164 return getattr(self, _ANDROID_DEVICES) 165 166 @android_devices.setter 167 def android_devices(self, devices): 168 """Set the list of AndroidDevice objects""" 169 setattr(self, _ANDROID_DEVICES, devices) 170 171 def __enter__(self): 172 return self 173 174 def __exit__(self, *args): 175 self._exec_func(self.cleanUp) 176 177 def getUserParams(self, req_param_names=[], opt_param_names=[], **kwargs): 178 """Unpacks user defined parameters in test config into individual 179 variables. 180 181 Instead of accessing the user param with self.user_params["xxx"], the 182 variable can be directly accessed with self.xxx. 183 184 A missing required param will raise an exception. If an optional param 185 is missing, an INFO line will be logged. 186 187 Args: 188 req_param_names: A list of names of the required user params. 189 opt_param_names: A list of names of the optional user params. 190 **kwargs: Arguments that provide default values. 191 e.g. getUserParams(required_list, opt_list, arg_a="hello") 192 self.arg_a will be "hello" unless it is specified again in 193 required_list or opt_list. 194 195 Raises: 196 BaseTestError is raised if a required user params is missing from 197 test config. 198 """ 199 for k, v in kwargs.items(): 200 setattr(self, k, v) 201 for name in req_param_names: 202 if name not in self.user_params: 203 raise errors.BaseTestError(("Missing required user param '%s' " 204 "in test configuration.") % name) 205 setattr(self, name, self.user_params[name]) 206 for name in opt_param_names: 207 if name not in self.user_params: 208 logging.info(("Missing optional user param '%s' in " 209 "configuration, continue."), name) 210 else: 211 setattr(self, name, self.user_params[name]) 212 213 def getUserParam(self, 214 param_name, 215 error_if_not_found=False, 216 log_warning_and_continue_if_not_found=False, 217 default_value=None, 218 to_str=False): 219 """Get the value of a single user parameter. 220 221 This method returns the value of specified user parameter. 222 Note: this method will not automatically set attribute using the parameter name and value. 223 224 Args: 225 param_name: string or list of string, denoting user parameter names. If provided 226 a single string, self.user_params["<param_name>"] will be accessed. 227 If provided multiple strings, 228 self.user_params["<param_name1>"]["<param_name2>"]["<param_name3>"]... 229 will be accessed. 230 error_if_not_found: bool, whether to raise error if parameter not exists. Default: 231 False 232 log_warning_and_continue_if_not_found: bool, log a warning message if parameter value 233 not found. 234 default_value: object, default value to return if not found. If error_if_not_found is 235 True, this parameter has no effect. Default: None 236 to_str: boolean, whether to convert the result object to string if not None. 237 Note, strings passing in from java json config are usually unicode. 238 239 Returns: 240 object, value of the specified parameter name chain if exists; 241 <default_value> if not exists. 242 """ 243 244 def ToStr(return_value): 245 """Check to_str option and convert to string if not None""" 246 if to_str and return_value is not None: 247 return str(return_value) 248 return return_value 249 250 if not param_name: 251 if error_if_not_found: 252 raise errors.BaseTestError("empty param_name provided") 253 logging.error("empty param_name") 254 return ToStr(default_value) 255 256 if not isinstance(param_name, list): 257 param_name = [param_name] 258 259 curr_obj = self.user_params 260 for param in param_name: 261 if param not in curr_obj: 262 msg = "Missing user param '%s' in test configuration." % param_name 263 if error_if_not_found: 264 raise errors.BaseTestError(msg) 265 elif log_warning_and_continue_if_not_found: 266 logging.warn(msg) 267 return ToStr(default_value) 268 curr_obj = curr_obj[param] 269 270 return ToStr(curr_obj) 271 272 def _setUpClass(self): 273 """Proxy function to guarantee the base implementation of setUpClass 274 is called. 275 """ 276 return self.setUpClass() 277 278 def setUpClass(self): 279 """Setup function that will be called before executing any test case in 280 the test class. 281 282 To signal setup failure, return False or raise an exception. If 283 exceptions were raised, the stack trace would appear in log, but the 284 exceptions would not propagate to upper levels. 285 286 Implementation is optional. 287 """ 288 pass 289 290 def _tearDownClass(self): 291 """Proxy function to guarantee the base implementation of tearDownClass 292 is called. 293 """ 294 ret = self.tearDownClass() 295 if self.log_uploading.enabled: 296 self.log_uploading.UploadLogs() 297 if self.web.enabled: 298 message_b = self.web.GenerateReportMessage(self.results.requested, 299 self.results.executed) 300 else: 301 message_b = '' 302 303 report_proto_path = os.path.join(logging.log_path, 304 _REPORT_MESSAGE_FILE_NAME) 305 306 if message_b: 307 logging.info('Result proto message path: %s', report_proto_path) 308 309 with open(report_proto_path, "wb") as f: 310 f.write(message_b) 311 312 return ret 313 314 def tearDownClass(self): 315 """Teardown function that will be called after all the selected test 316 cases in the test class have been executed. 317 318 Implementation is optional. 319 """ 320 pass 321 322 def _testEntry(self, test_record): 323 """Internal function to be called upon entry of a test case. 324 325 Args: 326 test_record: The TestResultRecord object for the test case going to 327 be executed. 328 """ 329 self._current_record = test_record 330 if self.web.enabled: 331 self.web.AddTestReport(test_record.test_name) 332 333 def _setUp(self, test_name): 334 """Proxy function to guarantee the base implementation of setUp is 335 called. 336 """ 337 if self.systrace.enabled: 338 self.systrace.StartSystrace() 339 return self.setUp() 340 341 def setUp(self): 342 """Setup function that will be called every time before executing each 343 test case in the test class. 344 345 To signal setup failure, return False or raise an exception. If 346 exceptions were raised, the stack trace would appear in log, but the 347 exceptions would not propagate to upper levels. 348 349 Implementation is optional. 350 """ 351 352 def _testExit(self): 353 """Internal function to be called upon exit of a test.""" 354 self._current_record = None 355 356 def _tearDown(self, test_name): 357 """Proxy function to guarantee the base implementation of tearDown 358 is called. 359 """ 360 if self.systrace.enabled: 361 self.systrace.ProcessAndUploadSystrace(test_name) 362 self.tearDown() 363 364 def tearDown(self): 365 """Teardown function that will be called every time a test case has 366 been executed. 367 368 Implementation is optional. 369 """ 370 371 def _onFail(self): 372 """Proxy function to guarantee the base implementation of onFail is 373 called. 374 """ 375 record = self._current_record 376 logging.error(record.details) 377 begin_time = logger.epochToLogLineTimestamp(record.begin_time) 378 logging.info(RESULT_LINE_TEMPLATE, record.test_name, record.result) 379 if self.web.enabled: 380 self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_FAIL) 381 self.onFail(record.test_name, begin_time) 382 if self._bug_report_on_failure: 383 self.CatchBugReport('%s-%s' % (self.TAG, record.test_name)) 384 385 def onFail(self, test_name, begin_time): 386 """A function that is executed upon a test case failure. 387 388 User implementation is optional. 389 390 Args: 391 test_name: Name of the test that triggered this function. 392 begin_time: Logline format timestamp taken when the test started. 393 """ 394 395 def _onPass(self): 396 """Proxy function to guarantee the base implementation of onPass is 397 called. 398 """ 399 record = self._current_record 400 test_name = record.test_name 401 begin_time = logger.epochToLogLineTimestamp(record.begin_time) 402 msg = record.details 403 if msg: 404 logging.info(msg) 405 logging.info(RESULT_LINE_TEMPLATE, test_name, record.result) 406 if self.web.enabled: 407 self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_PASS) 408 self.onPass(test_name, begin_time) 409 410 def onPass(self, test_name, begin_time): 411 """A function that is executed upon a test case passing. 412 413 Implementation is optional. 414 415 Args: 416 test_name: Name of the test that triggered this function. 417 begin_time: Logline format timestamp taken when the test started. 418 """ 419 420 def _onSkip(self): 421 """Proxy function to guarantee the base implementation of onSkip is 422 called. 423 """ 424 record = self._current_record 425 test_name = record.test_name 426 begin_time = logger.epochToLogLineTimestamp(record.begin_time) 427 logging.info(RESULT_LINE_TEMPLATE, test_name, record.result) 428 logging.info("Reason to skip: %s", record.details) 429 if self.web.enabled: 430 self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_SKIP) 431 self.onSkip(test_name, begin_time) 432 433 def onSkip(self, test_name, begin_time): 434 """A function that is executed upon a test case being skipped. 435 436 Implementation is optional. 437 438 Args: 439 test_name: Name of the test that triggered this function. 440 begin_time: Logline format timestamp taken when the test started. 441 """ 442 443 def _onSilent(self): 444 """Proxy function to guarantee the base implementation of onSilent is 445 called. 446 """ 447 record = self._current_record 448 test_name = record.test_name 449 begin_time = logger.epochToLogLineTimestamp(record.begin_time) 450 if self.web.enabled: 451 self.web.SetTestResult(None) 452 self.onSilent(test_name, begin_time) 453 454 def onSilent(self, test_name, begin_time): 455 """A function that is executed upon a test case being marked as silent. 456 457 Implementation is optional. 458 459 Args: 460 test_name: Name of the test that triggered this function. 461 begin_time: Logline format timestamp taken when the test started. 462 """ 463 464 def _onException(self): 465 """Proxy function to guarantee the base implementation of onException 466 is called. 467 """ 468 record = self._current_record 469 test_name = record.test_name 470 logging.exception(record.details) 471 begin_time = logger.epochToLogLineTimestamp(record.begin_time) 472 if self.web.enabled: 473 self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_EXCEPTION) 474 self.onException(test_name, begin_time) 475 if self._bug_report_on_failure: 476 self.CatchBugReport('%s-%s' % (self.TAG, record.test_name)) 477 478 def onException(self, test_name, begin_time): 479 """A function that is executed upon an unhandled exception from a test 480 case. 481 482 Implementation is optional. 483 484 Args: 485 test_name: Name of the test that triggered this function. 486 begin_time: Logline format timestamp taken when the test started. 487 """ 488 489 def _exec_procedure_func(self, func): 490 """Executes a procedure function like onPass, onFail etc. 491 492 This function will alternate the 'Result' of the test's record if 493 exceptions happened when executing the procedure function. 494 495 This will let signals.TestAbortAll through so abortAll works in all 496 procedure functions. 497 498 Args: 499 func: The procedure function to be executed. 500 """ 501 record = self._current_record 502 if record is None: 503 logging.error("Cannot execute %s. No record for current test.", 504 func.__name__) 505 return 506 try: 507 func() 508 except signals.TestAbortAll: 509 raise 510 except Exception as e: 511 logging.exception("Exception happened when executing %s for %s.", 512 func.__name__, record.test_name) 513 record.addError(func.__name__, e) 514 515 def addTableToResult(self, name, rows): 516 """Adds a table to current test record. 517 518 A subclass can call this method to add a table to _current_record when 519 running test cases. 520 521 Args: 522 name: String, the table name. 523 rows: A 2-dimensional list which contains the data. 524 """ 525 self._current_record.addTable(name, rows) 526 527 def filterOneTest(self, test_name, test_filter=None): 528 """Check test filters for a test name. 529 530 The first layer of filter is user defined test filters: 531 if a include filter is not empty, only tests in include filter will 532 be executed regardless whether they are also in exclude filter. Else 533 if include filter is empty, only tests not in exclude filter will be 534 executed. 535 536 The second layer of filter is checking _skip_all_testcases flag: 537 the subclass may set _skip_all_testcases to True in its implementation 538 of setUpClass. If the flag is set, this method raises signals.TestSkip. 539 540 The third layer of filter is checking abi bitness: 541 if a test has a suffix indicating the intended architecture bitness, 542 and the current abi bitness information is available, non matching tests 543 will be skipped. By our convention, this function will look for bitness in suffix 544 formated as "32bit", "32Bit", "32BIT", or 64 bit equivalents. 545 546 This method assumes const.SUFFIX_32BIT and const.SUFFIX_64BIT are in lower cases. 547 548 Args: 549 test_name: string, name of a test 550 test_filter: Filter object, test filter 551 552 Raises: 553 signals.TestSilent if a test should not be executed 554 signals.TestSkip if a test should be logged but not be executed 555 """ 556 self._filterOneTestThroughTestFilter(test_name, test_filter) 557 self._filterOneTestThroughAbiBitness(test_name) 558 559 def _filterOneTestThroughTestFilter(self, test_name, test_filter=None): 560 """Check test filter for the given test name. 561 562 Args: 563 test_name: string, name of a test 564 565 Raises: 566 signals.TestSilent if a test should not be executed 567 signals.TestSkip if a test should be logged but not be executed 568 """ 569 if not test_filter: 570 test_filter = self.test_filter 571 572 if not test_filter.Filter(test_name): 573 raise signals.TestSilent("Test case '%s' did not pass filters.") 574 575 if self._skip_all_testcases: 576 raise signals.TestSkip("All test cases skipped.") 577 578 def _filterOneTestThroughAbiBitness(self, test_name): 579 """Check test filter for the given test name. 580 581 Args: 582 test_name: string, name of a test 583 584 Raises: 585 signals.TestSilent if a test should not be executed 586 """ 587 asserts.skipIf( 588 self.abi_bitness and 589 ((self.skip_on_32bit_abi is True) and self.abi_bitness == "32") or 590 ((self.skip_on_64bit_abi is True) and self.abi_bitness == "64") or 591 (test_name.lower().endswith(const.SUFFIX_32BIT) and 592 self.abi_bitness != "32") or 593 (test_name.lower().endswith(const.SUFFIX_64BIT) and 594 self.abi_bitness != "64" and not self.run_32bit_on_64bit_abi), 595 "Test case '{}' excluded as ABI bitness is {}.".format( 596 test_name, self.abi_bitness)) 597 598 def execOneTest(self, test_name, test_func, args, **kwargs): 599 """Executes one test case and update test results. 600 601 Executes one test case, create a records.TestResultRecord object with 602 the execution information, and add the record to the test class's test 603 results. 604 605 Args: 606 test_name: Name of the test. 607 test_func: The test function. 608 args: A tuple of params. 609 kwargs: Extra kwargs. 610 """ 611 is_silenced = False 612 tr_record = records.TestResultRecord(test_name, self.TAG) 613 tr_record.testBegin() 614 logging.info("%s %s", TEST_CASE_TOKEN, test_name) 615 verdict = None 616 finished = False 617 try: 618 ret = self._testEntry(tr_record) 619 asserts.assertTrue(ret is not False, 620 "Setup test entry for %s failed." % test_name) 621 self.filterOneTest(test_name) 622 try: 623 ret = self._setUp(test_name) 624 asserts.assertTrue(ret is not False, 625 "Setup for %s failed." % test_name) 626 627 if args or kwargs: 628 verdict = test_func(*args, **kwargs) 629 else: 630 verdict = test_func() 631 finished = True 632 finally: 633 self._tearDown(test_name) 634 except (signals.TestFailure, AssertionError) as e: 635 tr_record.testFail(e) 636 self._exec_procedure_func(self._onFail) 637 finished = True 638 except signals.TestSkip as e: 639 # Test skipped. 640 tr_record.testSkip(e) 641 self._exec_procedure_func(self._onSkip) 642 finished = True 643 except (signals.TestAbortClass, signals.TestAbortAll) as e: 644 # Abort signals, pass along. 645 tr_record.testFail(e) 646 finished = True 647 raise e 648 except signals.TestPass as e: 649 # Explicit test pass. 650 tr_record.testPass(e) 651 self._exec_procedure_func(self._onPass) 652 finished = True 653 except signals.TestSilent as e: 654 # Suppress test reporting. 655 is_silenced = True 656 self._exec_procedure_func(self._onSilent) 657 self.results.removeRecord(tr_record) 658 finished = True 659 except Exception as e: 660 # Exception happened during test. 661 logging.exception(e) 662 tr_record.testError(e) 663 self._exec_procedure_func(self._onException) 664 self._exec_procedure_func(self._onFail) 665 finished = True 666 else: 667 # Keep supporting return False for now. 668 # TODO(angli): Deprecate return False support. 669 if verdict or (verdict is None): 670 # Test passed. 671 tr_record.testPass() 672 self._exec_procedure_func(self._onPass) 673 return 674 # Test failed because it didn't return True. 675 # This should be removed eventually. 676 tr_record.testFail() 677 self._exec_procedure_func(self._onFail) 678 finished = True 679 finally: 680 if not finished: 681 for device in self.android_devices: 682 device.shell.enabled = False 683 684 logging.error('Test timed out.') 685 tr_record.testError() 686 self._exec_procedure_func(self._onException) 687 self._exec_procedure_func(self._onFail) 688 689 if not is_silenced: 690 self.results.addRecord(tr_record) 691 self._testExit() 692 693 def runGeneratedTests(self, 694 test_func, 695 settings, 696 args=None, 697 kwargs=None, 698 tag="", 699 name_func=None): 700 """Runs generated test cases. 701 702 Generated test cases are not written down as functions, but as a list 703 of parameter sets. This way we reduce code repetition and improve 704 test case scalability. 705 706 Args: 707 test_func: The common logic shared by all these generated test 708 cases. This function should take at least one argument, 709 which is a parameter set. 710 settings: A list of strings representing parameter sets. These are 711 usually json strings that get loaded in the test_func. 712 args: Iterable of additional position args to be passed to 713 test_func. 714 kwargs: Dict of additional keyword args to be passed to test_func 715 tag: Name of this group of generated test cases. Ignored if 716 name_func is provided and operates properly. 717 name_func: A function that takes a test setting and generates a 718 proper test name. The test name should be shorter than 719 utils.MAX_FILENAME_LEN. Names over the limit will be 720 truncated. 721 722 Returns: 723 A list of settings that did not pass. 724 """ 725 args = args or () 726 kwargs = kwargs or {} 727 failed_settings = [] 728 for s in settings: 729 test_name = "{} {}".format(tag, s) 730 if name_func: 731 try: 732 test_name = name_func(s, *args, **kwargs) 733 except: 734 logging.exception(("Failed to get test name from " 735 "test_func. Fall back to default %s"), 736 test_name) 737 738 tr_record = records.TestResultRecord(test_name, self.TAG) 739 self.results.requested.append(tr_record) 740 if len(test_name) > utils.MAX_FILENAME_LEN: 741 test_name = test_name[:utils.MAX_FILENAME_LEN] 742 previous_success_cnt = len(self.results.passed) 743 self.execOneTest(test_name, test_func, (s, ) + args, **kwargs) 744 if len(self.results.passed) - previous_success_cnt != 1: 745 failed_settings.append(s) 746 return failed_settings 747 748 def _exec_func(self, func, *args): 749 """Executes a function with exception safeguard. 750 751 This will let signals.TestAbortAll through so abortAll works in all 752 procedure functions. 753 754 Args: 755 func: Function to be executed. 756 args: Arguments to be passed to the function. 757 758 Returns: 759 Whatever the function returns, or False if non-caught exception 760 occurred. 761 """ 762 try: 763 return func(*args) 764 except signals.TestAbortAll: 765 raise 766 except: 767 logging.exception("Exception happened when executing %s in %s.", 768 func.__name__, self.TAG) 769 return False 770 771 def _get_all_test_names(self): 772 """Finds all the function names that match the test case naming 773 convention in this class. 774 775 Returns: 776 A list of strings, each is a test case name. 777 """ 778 test_names = [] 779 for name in dir(self): 780 if name.startswith(STR_TEST) or name.startswith(STR_GENERATE): 781 attr_func = getattr(self, name) 782 if hasattr(attr_func, "__call__"): 783 test_names.append(name) 784 return test_names 785 786 def _get_test_funcs(self, test_names): 787 """Obtain the actual functions of test cases based on test names. 788 789 Args: 790 test_names: A list of strings, each string is a test case name. 791 792 Returns: 793 A list of tuples of (string, function). String is the test case 794 name, function is the actual test case function. 795 796 Raises: 797 errors.USERError is raised if the test name does not follow 798 naming convention "test_*". This can only be caused by user input 799 here. 800 """ 801 test_funcs = [] 802 for test_name in test_names: 803 if not hasattr(self, test_name): 804 logging.warning("%s does not have test case %s.", self.TAG, 805 test_name) 806 elif (test_name.startswith(STR_TEST) or 807 test_name.startswith(STR_GENERATE)): 808 test_funcs.append((test_name, getattr(self, test_name))) 809 else: 810 msg = ("Test case name %s does not follow naming convention " 811 "test*, abort.") % test_name 812 raise errors.USERError(msg) 813 814 return test_funcs 815 816 def run(self, test_names=None): 817 """Runs test cases within a test class by the order they appear in the 818 execution list. 819 820 One of these test cases lists will be executed, shown here in priority 821 order: 822 1. The test_names list, which is passed from cmd line. Invalid names 823 are guarded by cmd line arg parsing. 824 2. The self.tests list defined in test class. Invalid names are 825 ignored. 826 3. All function that matches test case naming convention in the test 827 class. 828 829 Args: 830 test_names: A list of string that are test case names requested in 831 cmd line. 832 833 Returns: 834 The test results object of this class. 835 """ 836 logging.info("==========> %s <==========", self.TAG) 837 # Devise the actual test cases to run in the test class. 838 if not test_names: 839 if self.tests: 840 # Specified by run list in class. 841 test_names = list(self.tests) 842 else: 843 # No test case specified by user, execute all in the test class 844 test_names = self._get_all_test_names() 845 self.results.requested = [ 846 records.TestResultRecord(test_name, self.TAG) 847 for test_name in test_names if test_name.startswith(STR_TEST) 848 ] 849 tests = self._get_test_funcs(test_names) 850 # Setup for the class. 851 try: 852 if self._setUpClass() is False: 853 raise signals.TestFailure("Failed to setup %s." % self.TAG) 854 except Exception as e: 855 logging.exception("Failed to setup %s.", self.TAG) 856 self.results.failClass(self.TAG, e) 857 self._exec_func(self._tearDownClass) 858 return self.results 859 # Run tests in order. 860 try: 861 for test_name, test_func in tests: 862 if test_name.startswith(STR_GENERATE): 863 logging.info( 864 "Executing generated test trigger function '%s'", 865 test_name) 866 test_func() 867 logging.info("Finished '%s'", test_name) 868 else: 869 self.execOneTest(test_name, test_func, None) 870 if self._skip_all_testcases and not self.results.executed: 871 self.results.skipClass( 872 self.TAG, 873 "All test cases skipped; unable to find any test case.") 874 return self.results 875 except signals.TestAbortClass: 876 logging.info("Received TestAbortClass signal") 877 return self.results 878 except signals.TestAbortAll as e: 879 logging.info("Received TestAbortAll signal") 880 # Piggy-back test results on this exception object so we don't lose 881 # results from this test class. 882 setattr(e, "results", self.results) 883 raise e 884 except Exception as e: 885 # Exception happened during test. 886 logging.exception(e) 887 raise e 888 finally: 889 self._exec_func(self._tearDownClass) 890 if self.web.enabled: 891 name, timestamp = self.web.GetTestModuleKeys() 892 self.results.setTestModuleKeys(name, timestamp) 893 logging.info("Summary for test class %s: %s", self.TAG, 894 self.results.summary()) 895 896 def cleanUp(self): 897 """A function that is executed upon completion of all tests cases 898 selected in the test class. 899 900 This function should clean up objects initialized in the constructor by 901 user. 902 """ 903 904 def CatchBugReport(self, prefix=''): 905 """Get device bugreport through adb command. 906 907 Args: 908 prefix: string, file name prefix. Usually in format of 909 <test_module>-<test_case> 910 """ 911 if prefix: 912 prefix = re.sub('[^\w\-_\. ]', '_', prefix) + '_' 913 914 for i in range(len(self.android_devices)): 915 device = self.android_devices[i] 916 bug_report_file_name = prefix + _BUG_REPORT_FILE_PREFIX + str( 917 i) + _BUG_REPORT_FILE_EXTENSION 918 bug_report_file_path = os.path.join(logging.log_path, 919 bug_report_file_name) 920 921 logging.info('Catching bugreport %s' % bug_report_file_path) 922 device.adb.bugreport(bug_report_file_path) 923