From 76b4693928920d7c30fa935b3c46a02b637a29e1 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:12 -0700 Subject: test/py: add various utility code Add various common utility functions. These will be used by a forthcoming re-written UMS test, and a brand-new DFU test. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/u_boot_utils.py | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 test/py/u_boot_utils.py (limited to 'test/py/u_boot_utils.py') diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py new file mode 100644 index 00000000000..539af618dbf --- /dev/null +++ b/test/py/u_boot_utils.py @@ -0,0 +1,171 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# Utility code shared across multiple tests. + +import hashlib +import os +import os.path +import sys +import time + +def md5sum_data(data): + '''Calculate the MD5 hash of some data. + + Args: + data: The data to hash. + + Returns: + The hash of the data, as a binary string. + ''' + + h = hashlib.md5() + h.update(data) + return h.digest() + +def md5sum_file(fn, max_length=None): + '''Calculate the MD5 hash of the contents of a file. + + Args: + fn: The filename of the file to hash. + max_length: The number of bytes to hash. If the file has more + bytes than this, they will be ignored. If None or omitted, the + entire file will be hashed. + + Returns: + The hash of the file content, as a binary string. + ''' + + with open(fn, 'rb') as fh: + if max_length: + params = [max_length] + else: + params = [] + data = fh.read(*params) + return md5sum_data(data) + +class PersistentRandomFile(object): + '''Generate and store information about a persistent file containing + random data.''' + + def __init__(self, u_boot_console, fn, size): + '''Create or process the persistent file. + + If the file does not exist, it is generated. + + If the file does exist, its content is hashed for later comparison. + + These files are always located in the "persistent data directory" of + the current test run. + + Args: + u_boot_console: A console connection to U-Boot. + fn: The filename (without path) to create. + size: The desired size of the file in bytes. + + Returns: + Nothing. + ''' + + self.fn = fn + + self.abs_fn = u_boot_console.config.persistent_data_dir + '/' + fn + + if os.path.exists(self.abs_fn): + u_boot_console.log.action('Persistent data file ' + self.abs_fn + + ' already exists') + self.content_hash = md5sum_file(self.abs_fn) + else: + u_boot_console.log.action('Generating ' + self.abs_fn + + ' (random, persistent, %d bytes)' % size) + data = os.urandom(size) + with open(self.abs_fn, 'wb') as fh: + fh.write(data) + self.content_hash = md5sum_data(data) + +def attempt_to_open_file(fn): + '''Attempt to open a file, without throwing exceptions. + + Any errors (exceptions) that occur during the attempt to open the file + are ignored. This is useful in order to test whether a file (in + particular, a device node) exists and can be successfully opened, in order + to poll for e.g. USB enumeration completion. + + Args: + fn: The filename to attempt to open. + + Returns: + An open file handle to the file, or None if the file could not be + opened. + ''' + + try: + return open(fn, 'rb') + except: + return None + +def wait_until_open_succeeds(fn): + '''Poll until a file can be opened, or a timeout occurs. + + Continually attempt to open a file, and return when this succeeds, or + raise an exception after a timeout. + + Args: + fn: The filename to attempt to open. + + Returns: + An open file handle to the file. + ''' + + for i in xrange(100): + fh = attempt_to_open_file(fn) + if fh: + return fh + time.sleep(0.1) + raise Exception('File could not be opened') + +def wait_until_file_open_fails(fn, ignore_errors): + '''Poll until a file cannot be opened, or a timeout occurs. + + Continually attempt to open a file, and return when this fails, or + raise an exception after a timeout. + + Args: + fn: The filename to attempt to open. + ignore_errors: Indicate whether to ignore timeout errors. If True, the + function will simply return if a timeout occurs, otherwise an + exception will be raised. + + Returns: + Nothing. + ''' + + for i in xrange(100): + fh = attempt_to_open_file(fn) + if not fh: + return + fh.close() + time.sleep(0.1) + if ignore_errors: + return + raise Exception('File can still be opened') + +def run_and_log(u_boot_console, cmd, ignore_errors=False): + '''Run a command and log its output. + + Args: + u_boot_console: A console connection to U-Boot. + cmd: The command to run, as an array of argv[]. + ignore_errors: Indicate whether to ignore errors. If True, the function + will simply return if the command cannot be executed or exits with + an error code, otherwise an exception will be raised if such + problems occur. + + Returns: + Nothing. + ''' + + runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) + runner.run(cmd, ignore_errors=ignore_errors) + runner.close() -- cgit v1.2.3 From 05266103349e8409f5fbd55197c75c7bb58f575d Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 21 Jan 2016 16:05:30 -0700 Subject: test/py: move find_ram_base() into u_boot_utils find_ram_base() is a shared utility function, not a core part of the U-Boot console interaction. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/u_boot_utils.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'test/py/u_boot_utils.py') diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py index 539af618dbf..522390a207e 100644 --- a/test/py/u_boot_utils.py +++ b/test/py/u_boot_utils.py @@ -169,3 +169,41 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) runner.run(cmd, ignore_errors=ignore_errors) runner.close() + +ram_base = None +def find_ram_base(u_boot_console): + '''Find the running U-Boot's RAM location. + + Probe the running U-Boot to determine the address of the first bank + of RAM. This is useful for tests that test reading/writing RAM, or + load/save files that aren't associated with some standard address + typically represented in an environment variable such as + ${kernel_addr_r}. The value is cached so that it only needs to be + actively read once. + + Args: + u_boot_console: A console connection to U-Boot. + + Returns: + The address of U-Boot's first RAM bank, as an integer. + ''' + + global ram_base + if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': + pytest.skip('bdinfo command not supported') + if ram_base == -1: + pytest.skip('Previously failed to find RAM bank start') + if ram_base is not None: + return ram_base + + with u_boot_console.log.section('find_ram_base'): + response = u_boot_console.run_command('bdinfo') + for l in response.split('\n'): + if '-> start' in l: + ram_base = int(l.split('=')[1].strip(), 16) + break + if ram_base is None: + ram_base = -1 + raise Exception('Failed to find RAM bank start in `bdinfo`') + + return ram_base -- cgit v1.2.3 From e8debf394fbba594fcfc267c61f8c6bbca395b06 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 13:41:30 -0700 Subject: test/py: use " for docstrings Python's coding style docs indicate to use " not ' for docstrings. test/py has other violations of the coding style docs, since the docs specify a stranger style than I would expect, but nobody has complained about those yet:-) Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- test/py/u_boot_utils.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'test/py/u_boot_utils.py') diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py index 522390a207e..72d24e42aa3 100644 --- a/test/py/u_boot_utils.py +++ b/test/py/u_boot_utils.py @@ -11,21 +11,21 @@ import sys import time def md5sum_data(data): - '''Calculate the MD5 hash of some data. + """Calculate the MD5 hash of some data. Args: data: The data to hash. Returns: The hash of the data, as a binary string. - ''' + """ h = hashlib.md5() h.update(data) return h.digest() def md5sum_file(fn, max_length=None): - '''Calculate the MD5 hash of the contents of a file. + """Calculate the MD5 hash of the contents of a file. Args: fn: The filename of the file to hash. @@ -35,7 +35,7 @@ def md5sum_file(fn, max_length=None): Returns: The hash of the file content, as a binary string. - ''' + """ with open(fn, 'rb') as fh: if max_length: @@ -46,11 +46,11 @@ def md5sum_file(fn, max_length=None): return md5sum_data(data) class PersistentRandomFile(object): - '''Generate and store information about a persistent file containing - random data.''' + """Generate and store information about a persistent file containing + random data.""" def __init__(self, u_boot_console, fn, size): - '''Create or process the persistent file. + """Create or process the persistent file. If the file does not exist, it is generated. @@ -66,7 +66,7 @@ class PersistentRandomFile(object): Returns: Nothing. - ''' + """ self.fn = fn @@ -85,7 +85,7 @@ class PersistentRandomFile(object): self.content_hash = md5sum_data(data) def attempt_to_open_file(fn): - '''Attempt to open a file, without throwing exceptions. + """Attempt to open a file, without throwing exceptions. Any errors (exceptions) that occur during the attempt to open the file are ignored. This is useful in order to test whether a file (in @@ -98,7 +98,7 @@ def attempt_to_open_file(fn): Returns: An open file handle to the file, or None if the file could not be opened. - ''' + """ try: return open(fn, 'rb') @@ -106,7 +106,7 @@ def attempt_to_open_file(fn): return None def wait_until_open_succeeds(fn): - '''Poll until a file can be opened, or a timeout occurs. + """Poll until a file can be opened, or a timeout occurs. Continually attempt to open a file, and return when this succeeds, or raise an exception after a timeout. @@ -116,7 +116,7 @@ def wait_until_open_succeeds(fn): Returns: An open file handle to the file. - ''' + """ for i in xrange(100): fh = attempt_to_open_file(fn) @@ -126,7 +126,7 @@ def wait_until_open_succeeds(fn): raise Exception('File could not be opened') def wait_until_file_open_fails(fn, ignore_errors): - '''Poll until a file cannot be opened, or a timeout occurs. + """Poll until a file cannot be opened, or a timeout occurs. Continually attempt to open a file, and return when this fails, or raise an exception after a timeout. @@ -139,7 +139,7 @@ def wait_until_file_open_fails(fn, ignore_errors): Returns: Nothing. - ''' + """ for i in xrange(100): fh = attempt_to_open_file(fn) @@ -152,7 +152,7 @@ def wait_until_file_open_fails(fn, ignore_errors): raise Exception('File can still be opened') def run_and_log(u_boot_console, cmd, ignore_errors=False): - '''Run a command and log its output. + """Run a command and log its output. Args: u_boot_console: A console connection to U-Boot. @@ -164,7 +164,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): Returns: Nothing. - ''' + """ runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) runner.run(cmd, ignore_errors=ignore_errors) @@ -172,7 +172,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): ram_base = None def find_ram_base(u_boot_console): - '''Find the running U-Boot's RAM location. + """Find the running U-Boot's RAM location. Probe the running U-Boot to determine the address of the first bank of RAM. This is useful for tests that test reading/writing RAM, or @@ -186,7 +186,7 @@ def find_ram_base(u_boot_console): Returns: The address of U-Boot's first RAM bank, as an integer. - ''' + """ global ram_base if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': -- cgit v1.2.3