1#!/usr/bin/env python3 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import fnmatch 18import functools 19import importlib 20import logging 21import os 22import traceback 23from concurrent.futures import ThreadPoolExecutor 24 25from acts import asserts 26from acts import error 27from acts import keys 28from acts import logger 29from acts import records 30from acts import signals 31from acts import tracelogger 32from acts import utils 33from acts.event import event_bus 34from acts.event import subscription_bundle 35from acts.event.decorators import subscribe_static 36from acts.event.event import TestCaseBeginEvent 37from acts.event.event import TestCaseEndEvent 38from acts.event.event import TestClassBeginEvent 39from acts.event.event import TestClassEndEvent 40from acts.event.subscription_bundle import SubscriptionBundle 41 42from mobly.base_test import BaseTestClass as MoblyBaseTest 43from mobly.records import ExceptionRecord 44 45# Macro strings for test result reporting 46TEST_CASE_TOKEN = "[Test Case]" 47RESULT_LINE_TEMPLATE = TEST_CASE_TOKEN + " %s %s" 48 49 50@subscribe_static(TestCaseBeginEvent) 51def _logcat_log_test_begin(event): 52 """Ensures that logcat is running. Write a logcat line indicating test case 53 begin.""" 54 test_instance = event.test_class 55 try: 56 for ad in getattr(test_instance, 'android_devices', []): 57 if not ad.is_adb_logcat_on: 58 ad.start_adb_logcat() 59 # Write test start token to adb log if android device is attached. 60 if not ad.skip_sl4a and ad.droid: 61 ad.droid.logV("%s BEGIN %s" % 62 (TEST_CASE_TOKEN, event.test_case_name)) 63 64 except error.ActsError as e: 65 test_instance.results.error.append( 66 ExceptionRecord(e, 'Logcat for test begin: %s' % 67 event.test_case_name)) 68 test_instance.log.error('BaseTest setup_test error: %s' % e.details) 69 except Exception as e: 70 test_instance.log.warning( 71 'Unable to send BEGIN log command to all devices.') 72 test_instance.log.warning('Error: %s' % e) 73 74 75@subscribe_static(TestCaseEndEvent) 76def _logcat_log_test_end(event): 77 """Write a logcat line indicating test case end.""" 78 test_instance = event.test_class 79 try: 80 # Write test end token to adb log if android device is attached. 81 for ad in getattr(test_instance, 'android_devices', []): 82 if not ad.skip_sl4a and ad.droid: 83 ad.droid.logV("%s END %s" % 84 (TEST_CASE_TOKEN, event.test_case_name)) 85 86 except error.ActsError as e: 87 test_instance.results.error.append( 88 ExceptionRecord(e, 89 'Logcat for test end: %s' % event.test_case_name)) 90 test_instance.log.error('BaseTest teardown_test error: %s' % e.details) 91 except Exception as e: 92 test_instance.log.warning( 93 'Unable to send END log command to all devices.') 94 test_instance.log.warning('Error: %s' % e) 95 96 97@subscribe_static(TestCaseBeginEvent) 98def _syslog_log_test_begin(event): 99 """This adds a BEGIN log message with the test name to the syslog of any 100 Fuchsia device""" 101 test_instance = event.test_class 102 try: 103 for fd in getattr(test_instance, 'fuchsia_devices', []): 104 if not fd.skip_sl4f: 105 fd.logging_lib.logI("%s BEGIN %s" % 106 (TEST_CASE_TOKEN, event.test_case_name)) 107 108 except Exception as e: 109 test_instance.log.warning( 110 'Unable to send BEGIN log command to all devices.') 111 test_instance.log.warning('Error: %s' % e) 112 113 114@subscribe_static(TestCaseEndEvent) 115def _syslog_log_test_end(event): 116 """This adds a END log message with the test name to the syslog of any 117 Fuchsia device""" 118 test_instance = event.test_class 119 try: 120 for fd in getattr(test_instance, 'fuchsia_devices', []): 121 if not fd.skip_sl4f: 122 fd.logging_lib.logI("%s END %s" % 123 (TEST_CASE_TOKEN, event.test_case_name)) 124 125 except Exception as e: 126 test_instance.log.warning( 127 'Unable to send END log command to all devices.') 128 test_instance.log.warning('Error: %s' % e) 129 130 131event_bus.register_subscription(_logcat_log_test_begin.subscription) 132event_bus.register_subscription(_logcat_log_test_end.subscription) 133event_bus.register_subscription(_syslog_log_test_begin.subscription) 134event_bus.register_subscription(_syslog_log_test_end.subscription) 135 136 137class Error(Exception): 138 """Raised for exceptions that occured in BaseTestClass.""" 139 140 141class BaseTestClass(MoblyBaseTest): 142 """Base class for all test classes to inherit from. Inherits some 143 functionality from Mobly's base test class. 144 145 This class gets all the controller objects from test_runner and executes 146 the test cases requested within itself. 147 148 Most attributes of this class are set at runtime based on the configuration 149 provided. 150 151 Attributes: 152 tests: A list of strings, each representing a test case name. 153 TAG: A string used to refer to a test class. Default is the test class 154 name. 155 log: A logger object used for logging. 156 results: A records.TestResult object for aggregating test results from 157 the execution of test cases. 158 controller_configs: A dict of controller configs provided by the user 159 via the testbed config. 160 consecutive_failures: Tracks the number of consecutive test case 161 failures within this class. 162 consecutive_failure_limit: Number of consecutive test failures to allow 163 before blocking remaining tests in the same 164 test class. 165 size_limit_reached: True if the size of the log directory has reached 166 its limit. 167 current_test_name: A string that's the name of the test case currently 168 being executed. If no test is executing, this should 169 be None. 170 """ 171 172 TAG = None 173 174 def __init__(self, configs): 175 """Initializes a BaseTestClass given a TestRunConfig, which provides 176 all of the config information for this test class. 177 178 Args: 179 configs: A config_parser.TestRunConfig object. 180 """ 181 super().__init__(configs) 182 183 self.__handle_file_user_params() 184 185 self.class_subscriptions = SubscriptionBundle() 186 self.class_subscriptions.register() 187 self.all_subscriptions = [self.class_subscriptions] 188 189 self.current_test_name = None 190 self.log = tracelogger.TraceLogger(logging.getLogger()) 191 # TODO: remove after converging log path definitions with mobly 192 self.log_path = configs.log_path 193 194 self.consecutive_failures = 0 195 self.consecutive_failure_limit = self.user_params.get( 196 'consecutive_failure_limit', -1) 197 self.size_limit_reached = False 198 self.retryable_exceptions = signals.TestFailure 199 200 def _import_builtin_controllers(self): 201 """Import built-in controller modules. 202 203 Go through the testbed configs, find any built-in controller configs 204 and import the corresponding controller module from acts.controllers 205 package. 206 207 Returns: 208 A list of controller modules. 209 """ 210 builtin_controllers = [] 211 for ctrl_name in keys.Config.builtin_controller_names.value: 212 if ctrl_name in self.controller_configs: 213 module_name = keys.get_module_name(ctrl_name) 214 module = importlib.import_module("acts.controllers.%s" % 215 module_name) 216 builtin_controllers.append(module) 217 return builtin_controllers 218 219 def __handle_file_user_params(self): 220 """For backwards compatibility, moves all contents of the "files" dict 221 into the root level of user_params. 222 223 This allows existing tests to run with the new Mobly-style format 224 without needing to make changes. 225 """ 226 for key, value in self.user_params.items(): 227 if key.endswith('files') and isinstance(value, dict): 228 new_user_params = dict(value) 229 new_user_params.update(self.user_params) 230 self.user_params = new_user_params 231 break 232 233 @staticmethod 234 def get_module_reference_name(a_module): 235 """Returns the module's reference name. 236 237 This is largely for backwards compatibility with log parsing. If the 238 module defines ACTS_CONTROLLER_REFERENCE_NAME, it will return that 239 value, or the module's submodule name. 240 241 Args: 242 a_module: Any module. Ideally, a controller module. 243 Returns: 244 A string corresponding to the module's name. 245 """ 246 if hasattr(a_module, 'ACTS_CONTROLLER_REFERENCE_NAME'): 247 return a_module.ACTS_CONTROLLER_REFERENCE_NAME 248 else: 249 return a_module.__name__.split('.')[-1] 250 251 def register_controller(self, 252 controller_module, 253 required=True, 254 builtin=False): 255 """Registers an ACTS controller module for a test class. Invokes Mobly's 256 implementation of register_controller. 257 258 An ACTS controller module is a Python lib that can be used to control 259 a device, service, or equipment. To be ACTS compatible, a controller 260 module needs to have the following members: 261 262 def create(configs): 263 [Required] Creates controller objects from configurations. 264 Args: 265 configs: A list of serialized data like string/dict. Each 266 element of the list is a configuration for a 267 controller object. 268 Returns: 269 A list of objects. 270 271 def destroy(objects): 272 [Required] Destroys controller objects created by the create 273 function. Each controller object shall be properly cleaned up 274 and all the resources held should be released, e.g. memory 275 allocation, sockets, file handlers etc. 276 Args: 277 A list of controller objects created by the create function. 278 279 def get_info(objects): 280 [Optional] Gets info from the controller objects used in a test 281 run. The info will be included in test_result_summary.json under 282 the key "ControllerInfo". Such information could include unique 283 ID, version, or anything that could be useful for describing the 284 test bed and debugging. 285 Args: 286 objects: A list of controller objects created by the create 287 function. 288 Returns: 289 A list of json serializable objects, each represents the 290 info of a controller object. The order of the info object 291 should follow that of the input objects. 292 Registering a controller module declares a test class's dependency the 293 controller. If the module config exists and the module matches the 294 controller interface, controller objects will be instantiated with 295 corresponding configs. The module should be imported first. 296 297 Args: 298 controller_module: A module that follows the controller module 299 interface. 300 required: A bool. If True, failing to register the specified 301 controller module raises exceptions. If False, returns None upon 302 failures. 303 builtin: Specifies that the module is a builtin controller module in 304 ACTS. If true, adds itself to test attributes. 305 Returns: 306 A list of controller objects instantiated from controller_module, or 307 None. 308 309 Raises: 310 When required is True, ControllerError is raised if no corresponding 311 config can be found. 312 Regardless of the value of "required", ControllerError is raised if 313 the controller module has already been registered or any other error 314 occurred in the registration process. 315 """ 316 module_ref_name = self.get_module_reference_name(controller_module) 317 module_config_name = controller_module.MOBLY_CONTROLLER_CONFIG_NAME 318 319 # Get controller objects from Mobly's register_controller 320 controllers = self._controller_manager.register_controller( 321 controller_module, required=required) 322 if not controllers: 323 return None 324 325 # Log controller information 326 # Implementation of "get_info" is optional for a controller module. 327 if hasattr(controller_module, "get_info"): 328 controller_info = controller_module.get_info(controllers) 329 self.log.info("Controller %s: %s", module_config_name, 330 controller_info) 331 332 if builtin: 333 setattr(self, module_ref_name, controllers) 334 return controllers 335 336 def _setup_class(self): 337 """Proxy function to guarantee the base implementation of setup_class 338 is called. 339 """ 340 event_bus.post(TestClassBeginEvent(self)) 341 # Import and register the built-in controller modules specified 342 # in testbed config. 343 for module in self._import_builtin_controllers(): 344 self.register_controller(module, builtin=True) 345 return self.setup_class() 346 347 def _teardown_class(self): 348 """Proxy function to guarantee the base implementation of teardown_class 349 is called. 350 """ 351 super()._teardown_class() 352 event_bus.post(TestClassEndEvent(self, self.results)) 353 354 def _setup_test(self, test_name): 355 """Proxy function to guarantee the base implementation of setup_test is 356 called. 357 """ 358 self.current_test_name = test_name 359 360 # Skip the test if the consecutive test case failure limit is reached. 361 if self.consecutive_failures == self.consecutive_failure_limit: 362 raise signals.TestError('Consecutive test failure') 363 364 return self.setup_test() 365 366 def setup_test(self): 367 """Setup function that will be called every time before executing each 368 test case in the test class. 369 370 To signal setup failure, return False or raise an exception. If 371 exceptions were raised, the stack trace would appear in log, but the 372 exceptions would not propagate to upper levels. 373 374 Implementation is optional. 375 """ 376 return True 377 378 def _teardown_test(self, test_name): 379 """Proxy function to guarantee the base implementation of teardown_test 380 is called. 381 """ 382 self.log.debug('Tearing down test %s' % test_name) 383 self.teardown_test() 384 385 def _on_fail(self, record): 386 """Proxy function to guarantee the base implementation of on_fail is 387 called. 388 389 Args: 390 record: The records.TestResultRecord object for the failed test 391 case. 392 """ 393 self.consecutive_failures += 1 394 if record.details: 395 self.log.error(record.details) 396 self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result) 397 self.on_fail(record.test_name, record.begin_time) 398 399 def on_fail(self, test_name, begin_time): 400 """A function that is executed upon a test case failure. 401 402 User implementation is optional. 403 404 Args: 405 test_name: Name of the test that triggered this function. 406 begin_time: Logline format timestamp taken when the test started. 407 """ 408 def _on_pass(self, record): 409 """Proxy function to guarantee the base implementation of on_pass is 410 called. 411 412 Args: 413 record: The records.TestResultRecord object for the passed test 414 case. 415 """ 416 self.consecutive_failures = 0 417 msg = record.details 418 if msg: 419 self.log.info(msg) 420 self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result) 421 self.on_pass(record.test_name, record.begin_time) 422 423 def on_pass(self, test_name, begin_time): 424 """A function that is executed upon a test case passing. 425 426 Implementation is optional. 427 428 Args: 429 test_name: Name of the test that triggered this function. 430 begin_time: Logline format timestamp taken when the test started. 431 """ 432 def _on_skip(self, record): 433 """Proxy function to guarantee the base implementation of on_skip is 434 called. 435 436 Args: 437 record: The records.TestResultRecord object for the skipped test 438 case. 439 """ 440 self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result) 441 self.log.info("Reason to skip: %s", record.details) 442 self.on_skip(record.test_name, record.begin_time) 443 444 def on_skip(self, test_name, begin_time): 445 """A function that is executed upon a test case being skipped. 446 447 Implementation is optional. 448 449 Args: 450 test_name: Name of the test that triggered this function. 451 begin_time: Logline format timestamp taken when the test started. 452 """ 453 def _on_exception(self, record): 454 """Proxy function to guarantee the base implementation of on_exception 455 is called. 456 457 Args: 458 record: The records.TestResultRecord object for the failed test 459 case. 460 """ 461 self.log.exception(record.details) 462 self.on_exception(record.test_name, record.begin_time) 463 464 def on_exception(self, test_name, begin_time): 465 """A function that is executed upon an unhandled exception from a test 466 case. 467 468 Implementation is optional. 469 470 Args: 471 test_name: Name of the test that triggered this function. 472 begin_time: Logline format timestamp taken when the test started. 473 """ 474 def on_retry(self): 475 """Function to run before retrying a test through get_func_with_retry. 476 477 This function runs when a test is automatically retried. The function 478 can be used to modify internal test parameters, for example, to retry 479 a test with slightly different input variables. 480 """ 481 def _exec_procedure_func(self, func, tr_record): 482 """Executes a procedure function like on_pass, on_fail etc. 483 484 This function will alternate the 'Result' of the test's record if 485 exceptions happened when executing the procedure function. 486 487 This will let signals.TestAbortAll through so abort_all works in all 488 procedure functions. 489 490 Args: 491 func: The procedure function to be executed. 492 tr_record: The TestResultRecord object associated with the test 493 case executed. 494 """ 495 try: 496 func(tr_record) 497 except signals.TestAbortAll: 498 raise 499 except Exception as e: 500 self.log.exception("Exception happened when executing %s for %s.", 501 func.__name__, self.current_test_name) 502 tr_record.add_error(func.__name__, e) 503 504 def exec_one_testcase(self, test_name, test_func): 505 """Executes one test case and update test results. 506 507 Executes one test case, create a records.TestResultRecord object with 508 the execution information, and add the record to the test class's test 509 results. 510 511 Args: 512 test_name: Name of the test. 513 test_func: The test function. 514 """ 515 class_name = self.__class__.__name__ 516 tr_record = records.TestResultRecord(test_name, class_name) 517 tr_record.test_begin() 518 self.begin_time = int(tr_record.begin_time) 519 self.log_begin_time = tr_record.log_begin_time 520 self.test_name = tr_record.test_name 521 event_bus.post(TestCaseBeginEvent(self, self.test_name)) 522 self.log.info("%s %s", TEST_CASE_TOKEN, test_name) 523 524 # Enable test retry if specified in the ACTS config 525 retry_tests = self.user_params.get('retry_tests', []) 526 full_test_name = '%s.%s' % (class_name, self.test_name) 527 if any(name in retry_tests for name in [class_name, full_test_name]): 528 test_func = self.get_func_with_retry(test_func) 529 530 verdict = None 531 test_signal = None 532 try: 533 try: 534 ret = self._setup_test(self.test_name) 535 asserts.assert_true(ret is not False, 536 "Setup for %s failed." % test_name) 537 verdict = test_func() 538 finally: 539 try: 540 self._teardown_test(self.test_name) 541 except signals.TestAbortAll: 542 raise 543 except Exception as e: 544 self.log.error(traceback.format_exc()) 545 tr_record.add_error("teardown_test", e) 546 except (signals.TestFailure, AssertionError) as e: 547 test_signal = e 548 if self.user_params.get( 549 keys.Config.key_test_failure_tracebacks.value, False): 550 self.log.exception(e) 551 tr_record.test_fail(e) 552 except signals.TestSkip as e: 553 # Test skipped. 554 test_signal = e 555 tr_record.test_skip(e) 556 except (signals.TestAbortClass, signals.TestAbortAll) as e: 557 # Abort signals, pass along. 558 test_signal = e 559 tr_record.test_fail(e) 560 raise e 561 except signals.TestPass as e: 562 # Explicit test pass. 563 test_signal = e 564 tr_record.test_pass(e) 565 except Exception as e: 566 test_signal = e 567 self.log.error(traceback.format_exc()) 568 # Exception happened during test. 569 tr_record.test_error(e) 570 else: 571 if verdict or (verdict is None): 572 # Test passed. 573 tr_record.test_pass() 574 return 575 tr_record.test_fail() 576 finally: 577 tr_record.update_record() 578 try: 579 # Execute post-test procedures 580 result = tr_record.result 581 if result == records.TestResultEnums.TEST_RESULT_PASS: 582 self._exec_procedure_func(self._on_pass, tr_record) 583 elif result == records.TestResultEnums.TEST_RESULT_FAIL: 584 self._exec_procedure_func(self._on_fail, tr_record) 585 elif result == records.TestResultEnums.TEST_RESULT_SKIP: 586 self._exec_procedure_func(self._on_skip, tr_record) 587 elif result == records.TestResultEnums.TEST_RESULT_ERROR: 588 self._exec_procedure_func(self._on_exception, tr_record) 589 self._exec_procedure_func(self._on_fail, tr_record) 590 finally: 591 self.results.add_record(tr_record) 592 self.summary_writer.dump(tr_record.to_dict(), 593 records.TestSummaryEntryType.RECORD) 594 self.current_test_name = None 595 event_bus.post( 596 TestCaseEndEvent(self, self.test_name, test_signal)) 597 598 def get_func_with_retry(self, func, attempts=2): 599 """Returns a wrapped test method that re-runs after failure. Return test 600 result upon success. If attempt limit reached, collect all failure 601 messages and raise a TestFailure signal. 602 603 Params: 604 func: The test method 605 attempts: Number of attempts to run test 606 607 Returns: result of the test method 608 """ 609 exceptions = self.retryable_exceptions 610 611 def wrapper(*args, **kwargs): 612 error_msgs = [] 613 extras = {} 614 retry = False 615 for i in range(attempts): 616 try: 617 if retry: 618 self.teardown_test() 619 self.setup_test() 620 self.on_retry() 621 return func(*args, **kwargs) 622 except exceptions as e: 623 retry = True 624 msg = 'Failure on attempt %d: %s' % (i + 1, e.details) 625 self.log.warning(msg) 626 error_msgs.append(msg) 627 if e.extras: 628 extras['Attempt %d' % (i + 1)] = e.extras 629 raise signals.TestFailure('\n'.join(error_msgs), extras) 630 631 return wrapper 632 633 def run_generated_testcases(self, 634 test_func, 635 settings, 636 args=None, 637 kwargs=None, 638 tag="", 639 name_func=None, 640 format_args=False): 641 """Deprecated. Please use pre_run and generate_tests. 642 643 Generated test cases are not written down as functions, but as a list 644 of parameter sets. This way we reduce code repetition and improve 645 test case scalability. 646 647 Args: 648 test_func: The common logic shared by all these generated test 649 cases. This function should take at least one argument, 650 which is a parameter set. 651 settings: A list of strings representing parameter sets. These are 652 usually json strings that get loaded in the test_func. 653 args: Iterable of additional position args to be passed to 654 test_func. 655 kwargs: Dict of additional keyword args to be passed to test_func 656 tag: Name of this group of generated test cases. Ignored if 657 name_func is provided and operates properly. 658 name_func: A function that takes a test setting and generates a 659 proper test name. The test name should be shorter than 660 utils.MAX_FILENAME_LEN. Names over the limit will be 661 truncated. 662 format_args: If True, args will be appended as the first argument 663 in the args list passed to test_func. 664 665 Returns: 666 A list of settings that did not pass. 667 """ 668 args = args or () 669 kwargs = kwargs or {} 670 failed_settings = [] 671 672 for setting in settings: 673 test_name = "{} {}".format(tag, setting) 674 675 if name_func: 676 try: 677 test_name = name_func(setting, *args, **kwargs) 678 except: 679 self.log.exception(("Failed to get test name from " 680 "test_func. Fall back to default %s"), 681 test_name) 682 683 self.results.requested.append(test_name) 684 685 if len(test_name) > utils.MAX_FILENAME_LEN: 686 test_name = test_name[:utils.MAX_FILENAME_LEN] 687 688 previous_success_cnt = len(self.results.passed) 689 690 if format_args: 691 self.exec_one_testcase( 692 test_name, 693 functools.partial(test_func, *(args + (setting, )), 694 **kwargs)) 695 else: 696 self.exec_one_testcase( 697 test_name, 698 functools.partial(test_func, *((setting, ) + args), 699 **kwargs)) 700 701 if len(self.results.passed) - previous_success_cnt != 1: 702 failed_settings.append(setting) 703 704 return failed_settings 705 706 def _exec_func(self, func, *args): 707 """Executes a function with exception safeguard. 708 709 This will let signals.TestAbortAll through so abort_all works in all 710 procedure functions. 711 712 Args: 713 func: Function to be executed. 714 args: Arguments to be passed to the function. 715 716 Returns: 717 Whatever the function returns, or False if unhandled exception 718 occured. 719 """ 720 try: 721 return func(*args) 722 except signals.TestAbortAll: 723 raise 724 except: 725 self.log.exception("Exception happened when executing %s in %s.", 726 func.__name__, self.TAG) 727 return False 728 729 def _block_all_test_cases(self, tests, reason='Failed class setup'): 730 """ 731 Block all passed in test cases. 732 Args: 733 tests: The tests to block. 734 reason: Message describing the reason that the tests are blocked. 735 Default is 'Failed class setup' 736 """ 737 for test_name, test_func in tests: 738 signal = signals.TestError(reason) 739 record = records.TestResultRecord(test_name, self.TAG) 740 record.test_begin() 741 if hasattr(test_func, 'gather'): 742 signal.extras = test_func.gather() 743 record.test_error(signal) 744 self.results.add_record(record) 745 self.summary_writer.dump(record.to_dict(), 746 records.TestSummaryEntryType.RECORD) 747 self._on_skip(record) 748 749 def run(self, test_names=None): 750 """Runs test cases within a test class by the order they appear in the 751 execution list. 752 753 One of these test cases lists will be executed, shown here in priority 754 order: 755 1. The test_names list, which is passed from cmd line. 756 2. The self.tests list defined in test class. Invalid names are 757 ignored. 758 3. All function that matches test case naming convention in the test 759 class. 760 761 Args: 762 test_names: A list of string that are test case names/patterns 763 requested in cmd line. 764 765 Returns: 766 The test results object of this class. 767 """ 768 # Executes pre-setup procedures, like generating test methods. 769 if not self._pre_run(): 770 return self.results 771 772 self.register_test_class_event_subscriptions() 773 self.log.info("==========> %s <==========", self.TAG) 774 # Devise the actual test cases to run in the test class. 775 if self.tests: 776 # Specified by run list in class. 777 valid_tests = list(self.tests) 778 else: 779 # No test case specified by user, gather the run list automatically. 780 valid_tests = self.get_existing_test_names() 781 if test_names: 782 # Match test cases with any of the user-specified patterns 783 matches = [] 784 for test_name in test_names: 785 for valid_test in valid_tests: 786 if (fnmatch.fnmatch(valid_test, test_name) 787 and valid_test not in matches): 788 matches.append(valid_test) 789 else: 790 matches = valid_tests 791 self.results.requested = matches 792 self.summary_writer.dump(self.results.requested_test_names_dict(), 793 records.TestSummaryEntryType.TEST_NAME_LIST) 794 tests = self._get_test_methods(matches) 795 796 # Setup for the class. 797 setup_fail = False 798 try: 799 if self._setup_class() is False: 800 self.log.error("Failed to setup %s.", self.TAG) 801 self._block_all_test_cases(tests) 802 setup_fail = True 803 except signals.TestAbortClass: 804 self.log.exception('Test class %s aborted' % self.TAG) 805 setup_fail = True 806 except Exception as e: 807 self.log.exception("Failed to setup %s.", self.TAG) 808 self._block_all_test_cases(tests) 809 setup_fail = True 810 if setup_fail: 811 self._exec_func(self._teardown_class) 812 self.log.info("Summary for test class %s: %s", self.TAG, 813 self.results.summary_str()) 814 return self.results 815 816 # Run tests in order. 817 test_case_iterations = self.user_params.get( 818 keys.Config.key_test_case_iterations.value, 1) 819 if any([substr in self.__class__.__name__ for substr in 820 ['Preflight', 'Postflight']]): 821 test_case_iterations = 1 822 try: 823 for test_name, test_func in tests: 824 for _ in range(test_case_iterations): 825 self.exec_one_testcase(test_name, test_func) 826 return self.results 827 except signals.TestAbortClass: 828 self.log.exception('Test class %s aborted' % self.TAG) 829 return self.results 830 except signals.TestAbortAll as e: 831 # Piggy-back test results on this exception object so we don't lose 832 # results from this test class. 833 setattr(e, "results", self.results) 834 raise e 835 finally: 836 self._exec_func(self._teardown_class) 837 self.log.info("Summary for test class %s: %s", self.TAG, 838 self.results.summary_str()) 839 840 def _ad_take_bugreport(self, ad, test_name, begin_time): 841 for i in range(3): 842 try: 843 ad.take_bug_report(test_name, begin_time) 844 return True 845 except Exception as e: 846 ad.log.error("bugreport attempt %s error: %s", i + 1, e) 847 848 def _ad_take_extra_logs(self, ad, test_name, begin_time): 849 result = True 850 if getattr(ad, "qxdm_log", False): 851 # Gather qxdm log modified 3 minutes earlier than test start time 852 if begin_time: 853 qxdm_begin_time = begin_time - 1000 * 60 * 3 854 else: 855 qxdm_begin_time = None 856 try: 857 ad.get_qxdm_logs(test_name, qxdm_begin_time) 858 except Exception as e: 859 ad.log.error("Failed to get QXDM log for %s with error %s", 860 test_name, e) 861 result = False 862 863 try: 864 ad.check_crash_report(test_name, begin_time, log_crash_report=True) 865 except Exception as e: 866 ad.log.error("Failed to check crash report for %s with error %s", 867 test_name, e) 868 result = False 869 return result 870 871 def _skip_bug_report(self, test_name): 872 """A function to check whether we should skip creating a bug report. 873 874 Args: 875 test_name: The test case name 876 877 Returns: True if bug report is to be skipped. 878 """ 879 if "no_bug_report_on_fail" in self.user_params: 880 return True 881 882 # If the current test class or test case is found in the set of 883 # problematic tests, we skip bugreport and other failure artifact 884 # creation. 885 class_name = self.__class__.__name__ 886 quiet_tests = self.user_params.get('quiet_tests', []) 887 if class_name in quiet_tests: 888 self.log.info( 889 "Skipping bug report, as directed for this test class.") 890 return True 891 full_test_name = '%s.%s' % (class_name, test_name) 892 if full_test_name in quiet_tests: 893 self.log.info( 894 "Skipping bug report, as directed for this test case.") 895 return True 896 897 # Once we hit a certain log path size, it's not going to get smaller. 898 # We cache the result so we don't have to keep doing directory walks. 899 if self.size_limit_reached: 900 return True 901 try: 902 max_log_size = int( 903 self.user_params.get("soft_output_size_limit") or "invalid") 904 log_path = getattr(logging, "log_path", None) 905 if log_path: 906 curr_log_size = utils.get_directory_size(log_path) 907 if curr_log_size > max_log_size: 908 self.log.info( 909 "Skipping bug report, as we've reached the size limit." 910 ) 911 self.size_limit_reached = True 912 return True 913 except ValueError: 914 pass 915 return False 916 917 def _take_bug_report(self, test_name, begin_time): 918 if self._skip_bug_report(test_name): 919 return 920 921 executor = ThreadPoolExecutor(max_workers=10) 922 for ad in getattr(self, 'android_devices', []): 923 executor.submit(self._ad_take_bugreport, ad, test_name, begin_time) 924 executor.submit(self._ad_take_extra_logs, ad, test_name, 925 begin_time) 926 executor.shutdown() 927 928 def _reboot_device(self, ad): 929 ad.log.info("Rebooting device.") 930 ad = ad.reboot() 931 932 def _cleanup_logger_sessions(self): 933 for (mylogger, session) in self.logger_sessions: 934 self.log.info("Resetting a diagnostic session %s, %s", mylogger, 935 session) 936 mylogger.reset() 937 self.logger_sessions = [] 938 939 def _pull_diag_logs(self, test_name, begin_time): 940 for (mylogger, session) in self.logger_sessions: 941 self.log.info("Pulling diagnostic session %s", mylogger) 942 mylogger.stop(session) 943 diag_path = os.path.join( 944 self.log_path, logger.epoch_to_log_line_timestamp(begin_time)) 945 os.makedirs(diag_path, exist_ok=True) 946 mylogger.pull(session, diag_path) 947 948 def register_test_class_event_subscriptions(self): 949 self.class_subscriptions = subscription_bundle.create_from_instance( 950 self) 951 self.class_subscriptions.register() 952 953 def unregister_test_class_event_subscriptions(self): 954 for package in self.all_subscriptions: 955 package.unregister() 956