Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions devlib/utils/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)


Expand Down
25 changes: 25 additions & 0 deletions devlib/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -590,6 +591,30 @@ def __str__(self):
return message.format(self.filepath, self.lineno, self.message)


def load_struct_from_yaml(filepath):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now we don't want the testing infrastructure to be a public API, so this needs a non-public name starting with a single underscore (https://peps.python.org/pep-0008/)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_struct_from_yaml() is not for testing purposes only.
It's just moved from WA to devlib: ARM-software/workload-automation#1248

"""
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what LoadSyntaxError was initially for. Currently it seems like dead code in devlib. Unless it really adds something, I'd recommend just letting the YAMLError bubble up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoadSyntaxError() is referenced in load_struct_from_python(), too.
I don't know the history, but apparently it makes the syntax error location more explicit, IMHO.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this in WA to try and provide a more friendly error message to clearly indicate the location of the syntax error.
Since this code is being moved, I'd vote for keeping the same error class.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I did not look beyond devlib codebase



RAND_MOD_NAME_LEN = 30
BAD_CHARS = string.punctuation + string.whitespace
TRANS_TABLE = str.maketrans(BAD_CHARS, '_' * len(BAD_CHARS))
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
5 changes: 5 additions & 0 deletions tests/target_configs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LocalLinuxTarget:
entry-0:
connection_settings:
unrooted: True

68 changes: 48 additions & 20 deletions tests/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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-')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use tempfile.TemporaryDirectory, as a context manager.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest version of this code:

    with target.make_temp() as tempdir:
        ...
        raw_result = target.read_tree_values_flat(tempdir)
        result = {os.path.basename(k): v for k, v in raw_result.items()}

And this is Target()::make_temp() implementation (295f126):

    @contextmanager
    def make_temp(self, is_directory=True, directory='', prefix='devlib-test'):
        ...
        try:
            cmd = f'mktemp -p {directory} {prefix}-XXXXXX'
            if is_directory:
                cmd += ' -d'

            temp_obj = self.execute(cmd).strip()
            yield temp_obj
        finally:
            if temp_obj is not None:
                self.remove(temp_obj)

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