diff --git a/devlib/utils/android.py b/devlib/utils/android.py index 36f064a61..6f2eb87ae 100755 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -294,6 +294,10 @@ def __init__( total_transfer_timeout=total_transfer_timeout, transfer_poll_period=transfer_poll_period, ) + + self.logger.debug('server=%s port=%s device=%s as_root=%s', + adb_server, adb_port, device, adb_as_root) + self.timeout = timeout if timeout is not None else self.default_timeout if device is None: device = adb_get_device(timeout=timeout, adb_server=adb_server, adb_port=adb_port) @@ -545,9 +549,9 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None, ad break time.sleep(10) else: # did not connect to the device - message = 'Could not connect to {}'.format(device or 'a device') + message = f'Could not connect to {device or "a device"} at {adb_server}:{adb_port}' if output: - message += '; got: "{}"'.format(output) + message += f'; got: {output}' raise HostError(message) diff --git a/devlib/utils/misc.py b/devlib/utils/misc.py index 365788545..e1e9dfa18 100644 --- a/devlib/utils/misc.py +++ b/devlib/utils/misc.py @@ -23,6 +23,7 @@ from itertools import groupby from operator import itemgetter from weakref import WeakSet +from ruamel.yaml import YAML import ctypes import logging @@ -590,6 +591,30 @@ def __str__(self): return message.format(self.filepath, self.lineno, self.message) +def load_struct_from_yaml(filepath): + """ + Parses a config structure from a YAML file. + The structure should be composed of basic Python types. + + :param filepath: Input file which contains YAML data. + :type filepath: str + + :raises LoadSyntaxError: if there is a syntax error in YAML data. + + :return: A dictionary which contains parsed YAML data + :rtype: Dict + """ + + try: + yaml = YAML(typ='safe', pure=True) + with open(filepath, 'r', encoding='utf-8') as file_handler: + return yaml.load(file_handler) + except yaml.YAMLError as ex: + message = ex.message if hasattr(ex, 'message') else '' + lineno = ex.problem_mark.line if hasattr(ex, 'problem_mark') else None + raise LoadSyntaxError(message, filepath=filepath, lineno=lineno) from ex + + RAND_MOD_NAME_LEN = 30 BAD_CHARS = string.punctuation + string.whitespace TRANS_TABLE = str.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) diff --git a/setup.py b/setup.py index 06b20952c..e8b7d0fbe 100644 --- a/setup.py +++ b/setup.py @@ -102,9 +102,11 @@ def _load_path(filepath): 'wrapt', # Basic for construction of decorator functions 'numpy', 'pandas', + 'pytest', 'lxml', # More robust xml parsing 'nest_asyncio', # Allows running nested asyncio loops 'future', # for the "past" Python package + 'ruamel.yaml >= 0.15.72', # YAML formatted config parsing ], extras_require={ 'daq': ['daqpower>=2'], diff --git a/tests/target_configs.yaml b/tests/target_configs.yaml new file mode 100644 index 000000000..47e00ec4e --- /dev/null +++ b/tests/target_configs.yaml @@ -0,0 +1,5 @@ +LocalLinuxTarget: + entry-0: + connection_settings: + unrooted: True + diff --git a/tests/test_target.py b/tests/test_target.py index 230aa35e6..0fa3fbf43 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -14,35 +14,63 @@ # limitations under the License. # +"""Module for testing targets.""" + import os import shutil import tempfile -from unittest import TestCase +from pprint import pp +import pytest from devlib import LocalLinuxTarget +from devlib.utils.misc import load_struct_from_yaml + + +def build_targets(): + """Read targets from a YAML formatted config file""" + + config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'target_configs.yaml') + + target_configs = load_struct_from_yaml(config_file) + if target_configs is None: + raise ValueError(f'{config_file} looks empty!') + + targets = [] + + if target_configs.get('LocalLinuxTarget') is not None: + print('> LocalLinux targets:') + for entry in target_configs['LocalLinuxTarget'].values(): + pp(entry) + ll_target = LocalLinuxTarget(connection_settings=entry['connection_settings']) + targets.append(ll_target) + + return targets + +@pytest.mark.parametrize("target", build_targets()) +def test_read_multiline_values(target): + """ + Test Target.read_tree_values_flat() -class TestReadTreeValues(TestCase): + :param target: Type of target per :class:`Target` based classes. + :type target: Target + """ - def test_read_multiline_values(self): - data = { - 'test1': '1', - 'test2': '2\n\n', - 'test3': '3\n\n4\n\n', - } + data = { + 'test1': '1', + 'test2': '2\n\n', + 'test3': '3\n\n4\n\n', + } - tempdir = tempfile.mkdtemp(prefix='devlib-test-') - for key, value in data.items(): - path = os.path.join(tempdir, key) - with open(path, 'w') as wfh: - wfh.write(value) + tempdir = tempfile.mkdtemp(prefix='devlib-test-') + for key, value in data.items(): + path = os.path.join(tempdir, key) + with open(path, 'w', encoding='utf-8') as wfh: + wfh.write(value) - t = LocalLinuxTarget(connection_settings={'unrooted': True}) - raw_result = t.read_tree_values_flat(tempdir) - result = {os.path.basename(k): v for k, v in raw_result.items()} + raw_result = target.read_tree_values_flat(tempdir) + result = {os.path.basename(k): v for k, v in raw_result.items()} - shutil.rmtree(tempdir) + shutil.rmtree(tempdir) - self.assertEqual({k: v.strip() - for k, v in data.items()}, - result) + assert {k: v.strip() for k, v in data.items()} == result