def __init__(self, test_configs, run_list): """Constructor for TestRunner. During construction, the input config_parser.TestRunConfig object is processed and populated with information specific to a test run. The config object is later passed to each test class for execution. Args: test_configs: A config_parser.TestRunConfig object. run_list: A list of tuples specifying what tests to run. """ self.test_run_info = None self.test_configs = test_configs test_bed_name = self.test_configs.test_bed_name start_time = logger.get_log_file_timestamp() self.id = '%s@%s' % (test_bed_name, start_time) # log_path should be set before parsing configs. l_path = os.path.join(self.test_configs.log_path, test_bed_name, start_time) self.log_path = os.path.abspath(l_path) logger.setup_test_logger(self.log_path, test_bed_name) self.controller_registry = {} self.controller_destructors = {} self.run_list = run_list self.results = records.TestResult() self.running = False self.test_classes = {}
def take_bug_reports(ads, test_name=None, begin_time=None, destination=None): """Takes bug reports on a list of android devices. If you want to take a bug report, call this function with a list of android_device objects in on_fail. But reports will be taken on all the devices in the list concurrently. Bug report takes a relative long time to take, so use this cautiously. Args: ads: A list of AndroidDevice instances. test_name: Name of the test method that triggered this bug report. If None, the default name "bugreport" will be used. begin_time: timestamp taken when the test started, can be either string or int. If None, the current time will be used. destination: string, path to the directory where the bugreport should be saved. """ if begin_time is None: begin_time = mobly_logger.get_log_file_timestamp() else: begin_time = mobly_logger.sanitize_filename(str(begin_time)) def take_br(test_name, begin_time, ad, destination): ad.take_bug_report(test_name=test_name, begin_time=begin_time, destination=destination) args = [(test_name, begin_time, ad, destination) for ad in ads] utils.concurrent_exec(take_br, args)
def take_bug_report(self, test_name=None, begin_time=None, timeout=300, destination=None): """Takes a bug report on the device and stores it in a file. Args: test_name: Name of the test method that triggered this bug report. begin_time: Timestamp of when the test started. If not set, then this will default to the current time. timeout: float, the number of seconds to wait for bugreport to complete, default is 5min. destination: string, path to the directory where the bugreport should be saved. Returns: A string that is the absolute path to the bug report on the host. """ prefix = DEFAULT_BUG_REPORT_NAME if test_name: prefix = '%s,%s' % (DEFAULT_BUG_REPORT_NAME, test_name) if begin_time is None: begin_time = mobly_logger.get_log_file_timestamp() new_br = True try: stdout = self.adb.shell('bugreportz -v').decode('utf-8') # This check is necessary for builds before N, where adb shell's ret # code and stderr are not propagated properly. if 'not found' in stdout: new_br = False except adb.AdbError: new_br = False if destination is None: destination = os.path.join(self.log_path, 'BugReports') br_path = utils.abs_path(destination) utils.create_dir(br_path) filename = self.generate_filename(prefix, str(begin_time), 'txt') if new_br: filename = filename.replace('.txt', '.zip') full_out_path = os.path.join(br_path, filename) # in case device restarted, wait for adb interface to return self.wait_for_boot_completion() self.log.debug('Start taking bugreport.') if new_br: out = self.adb.shell('bugreportz', timeout=timeout).decode('utf-8') if not out.startswith('OK'): raise DeviceError(self, 'Failed to take bugreport: %s' % out) br_out_path = out.split(':')[1].strip() self.adb.pull([br_out_path, full_out_path]) else: # shell=True as this command redirects the stdout to a local file # using shell redirection. self.adb.bugreport(' > "%s"' % full_out_path, shell=True, timeout=timeout) self.log.debug('Bugreport taken at %s.', full_out_path) return full_out_path
def start_capture(self, override_configs=None, additional_args=None, duration=None, packet_count=None): """See base class documentation """ if self._process is not None: raise sniffer.InvalidOperationError( "Trying to start a sniff while another is still running!") capture_dir = os.path.join(self._logger.log_path, "Sniffer-{}".format(self._interface)) os.makedirs(capture_dir, exist_ok=True) self._capture_file_path = os.path.join( capture_dir, "capture_{}.pcap".format(logger.get_log_file_timestamp())) self._pre_capture_config(override_configs) _, self._temp_capture_file_path = tempfile.mkstemp(suffix=".pcap") cmd = self._get_command_line(additional_args=additional_args, duration=duration, packet_count=packet_count) self._process = utils.start_standing_subprocess(cmd) return sniffer.ActiveCaptureContext(self, duration)
def run(self): """Executes tests. This will instantiate controller and test classes, execute tests, and print a summary. Raises: Error: if no tests have previously been added to this runner using add_test_class(...). """ if not self._test_run_infos: raise Error('No tests to execute.') start_time = logger.get_log_file_timestamp() log_path = os.path.join(self._log_dir, self._test_bed_name, start_time) summary_writer = records.TestSummaryWriter( os.path.join(log_path, records.OUTPUT_FILE_SUMMARY)) logger.setup_test_logger(log_path, self._test_bed_name) try: for test_run_info in self._test_run_infos: # Set up the test-specific config test_config = test_run_info.config.copy() test_config.log_path = log_path test_config.register_controller = functools.partial( self._register_controller, test_config) test_config.summary_writer = summary_writer try: self._run_test_class(test_config, test_run_info.test_class, test_run_info.tests) except signals.TestAbortAll as e: logging.warning( 'Abort all subsequent test classes. Reason: %s', e) raise finally: self._unregister_controllers() finally: # Write controller info and summary to summary file. summary_writer.dump(self.results.controller_info, records.TestSummaryEntryType.CONTROLLER_INFO) summary_writer.dump(self.results.summary_dict(), records.TestSummaryEntryType.SUMMARY) # Stop and show summary. msg = '\nSummary for test run %s@%s: %s\n' % ( self._test_bed_name, start_time, self.results.summary_str()) self._write_results_json_str(log_path) logging.info(msg.strip()) logger.kill_test_logger(logging.getLogger())
def __init__(self, test_configs, run_list): self.test_run_info = {} self.test_configs = test_configs self.testbed_configs = self.test_configs[keys.Config.key_testbed.value] self.testbed_name = self.testbed_configs[ keys.Config.key_testbed_name.value] start_time = logger.get_log_file_timestamp() self.id = "{}@{}".format(self.testbed_name, start_time) # log_path should be set before parsing configs. l_path = os.path.join( self.test_configs[keys.Config.key_log_path.value], self.testbed_name, start_time) self.log_path = os.path.abspath(l_path) logger.setup_test_logger(self.log_path, self.testbed_name) self.log = logging.getLogger() self.controller_registry = {} self.controller_destructors = {} self.run_list = run_list self.results = records.TestResult() self.running = False
def generate_test_run_log_path(self): """Geneartes the log path for a test run. The log path includes a timestamp that is set in this call. There is usually a minor difference between this timestamp and the actual starting point of the test run. This is because the log path must be set up *before* the test run actually starts, so all information of a test run can be properly captured. The generated value can be accessed via `self.root_output_path`. Returns: String, the generated log path. """ self._logger_start_time = logger.get_log_file_timestamp() self.root_output_path = os.path.join(self._log_dir, self._testbed_name, self._logger_start_time) return self.root_output_path
def generate_filename(self, file_type, time_identifier=None, extension_name=None): """Generates a name for an output file related to this device. The name follows the pattern: {file type},{debug_tag},{serial},{model},{time identifier}.{ext} "debug_tag" is only added if it's different from the serial. "ext" is added if specified by user. Args: file_type: string, type of this file, like "logcat" etc. time_identifier: string or RuntimeTestInfo. If a `RuntimeTestInfo` is passed in, the `signature` of the test case will be used. If a string is passed in, the string itself will be used. Otherwise the current timestamp will be used. extension_name: string, the extension name of the file. Returns: String, the filename generated. """ time_str = time_identifier if time_identifier is None: time_str = mobly_logger.get_log_file_timestamp() elif isinstance(time_identifier, runtime_test_info.RuntimeTestInfo): time_str = time_identifier.signature filename_tokens = [file_type] if self.debug_tag != self.serial: filename_tokens.append(self.debug_tag) filename_tokens.extend([self.serial, self.model, time_str]) filename_str = ','.join(filename_tokens) if extension_name is not None: filename_str = '%s.%s' % (filename_str, extension_name) filename_str = mobly_logger.sanitize_filename(filename_str) self.log.debug('Generated filename: %s', filename_str) return filename_str
def setup_logger(self): """Sets up logging for the next test run. This is called automatically in 'run', so normally, this method doesn't need to be called. Only use this method if you want to use Mobly's logger before the test run starts. .. code-block:: python tr = TestRunner(...) tr.setup_logger() logging.info(...) tr.run() """ if self._log_path is not None: return self._start_time = logger.get_log_file_timestamp() self._log_path = os.path.join(self._log_dir, self._test_bed_name, self._start_time) logger.setup_test_logger(self._log_path, self._test_bed_name)
def _update_log_path(self): """Updates the logging values with the current timestamp.""" self._start_time = logger.get_log_file_timestamp() self._log_path = os.path.join(self._log_dir, self._test_bed_name, self._start_time)