aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/test/py/u_boot_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/test/py/u_boot_utils.py')
-rw-r--r--roms/u-boot/test/py/u_boot_utils.py341
1 files changed, 341 insertions, 0 deletions
diff --git a/roms/u-boot/test/py/u_boot_utils.py b/roms/u-boot/test/py/u_boot_utils.py
new file mode 100644
index 000000000..939d82eec
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_utils.py
@@ -0,0 +1,341 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+# Utility code shared across multiple tests.
+
+import hashlib
+import inspect
+import os
+import os.path
+import pytest
+import sys
+import time
+import re
+
+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 range(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 range(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[], or a string.
+ If a string, note that it is split up so that quoted spaces
+ will not be preserved. E.g. "fred and" becomes ['"fred', 'and"']
+ 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:
+ The output as a string.
+ """
+ if isinstance(cmd, str):
+ cmd = cmd.split()
+ runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
+ output = runner.run(cmd, ignore_errors=ignore_errors)
+ runner.close()
+ return output
+
+def run_and_log_expect_exception(u_boot_console, cmd, retcode, msg):
+ """Run a command that is expected to fail.
+
+ This runs a command and checks that it fails with the expected return code
+ and exception method. If not, an exception is raised.
+
+ Args:
+ u_boot_console: A console connection to U-Boot.
+ cmd: The command to run, as an array of argv[].
+ retcode: Expected non-zero return code from the command.
+ msg: String that should be contained within the command's output.
+ """
+ try:
+ runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
+ runner.run(cmd)
+ except Exception as e:
+ assert(retcode == runner.exit_status)
+ assert(msg in runner.output)
+ else:
+ raise Exception("Expected an exception with retcode %d message '%s',"
+ "but it was not raised" % (retcode, msg))
+ finally:
+ 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 or 'memstart =' 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`')
+
+ # We don't want ram_base to be zero as some functions test if the given
+ # address is NULL (0). Besides, on some RISC-V targets the low memory
+ # is protected that prevents S-mode U-Boot from access.
+ # Let's add 2MiB then (size of an ARM LPAE/v8 section).
+
+ ram_base += 1024 * 1024 * 2
+
+ return ram_base
+
+class PersistentFileHelperCtxMgr(object):
+ """A context manager for Python's "with" statement, which ensures that any
+ generated file is deleted (and hence regenerated) if its mtime is older
+ than the mtime of the Python module which generated it, and gets an mtime
+ newer than the mtime of the Python module which generated after it is
+ generated. Objects of this type should be created by factory function
+ persistent_file_helper rather than directly."""
+
+ def __init__(self, log, filename):
+ """Initialize a new object.
+
+ Args:
+ log: The Logfile object to log to.
+ filename: The filename of the generated file.
+
+ Returns:
+ Nothing.
+ """
+
+ self.log = log
+ self.filename = filename
+
+ def __enter__(self):
+ frame = inspect.stack()[1]
+ module = inspect.getmodule(frame[0])
+ self.module_filename = module.__file__
+ self.module_timestamp = os.path.getmtime(self.module_filename)
+
+ if os.path.exists(self.filename):
+ filename_timestamp = os.path.getmtime(self.filename)
+ if filename_timestamp < self.module_timestamp:
+ self.log.action('Removing stale generated file ' +
+ self.filename)
+ os.unlink(self.filename)
+
+ def __exit__(self, extype, value, traceback):
+ if extype:
+ try:
+ os.path.unlink(self.filename)
+ except:
+ pass
+ return
+ logged = False
+ for i in range(20):
+ filename_timestamp = os.path.getmtime(self.filename)
+ if filename_timestamp > self.module_timestamp:
+ break
+ if not logged:
+ self.log.action(
+ 'Waiting for generated file timestamp to increase')
+ logged = True
+ os.utime(self.filename)
+ time.sleep(0.1)
+
+def persistent_file_helper(u_boot_log, filename):
+ """Manage the timestamps and regeneration of a persistent generated
+ file. This function creates a context manager for Python's "with"
+ statement
+
+ Usage:
+ with persistent_file_helper(u_boot_console.log, filename):
+ code to generate the file, if it's missing.
+
+ Args:
+ u_boot_log: u_boot_console.log.
+ filename: The filename of the generated file.
+
+ Returns:
+ A context manager object.
+ """
+
+ return PersistentFileHelperCtxMgr(u_boot_log, filename)
+
+def crc32(u_boot_console, address, count):
+ """Helper function used to compute the CRC32 value of a section of RAM.
+
+ Args:
+ u_boot_console: A U-Boot console connection.
+ address: Address where data starts.
+ count: Amount of data to use for calculation.
+
+ Returns:
+ CRC32 value
+ """
+
+ bcfg = u_boot_console.config.buildconfig
+ has_cmd_crc32 = bcfg.get('config_cmd_crc32', 'n') == 'y'
+ assert has_cmd_crc32, 'Cannot compute crc32 without CONFIG_CMD_CRC32.'
+ output = u_boot_console.run_command('crc32 %08x %x' % (address, count))
+
+ m = re.search('==> ([0-9a-fA-F]{8})$', output)
+ assert m, 'CRC32 operation failed.'
+
+ return m.group(1)