diff --git a/mobly/base_test.py b/mobly/base_test.py index cdd91c2b..78651ba8 100644 --- a/mobly/base_test.py +++ b/mobly/base_test.py @@ -34,8 +34,8 @@ RESULT_LINE_TEMPLATE = TEST_CASE_TOKEN + ' %s %s' TEST_SELECTOR_REGEX_PREFIX = 're:' -TEST_STAGE_BEGIN_LOG_TEMPLATE = '[{parent_token}]#{child_token} >>> BEGIN >>>' -TEST_STAGE_END_LOG_TEMPLATE = '[{parent_token}]#{child_token} <<< END <<<' +TEST_STAGE_BEGIN_LOG_TEMPLATE = '[%s]#%s >>> BEGIN >>>' +TEST_STAGE_END_LOG_TEMPLATE = '[%s]#%s <<< END <<<' # Names of execution stages, in the order they happen during test runs. STAGE_NAME_PRE_RUN = 'pre_run' @@ -195,10 +195,7 @@ def __init__(self, configs): self.tests = [] class_identifier = self.__class__.__name__ if configs.test_class_name_suffix: - class_identifier = '%s_%s' % ( - class_identifier, - configs.test_class_name_suffix, - ) + class_identifier = f'{class_identifier}_{configs.test_class_name_suffix}' if self.TAG is None: self.TAG = class_identifier # Set params. @@ -257,7 +254,7 @@ def unpack_userparams( continue if name not in self.user_params: raise Error( - 'Missing required user param "%s" in test configuration.' % name + f'Missing required user param "{name}" in test configuration.' ) setattr(self, name, self.user_params[name]) for name in opt_param_names: @@ -509,19 +506,11 @@ def _log_test_stage(self, stage_name): # reference tag as the parent token instead. if parent_token == stage_name: parent_token = self.TAG - logging.debug( - TEST_STAGE_BEGIN_LOG_TEMPLATE.format( - parent_token=parent_token, child_token=stage_name - ) - ) + logging.debug(TEST_STAGE_BEGIN_LOG_TEMPLATE, parent_token, stage_name) try: yield finally: - logging.debug( - TEST_STAGE_END_LOG_TEMPLATE.format( - parent_token=parent_token, child_token=stage_name - ) - ) + logging.debug(TEST_STAGE_END_LOG_TEMPLATE, parent_token, stage_name) def _setup_test(self, test_name): """Proxy function to guarantee the base implementation of setup_test is @@ -910,13 +899,12 @@ def generate_tests(self, test_logic, name_func, arg_sets, uid_func=None): is the corresponding UID. """ self._assert_function_names_in_stack([STAGE_NAME_PRE_RUN]) - root_msg = 'During test generation of "%s":' % test_logic.__name__ + root_msg = f'During test generation of "{test_logic.__name__}":' for args in arg_sets: test_name = name_func(*args) if test_name in self.get_existing_test_names(): raise Error( - '%s Test name "%s" already exists, cannot be duplicated!' - % (root_msg, test_name) + f'{root_msg} Test name "{test_name}" already exists, cannot be duplicated!' ) test_func = functools.partial(test_logic, *args) # If the `test_logic` method is decorated by `retry` or `repeat` @@ -1054,8 +1042,8 @@ def _get_regex_matching_test_methods(self, test_name_regex): def _assert_valid_test_name(self, test_name): if not test_name.startswith('test_'): raise Error( - 'Test method name %s does not follow naming ' - 'convention test_*, abort.' % test_name + f'Test method name {test_name} does not follow naming ' + 'convention test_*, abort.' ) def _skip_remaining_tests(self, exception): @@ -1174,11 +1162,11 @@ def run(self, test_names=None): self.exec_one_test(test_name, test_method) return self.results except signals.TestAbortClass as e: - e.details = 'Test class aborted due to: %s' % e.details + e.details = f'Test class aborted due to: {e.details}' self._skip_remaining_tests(e) return self.results except signals.TestAbortAll as e: - e.details = 'All remaining tests aborted due to: %s' % e.details + e.details = f'All remaining tests aborted due to: {e.details}' self._skip_remaining_tests(e) # Piggy-back test results on this exception object so we don't lose # results from this test class. diff --git a/mobly/controllers/android_device_lib/fastboot.py b/mobly/controllers/android_device_lib/fastboot.py index 0591e206..35d07cbc 100644 --- a/mobly/controllers/android_device_lib/fastboot.py +++ b/mobly/controllers/android_device_lib/fastboot.py @@ -75,7 +75,7 @@ def __init__(self, serial=''): def fastboot_str(self): if self.serial: - return '{} -s {}'.format(FASTBOOT, self.serial) + return f'{FASTBOOT} -s {self.serial}' return FASTBOOT def _exec_fastboot_cmd(self, name, arg_str, timeout=DEFAULT_TIMEOUT_SEC): diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py index d272782d..abdd8e72 100644 --- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py +++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py @@ -211,9 +211,7 @@ def connect(self, uid=UNKNOWN_UID, cmd=JsonRpcCommand.INIT): # Retry using '127.0.0.1' for IPv4 enabled machines that only resolve # 'localhost' to '[::1]'. self.log.debug( - 'Failed to connect to localhost, trying 127.0.0.1: {}'.format( - str(err) - ) + 'Failed to connect to localhost, trying 127.0.0.1: %s', err ) self._conn = socket.create_connection( ('127.0.0.1', self.host_port), _SOCKET_CONNECTION_TIMEOUT diff --git a/mobly/controllers/iperf_server.py b/mobly/controllers/iperf_server.py index e99b298a..51101bdf 100644 --- a/mobly/controllers/iperf_server.py +++ b/mobly/controllers/iperf_server.py @@ -115,8 +115,8 @@ class IPerfServer: def __init__(self, port, log_path): self.port = port - self.log_path = os.path.join(log_path, 'iPerf{}'.format(self.port)) - self.iperf_str = 'iperf3 -s -J -p {}'.format(port) + self.log_path = os.path.join(log_path, f'iPerf{self.port}') + self.iperf_str = f'iperf3 -s -J -p {port}' self.iperf_process = None self.log_files = [] self.started = False @@ -135,11 +135,9 @@ def start(self, extra_args='', tag=''): utils.create_dir(self.log_path) if tag: tag = tag + ',' - out_file_name = 'IPerfServer,{},{}{}.log'.format( - self.port, tag, len(self.log_files) - ) + out_file_name = f'IPerfServer,{self.port},{tag}{len(self.log_files)}.log' full_out_path = os.path.join(self.log_path, out_file_name) - cmd = '%s %s > %s' % (self.iperf_str, extra_args, full_out_path) + cmd = f'{self.iperf_str} {extra_args} > {full_out_path}' self.iperf_process = utils.start_standing_subprocess(cmd, shell=True) self.log_files.append(full_out_path) self.started = True diff --git a/mobly/controllers/sniffer.py b/mobly/controllers/sniffer.py index e1f3460b..3ae6311e 100644 --- a/mobly/controllers/sniffer.py +++ b/mobly/controllers/sniffer.py @@ -38,8 +38,8 @@ def create(configs): sniffer_subtype = c["SubType"] interface = c["Interface"] base_configs = c["BaseConfigs"] - module_name = "mobly.controllers.sniffer_lib.{}.{}".format( - sniffer_type, sniffer_subtype + module_name = ( + f"mobly.controllers.sniffer_lib.{sniffer_type}.{sniffer_subtype}" ) module = importlib.import_module(module_name) objs.append( diff --git a/mobly/controllers/sniffer_lib/local/local_base.py b/mobly/controllers/sniffer_lib/local/local_base.py index 0b2f6bf7..730aeecf 100644 --- a/mobly/controllers/sniffer_lib/local/local_base.py +++ b/mobly/controllers/sniffer_lib/local/local_base.py @@ -122,11 +122,11 @@ def start_capture( "Trying to start a sniff while another is still running!" ) capture_dir = os.path.join( - self._logger.log_path, "Sniffer-{}".format(self._interface) + self._logger.log_path, f"Sniffer-{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()) + capture_dir, f"capture_{logger.get_log_file_timestamp()}.pcap" ) self._pre_capture_config(override_configs) diff --git a/mobly/controllers/sniffer_lib/local/tcpdump.py b/mobly/controllers/sniffer_lib/local/tcpdump.py index 4f54cdb6..493438ab 100644 --- a/mobly/controllers/sniffer_lib/local/tcpdump.py +++ b/mobly/controllers/sniffer_lib/local/tcpdump.py @@ -26,7 +26,7 @@ def __init__(self, config_path, logger, base_configs=None): super().__init__(config_path, logger, base_configs=base_configs) - self._executable_path = shutil.which("tcpdump") + self._executable_path = shutil.which('tcpdump') if self._executable_path is None: raise sniffer.SnifferError( "Cannot find a path to the 'tcpdump' executable" @@ -34,20 +34,18 @@ def __init__(self, config_path, logger, base_configs=None): def get_descriptor(self): """See base class documentation""" - return "local-tcpdump-{}".format(self._interface) + return f'local-tcpdump-{self._interface}' def get_subtype(self): """See base class documentation""" - return "tcpdump" + return 'tcpdump' def _get_command_line( self, additional_args=None, duration=None, packet_count=None ): - cmd = "{} -i {} -w {}".format( - self._executable_path, self._interface, self._temp_capture_file_path - ) + cmd = f'{self._executable_path} -i {self._interface} -w {self._temp_capture_file_path}' if packet_count is not None: - cmd = "{} -c {}".format(cmd, packet_count) + cmd = f'{cmd} -c {packet_count}' if additional_args is not None: - cmd = "{} {}".format(cmd, additional_args) + cmd = f'{cmd} {additional_args}' return cmd diff --git a/mobly/controllers/sniffer_lib/local/tshark.py b/mobly/controllers/sniffer_lib/local/tshark.py index e48b1c6f..695a8b68 100644 --- a/mobly/controllers/sniffer_lib/local/tshark.py +++ b/mobly/controllers/sniffer_lib/local/tshark.py @@ -26,8 +26,8 @@ def __init__(self, config_path, logger, base_configs=None): super().__init__(config_path, logger, base_configs=base_configs) - self._executable_path = shutil.which("tshark") or shutil.which( - "/usr/local/bin/tshark" + self._executable_path = shutil.which('tshark') or shutil.which( + '/usr/local/bin/tshark' ) if self._executable_path is None: raise sniffer.SnifferError( @@ -37,22 +37,20 @@ def __init__(self, config_path, logger, base_configs=None): def get_descriptor(self): """See base class documentation""" - return "local-tshark-{}-ch{}".format(self._interface) + return f'local-tshark-{self._interface}' def get_subtype(self): """See base class documentation""" - return "tshark" + return 'tshark' def _get_command_line( self, additional_args=None, duration=None, packet_count=None ): - cmd = "{} -i {} -w {}".format( - self._executable_path, self._interface, self._temp_capture_file_path - ) + cmd = f'{self._executable_path} -i {self._interface} -w {self._temp_capture_file_path}' if duration is not None: - cmd = "{} -a duration:{}".format(cmd, duration) + cmd = f'{cmd} -a duration:{duration}' if packet_count is not None: - cmd = "{} -c {}".format(cmd, packet_count) + cmd = f'{cmd} -c {packet_count}' if additional_args is not None: - cmd = "{} {}".format(cmd, additional_args) + cmd = f'{cmd} {additional_args}' return cmd diff --git a/mobly/logger.py b/mobly/logger.py index 1d85d35b..ff6b19c3 100644 --- a/mobly/logger.py +++ b/mobly/logger.py @@ -117,9 +117,7 @@ def epoch_to_log_line_timestamp(epoch_time, time_zone=None): Args: epoch_time: integer, an epoch timestamp in ms. - time_zone: instance of tzinfo, time zone information. - Using pytz rather than python 3.2 time_zone implementation for - python 2 compatibility reasons. + time_zone: instance of datetime.tzinfo, time zone information. Returns: A string that is the corresponding timestamp in log line timestamp diff --git a/mobly/utils.py b/mobly/utils.py index 2defb698..e0df8cf1 100644 --- a/mobly/utils.py +++ b/mobly/utils.py @@ -397,7 +397,8 @@ def run_command( timeout=..., cwd=..., env=..., - universal_newlines: Literal[False] = ..., + text: Literal[False] = ..., + universal_newlines: Literal[False] | None = ..., ) -> tuple[int, bytes, bytes]: ... @@ -411,7 +412,8 @@ def run_command( timeout=..., cwd=..., env=..., - universal_newlines: Literal[True] = ..., + text: Literal[True] = ..., + universal_newlines: Literal[True] | None = ..., ) -> tuple[int, str, str]: ... @@ -424,7 +426,8 @@ def run_command( timeout=None, cwd=None, env=None, - universal_newlines=False, + text=False, + universal_newlines=None, ): """Runs a command in a subprocess. @@ -452,8 +455,9 @@ def run_command( env: dict, a mapping that defines the environment variables for the new process. Default behavior is inheriting the current process' environment. - universal_newlines: bool, True to open file objects in text mode, False in + text: bool, True to open file objects in text mode, False in binary mode. + universal_newlines: bool, legacy alias for `text`. Returns: A 3-tuple of the consisting of the return code, the std output, and the @@ -462,6 +466,8 @@ def run_command( Raises: subprocess.TimeoutExpired: The command timed out. """ + if universal_newlines is not None: + text = universal_newlines if stdout is None: stdout = subprocess.PIPE if stderr is None: @@ -473,7 +479,7 @@ def run_command( shell=shell, cwd=cwd, env=env, - text=universal_newlines, # "text" is introdcued in Python 3.7. + text=text, ) out, err = None, None try: diff --git a/pyproject.toml b/pyproject.toml index deb56698..476302f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ Homepage = "https://github.com/google/mobly" Download = "https://github.com/google/mobly/tarball/1.13.1" [project.optional-dependencies] -testing = [ "mock", "pytest", "pytz",] +testing = [ "pytest",] [tool.setuptools] include-package-data = false diff --git a/tests/mobly/base_test_test.py b/tests/mobly/base_test_test.py index d74ed054..92916d83 100755 --- a/tests/mobly/base_test_test.py +++ b/tests/mobly/base_test_test.py @@ -3060,7 +3060,9 @@ class RecoverableError(Exception): except RecoverableError: pass - logging_patch.debug.assert_called_with('[TestClass]#stage <<< END <<<') + logging_patch.debug.assert_called_with( + base_test.TEST_STAGE_END_LOG_TEMPLATE, 'TestClass', 'stage' + ) if __name__ == '__main__': diff --git a/tests/mobly/controllers/android_device_lib/apk_utils_test.py b/tests/mobly/controllers/android_device_lib/apk_utils_test.py index 36f2ed2b..5698274d 100644 --- a/tests/mobly/controllers/android_device_lib/apk_utils_test.py +++ b/tests/mobly/controllers/android_device_lib/apk_utils_test.py @@ -27,7 +27,7 @@ class ApkUtilsTest(unittest.TestCase): def setUp(self): - super(ApkUtilsTest, self).setUp() + super().setUp() self.mock_device = mock.MagicMock() self.mock_device.adb.current_user_id = 0 diff --git a/tests/mobly/logger_test.py b/tests/mobly/logger_test.py index d975ac79..e10bca93 100755 --- a/tests/mobly/logger_test.py +++ b/tests/mobly/logger_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime import logging import os import shutil @@ -20,7 +21,6 @@ from unittest import mock from mobly import logger -import pytz class LoggerTest(unittest.TestCase): @@ -34,7 +34,7 @@ def tearDown(self): def test_epoch_to_log_line_timestamp(self): actual_stamp = logger.epoch_to_log_line_timestamp( - 1469134262116, time_zone=pytz.utc + 1469134262116, time_zone=datetime.timezone.utc ) self.assertEqual('07-21 20:51:02.116', actual_stamp) diff --git a/tests/mobly/utils_test.py b/tests/mobly/utils_test.py index 84f5afd6..2c29dfcf 100755 --- a/tests/mobly/utils_test.py +++ b/tests/mobly/utils_test.py @@ -22,7 +22,6 @@ import signal import socket import subprocess -import sys import tempfile import threading import time @@ -364,6 +363,16 @@ def test_run_command_with_universal_newlines_true(self): self.assertIsInstance(out, str) + def test_run_command_with_text_false(self): + _, out, _ = utils.run_command(self.sleep_cmd(0.01), text=False) + + self.assertIsInstance(out, bytes) + + def test_run_command_with_text_true(self): + _, out, _ = utils.run_command(self.sleep_cmd(0.01), text=True) + + self.assertIsInstance(out, str) + def test_start_standing_subproc(self): try: p = utils.start_standing_subprocess(self.sleep_cmd(4)) @@ -487,10 +496,6 @@ def test_stop_standing_subproc_and_descendants(self): subprocess_a.join(timeout=1) mock_subprocess_a_popen.wait.assert_called_once() - @unittest.skipIf( - sys.version_info >= (3, 4) and sys.version_info < (3, 5), - 'Python 3.4 does not support `None` max_workers.', - ) def test_concurrent_exec_when_none_workers(self): def adder(a, b): return a + b diff --git a/tox.ini b/tox.ini index 402351a9..4be967ae 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ envlist = py3 [testenv] deps = pytest - pytz commands = pytest