Home | History | Annotate | Download | only in acts
      1 #!/usr/bin/env python3.4
      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 import logging
     17 import os
     18 import traceback
     19 from concurrent.futures import ThreadPoolExecutor
     20 
     21 from acts import asserts
     22 from acts import keys
     23 from acts import logger
     24 from acts import records
     25 from acts import signals
     26 from acts import tracelogger
     27 from acts import utils
     28 
     29 # Macro strings for test result reporting
     30 TEST_CASE_TOKEN = "[Test Case]"
     31 RESULT_LINE_TEMPLATE = TEST_CASE_TOKEN + " %s %s"
     32 
     33 
     34 class Error(Exception):
     35     """Raised for exceptions that occured in BaseTestClass."""
     36 
     37 
     38 class BaseTestClass(object):
     39     """Base class for all test classes to inherit from.
     40 
     41     This class gets all the controller objects from test_runner and executes
     42     the test cases requested within itself.
     43 
     44     Most attributes of this class are set at runtime based on the configuration
     45     provided.
     46 
     47     Attributes:
     48         tests: A list of strings, each representing a test case name.
     49         TAG: A string used to refer to a test class. Default is the test class
     50              name.
     51         log: A logger object used for logging.
     52         results: A records.TestResult object for aggregating test results from
     53                  the execution of test cases.
     54         current_test_name: A string that's the name of the test case currently
     55                            being executed. If no test is executing, this should
     56                            be None.
     57     """
     58 
     59     TAG = None
     60 
     61     def __init__(self, configs):
     62         self.tests = []
     63         if not self.TAG:
     64             self.TAG = self.__class__.__name__
     65         # Set all the controller objects and params.
     66         for name, value in configs.items():
     67             setattr(self, name, value)
     68         self.results = records.TestResult()
     69         self.current_test_name = None
     70         self.log = tracelogger.TraceLogger(self.log)
     71         self.size_limit_reached = False
     72         if 'android_devices' in self.__dict__:
     73             for ad in self.android_devices:
     74                 if ad.droid:
     75                     utils.set_location_service(ad, False)
     76                     utils.sync_device_time(ad)
     77 
     78     def __enter__(self):
     79         return self
     80 
     81     def __exit__(self, *args):
     82         self._exec_func(self.clean_up)
     83 
     84     def unpack_userparams(self,
     85                           req_param_names=[],
     86                           opt_param_names=[],
     87                           **kwargs):
     88         """An optional function that unpacks user defined parameters into
     89         individual variables.
     90 
     91         After unpacking, the params can be directly accessed with self.xxx.
     92 
     93         If a required param is not provided, an exception is raised. If an
     94         optional param is not provided, a warning line will be logged.
     95 
     96         To provide a param, add it in the config file or pass it in as a kwarg.
     97         If a param appears in both the config file and kwarg, the value in the
     98         config file is used.
     99 
    100         User params from the config file can also be directly accessed in
    101         self.user_params.
    102 
    103         Args:
    104             req_param_names: A list of names of the required user params.
    105             opt_param_names: A list of names of the optional user params.
    106             **kwargs: Arguments that provide default values.
    107                 e.g. unpack_userparams(required_list, opt_list, arg_a="hello")
    108                      self.arg_a will be "hello" unless it is specified again in
    109                      required_list or opt_list.
    110 
    111         Raises:
    112             Error is raised if a required user params is not provided.
    113         """
    114         for k, v in kwargs.items():
    115             if k in self.user_params:
    116                 v = self.user_params[k]
    117             setattr(self, k, v)
    118         for name in req_param_names:
    119             if hasattr(self, name):
    120                 continue
    121             if name not in self.user_params:
    122                 raise Error(("Missing required user param '%s' in test "
    123                              "configuration.") % name)
    124             setattr(self, name, self.user_params[name])
    125         for name in opt_param_names:
    126             if hasattr(self, name):
    127                 continue
    128             if name in self.user_params:
    129                 setattr(self, name, self.user_params[name])
    130             else:
    131                 self.log.warning(("Missing optional user param '%s' in "
    132                                   "configuration, continue."), name)
    133 
    134         capablity_of_devices = utils.CapablityPerDevice
    135         if "additional_energy_info_models" in self.user_params:
    136             self.energy_info_models = (capablity_of_devices.energy_info_models
    137                                        + self.additional_energy_info_models)
    138         else:
    139             self.energy_info_models = capablity_of_devices.energy_info_models
    140         self.user_params["energy_info_models"] = self.energy_info_models
    141 
    142         if "additional_tdls_models" in self.user_params:
    143             self.tdls_models = (capablity_of_devices.energy_info_models +
    144                                 self.additional_tdls_models)
    145         else:
    146             self.tdls_models = capablity_of_devices.energy_info_models
    147         self.user_params["tdls_models"] = self.tdls_models
    148 
    149     def _setup_class(self):
    150         """Proxy function to guarantee the base implementation of setup_class
    151         is called.
    152         """
    153         return self.setup_class()
    154 
    155     def setup_class(self):
    156         """Setup function that will be called before executing any test case in
    157         the test class.
    158 
    159         To signal setup failure, return False or raise an exception. If
    160         exceptions were raised, the stack trace would appear in log, but the
    161         exceptions would not propagate to upper levels.
    162 
    163         Implementation is optional.
    164         """
    165 
    166     def teardown_class(self):
    167         """Teardown function that will be called after all the selected test
    168         cases in the test class have been executed.
    169 
    170         Implementation is optional.
    171         """
    172 
    173     def _setup_test(self, test_name):
    174         """Proxy function to guarantee the base implementation of setup_test is
    175         called.
    176         """
    177         self.current_test_name = test_name
    178         try:
    179             # Write test start token to adb log if android device is attached.
    180             if hasattr(self, 'android_devices'):
    181                 for ad in self.android_devices:
    182                     if not ad.skip_sl4a:
    183                         ad.droid.logV("%s BEGIN %s" % (TEST_CASE_TOKEN,
    184                                                        test_name))
    185         except Exception as e:
    186             self.log.warning(
    187                 'Unable to send BEGIN log command to all devices.')
    188             self.log.warning('Error: %s' % e)
    189         return self.setup_test()
    190 
    191     def setup_test(self):
    192         """Setup function that will be called every time before executing each
    193         test case in the test class.
    194 
    195         To signal setup failure, return False or raise an exception. If
    196         exceptions were raised, the stack trace would appear in log, but the
    197         exceptions would not propagate to upper levels.
    198 
    199         Implementation is optional.
    200         """
    201         return True
    202 
    203     def _teardown_test(self, test_name):
    204         """Proxy function to guarantee the base implementation of teardown_test
    205         is called.
    206         """
    207         self.log.debug('Tearing down test %s' % test_name)
    208         try:
    209             # Write test end token to adb log if android device is attached.
    210             for ad in self.android_devices:
    211                 ad.droid.logV("%s END %s" % (TEST_CASE_TOKEN, test_name))
    212         except Exception as e:
    213             self.log.warning('Unable to send END log command to all devices.')
    214             self.log.warning('Error: %s' % e)
    215         try:
    216             self.teardown_test()
    217         finally:
    218             self.current_test_name = None
    219 
    220     def teardown_test(self):
    221         """Teardown function that will be called every time a test case has
    222         been executed.
    223 
    224         Implementation is optional.
    225         """
    226 
    227     def _on_fail(self, record):
    228         """Proxy function to guarantee the base implementation of on_fail is
    229         called.
    230 
    231         Args:
    232             record: The records.TestResultRecord object for the failed test
    233                     case.
    234         """
    235         if record.details:
    236             self.log.error(record.details)
    237         self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result)
    238         self.on_fail(record.test_name, record.begin_time)
    239 
    240     def on_fail(self, test_name, begin_time):
    241         """A function that is executed upon a test case failure.
    242 
    243         User implementation is optional.
    244 
    245         Args:
    246             test_name: Name of the test that triggered this function.
    247             begin_time: Logline format timestamp taken when the test started.
    248         """
    249 
    250     def _on_pass(self, record):
    251         """Proxy function to guarantee the base implementation of on_pass is
    252         called.
    253 
    254         Args:
    255             record: The records.TestResultRecord object for the passed test
    256                     case.
    257         """
    258         msg = record.details
    259         if msg:
    260             self.log.info(msg)
    261         self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result)
    262         self.on_pass(record.test_name, record.begin_time)
    263 
    264     def on_pass(self, test_name, begin_time):
    265         """A function that is executed upon a test case passing.
    266 
    267         Implementation is optional.
    268 
    269         Args:
    270             test_name: Name of the test that triggered this function.
    271             begin_time: Logline format timestamp taken when the test started.
    272         """
    273 
    274     def _on_skip(self, record):
    275         """Proxy function to guarantee the base implementation of on_skip is
    276         called.
    277 
    278         Args:
    279             record: The records.TestResultRecord object for the skipped test
    280                     case.
    281         """
    282         self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result)
    283         self.log.info("Reason to skip: %s", record.details)
    284         self.on_skip(record.test_name, record.begin_time)
    285 
    286     def on_skip(self, test_name, begin_time):
    287         """A function that is executed upon a test case being skipped.
    288 
    289         Implementation is optional.
    290 
    291         Args:
    292             test_name: Name of the test that triggered this function.
    293             begin_time: Logline format timestamp taken when the test started.
    294         """
    295 
    296     def _on_blocked(self, record):
    297         """Proxy function to guarantee the base implementation of on_blocked
    298         is called.
    299 
    300         Args:
    301             record: The records.TestResultRecord object for the blocked test
    302                     case.
    303         """
    304         self.log.info(RESULT_LINE_TEMPLATE, record.test_name, record.result)
    305         self.log.info("Reason to block: %s", record.details)
    306         self.on_blocked(record.test_name, record.begin_time)
    307 
    308     def on_blocked(self, test_name, begin_time):
    309         """A function that is executed upon a test begin skipped.
    310 
    311         Args:
    312             test_name: Name of the test that triggered this function.
    313             begin_time: Logline format timestamp taken when the test started.
    314         """
    315 
    316     def _on_exception(self, record):
    317         """Proxy function to guarantee the base implementation of on_exception
    318         is called.
    319 
    320         Args:
    321             record: The records.TestResultRecord object for the failed test
    322                     case.
    323         """
    324         self.log.exception(record.details)
    325         self.on_exception(record.test_name, record.begin_time)
    326 
    327     def on_exception(self, test_name, begin_time):
    328         """A function that is executed upon an unhandled exception from a test
    329         case.
    330 
    331         Implementation is optional.
    332 
    333         Args:
    334             test_name: Name of the test that triggered this function.
    335             begin_time: Logline format timestamp taken when the test started.
    336         """
    337 
    338     def _exec_procedure_func(self, func, tr_record):
    339         """Executes a procedure function like on_pass, on_fail etc.
    340 
    341         This function will alternate the 'Result' of the test's record if
    342         exceptions happened when executing the procedure function.
    343 
    344         This will let signals.TestAbortAll through so abort_all works in all
    345         procedure functions.
    346 
    347         Args:
    348             func: The procedure function to be executed.
    349             tr_record: The TestResultRecord object associated with the test
    350                        case executed.
    351         """
    352         try:
    353             func(tr_record)
    354         except signals.TestAbortAll:
    355             raise
    356         except Exception as e:
    357             self.log.exception("Exception happened when executing %s for %s.",
    358                                func.__name__, self.current_test_name)
    359             tr_record.add_error(func.__name__, e)
    360 
    361     def exec_one_testcase(self, test_name, test_func, args, **kwargs):
    362         """Executes one test case and update test results.
    363 
    364         Executes one test case, create a records.TestResultRecord object with
    365         the execution information, and add the record to the test class's test
    366         results.
    367 
    368         Args:
    369             test_name: Name of the test.
    370             test_func: The test function.
    371             args: A tuple of params.
    372             kwargs: Extra kwargs.
    373         """
    374         is_generate_trigger = False
    375         tr_record = records.TestResultRecord(test_name, self.TAG)
    376         tr_record.test_begin()
    377         self.begin_time = int(tr_record.begin_time)
    378         self.log_begin_time = tr_record.log_begin_time
    379         self.test_name = tr_record.test_name
    380         self.log.info("%s %s", TEST_CASE_TOKEN, test_name)
    381         verdict = None
    382         try:
    383             try:
    384                 if hasattr(self, 'android_devices'):
    385                     for ad in self.android_devices:
    386                         if not ad.is_adb_logcat_on:
    387                             ad.start_adb_logcat(cont_logcat_file=True)
    388                 ret = self._setup_test(self.test_name)
    389                 asserts.assert_true(ret is not False,
    390                                     "Setup for %s failed." % test_name)
    391                 if args or kwargs:
    392                     verdict = test_func(*args, **kwargs)
    393                 else:
    394                     verdict = test_func()
    395             finally:
    396                 try:
    397                     self._teardown_test(self.test_name)
    398                 except signals.TestAbortAll:
    399                     raise
    400                 except Exception as e:
    401                     self.log.error(traceback.format_exc())
    402                     tr_record.add_error("teardown_test", e)
    403                     self._exec_procedure_func(self._on_exception, tr_record)
    404         except (signals.TestFailure, AssertionError) as e:
    405             if self.user_params.get(
    406                     keys.Config.key_test_failure_tracebacks.value, False):
    407                 self.log.exception(e)
    408             else:
    409                 self.log.error(e)
    410             tr_record.test_fail(e)
    411             self._exec_procedure_func(self._on_fail, tr_record)
    412         except signals.TestSkip as e:
    413             # Test skipped.
    414             tr_record.test_skip(e)
    415             self._exec_procedure_func(self._on_skip, tr_record)
    416         except (signals.TestAbortClass, signals.TestAbortAll) as e:
    417             # Abort signals, pass along.
    418             tr_record.test_fail(e)
    419             self._exec_procedure_func(self._on_fail, tr_record)
    420             raise e
    421         except signals.TestPass as e:
    422             # Explicit test pass.
    423             tr_record.test_pass(e)
    424             self._exec_procedure_func(self._on_pass, tr_record)
    425         except signals.TestSilent as e:
    426             # This is a trigger test for generated tests, suppress reporting.
    427             is_generate_trigger = True
    428             self.results.requested.remove(test_name)
    429         except signals.TestBlocked as e:
    430             tr_record.test_blocked(e)
    431             self._exec_procedure_func(self._on_blocked, tr_record)
    432         except Exception as e:
    433             self.log.error(traceback.format_exc())
    434             # Exception happened during test.
    435             tr_record.test_unknown(e)
    436             self._exec_procedure_func(self._on_exception, tr_record)
    437             self._exec_procedure_func(self._on_fail, tr_record)
    438         else:
    439             if verdict or (verdict is None):
    440                 # Test passed.
    441                 tr_record.test_pass()
    442                 self._exec_procedure_func(self._on_pass, tr_record)
    443                 return
    444             tr_record.test_fail()
    445             self._exec_procedure_func(self._on_fail, tr_record)
    446         finally:
    447             if not is_generate_trigger:
    448                 self.results.add_record(tr_record)
    449 
    450     def run_generated_testcases(self,
    451                                 test_func,
    452                                 settings,
    453                                 args=None,
    454                                 kwargs=None,
    455                                 tag="",
    456                                 name_func=None,
    457                                 format_args=False):
    458         """Runs generated test cases.
    459 
    460         Generated test cases are not written down as functions, but as a list
    461         of parameter sets. This way we reduce code repetition and improve
    462         test case scalability.
    463 
    464         Args:
    465             test_func: The common logic shared by all these generated test
    466                        cases. This function should take at least one argument,
    467                        which is a parameter set.
    468             settings: A list of strings representing parameter sets. These are
    469                       usually json strings that get loaded in the test_func.
    470             args: Iterable of additional position args to be passed to
    471                   test_func.
    472             kwargs: Dict of additional keyword args to be passed to test_func
    473             tag: Name of this group of generated test cases. Ignored if
    474                  name_func is provided and operates properly.
    475             name_func: A function that takes a test setting and generates a
    476                        proper test name. The test name should be shorter than
    477                        utils.MAX_FILENAME_LEN. Names over the limit will be
    478                        truncated.
    479             format_args: If True, args will be appended as the first argument
    480                          in the args list passed to test_func.
    481 
    482         Returns:
    483             A list of settings that did not pass.
    484         """
    485         args = args or ()
    486         kwargs = kwargs or {}
    487         failed_settings = []
    488 
    489         for setting in settings:
    490             test_name = "{} {}".format(tag, setting)
    491 
    492             if name_func:
    493                 try:
    494                     test_name = name_func(setting, *args, **kwargs)
    495                 except:
    496                     self.log.exception(("Failed to get test name from "
    497                                         "test_func. Fall back to default %s"),
    498                                        test_name)
    499 
    500             self.results.requested.append(test_name)
    501 
    502             if len(test_name) > utils.MAX_FILENAME_LEN:
    503                 test_name = test_name[:utils.MAX_FILENAME_LEN]
    504 
    505             previous_success_cnt = len(self.results.passed)
    506 
    507             if format_args:
    508                 self.exec_one_testcase(test_name, test_func,
    509                                        args + (setting,), **kwargs)
    510             else:
    511                 self.exec_one_testcase(test_name, test_func,
    512                                        (setting,) + args, **kwargs)
    513 
    514             if len(self.results.passed) - previous_success_cnt != 1:
    515                 failed_settings.append(setting)
    516 
    517         return failed_settings
    518 
    519     def _exec_func(self, func, *args):
    520         """Executes a function with exception safeguard.
    521 
    522         This will let signals.TestAbortAll through so abort_all works in all
    523         procedure functions.
    524 
    525         Args:
    526             func: Function to be executed.
    527             args: Arguments to be passed to the function.
    528 
    529         Returns:
    530             Whatever the function returns, or False if unhandled exception
    531             occured.
    532         """
    533         try:
    534             return func(*args)
    535         except signals.TestAbortAll:
    536             raise
    537         except:
    538             self.log.exception("Exception happened when executing %s in %s.",
    539                                func.__name__, self.TAG)
    540             return False
    541 
    542     def _get_all_test_names(self):
    543         """Finds all the function names that match the test case naming
    544         convention in this class.
    545 
    546         Returns:
    547             A list of strings, each is a test case name.
    548         """
    549         test_names = []
    550         for name in dir(self):
    551             if name.startswith("test_"):
    552                 test_names.append(name)
    553         return test_names
    554 
    555     def _get_test_funcs(self, test_names):
    556         """Obtain the actual functions of test cases based on test names.
    557 
    558         Args:
    559             test_names: A list of strings, each string is a test case name.
    560 
    561         Returns:
    562             A list of tuples of (string, function). String is the test case
    563             name, function is the actual test case function.
    564 
    565         Raises:
    566             Error is raised if the test name does not follow
    567             naming convention "test_*". This can only be caused by user input
    568             here.
    569         """
    570         test_funcs = []
    571         for test_name in test_names:
    572             test_funcs.append(self._get_test_func(test_name))
    573 
    574         return test_funcs
    575 
    576     def _get_test_func(self, test_name):
    577         """Obtain the actual function of test cases based on the test name.
    578 
    579         Args:
    580             test_name: String, The name of the test.
    581 
    582         Returns:
    583             A tuples of (string, function). String is the test case
    584             name, function is the actual test case function.
    585 
    586         Raises:
    587             Error is raised if the test name does not follow
    588             naming convention "test_*". This can only be caused by user input
    589             here.
    590         """
    591         if not test_name.startswith("test_"):
    592             raise Error(("Test case name %s does not follow naming "
    593                          "convention test_*, abort.") % test_name)
    594         try:
    595             return test_name, getattr(self, test_name)
    596         except:
    597 
    598             def test_skip_func(*args, **kwargs):
    599                 raise signals.TestSkip("Test %s does not exist" % test_name)
    600 
    601             self.log.info("Test case %s not found in %s.", test_name, self.TAG)
    602             return test_name, test_skip_func
    603 
    604     def _block_all_test_cases(self, tests):
    605         """
    606         Block all passed in test cases.
    607         Args:
    608             tests: The tests to block.
    609         """
    610         for test_name, test_func in tests:
    611             signal = signals.TestBlocked("Failed class setup")
    612             record = records.TestResultRecord(test_name, self.TAG)
    613             record.test_begin()
    614             if hasattr(test_func, 'gather'):
    615                 signal.extras = test_func.gather()
    616             record.test_blocked(signal)
    617             self.results.add_record(record)
    618             self._on_blocked(record)
    619 
    620     def run(self, test_names=None, test_case_iterations=1):
    621         """Runs test cases within a test class by the order they appear in the
    622         execution list.
    623 
    624         One of these test cases lists will be executed, shown here in priority
    625         order:
    626         1. The test_names list, which is passed from cmd line. Invalid names
    627            are guarded by cmd line arg parsing.
    628         2. The self.tests list defined in test class. Invalid names are
    629            ignored.
    630         3. All function that matches test case naming convention in the test
    631            class.
    632 
    633         Args:
    634             test_names: A list of string that are test case names requested in
    635                 cmd line.
    636 
    637         Returns:
    638             The test results object of this class.
    639         """
    640         self.log.info("==========> %s <==========", self.TAG)
    641         # Devise the actual test cases to run in the test class.
    642         if not test_names:
    643             if self.tests:
    644                 # Specified by run list in class.
    645                 test_names = list(self.tests)
    646             else:
    647                 # No test case specified by user, execute all in the test class
    648                 test_names = self._get_all_test_names()
    649         self.results.requested = test_names
    650         tests = self._get_test_funcs(test_names)
    651         # A TestResultRecord used for when setup_class fails.
    652         # Setup for the class.
    653         try:
    654             if self._setup_class() is False:
    655                 self.log.error("Failed to setup %s.", self.TAG)
    656                 self._block_all_test_cases(tests)
    657                 return self.results
    658         except Exception as e:
    659             self.log.exception("Failed to setup %s.", self.TAG)
    660             self._exec_func(self.teardown_class)
    661             self._block_all_test_cases(tests)
    662             return self.results
    663         # Run tests in order.
    664         try:
    665             for test_name, test_func in tests:
    666                 for _ in range(test_case_iterations):
    667                     self.exec_one_testcase(test_name, test_func, self.cli_args)
    668             return self.results
    669         except signals.TestAbortClass:
    670             return self.results
    671         except signals.TestAbortAll as e:
    672             # Piggy-back test results on this exception object so we don't lose
    673             # results from this test class.
    674             setattr(e, "results", self.results)
    675             raise e
    676         finally:
    677             self._exec_func(self.teardown_class)
    678             self.log.info("Summary for test class %s: %s", self.TAG,
    679                           self.results.summary_str())
    680 
    681     def clean_up(self):
    682         """A function that is executed upon completion of all tests cases
    683         selected in the test class.
    684 
    685         This function should clean up objects initialized in the constructor by
    686         user.
    687         """
    688 
    689     def _ad_take_bugreport(self, ad, test_name, begin_time):
    690         for i in range(3):
    691             try:
    692                 ad.take_bug_report(test_name, begin_time)
    693                 return True
    694             except Exception as e:
    695                 ad.log.error("bugreport attempt %s error: %s", i + 1, e)
    696 
    697     def _ad_take_extra_logs(self, ad, test_name, begin_time):
    698         result = True
    699         if getattr(ad, "qxdm_log", False):
    700             # Gather qxdm log modified 3 minutes earlier than test start time
    701             if begin_time:
    702                 qxdm_begin_time = begin_time - 1000 * 60 * 3
    703             else:
    704                 qxdm_begin_time = None
    705             try:
    706                 ad.get_qxdm_logs(test_name, qxdm_begin_time)
    707             except Exception as e:
    708                 ad.log.error("Failed to get QXDM log for %s with error %s",
    709                              test_name, e)
    710                 result = False
    711 
    712         try:
    713             ad.check_crash_report(test_name, begin_time, log_crash_report=True)
    714         except Exception as e:
    715             ad.log.error("Failed to check crash report for %s with error %s",
    716                          test_name, e)
    717             result = False
    718         return result
    719 
    720     def _skip_bug_report(self):
    721         """A function to check whether we should skip creating a bug report."""
    722         if "no_bug_report_on_fail" in self.user_params:
    723             return True
    724 
    725         # Once we hit a certain log path size, it's not going to get smaller.
    726         # We cache the result so we don't have to keep doing directory walks.
    727         if self.size_limit_reached:
    728             return True
    729         try:
    730             max_log_size = int(
    731                 self.user_params.get("soft_output_size_limit") or "invalid")
    732             log_path = getattr(logging, "log_path", None)
    733             if log_path:
    734                 curr_log_size = utils.get_directory_size(log_path)
    735                 if curr_log_size > max_log_size:
    736                     self.log.info(
    737                         "Skipping bug report, as we've reached the size limit."
    738                     )
    739                     self.size_limit_reached = True
    740                     return True
    741         except ValueError:
    742             pass
    743         return False
    744 
    745     def _take_bug_report(self, test_name, begin_time):
    746         if self._skip_bug_report():
    747             return
    748 
    749         executor = ThreadPoolExecutor(max_workers=10)
    750         for ad in getattr(self, 'android_devices', []):
    751             executor.submit(self._ad_take_bugreport, ad, test_name, begin_time)
    752             executor.submit(self._ad_take_extra_logs, ad, test_name,
    753                             begin_time)
    754         executor.shutdown()
    755 
    756     def _reboot_device(self, ad):
    757         ad.log.info("Rebooting device.")
    758         ad = ad.reboot()
    759 
    760     def _cleanup_logger_sessions(self):
    761         for (mylogger, session) in self.logger_sessions:
    762             self.log.info("Resetting a diagnostic session %s, %s", mylogger,
    763                           session)
    764             mylogger.reset()
    765         self.logger_sessions = []
    766 
    767     def _pull_diag_logs(self, test_name, begin_time):
    768         for (mylogger, session) in self.logger_sessions:
    769             self.log.info("Pulling diagnostic session %s", mylogger)
    770             mylogger.stop(session)
    771             diag_path = os.path.join(
    772                 self.log_path, logger.epoch_to_log_line_timestamp(begin_time))
    773             utils.create_dir(diag_path)
    774             mylogger.pull(session, diag_path)
    775