[PATCH 00/16] test/py: Some code tidy-updates

From: Simon Glass <sjg@chromium.org> This series includes various improvements to the test/py close: - Fix some pylint warnings - Log the PYTHONPATH when no py hooks are found - Improve how the pattern list is handled - Move 'expect' handling into ConsoleBase - Add an exported function to shutdown U-Boot in tests - A few other minor things Simon Glass (16): test/py: Log the PYTHONPATH on error test/py: Tidy up pylint warnings in console_base test/py: Tidy up comments in ConsoleBase() test/py: Tidy up pylint warnings in console_sandbox test/py: Tidy up pylint warnings in console_sandbox test/py: Tidy up pylint warnings in spawn test/py: Create a named tuple for the pattern list test/py: Rename bad_pattern_defs to PATTERNS test/py: Maintain a list of available patterns test/py: Provide an expect() function for use by tests test/py: Move exceptions to console_base test/py: Move expect() function into console_base test/py: Move the timeout to console_base test/py: Move member variables from spawn to console_base test/py: Split up _wait_for_boot_prompt test/py: Add a proper function to shut down U-Boot test/py/conftest.py | 4 +- test/py/console_base.py | 508 +++++++++++++++++++---------- test/py/console_board.py | 4 +- test/py/console_sandbox.py | 36 +- test/py/spawn.py | 189 ++--------- test/py/tests/test_bootmenu.py | 8 +- test/py/tests/test_distro.py | 14 +- test/py/tests/test_efi_selftest.py | 42 +-- test/py/tests/test_eficonfig.py | 34 +- test/py/tests/test_net.py | 4 +- test/py/tests/test_net_boot.py | 24 +- test/py/tests/test_sandbox_exit.py | 4 +- test/py/tests/test_saveenv.py | 2 +- test/py/tests/test_zynqmp_rpu.py | 3 +- 14 files changed, 451 insertions(+), 425 deletions(-) -- 2.43.0 base-commit: 4eeb84fb23ab19fe7d39abc840955f714b3aaefd branch: tesc2

From: Simon Glass <sjg@chromium.org> When no hook scripts are found, log the PYTHONPATH to aid debugging. Use a separate variable to avoid an error on Python 3.10 and a pylint 3.3.4 warning: E0001: Parsing failed: 'f-string expression part cannot include a backslash (conftest, line 311)' (syntax-error) Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/py/conftest.py b/test/py/conftest.py index 16f28db6e23..8b198c93ccc 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -312,6 +312,8 @@ def pytest_configure(config): log.info(f"Loaded {module}") if not_found: + paths = '\n'.join(sys.path) + log.info(f"PYTHONPATH: {paths}") log.warning(f"Failed to find modules: {' '.join(not_found)}") ubconfig.buildconfig = dict() -- 2.43.0

From: Simon Glass <sjg@chromium.org> There are quite a few warnings which makes it confusing when editing this file. Resolve the easy ones, leaving: 125:0: R0902: Too many instance attributes (14/7) (too-many-instance-attributes) 212:4: R0912: Too many branches (14/12) (too-many-branches) 271:4: R0913: Too many arguments (6/5) (too-many-arguments) 271:4: R0912: Too many branches (13/12) (too-many-branches) 454:8: W0702: No exception type(s) specified (bare-except) 531:8: W0702: No exception type(s) specified (bare-except) Rename wait_for_boot_prompt() so it is clear that it is an internal function. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 160 ++++++++++++++++++---------------------- test/py/spawn.py | 4 +- 2 files changed, 74 insertions(+), 90 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 56201052283..7f9c9979c32 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -2,17 +2,17 @@ # Copyright (c) 2015 Stephen Warren # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. -# Common logic to interact with U-Boot via the console. This class provides -# the interface that tests use to execute U-Boot shell commands and wait for -# their results. Sub-classes exist to perform board-type-specific setup -# operations, such as spawning a sub-process for Sandbox, or attaching to the -# serial console of real hardware. - -import multiplexed_log -import os -import pytest +"""Common logic to interact with U-Boot via the console. + +Provides the interface that tests use to execute U-Boot shell commands and wait +for their results. Sub-classes exist to perform board-type-specific setup +operations, such as spawning a sub-process for Sandbox, or attaching to the +serial console of real hardware. +""" + import re import sys + import spawn from spawn import BootFail, Timeout, Unexpected, handle_exception @@ -40,6 +40,11 @@ TIMEOUT_CMD_MS = 10000 # Command-echo timeout # situations. TIMEOUT_PREPARE_MS = 3 * 60 * 1000 +# Named patterns we can look for in the console output. These can indicate an +# error has occurred +# Tuple: +# str: name of pattern +# re.Pattern: Regex to check this pattern in the console output bad_pattern_defs = ( ('spl_signon', pattern_u_boot_spl_signon), ('main_signon', pattern_u_boot_main_signon), @@ -49,7 +54,8 @@ bad_pattern_defs = ( ('error_please_reset', pattern_error_please_reset), ) -class ConsoleDisableCheck(object): + +class ConsoleDisableCheck(): """Context manager (for Python's with statement) that temporarily disables the specified console output error check. This is useful when deliberately executing a command that is known to trigger one of the error checks, in @@ -69,7 +75,8 @@ class ConsoleDisableCheck(object): self.console.disable_check_count[self.check_type] -= 1 self.console.eval_bad_patterns() -class ConsoleEnableCheck(object): + +class ConsoleEnableCheck(): """Context manager (for Python's with statement) that temporarily enables the specified console output error check. This is useful when executing a command that might raise an extra bad pattern, beyond the default bad @@ -81,8 +88,10 @@ class ConsoleEnableCheck(object): self.console = console self.check_type = check_type self.check_pattern = check_pattern + self.default_bad_patterns = None def __enter__(self): + # pylint:disable=W0603 global bad_pattern_defs self.default_bad_patterns = bad_pattern_defs bad_pattern_defs += ((self.check_type, self.check_pattern),) @@ -90,12 +99,14 @@ class ConsoleEnableCheck(object): self.console.eval_bad_patterns() def __exit__(self, extype, value, traceback): + # pylint:disable=W0603 global bad_pattern_defs bad_pattern_defs = self.default_bad_patterns self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} self.console.eval_bad_patterns() -class ConsoleSetupTimeout(object): + +class ConsoleSetupTimeout(): """Context manager (for Python's with statement) that temporarily sets up timeout for specific command. This is useful when execution time is greater then default 30s.""" @@ -111,7 +122,8 @@ class ConsoleSetupTimeout(object): def __exit__(self, extype, value, traceback): self.p.timeout = self.orig_timeout -class ConsoleBase(object): + +class ConsoleBase(): """The interface through which test functions interact with the U-Boot console. This primarily involves executing shell commands, capturing their results, and checking for common error conditions. Some common utilities @@ -132,11 +144,7 @@ class ConsoleBase(object): should be set less than the UART RX FIFO size to avoid overflow, assuming that U-Boot can't keep up with full-rate traffic at the baud rate. - - Returns: - Nothing. """ - self.log = log self.config = config self.max_fifo_fill = max_fifo_fill @@ -153,16 +161,19 @@ class ConsoleBase(object): self.at_prompt = False self.at_prompt_logevt = None self.lab_mode = False + self.u_boot_version_string = None def get_spawn(self): - # This is not called, ssubclass must define this. - # Return a value to avoid: - # console_base.py:348:12: E1128: Assigning result of a function - # call, where the function returns None (assignment-from-none) - return spawn.Spawn([]) + """This is not called, ssubclass must define this. + Return a value to avoid: + console_base.py:348:12: E1128: Assigning result of a function + call, where the function returns None (assignment-from-none) + """ + return spawn.Spawn([]) def eval_bad_patterns(self): + """Set up lists of regexes for patterns we don't expect on console""" self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \ if self.disable_check_count[pat[PAT_ID]] == 0] self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \ @@ -177,11 +188,7 @@ class ConsoleBase(object): Args: None. - - Returns: - Nothing. """ - if self.p: self.log.start_section('Stopping U-Boot') close_type = self.p.close() @@ -195,12 +202,14 @@ class ConsoleBase(object): This tells us that we will get a 'lab ready' message when the board is ready for use. We don't need to look for signon messages. """ - self.log.info(f'test.py: Lab mode is active') + self.log.info('test.py: Lab mode is active') self.p.timeout = TIMEOUT_PREPARE_MS self.lab_mode = True - def wait_for_boot_prompt(self, loop_num = 1): - """Wait for the boot up until command prompt. This is for internal use only. + def _wait_for_boot_prompt(self, loop_num=1): + """Wait for the boot up until command prompt. + + This is for internal use only. """ try: self.log.info('Waiting for U-Boot to be ready') @@ -217,7 +226,7 @@ class ConsoleBase(object): if m == 1: self.set_lab_mode() break - elif m != 0: + if m != 0: raise BootFail('Bad pattern found on SPL console: ' + self.bad_pattern_ids[m - 1]) env_spl_banner_times -= 1 @@ -238,10 +247,10 @@ class ConsoleBase(object): if m == 0: self.log.info(f'Found ready prompt {m}') break - elif m == 1: + if m == 1: m = pattern_ready_prompt.search(self.p.after) self.u_boot_version_string = m.group(2) - self.log.info(f'Lab: Board is ready') + self.log.info('Lab: Board is ready') self.p.timeout = TIMEOUT_MS break if m == 2: @@ -251,13 +260,13 @@ class ConsoleBase(object): if not self.lab_mode: raise BootFail('Missing prompt / ready message on console: ' + self.bad_pattern_ids[m - 3]) - self.log.info(f'U-Boot is ready') + self.log.info('U-Boot is ready') finally: self.log.timestamp() def run_command(self, cmd, wait_for_echo=True, send_nl=True, - wait_for_prompt=True, wait_for_reboot=False): + wait_for_prompt=True, wait_for_reboot=False): """Execute a command via the U-Boot console. The command is always sent to U-Boot. @@ -277,27 +286,25 @@ class ConsoleBase(object): running command such as "ums". Args: - cmd: The command to send. - wait_for_echo: Boolean indicating whether to wait for U-Boot to + cmd (str): The command to send. + wait_for_echo (bool): Indicates whether to wait for U-Boot to echo the command text back to its output. - send_nl: Boolean indicating whether to send a newline character + send_nl (bool): Indicates whether to send a newline character after the command string. - wait_for_prompt: Boolean indicating whether to wait for the + wait_for_prompt (bool): Indicates whether to wait for the command prompt to be sent by U-Boot. This typically occurs immediately after the command has been executed. - wait_for_reboot: Boolean indication whether to wait for the - reboot U-Boot. If this sets True, wait_for_prompt must also - be True. + wait_for_reboot (bool): Indicates whether to wait U-Boot ro reboot. + If True, wait_for_prompt must also be True. Returns: If wait_for_prompt == False: - Nothing. + Empty string. Else: The output from U-Boot during command execution. In other words, the text U-Boot emitted between the point it echod the command string and emitted the subsequent command prompts. """ - if self.at_prompt and \ self.at_prompt_logevt != self.logstream.logfile.cur_evt: self.logstream.write(self.prompt, implicit=True) @@ -324,12 +331,13 @@ class ConsoleBase(object): m = self.p.expect([chunk] + self.bad_patterns) if m != 0: self.at_prompt = False - raise BootFail(f"Failed to get echo on console (cmd '{cmd}':rem '{rem}'): " + - self.bad_pattern_ids[m - 1]) + raise BootFail('Failed to get echo on console ' + f"(cmd '{cmd}':rem '{rem}'): " + + self.bad_pattern_ids[m - 1]) if not wait_for_prompt: - return + return '' if wait_for_reboot: - self.wait_for_boot_prompt() + self._wait_for_boot_prompt() else: m = self.p.expect([self.prompt_compiled] + self.bad_patterns) if m != 0: @@ -352,6 +360,7 @@ class ConsoleBase(object): raise finally: self.log.timestamp() + return '' def run_command_list(self, cmds): """Run a list of commands. @@ -360,7 +369,7 @@ class ConsoleBase(object): for each command in a list. Args: - cmd: List of commands (each a string). + cmds (list of str): List of commands Returns: A list of output strings from each command, one element for each command. @@ -403,14 +412,10 @@ class ConsoleBase(object): location in the log file. Args: - text: The text to wait for; either a string (containing raw text, - not a regular expression) or an re object. - - Returns: - Nothing. + text (str or re): The text to wait for; either a string (containing + raw text, not a regular expression) or an re object. """ - - if type(text) == type(''): + if isinstance(text, str): text = re.escape(text) m = self.p.expect([text] + self.bad_patterns) if m != 0: @@ -428,14 +433,7 @@ class ConsoleBase(object): exists. In such a case, it is useful to log U-Boot's console output in case U-Boot printed clues as to why the host-side even did not occur. This function will do that. - - Args: - None. - - Returns: - Nothing. """ - # If we are already not connected to U-Boot, there's nothing to drain. # This should only happen when a previous call to run_command() or # wait_for() failed (and hence the output has already been logged), or @@ -474,14 +472,10 @@ class ConsoleBase(object): This is an internal function and should not be called directly. Args: - expect_reset: Boolean indication whether this boot is expected + expect_reset (bool): Indicates whether this boot is expected to be reset while the 1st boot process after main boot before prompt. False by default. - - Returns: - Nothing. """ - if self.p: # Reset the console timeout value as some tests may change # its default value during the execution @@ -509,7 +503,7 @@ class ConsoleBase(object): loop_num = 2 else: loop_num = 1 - self.wait_for_boot_prompt(loop_num = loop_num) + self._wait_for_boot_prompt(loop_num = loop_num) self.at_prompt = True self.at_prompt_logevt = self.logstream.logfile.cur_evt except Exception as ex: @@ -527,14 +521,7 @@ class ConsoleBase(object): connection with a fresh U-Boot instance. This is an internal function and should not be called directly. - - Args: - None. - - Returns: - Nothing. """ - try: if self.p: self.p.close() @@ -564,13 +551,12 @@ class ConsoleBase(object): duplicating the signon text regex in a test function. Args: - text: The command output text to check. + text (str): The command output text to check. - Returns: - Nothing. An exception is raised if the validation fails. + Raises: + Assertion if the validation fails. """ - - assert(self.u_boot_version_string in text) + assert self.u_boot_version_string in text def disable_check(self, check_type): """Temporarily disable an error check of U-Boot's output. @@ -579,13 +565,12 @@ class ConsoleBase(object): temporarily disables a particular console output error check. Args: - check_type: The type of error-check to disable. Valid values may - be found in self.disable_check_count above. + check_type (str): The type of error-check to disable, see + bad_pattern_defs Returns: A context manager object. """ - return ConsoleDisableCheck(self, check_type) def enable_check(self, check_type, check_pattern): @@ -596,14 +581,14 @@ class ConsoleBase(object): arguments form a new element of bad_pattern_defs defined above. Args: - check_type: The type of error-check or bad pattern to enable. - check_pattern: The regexes for text error pattern or bad pattern + check_type (str): The type of error-check to disable, see + bad_pattern_defs + check_pattern (re.Pattern): Regex for text error / bad pattern to be checked. Returns: A context manager object. """ - return ConsoleEnableCheck(self, check_type, check_pattern) def temporary_timeout(self, timeout): @@ -613,10 +598,9 @@ class ConsoleBase(object): temporarily change timeout. Args: - timeout: Time in milliseconds. + timeout (int): Time in milliseconds. Returns: A context manager object. """ - return ConsoleSetupTimeout(self, timeout) diff --git a/test/py/spawn.py b/test/py/spawn.py index c703454389d..6f9417a57d3 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -180,11 +180,11 @@ class Spawn: if os.WIFEXITED(status): self.exit_code = os.WEXITSTATUS(status) - self.exit_info = 'status %d' % self.exit_code + self.exit_info = f'status {self.exit_code}' elif os.WIFSIGNALED(status): signum = os.WTERMSIG(status) self.exit_code = -signum - self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum).name) + self.exit_info = f'signal {signum} ({signal.Signals(signum).name})' self.waited = True return False, self.exit_code, self.exit_info -- 2.43.0

From: Simon Glass <sjg@chromium.org> Some of the attributes are missing comments. Add these and tidy up a few existing ones. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 7f9c9979c32..b515604e6b4 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -135,15 +135,35 @@ class ConsoleBase(): Can only usefully be called by sub-classes. Args: - log: A multiplexed_log.Logfile object, to which the U-Boot output - will be logged. - config: A configuration data structure, as built by conftest.py. - max_fifo_fill: The maximum number of characters to send to U-Boot + log (multiplexed_log.Logfile): Log to which the U-Boot output is + logged. + config (ArbitraryAttributeContainer): ubman_fix.config, as built by + conftest.py. + max_fifo_fill (int): The max number of characters to send to U-Boot command-line before waiting for U-Boot to echo the characters back. For UART-based HW without HW flow control, this value should be set less than the UART RX FIFO size to avoid overflow, assuming that U-Boot can't keep up with full-rate traffic at the baud rate. + + Properties: + logstream (LogfileStream): Log stream being used + prompt (str): Prompt string expected from U-Boot + p (spawn.Spawn): Means of communicating with running U-Boot via a + console + disable_check_count: dict of 'nest counts' for patterns + key (str): NamedPattern.name + value (int): 0 if not disabled, >0 for the number of 'requests + to disable' that have been received for this pattern + at_prompt (bool): True if the running U-Boot is at a prompt and + thus ready to receive commands + at_prompt_logevt (int): Logstream event number when the prompt was + detected. This is used to avoid logging the prompt twice + lab_mode (bool): True if the lab is responsible for getting U-Boot + to a prompt, i.e. able to process commands on the console + u_boot_version_string (str): Version string obtained from U-Boot as + it booted. In lab mode this is provided by + pattern_ready_prompt """ self.log = log self.config = config @@ -163,6 +183,8 @@ class ConsoleBase(): self.lab_mode = False self.u_boot_version_string = None + self.eval_bad_patterns() + def get_spawn(self): """This is not called, ssubclass must define this. -- 2.43.0

From: Simon Glass <sjg@chromium.org> There are quite a few warnings which makes it confusing when editing this file. Resolve the easy ones, leaving: 125:0: R0902: Too many instance attributes (14/7) (too-many-instance-attributes) 212:4: R0912: Too many branches (14/12) (too-many-branches) 271:4: R0913: Too many arguments (6/5) (too-many-arguments) 271:4: R0912: Too many branches (13/12) (too-many-branches) 454:8: W0702: No exception type(s) specified (bare-except) 531:8: W0702: No exception type(s) specified (bare-except) Rename wait_for_boot_prompt() so it is clear that it is an internal function. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_board.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/py/console_board.py b/test/py/console_board.py index bacb1e2526c..c6d5e333f03 100644 --- a/test/py/console_board.py +++ b/test/py/console_board.py @@ -34,7 +34,7 @@ class ConsoleExecAttach(ConsoleBase): # 1 would be safe anywhere, but is very slow (a pexpect issue?). # 16 is a common FIFO size. # HW flow control would mean this could be infinite. - super(ConsoleExecAttach, self).__init__(log, config, max_fifo_fill=16) + super().__init__(log, config, max_fifo_fill=16) with self.log.section('flash'): self.log.action('Flashing U-Boot') -- 2.43.0

From: Simon Glass <sjg@chromium.org> There are quite a few warnings which makes it confusing when editing this file. Resolve them. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_sandbox.py | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/test/py/console_sandbox.py b/test/py/console_sandbox.py index dd721067f8f..c9a5a057bb8 100644 --- a/test/py/console_sandbox.py +++ b/test/py/console_sandbox.py @@ -18,14 +18,11 @@ class ConsoleSandbox(ConsoleBase): """Initialize a U-Boot console connection. Args: - log: A multiplexed_log.Logfile instance. - config: A "configuration" object as defined in conftest.py. - - Returns: - Nothing. + log (multiplexed_log.Logfile): Log file to write to + config (ArbitraryAttributeContainer): ubconfig "configuration" + object as defined in conftest.py """ - - super(ConsoleSandbox, self).__init__(log, config, max_fifo_fill=1024) + super().__init__(log, config, max_fifo_fill=1024) self.sandbox_flags = [] self.use_dtb = True @@ -35,13 +32,9 @@ class ConsoleSandbox(ConsoleBase): A new sandbox process is created, so that U-Boot begins running from scratch. - Args: - None. - Returns: A spawn.Spawn object that is attached to U-Boot. """ - bcfg = self.config.buildconfig config_spl = bcfg.get('config_spl', 'n') == 'y' config_vpl = bcfg.get('config_vpl', 'n') == 'y' @@ -64,13 +57,13 @@ class ConsoleSandbox(ConsoleBase): """Run U-Boot with the given command-line flags Args: - flags: List of flags to pass, each a string - use_dtb: True to use a device tree file, False to run without one + flags (list of str): List of flags to pass + use_dtb (bool): True to use a device tree file, False to run without + one Returns: A spawn.Spawn object that is attached to U-Boot. """ - try: self.sandbox_flags = flags self.use_dtb = use_dtb @@ -83,13 +76,9 @@ class ConsoleSandbox(ConsoleBase): """Send a specific Unix signal to the sandbox process. Args: - sig: The Unix signal to send to the process. - - Returns: - Nothing. + sig (int): Unix signal to send to the process """ - - self.log.action('kill %d' % sig) + self.log.action(f'kill {sig}') self.p.kill(sig) def validate_exited(self): @@ -98,16 +87,12 @@ class ConsoleSandbox(ConsoleBase): If required, this function waits a reasonable time for the process to exit. - Args: - None. - Returns: Boolean indicating whether the process has exited. """ - p = self.p self.p = None - for i in range(100): + for _ in range(100): ret = not p.isalive() if ret: break -- 2.43.0

From: Simon Glass <sjg@chromium.org> There are quite a few warnings which makes it confusing when editing this file. Resolve the easy ones, with these remaining: 55:0: R0913: Too many arguments (7/5) (too-many-arguments) 91:0: R0902: Too many instance attributes (14/7) (too-many-instance-attributes) 136:12: W0702: No exception type(s) specified (bare-except) 246:4: R0912: Too many branches (14/12) (too-many-branches) Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/spawn.py | 64 +++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/test/py/spawn.py b/test/py/spawn.py index 6f9417a57d3..49b9f2729ac 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -3,19 +3,36 @@ """ Logic to spawn a sub-process and interact with its stdio. + +This is used by console_board and console_sandbox + +- console_board (for real hardware): Spawns 'u-boot-test-console' and provides + access to the console input/output +- console_sandbox (for sandbox): Spawns 'u-boot' and provides access to the + console input/output + +In both cases, Spawn provides a way to send console commands and receive the +response from U-Boot. An expect() function helps to simplify things for the +higher levels. + +The code in this file should be generic, i.e. not specific to sandbox or real +hardware. + +Within the console_*py files, self.p is used to refer to the Spawn() object, +perhaps short for 'process'. """ import io import os import re import pty -import pytest import signal import select import sys import termios import time import traceback +import pytest # Character to send (twice) to exit the terminal EXIT_CHAR = 0x1d # FS (Ctrl + ]) @@ -83,9 +100,10 @@ class Spawn: """Spawn (fork/exec) the sub-process. Args: - args: array of processs arguments. argv[0] is the command to - execute. - cwd: the directory to run the process in, or None for no change. + args (list of str): processs arguments. argv[0] is the command to + execute. + cwd (str or None): the directory to run the process in, or None for + no change. decode_signal (bool): True to indicate the exception number when something goes wrong @@ -128,7 +146,7 @@ class Spawn: isatty = os.isatty(sys.stdout.fileno()) # with --capture=tee-sys we cannot call fileno() - except io.UnsupportedOperation as exc: + except io.UnsupportedOperation: pass if isatty: new = termios.tcgetattr(self.fd) @@ -152,12 +170,8 @@ class Spawn: """Send unix signal "sig" to the child process. Args: - sig: The signal number to send. - - Returns: - Nothing. + sig (int): The signal number to send """ - os.kill(self.pid, sig) def checkalive(self): @@ -169,7 +183,6 @@ class Spawn: 0 if process is alive, else exit code of process string describing what happened ('' or 'status/signal n') """ - if self.waited: return False, self.exit_code, self.exit_info @@ -191,11 +204,8 @@ class Spawn: def isalive(self): """Determine whether the child process is still running. - Args: - None. - Returns: - Boolean indicating whether process is alive. + bool: indicating whether process is alive """ return self.checkalive()[0] @@ -203,12 +213,8 @@ class Spawn: """Send data to the sub-process's stdin. Args: - data: The data to send to the process. - - Returns: - Nothing. + data (str): The data to send to the process. """ - os.write(self.fd, data.encode(errors='replace')) def receive(self, num_bytes): @@ -233,7 +239,7 @@ class Spawn: alive, _, info = self.checkalive() if alive: raise err - raise ValueError('U-Boot exited with %s' % info) + raise ValueError(f'U-Boot exited with {info}') from err raise return c @@ -244,30 +250,28 @@ class Spawn: supplied list of patterns, or for a timeout to occur. Args: - patterns: A list of strings or regex objects that we expect to + patterns (list of str or regex.Regex): Patterns we expect to see in the sub-process' stdout. Returns: - The index within the patterns array of the pattern the process + int: index within the patterns array of the pattern the process emitted. Notable exceptions: Timeout, if the process did not emit any of the patterns within the expected time. """ - - for pi in range(len(patterns)): - if type(patterns[pi]) == type(''): - patterns[pi] = re.compile(patterns[pi]) + for pi, pat in enumerate(patterns): + if isinstance(pat, str): + patterns[pi] = re.compile(pat) tstart_s = time.time() try: while True: earliest_m = None earliest_pi = None - for pi in range(len(patterns)): - pattern = patterns[pi] - m = pattern.search(self.buf) + for pi, pat in enumerate(patterns): + m = pat.search(self.buf) if not m: continue if earliest_m and m.start() >= earliest_m.start(): -- 2.43.0

From: Simon Glass <sjg@chromium.org> It is a bit clumsy to have to subscript a list. Use a named tuple instead. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index b515604e6b4..d03e5b017c6 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -10,6 +10,7 @@ operations, such as spawning a sub-process for Sandbox, or attaching to the serial console of real hardware. """ +from collections import namedtuple import re import sys @@ -26,9 +27,6 @@ pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ## pattern_ready_prompt = re.compile('{lab ready in (.*)s: (.*)}') pattern_lab_mode = re.compile('{lab mode.*}') -PAT_ID = 0 -PAT_RE = 1 - # Timeout before expecting the console to be ready (in milliseconds) TIMEOUT_MS = 30000 # Standard timeout TIMEOUT_CMD_MS = 10000 # Command-echo timeout @@ -40,18 +38,20 @@ TIMEOUT_CMD_MS = 10000 # Command-echo timeout # situations. TIMEOUT_PREPARE_MS = 3 * 60 * 1000 -# Named patterns we can look for in the console output. These can indicate an -# error has occurred -# Tuple: +# Named pattern used by this module: # str: name of pattern # re.Pattern: Regex to check this pattern in the console output +NamedPattern = namedtuple('PATTERN', 'name,pattern') + +# Named patterns we can look for in the console output. These can indicate an +# error has occurred bad_pattern_defs = ( - ('spl_signon', pattern_u_boot_spl_signon), - ('main_signon', pattern_u_boot_main_signon), - ('stop_autoboot_prompt', pattern_stop_autoboot_prompt), - ('unknown_command', pattern_unknown_command), - ('error_notification', pattern_error_notification), - ('error_please_reset', pattern_error_please_reset), + NamedPattern('spl_signon', pattern_u_boot_spl_signon), + NamedPattern('main_signon', pattern_u_boot_main_signon), + NamedPattern('stop_autoboot_prompt', pattern_stop_autoboot_prompt), + NamedPattern('unknown_command', pattern_unknown_command), + NamedPattern('error_notification', pattern_error_notification), + NamedPattern('error_please_reset', pattern_error_please_reset), ) @@ -95,14 +95,14 @@ class ConsoleEnableCheck(): global bad_pattern_defs self.default_bad_patterns = bad_pattern_defs bad_pattern_defs += ((self.check_type, self.check_pattern),) - self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} + self.console.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} self.console.eval_bad_patterns() def __exit__(self, extype, value, traceback): # pylint:disable=W0603 global bad_pattern_defs bad_pattern_defs = self.default_bad_patterns - self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} + self.console.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} self.console.eval_bad_patterns() @@ -175,7 +175,7 @@ class ConsoleBase(): self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE) self.p = None - self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} + self.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} self.eval_bad_patterns() self.at_prompt = False @@ -196,10 +196,10 @@ class ConsoleBase(): def eval_bad_patterns(self): """Set up lists of regexes for patterns we don't expect on console""" - self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \ - if self.disable_check_count[pat[PAT_ID]] == 0] - self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \ - if self.disable_check_count[pat[PAT_ID]] == 0] + self.bad_patterns = [pat.pattern for pat in bad_pattern_defs + if not self.disable_check_count[pat.name]] + self.bad_pattern_ids = [pat.name for pat in bad_pattern_defs + if not self.disable_check_count[pat.name]] def close(self): """Terminate the connection to the U-Boot console. -- 2.43.0

From: Simon Glass <sjg@chromium.org> This name is quite confusing. Not all of the patterns are necessarily bad. For example, main_signon is expected on start-up. Rename it to the more neutral 'PATTERNS', using capitals since it is a global. Rename eval_bad_patterns() also. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index d03e5b017c6..14dfabb96c3 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -45,7 +45,7 @@ NamedPattern = namedtuple('PATTERN', 'name,pattern') # Named patterns we can look for in the console output. These can indicate an # error has occurred -bad_pattern_defs = ( +PATTERNS = ( NamedPattern('spl_signon', pattern_u_boot_spl_signon), NamedPattern('main_signon', pattern_u_boot_main_signon), NamedPattern('stop_autoboot_prompt', pattern_stop_autoboot_prompt), @@ -69,11 +69,11 @@ class ConsoleDisableCheck(): def __enter__(self): self.console.disable_check_count[self.check_type] += 1 - self.console.eval_bad_patterns() + self.console.eval_patterns() def __exit__(self, extype, value, traceback): self.console.disable_check_count[self.check_type] -= 1 - self.console.eval_bad_patterns() + self.console.eval_patterns() class ConsoleEnableCheck(): @@ -92,18 +92,18 @@ class ConsoleEnableCheck(): def __enter__(self): # pylint:disable=W0603 - global bad_pattern_defs - self.default_bad_patterns = bad_pattern_defs - bad_pattern_defs += ((self.check_type, self.check_pattern),) - self.console.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} - self.console.eval_bad_patterns() + global PATTERNS + self.default_bad_patterns = PATTERNS + PATTERNS += ((self.check_type, self.check_pattern),) + self.console.disable_check_count = {pat.name: 0 for pat in PATTERNS} + self.console.eval_patterns() def __exit__(self, extype, value, traceback): # pylint:disable=W0603 - global bad_pattern_defs - bad_pattern_defs = self.default_bad_patterns - self.console.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} - self.console.eval_bad_patterns() + global PATTERNS + PATTERNS = self.default_bad_patterns + self.console.disable_check_count = {pat.name: 0 for pat in PATTERNS} + self.console.eval_patterns() class ConsoleSetupTimeout(): @@ -175,15 +175,13 @@ class ConsoleBase(): self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE) self.p = None - self.disable_check_count = {pat.name: 0 for pat in bad_pattern_defs} - self.eval_bad_patterns() - + self.disable_check_count = {pat.name: 0 for pat in PATTERNS} self.at_prompt = False self.at_prompt_logevt = None self.lab_mode = False self.u_boot_version_string = None - self.eval_bad_patterns() + self.eval_patterns() def get_spawn(self): """This is not called, ssubclass must define this. @@ -194,11 +192,11 @@ class ConsoleBase(): """ return spawn.Spawn([]) - def eval_bad_patterns(self): + def eval_patterns(self): """Set up lists of regexes for patterns we don't expect on console""" - self.bad_patterns = [pat.pattern for pat in bad_pattern_defs + self.bad_patterns = [pat.pattern for pat in PATTERNS if not self.disable_check_count[pat.name]] - self.bad_pattern_ids = [pat.name for pat in bad_pattern_defs + self.bad_pattern_ids = [pat.name for pat in PATTERNS if not self.disable_check_count[pat.name]] def close(self): -- 2.43.0

From: Simon Glass <sjg@chromium.org> Some tests want to augment the list, so rather than using the global PATTERNS values, create an avail_patterns property in ConsoleBase With this we can avoid changing the global. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 14dfabb96c3..8fbad5a32b6 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -91,19 +91,17 @@ class ConsoleEnableCheck(): self.default_bad_patterns = None def __enter__(self): - # pylint:disable=W0603 - global PATTERNS - self.default_bad_patterns = PATTERNS - PATTERNS += ((self.check_type, self.check_pattern),) - self.console.disable_check_count = {pat.name: 0 for pat in PATTERNS} - self.console.eval_patterns() + cons = self.console + self.default_bad_patterns = cons.avail_patterns + cons.avail_patterns.append((self.check_type, self.check_pattern)) + cons.disable_check_count = {pat.name: 0 for pat in PATTERNS} + cons.eval_patterns() def __exit__(self, extype, value, traceback): - # pylint:disable=W0603 - global PATTERNS - PATTERNS = self.default_bad_patterns - self.console.disable_check_count = {pat.name: 0 for pat in PATTERNS} - self.console.eval_patterns() + cons = self.console + cons.avail_patterns = self.default_bad_patterns + cons.disable_check_count = {pat.name: 0 for pat in PATTERNS} + cons.eval_patterns() class ConsoleSetupTimeout(): @@ -151,6 +149,8 @@ class ConsoleBase(): prompt (str): Prompt string expected from U-Boot p (spawn.Spawn): Means of communicating with running U-Boot via a console + avail_patterns (list of NamedPattern): Normally the same as + PATTERNS but can be adjusted by tests disable_check_count: dict of 'nest counts' for patterns key (str): NamedPattern.name value (int): 0 if not disabled, >0 for the number of 'requests @@ -175,7 +175,8 @@ class ConsoleBase(): self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE) self.p = None - self.disable_check_count = {pat.name: 0 for pat in PATTERNS} + self.avail_patterns = PATTERNS + self.disable_check_count = {pat.name: 0 for pat in self.avail_patterns} self.at_prompt = False self.at_prompt_logevt = None self.lab_mode = False @@ -194,9 +195,9 @@ class ConsoleBase(): def eval_patterns(self): """Set up lists of regexes for patterns we don't expect on console""" - self.bad_patterns = [pat.pattern for pat in PATTERNS + self.bad_patterns = [pat.pattern for pat in self.avail_patterns if not self.disable_check_count[pat.name]] - self.bad_pattern_ids = [pat.name for pat in PATTERNS + self.bad_pattern_ids = [pat.name for pat in self.avail_patterns if not self.disable_check_count[pat.name]] def close(self): -- 2.43.0

From: Simon Glass <sjg@chromium.org> At present some tests are directly calling the Spawn() object in order to check for required output. This is a bit messy. Add a function to the ubman fixture to handle this. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 7 +++++ test/py/tests/test_bootmenu.py | 8 +++--- test/py/tests/test_distro.py | 10 +++---- test/py/tests/test_efi_selftest.py | 42 +++++++++++++++--------------- test/py/tests/test_eficonfig.py | 34 ++++++++++++------------ test/py/tests/test_net_boot.py | 6 ++--- test/py/tests/test_sandbox_exit.py | 4 +-- test/py/tests/test_saveenv.py | 2 +- 8 files changed, 60 insertions(+), 53 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 8fbad5a32b6..194db5fedf2 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -625,3 +625,10 @@ class ConsoleBase(): A context manager object. """ return ConsoleSetupTimeout(self, timeout) + + def expect(self, patterns): + """Call the Spawn.expect() function + + This is provided as a way for tests to check board output. + """ + self.p.expect(patterns) diff --git a/test/py/tests/test_bootmenu.py b/test/py/tests/test_bootmenu.py index 66f3fb8a131..a9b8f0c685e 100644 --- a/test/py/tests/test_bootmenu.py +++ b/test/py/tests/test_bootmenu.py @@ -18,24 +18,24 @@ def test_bootmenu(ubman): ubman.run_command('setenv bootmenu_2 test 3=echo ok 3') ubman.run_command('bootmenu 2', wait_for_prompt=False) for i in ('U-Boot Boot Menu', 'test 1', 'test 2', 'test 3', 'autoboot'): - ubman.p.expect([i]) + ubman.expect([i]) # Press enter key to execute default entry response = ubman.run_command(cmd='\x0d', wait_for_echo=False, send_nl=False) assert 'ok 2' in response ubman.run_command('bootmenu 2', wait_for_prompt=False) - ubman.p.expect(['autoboot']) + ubman.expect(['autoboot']) # Press up key to select prior entry followed by the enter key response = ubman.run_command(cmd='\x1b\x5b\x41\x0d', wait_for_echo=False, send_nl=False) assert 'ok 1' in response ubman.run_command('bootmenu 2', wait_for_prompt=False) - ubman.p.expect(['autoboot']) + ubman.expect(['autoboot']) # Press down key to select next entry followed by the enter key response = ubman.run_command(cmd='\x1b\x5b\x42\x0d', wait_for_echo=False, send_nl=False) assert 'ok 3' in response ubman.run_command('bootmenu 2; echo rc:$?', wait_for_prompt=False) - ubman.p.expect(['autoboot']) + ubman.expect(['autoboot']) # Press the escape key response = ubman.run_command(cmd='\x1b', wait_for_echo=False, send_nl=False) assert 'ok' not in response diff --git a/test/py/tests/test_distro.py b/test/py/tests/test_distro.py index e0874d75e35..3e67d30a3f7 100644 --- a/test/py/tests/test_distro.py +++ b/test/py/tests/test_distro.py @@ -16,14 +16,14 @@ def test_distro(ubman): with ubman.log.section('Grub'): # Wait for grub to come up and offset a menu - ubman.p.expect(['Try or Install Ubuntu']) + ubman.expect(['Try or Install Ubuntu']) # Press 'e' to edit the command line ubman.log.info("Pressing 'e'") ubman.run_command('e', wait_for_prompt=False, send_nl=False) # Wait until we see the editor appear - ubman.p.expect(['/casper/initrd']) + ubman.expect(['/casper/initrd']) # Go down to the 'linux' line. Avoid using down-arrow as that includes # an Escape character, which may be parsed by Grub as such, causing it @@ -48,15 +48,15 @@ def test_distro(ubman): # Tell grub to boot ubman.log.info("boot") ubman.ctrl('X') - ubman.p.expect(['Booting a command list']) + ubman.expect(['Booting a command list']) with ubman.log.section('Linux'): # Linux should start immediately - ubman.p.expect(['Linux version']) + ubman.expect(['Linux version']) with ubman.log.section('Ubuntu'): # Shortly later, we should see this banner - ubman.p.expect(['Welcome to .*Ubuntu 24.04.1 LTS.*!']) + ubman.expect(['Welcome to .*Ubuntu 24.04.1 LTS.*!']) ubman.restart_uboot() diff --git a/test/py/tests/test_efi_selftest.py b/test/py/tests/test_efi_selftest.py index 12cbe5caa9b..4ee1124e0ff 100644 --- a/test/py/tests/test_efi_selftest.py +++ b/test/py/tests/test_efi_selftest.py @@ -16,7 +16,7 @@ def test_efi_selftest_base(ubman): """ ubman.run_command(cmd='setenv efi_selftest') ubman.run_command(cmd='bootefi selftest', wait_for_prompt=False) - if ubman.p.expect(['Summary: 0 failures', 'Press any key']): + if ubman.expect(['Summary: 0 failures', 'Press any key']): raise Exception('Failures occurred during the EFI selftest') ubman.restart_uboot() @@ -39,7 +39,7 @@ def test_efi_selftest_device_tree(ubman): ubman.run_command(cmd='setenv efi_test "${serial#}x"') ubman.run_command(cmd='test "${efi_test}" = x && setenv serial# 0') ubman.run_command(cmd='bootefi selftest ${fdtcontroladdr}', wait_for_prompt=False) - if ubman.p.expect(['serial-number:', 'U-Boot']): + if ubman.expect(['serial-number:', 'U-Boot']): raise Exception('serial-number missing in device tree') ubman.restart_uboot() @@ -56,7 +56,7 @@ def test_efi_selftest_watchdog_reboot(ubman): assert '\'watchdog reboot\'' in output ubman.run_command(cmd='setenv efi_selftest watchdog reboot') ubman.run_command(cmd='bootefi selftest', wait_for_prompt=False) - if ubman.p.expect(['resetting', 'U-Boot']): + if ubman.expect(['resetting', 'U-Boot']): raise Exception('Reset failed in \'watchdog reboot\' test') ubman.run_command(cmd='', send_nl=False, wait_for_reboot=True) @@ -70,48 +70,48 @@ def test_efi_selftest_text_input(ubman): """ ubman.run_command(cmd='setenv efi_selftest text input') ubman.run_command(cmd='bootefi selftest', wait_for_prompt=False) - if ubman.p.expect([r'To terminate type \'x\'']): + if ubman.expect([r'To terminate type \'x\'']): raise Exception('No prompt for \'text input\' test') ubman.drain_console() # EOT ubman.run_command(cmd=chr(4), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 4 \(unknown\), scan code 0 \(Null\)']): + if ubman.expect([r'Unicode char 4 \(unknown\), scan code 0 \(Null\)']): raise Exception('EOT failed in \'text input\' test') ubman.drain_console() # BS ubman.run_command(cmd=chr(8), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 8 \(BS\), scan code 0 \(Null\)']): + if ubman.expect([r'Unicode char 8 \(BS\), scan code 0 \(Null\)']): raise Exception('BS failed in \'text input\' test') ubman.drain_console() # TAB ubman.run_command(cmd=chr(9), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 9 \(TAB\), scan code 0 \(Null\)']): + if ubman.expect([r'Unicode char 9 \(TAB\), scan code 0 \(Null\)']): raise Exception('BS failed in \'text input\' test') ubman.drain_console() # a ubman.run_command(cmd='a', wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)']): + if ubman.expect([r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)']): raise Exception('\'a\' failed in \'text input\' test') ubman.drain_console() # UP escape sequence ubman.run_command(cmd=chr(27) + '[A', wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 0 \(Null\), scan code 1 \(Up\)']): + if ubman.expect([r'Unicode char 0 \(Null\), scan code 1 \(Up\)']): raise Exception('UP failed in \'text input\' test') ubman.drain_console() # Euro sign ubman.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 8364 \(\'']): + if ubman.expect([r'Unicode char 8364 \(\'']): raise Exception('Euro sign failed in \'text input\' test') ubman.drain_console() ubman.run_command(cmd='x', wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect(['Summary: 0 failures', 'Press any key']): + if ubman.expect(['Summary: 0 failures', 'Press any key']): raise Exception('Failures occurred during the EFI selftest') ubman.restart_uboot() @@ -125,55 +125,55 @@ def test_efi_selftest_text_input_ex(ubman): """ ubman.run_command(cmd='setenv efi_selftest extended text input') ubman.run_command(cmd='bootefi selftest', wait_for_prompt=False) - if ubman.p.expect([r'To terminate type \'CTRL\+x\'']): + if ubman.expect([r'To terminate type \'CTRL\+x\'']): raise Exception('No prompt for \'text input\' test') ubman.drain_console() # EOT ubman.run_command(cmd=chr(4), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 100 \(\'d\'\), scan code 0 \(CTRL\+Null\)']): + if ubman.expect([r'Unicode char 100 \(\'d\'\), scan code 0 \(CTRL\+Null\)']): raise Exception('EOT failed in \'text input\' test') ubman.drain_console() # BS ubman.run_command(cmd=chr(8), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 8 \(BS\), scan code 0 \(\+Null\)']): + if ubman.expect([r'Unicode char 8 \(BS\), scan code 0 \(\+Null\)']): raise Exception('BS failed in \'text input\' test') ubman.drain_console() # TAB ubman.run_command(cmd=chr(9), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 9 \(TAB\), scan code 0 \(\+Null\)']): + if ubman.expect([r'Unicode char 9 \(TAB\), scan code 0 \(\+Null\)']): raise Exception('TAB failed in \'text input\' test') ubman.drain_console() # a ubman.run_command(cmd='a', wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)']): + if ubman.expect([r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)']): raise Exception('\'a\' failed in \'text input\' test') ubman.drain_console() # UP escape sequence ubman.run_command(cmd=chr(27) + '[A', wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 0 \(Null\), scan code 1 \(\+Up\)']): + if ubman.expect([r'Unicode char 0 \(Null\), scan code 1 \(\+Up\)']): raise Exception('UP failed in \'text input\' test') ubman.drain_console() # Euro sign ubman.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 8364 \(\'']): + if ubman.expect([r'Unicode char 8364 \(\'']): raise Exception('Euro sign failed in \'text input\' test') ubman.drain_console() # SHIFT+ALT+FN 5 ubman.run_command(cmd=b'\x1b\x5b\x31\x35\x3b\x34\x7e'.decode(), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect([r'Unicode char 0 \(Null\), scan code 15 \(SHIFT\+ALT\+FN 5\)']): + if ubman.expect([r'Unicode char 0 \(Null\), scan code 15 \(SHIFT\+ALT\+FN 5\)']): raise Exception('SHIFT+ALT+FN 5 failed in \'text input\' test') ubman.drain_console() ubman.run_command(cmd=chr(24), wait_for_echo=False, send_nl=False, wait_for_prompt=False) - if ubman.p.expect(['Summary: 0 failures', 'Press any key']): + if ubman.expect(['Summary: 0 failures', 'Press any key']): raise Exception('Failures occurred during the EFI selftest') ubman.restart_uboot() @@ -192,6 +192,6 @@ def test_efi_selftest_tcg2(ubman): assert '\'tcg2\'' in output ubman.run_command(cmd='setenv efi_selftest tcg2') ubman.run_command(cmd='bootefi selftest', wait_for_prompt=False) - if ubman.p.expect(['Summary: 0 failures', 'Press any key']): + if ubman.expect(['Summary: 0 failures', 'Press any key']): raise Exception('Failures occurred during the EFI selftest') ubman.restart_uboot() diff --git a/test/py/tests/test_eficonfig.py b/test/py/tests/test_eficonfig.py index ac1600ec252..20ea8d9f28f 100644 --- a/test/py/tests/test_eficonfig.py +++ b/test/py/tests/test_eficonfig.py @@ -62,7 +62,7 @@ def test_efi_eficonfig(ubman): wait_for_echo=False, send_nl=False) if expect_str is not None: for i in expect_str: - ubman.p.expect([i]) + ubman.expect([i]) def press_up_down_enter_and_wait(up_count, down_count, enter, expect_str): # press UP key @@ -80,7 +80,7 @@ def test_efi_eficonfig(ubman): # wait expected output if expect_str is not None: for i in expect_str: - ubman.p.expect([i]) + ubman.expect([i]) def press_escape_key(wait_prompt): ubman.run_command(cmd='\x1b', wait_for_prompt=wait_prompt, wait_for_echo=False, send_nl=False) @@ -92,7 +92,7 @@ def test_efi_eficonfig(ubman): def check_current_is_maintenance_menu(): for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option', 'Change Boot Order', 'Delete Boot Option', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) """ Unit test for "eficonfig" command The menu-driven interface is used to set up UEFI load options. @@ -117,12 +117,12 @@ def test_efi_eficonfig(ubman): ubman.run_command('eficonfig', wait_for_prompt=False) for i in ('UEFI Maintenance Menu', 'Add Boot Option', 'Edit Boot Option', 'Change Boot Order', 'Delete Boot Option', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Select "Add Boot Option" press_enter_key(False) for i in ('Add Boot Option', 'Description:', 'File', 'Initrd File', 'Optional Data', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) press_escape_key(False) check_current_is_maintenance_menu() # return to U-Boot console @@ -140,7 +140,7 @@ def test_efi_eficonfig(ubman): # Change the Boot Order press_up_down_enter_and_wait(0, 2, True, 'Quit') for i in ('host 0:1', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # disable auto generated boot option for succeeding test ubman.run_command(cmd=' ', wait_for_prompt=False, wait_for_echo=False, send_nl=False) @@ -182,7 +182,7 @@ def test_efi_eficonfig(ubman): send_user_input_and_wait('nocolor', None) for i in ('Description: test 1', 'File: host 0:1/initrddump.efi', 'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Save the Boot Option press_up_down_enter_and_wait(0, 4, True, None) @@ -231,7 +231,7 @@ def test_efi_eficonfig(ubman): send_user_input_and_wait('nocolor', None) for i in ('Description: test 2', 'File: host 0:1/initrddump.efi', 'Initrd File: host 0:1/initrd-2.img', 'Optional Data: nocolor', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Save the Boot Option press_up_down_enter_and_wait(0, 4, True, 'Quit') @@ -243,7 +243,7 @@ def test_efi_eficonfig(ubman): ubman.run_command(cmd='+', wait_for_prompt=False, wait_for_echo=False, send_nl=False) for i in ('test 2', 'test 1', 'host 0:1', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Save the BootOrder press_up_down_enter_and_wait(0, 3, True, None) check_current_is_maintenance_menu() @@ -265,12 +265,12 @@ def test_efi_eficonfig(ubman): press_up_down_enter_and_wait(0, 2, True, None) # Check the current BootOrder for i in ('test 2', 'test 1', 'host 0:1', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # move 'test 2' to the second entry ubman.run_command(cmd='-', wait_for_prompt=False, wait_for_echo=False, send_nl=False) for i in ('test 1', 'test 2', 'host 0:1', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Save the BootOrder press_up_down_enter_and_wait(0, 2, True, None) check_current_is_maintenance_menu() @@ -291,12 +291,12 @@ def test_efi_eficonfig(ubman): press_up_down_enter_and_wait(0, 3, True, None) # Check the current BootOrder for i in ('test 1', 'test 2', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Delete 'test 2' press_up_down_enter_and_wait(0, 1, True, None) for i in ('test 1', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) press_escape_key(False) check_current_is_maintenance_menu() # Return to U-Boot console @@ -310,11 +310,11 @@ def test_efi_eficonfig(ubman): press_up_down_enter_and_wait(0, 1, True, None) # Check the current BootOrder for i in ('test 1', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) press_up_down_enter_and_wait(0, 0, True, None) for i in ('Description: test 1', 'File: host 0:1/initrddump.efi', 'Initrd File: host 0:1/initrd-1.img', 'Optional Data: nocolor', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Press the enter key to select 'Description:' entry, then enter Description press_up_down_enter_and_wait(0, 0, True, 'Enter description:') @@ -343,7 +343,7 @@ def test_efi_eficonfig(ubman): send_user_input_and_wait('', None) for i in ('Description: test 3', 'File: host 0:1/initrddump.efi', 'Initrd File: host 0:1/initrd-2.img', 'Optional Data:', 'Save', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Save the Boot Option press_up_down_enter_and_wait(0, 4, True, 'Quit') @@ -367,7 +367,7 @@ def test_efi_eficonfig(ubman): press_up_down_enter_and_wait(0, 3, True, None) # Check the current BootOrder for i in ('test 3', 'Quit'): - ubman.p.expect([i]) + ubman.expect([i]) # Delete 'test 3' press_up_down_enter_and_wait(0, 0, True, 'Quit') diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py index abf6dfbaf5e..d9473777111 100644 --- a/test/py/tests/test_net_boot.py +++ b/test/py/tests/test_net_boot.py @@ -318,7 +318,7 @@ def test_net_pxe_boot_config(ubman): # should not boot it and come out to u-boot prompt ubman.wait_for('Enter choice:') ubman.run_command(local_label, wait_for_prompt=False) - expected_str = ubman.p.expect([exp_str_local]) + expected_str = ubman.expect([exp_str_local]) assert ( expected_str == 0 ), f'Expected string: {exp_str_local} did not match!' @@ -329,7 +329,7 @@ def test_net_pxe_boot_config(ubman): ubman.run_command(pxe_boot_cmd, wait_for_prompt=False) ubman.wait_for('Enter choice:') ubman.run_command(empty_label, wait_for_prompt=False) - expected_str = ubman.p.expect([exp_str_empty]) + expected_str = ubman.expect([exp_str_empty]) assert ( expected_str == 0 ), f'Expected string: {exp_str_empty} did not match!' @@ -389,7 +389,7 @@ def test_net_pxe_boot_config_invalid(ubman): # label and if it fails it should load the default label to boot ubman.wait_for('Enter choice:') ubman.run_command(invalid_label, wait_for_prompt=False) - expected_str = ubman.p.expect([exp_str_invalid]) + expected_str = ubman.expect([exp_str_invalid]) assert ( expected_str == 0 ), f'Expected string: {exp_str_invalid} did not match!' diff --git a/test/py/tests/test_sandbox_exit.py b/test/py/tests/test_sandbox_exit.py index 9610adf1fe7..849fd477941 100644 --- a/test/py/tests/test_sandbox_exit.py +++ b/test/py/tests/test_sandbox_exit.py @@ -27,10 +27,10 @@ def test_exception_reset(ubman): """Test that SIGILL causes a reset.""" ubman.run_command('exception undefined', wait_for_prompt=False) - m = ubman.p.expect(['resetting ...', 'U-Boot']) + m = ubman.expect(['resetting ...', 'U-Boot']) if m != 0: raise Exception('SIGILL did not lead to reset') - m = ubman.p.expect(['U-Boot', '=>']) + m = ubman.expect(['U-Boot', '=>']) if m != 0: raise Exception('SIGILL did not lead to reset') ubman.restart_uboot() diff --git a/test/py/tests/test_saveenv.py b/test/py/tests/test_saveenv.py index 019b229d30e..555284906da 100644 --- a/test/py/tests/test_saveenv.py +++ b/test/py/tests/test_saveenv.py @@ -61,7 +61,7 @@ def check_env(ubman, var_name, var_value): assert ret_code(ubman).endswith('0') else: ubman.p.send(f'printenv {var_name}\n') - output = ubman.p.expect(['not defined']) + output = ubman.expect(['not defined']) assert output == 0 assert ret_code(ubman).endswith('1') -- 2.43.0

From: Simon Glass <sjg@chromium.org> As a first step towards moving the expect() handler to console_base, move the exceptions there. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/conftest.py | 2 +- test/py/console_base.py | 55 ++++++++++++++++++++++++++++++++++++++++- test/py/spawn.py | 50 ------------------------------------- 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/test/py/conftest.py b/test/py/conftest.py index 8b198c93ccc..f0101ed6b3d 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -25,7 +25,7 @@ import re from _pytest.runner import runtestprotocol import subprocess import sys -from spawn import BootFail, Timeout, Unexpected, handle_exception +from console_base import BootFail, Timeout, Unexpected, handle_exception import time # Globals: The HTML log file, and the top-level fixture diff --git a/test/py/console_base.py b/test/py/console_base.py index 194db5fedf2..25adcda215b 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -13,9 +13,9 @@ serial console of real hardware. from collections import namedtuple import re import sys +import pytest import spawn -from spawn import BootFail, Timeout, Unexpected, handle_exception # Regexes for text we expect U-Boot to send to the console. pattern_u_boot_spl_signon = re.compile('(U-Boot Concept SPL \\d{4}\\.\\d{2}[^\r\n]*\\))') @@ -55,6 +55,59 @@ PATTERNS = ( ) +class Timeout(Exception): + """An exception sub-class that indicates that a timeout occurred.""" + + +class BootFail(Exception): + """An exception sub-class that indicates that a boot failure occurred. + + This is used when a bad pattern is seen when waiting for the boot prompt. + It is regarded as fatal, to avoid trying to boot the again and again to no + avail. + """ + + +class Unexpected(Exception): + """An exception sub-class that indicates that unexpected test was seen.""" + + +def handle_exception(ubconfig, console, log, err, name, fatal, output=''): + """Handle an exception from the console + + Exceptions can occur when there is unexpected output or due to the board + crashing or hanging. Some exceptions are likely fatal, where retrying will + just chew up time to no available. In those cases it is best to cause + further tests be skipped. + + Args: + ubconfig (ArbitraryAttributeContainer): ubconfig object + log (Logfile): Place to log errors + console (ConsoleBase): Console to clean up, if fatal + err (Exception): Exception which was thrown + name (str): Name of problem, to log + fatal (bool): True to abort all tests + output (str): Extra output to report on boot failure. This can show the + target's console output as it tried to boot + """ + msg = f'{name}: ' + if fatal: + msg += 'Marking connection bad - no other tests will run' + else: + msg += 'Assuming that lab is healthy' + print(msg) + log.error(msg) + log.error(f'Error: {err}') + + if output: + msg += f'; output {output}' + + if fatal: + ubconfig.connection_ok = False + console.cleanup_spawn() + pytest.exit(msg) + + class ConsoleDisableCheck(): """Context manager (for Python's with statement) that temporarily disables the specified console output error check. This is useful when deliberately diff --git a/test/py/spawn.py b/test/py/spawn.py index 49b9f2729ac..65555e0d901 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -37,56 +37,6 @@ import pytest # Character to send (twice) to exit the terminal EXIT_CHAR = 0x1d # FS (Ctrl + ]) -class Timeout(Exception): - """An exception sub-class that indicates that a timeout occurred.""" - -class BootFail(Exception): - """An exception sub-class that indicates that a boot failure occurred. - - This is used when a bad pattern is seen when waiting for the boot prompt. - It is regarded as fatal, to avoid trying to boot the again and again to no - avail. - """ - -class Unexpected(Exception): - """An exception sub-class that indicates that unexpected test was seen.""" - - -def handle_exception(ubconfig, console, log, err, name, fatal, output=''): - """Handle an exception from the console - - Exceptions can occur when there is unexpected output or due to the board - crashing or hanging. Some exceptions are likely fatal, where retrying will - just chew up time to no available. In those cases it is best to cause - further tests be skipped. - - Args: - ubconfig (ArbitraryAttributeContainer): ubconfig object - log (Logfile): Place to log errors - console (ConsoleBase): Console to clean up, if fatal - err (Exception): Exception which was thrown - name (str): Name of problem, to log - fatal (bool): True to abort all tests - output (str): Extra output to report on boot failure. This can show the - target's console output as it tried to boot - """ - msg = f'{name}: ' - if fatal: - msg += 'Marking connection bad - no other tests will run' - else: - msg += 'Assuming that lab is healthy' - print(msg) - log.error(msg) - log.error(f'Error: {err}') - - if output: - msg += f'; output {output}' - - if fatal: - ubconfig.connection_ok = False - console.cleanup_spawn() - pytest.exit(msg) - class Spawn: """Represents the stdio of a freshly created sub-process. Commands may be -- 2.43.0

From: Simon Glass <sjg@chromium.org> This function is only used from console_base and it is always accessed via a 'self.p.expect()' expression, which is confusing. Checking for expected output doesn't really relate to spawning in any case, so move this function to console_base For now the spawn class is kept as it is, to reduce the change in each patch. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 80 +++++++++++++++++++++++++++++++----- test/py/spawn.py | 67 ------------------------------ test/py/tests/test_distro.py | 4 +- 3 files changed, 72 insertions(+), 79 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 25adcda215b..f00a66aca2b 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -13,6 +13,7 @@ serial console of real hardware. from collections import namedtuple import re import sys +import time import pytest import spawn @@ -295,7 +296,7 @@ class ConsoleBase(): while not self.lab_mode and loop_num > 0: loop_num -= 1 while config_spl_serial and not env_spl_skipped and env_spl_banner_times > 0: - m = self.p.expect([pattern_u_boot_spl_signon, + m = self.expect([pattern_u_boot_spl_signon, pattern_lab_mode] + self.bad_patterns) if m == 1: self.set_lab_mode() @@ -306,7 +307,7 @@ class ConsoleBase(): env_spl_banner_times -= 1 if not self.lab_mode: - m = self.p.expect([pattern_u_boot_main_signon, + m = self.expect([pattern_u_boot_main_signon, pattern_lab_mode] + self.bad_patterns) if m == 1: self.set_lab_mode() @@ -316,7 +317,7 @@ class ConsoleBase(): if not self.lab_mode: self.u_boot_version_string = self.p.after while True: - m = self.p.expect([self.prompt_compiled, pattern_ready_prompt, + m = self.expect([self.prompt_compiled, pattern_ready_prompt, pattern_stop_autoboot_prompt] + self.bad_patterns) if m == 0: self.log.info(f'Found ready prompt {m}') @@ -402,7 +403,7 @@ class ConsoleBase(): continue chunk = re.escape(chunk) chunk = chunk.replace('\\\n', '[\r\n]') - m = self.p.expect([chunk] + self.bad_patterns) + m = self.expect([chunk] + self.bad_patterns) if m != 0: self.at_prompt = False raise BootFail('Failed to get echo on console ' @@ -413,7 +414,7 @@ class ConsoleBase(): if wait_for_reboot: self._wait_for_boot_prompt() else: - m = self.p.expect([self.prompt_compiled] + self.bad_patterns) + m = self.expect([self.prompt_compiled] + self.bad_patterns) if m != 0: self.at_prompt = False raise BootFail('Missing prompt on console: ' + @@ -491,7 +492,7 @@ class ConsoleBase(): """ if isinstance(text, str): text = re.escape(text) - m = self.p.expect([text] + self.bad_patterns) + m = self.expect([text] + self.bad_patterns) if m != 0: raise Unexpected( "Unexpected pattern found on console (exp '{text}': " + @@ -521,7 +522,7 @@ class ConsoleBase(): self.p.timeout = 1000 # Wait for something U-Boot will likely never send. This will # cause the console output to be read and logged. - self.p.expect(['This should never match U-Boot output']) + self.expect(['This should never match U-Boot output']) except: # We expect a timeout, since U-Boot won't print what we waited # for. Squash it when it happens. @@ -680,8 +681,67 @@ class ConsoleBase(): return ConsoleSetupTimeout(self, timeout) def expect(self, patterns): - """Call the Spawn.expect() function + """Wait for the sub-process to emit specific data. - This is provided as a way for tests to check board output. + This function waits for the process to emit one pattern from the + supplied list of patterns, or for a timeout to occur. + + Args: + patterns (list of str or regex.Regex): Patterns we expect to + see in the sub-process' stdout. + + Returns: + int: index within the patterns array of the pattern the process + emitted. + + Notable exceptions: + Timeout, if the process did not emit any of the patterns within + the expected time. """ - self.p.expect(patterns) + for pi, pat in enumerate(patterns): + if isinstance(pat, str): + patterns[pi] = re.compile(pat) + + tstart_s = time.time() + try: + while True: + earliest_m = None + earliest_pi = None + for pi, pat in enumerate(patterns): + m = pat.search(self.p.buf) + if not m: + continue + if earliest_m and m.start() >= earliest_m.start(): + continue + earliest_m = m + earliest_pi = pi + if earliest_m: + pos = earliest_m.start() + posafter = earliest_m.end() + self.p.before = self.p.buf[:pos] + self.p.after = self.p.buf[pos:posafter] + self.p.output += self.p.buf[:posafter] + self.p.buf = self.p.buf[posafter:] + return earliest_pi + tnow_s = time.time() + if self.p.timeout: + tdelta_ms = (tnow_s - tstart_s) * 1000 + poll_maxwait = self.p.timeout - tdelta_ms + if tdelta_ms > self.p.timeout: + raise Timeout() + else: + poll_maxwait = None + events = self.p.poll.poll(poll_maxwait) + if not events: + raise Timeout() + c = self.p.receive(1024) + if self.p.logfile_read: + self.p.logfile_read.write(c) + self.p.buf += c + # count=0 is supposed to be the default, which indicates + # unlimited substitutions, but in practice the version of + # Python in Ubuntu 14.04 appears to default to count=2! + self.p.buf = self.p.re_vt100.sub('', self.p.buf, count=1000000) + finally: + if self.p.logfile_read: + self.p.logfile_read.flush() diff --git a/test/py/spawn.py b/test/py/spawn.py index 65555e0d901..b452c5ccc78 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -32,7 +32,6 @@ import sys import termios import time import traceback -import pytest # Character to send (twice) to exit the terminal EXIT_CHAR = 0x1d # FS (Ctrl + ]) @@ -193,72 +192,6 @@ class Spawn: raise return c - def expect(self, patterns): - """Wait for the sub-process to emit specific data. - - This function waits for the process to emit one pattern from the - supplied list of patterns, or for a timeout to occur. - - Args: - patterns (list of str or regex.Regex): Patterns we expect to - see in the sub-process' stdout. - - Returns: - int: index within the patterns array of the pattern the process - emitted. - - Notable exceptions: - Timeout, if the process did not emit any of the patterns within - the expected time. - """ - for pi, pat in enumerate(patterns): - if isinstance(pat, str): - patterns[pi] = re.compile(pat) - - tstart_s = time.time() - try: - while True: - earliest_m = None - earliest_pi = None - for pi, pat in enumerate(patterns): - m = pat.search(self.buf) - if not m: - continue - if earliest_m and m.start() >= earliest_m.start(): - continue - earliest_m = m - earliest_pi = pi - if earliest_m: - pos = earliest_m.start() - posafter = earliest_m.end() - self.before = self.buf[:pos] - self.after = self.buf[pos:posafter] - self.output += self.buf[:posafter] - self.buf = self.buf[posafter:] - return earliest_pi - tnow_s = time.time() - if self.timeout: - tdelta_ms = (tnow_s - tstart_s) * 1000 - poll_maxwait = self.timeout - tdelta_ms - if tdelta_ms > self.timeout: - raise Timeout() - else: - poll_maxwait = None - events = self.poll.poll(poll_maxwait) - if not events: - raise Timeout() - c = self.receive(1024) - if self.logfile_read: - self.logfile_read.write(c) - self.buf += c - # count=0 is supposed to be the default, which indicates - # unlimited substitutions, but in practice the version of - # Python in Ubuntu 14.04 appears to default to count=2! - self.buf = self.re_vt100.sub('', self.buf, count=1000000) - finally: - if self.logfile_read: - self.logfile_read.flush() - def close(self): """Close the stdio connection to the sub-process. diff --git a/test/py/tests/test_distro.py b/test/py/tests/test_distro.py index 3e67d30a3f7..a10a43c48f9 100644 --- a/test/py/tests/test_distro.py +++ b/test/py/tests/test_distro.py @@ -68,9 +68,9 @@ def test_distro_script(ubman): ubman.run_command('boot', wait_for_prompt=False) # This is the start of userspace - ubman.p.expect(['Welcome to TDX Wayland']) + ubman.expect(['Welcome to TDX Wayland']) # Shortly later, we should see this banner - ubman.p.expect(['Colibri-iMX8X_Reference-Multimedia-Image']) + ubman.expect(['Colibri-iMX8X_Reference-Multimedia-Image']) ubman.restart_uboot() -- 2.43.0

From: Simon Glass <sjg@chromium.org> This timeout relates to the expect() function so move it into the same file and class. Reset the timeout when a new spawn is created, to mimic the existing behabiour. Update a few tests which access the timeout directly. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 32 ++++++++++++++++++-------------- test/py/spawn.py | 1 - test/py/tests/test_net.py | 4 ++-- test/py/tests/test_net_boot.py | 6 +++--- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index f00a66aca2b..c0ba6660354 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -164,15 +164,15 @@ class ConsoleSetupTimeout(): then default 30s.""" def __init__(self, console, timeout): - self.p = console.p - self.orig_timeout = self.p.timeout - self.p.timeout = timeout + self.console = console + self.orig_timeout = self.console.timeout + self.console.timeout = timeout def __enter__(self): return self def __exit__(self, extype, value, traceback): - self.p.timeout = self.orig_timeout + self.console.timeout = self.orig_timeout class ConsoleBase(): @@ -218,6 +218,8 @@ class ConsoleBase(): u_boot_version_string (str): Version string obtained from U-Boot as it booted. In lab mode this is provided by pattern_ready_prompt + timeout (str): Timeout in seconds before giving up and aborting the + test """ self.log = log self.config = config @@ -235,6 +237,7 @@ class ConsoleBase(): self.at_prompt_logevt = None self.lab_mode = False self.u_boot_version_string = None + self.timeout = None self.eval_patterns() @@ -278,7 +281,7 @@ class ConsoleBase(): ready for use. We don't need to look for signon messages. """ self.log.info('test.py: Lab mode is active') - self.p.timeout = TIMEOUT_PREPARE_MS + self.timeout = TIMEOUT_PREPARE_MS self.lab_mode = True def _wait_for_boot_prompt(self, loop_num=1): @@ -326,7 +329,7 @@ class ConsoleBase(): m = pattern_ready_prompt.search(self.p.after) self.u_boot_version_string = m.group(2) self.log.info('Lab: Board is ready') - self.p.timeout = TIMEOUT_MS + self.timeout = TIMEOUT_MS break if m == 2: self.log.info(f'Found autoboot prompt {m}') @@ -516,10 +519,10 @@ class ConsoleBase(): if not self.p: return - orig_timeout = self.p.timeout + orig_timeout = self.timeout try: # Drain the log for a relatively short time. - self.p.timeout = 1000 + self.timeout = 1000 # Wait for something U-Boot will likely never send. This will # cause the console output to be read and logged. self.expect(['This should never match U-Boot output']) @@ -536,7 +539,7 @@ class ConsoleBase(): # correctly terminate any log sections, etc. pass finally: - self.p.timeout = orig_timeout + self.timeout = orig_timeout def ensure_spawned(self, expect_reset=False): """Ensure a connection to a correctly running U-Boot instance. @@ -555,18 +558,19 @@ class ConsoleBase(): # Reset the console timeout value as some tests may change # its default value during the execution if not self.config.gdbserver: - self.p.timeout = TIMEOUT_MS + self.timeout = TIMEOUT_MS return try: self.log.start_section('Starting U-Boot') self.at_prompt = False + self.timeout = None self.p = self.get_spawn() # Real targets can take a long time to scroll large amounts of # text if LCD is enabled. This value may need tweaking in the # future, possibly per-test to be optimal. This works for 'help' # on board 'seaboard'. if not self.config.gdbserver: - self.p.timeout = TIMEOUT_MS + self.timeout = TIMEOUT_MS self.p.logfile_read = self.logstream if self.config.use_running_system: # Send an empty command to set up the 'expect' logic. This has @@ -724,10 +728,10 @@ class ConsoleBase(): self.p.buf = self.p.buf[posafter:] return earliest_pi tnow_s = time.time() - if self.p.timeout: + if self.timeout: tdelta_ms = (tnow_s - tstart_s) * 1000 - poll_maxwait = self.p.timeout - tdelta_ms - if tdelta_ms > self.p.timeout: + poll_maxwait = self.timeout - tdelta_ms + if tdelta_ms > self.timeout: raise Timeout() else: poll_maxwait = None diff --git a/test/py/spawn.py b/test/py/spawn.py index b452c5ccc78..9fe7f15f0ec 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -68,7 +68,6 @@ class Spawn: self.logfile_read = None self.before = '' self.after = '' - self.timeout = None # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escap... self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I) diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 4732e4b57f8..9a9f74a61af 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -358,7 +358,7 @@ def test_net_pxe_get(ubman): pytest.skip("No PXE readable file to read") addr = f.get("addr", None) - timeout = f.get("timeout", ubman.p.timeout) + timeout = f.get("timeout", ubman.timeout) pxeuuid = uuid.uuid1() ubman.run_command(f"setenv pxeuuid {pxeuuid}") @@ -416,7 +416,7 @@ def test_net_tftpput(ubman): addr = utils.find_ram_base(ubman) sz = f.get("size", None) - timeout = f.get("timeout", ubman.p.timeout) + timeout = f.get("timeout", ubman.timeout) fn = f["fn"] fnu = f.get("fnu", "_".join([datetime.datetime.now().strftime("%y%m%d%H%M%S"), fn])) expected_text = "Bytes transferred = " diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py index d9473777111..93ef1edf033 100644 --- a/test/py/tests/test_net_boot.py +++ b/test/py/tests/test_net_boot.py @@ -224,7 +224,7 @@ def test_net_pxe_boot(ubman): f, bootfile = setup_pxe_boot(ubman) addr = f.get('addr', None) - timeout = f.get('timeout', ubman.p.timeout) + timeout = f.get('timeout', ubman.timeout) fn = f['fn'] if addr: @@ -275,7 +275,7 @@ def test_net_pxe_boot_config(ubman): f, bootfile = setup_pxe_boot(ubman) addr = f.get('addr', None) - timeout = f.get('timeout', ubman.p.timeout) + timeout = f.get('timeout', ubman.timeout) fn = f['fn'] local_label = f['local_label'] empty_label = f['empty_label'] @@ -354,7 +354,7 @@ def test_net_pxe_boot_config_invalid(ubman): f, bootfile = setup_pxe_boot(ubman) addr = f.get('addr', None) - timeout = f.get('timeout', ubman.p.timeout) + timeout = f.get('timeout', ubman.timeout) fn = f['fn'] invalid_label = f['invalid_label'] exp_str_invalid = f['exp_str_invalid'] -- 2.43.0

From: Simon Glass <sjg@chromium.org> Now that all the expect() functionality is in console_base, move the variables there too. Ensure they are reset when a new connection is spawned. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 64 +++++++++++++++++++++++++++----------- test/py/console_board.py | 2 +- test/py/console_sandbox.py | 1 + test/py/spawn.py | 19 ----------- 4 files changed, 48 insertions(+), 38 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index c0ba6660354..b0c476aa99e 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -218,8 +218,16 @@ class ConsoleBase(): u_boot_version_string (str): Version string obtained from U-Boot as it booted. In lab mode this is provided by pattern_ready_prompt + buf (str): Buffer of characters received from the console, still to + be processed + output (str); All data received from the console + before (str): Data before the matching string + after (str): String which patches the expected output timeout (str): Timeout in seconds before giving up and aborting the test + logfile_read (multiplexed_log.Logfile): Logfile used for logging + output + re_vt100 (re.Regex): Regex for filtering out vt100 characters """ self.log = log self.config = config @@ -237,17 +245,29 @@ class ConsoleBase(): self.at_prompt_logevt = None self.lab_mode = False self.u_boot_version_string = None - self.timeout = None + self.reset() + # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escap... + self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I) self.eval_patterns() + def reset(self): + """Reset all settings as we are about to spawn a new connection""" + self.buf = '' + self.output = '' + self.before = '' + self.after = '' + self.timeout = None + self.logfile_read = None + def get_spawn(self): - """This is not called, ssubclass must define this. + """This must be called by subclasses, to reset the system Return a value to avoid: console_base.py:348:12: E1128: Assigning result of a function call, where the function returns None (assignment-from-none) """ + self.reset() return spawn.Spawn([]) def eval_patterns(self): @@ -318,7 +338,7 @@ class ConsoleBase(): raise BootFail('Bad pattern found on console: ' + self.bad_pattern_ids[m - 1]) if not self.lab_mode: - self.u_boot_version_string = self.p.after + self.u_boot_version_string = self.after while True: m = self.expect([self.prompt_compiled, pattern_ready_prompt, pattern_stop_autoboot_prompt] + self.bad_patterns) @@ -326,7 +346,7 @@ class ConsoleBase(): self.log.info(f'Found ready prompt {m}') break if m == 1: - m = pattern_ready_prompt.search(self.p.after) + m = pattern_ready_prompt.search(self.after) self.u_boot_version_string = m.group(2) self.log.info('Lab: Board is ready') self.timeout = TIMEOUT_MS @@ -426,7 +446,7 @@ class ConsoleBase(): self.at_prompt_logevt = self.logstream.logfile.cur_evt # Only strip \r\n; space/TAB might be significant if testing # indentation. - return self.p.before.strip('\r\n') + return self.before.strip('\r\n') except Timeout as exc: handle_exception(self.config, self, self.log, exc, f"Lab failure: Timeout executing '{cmd}'", True) @@ -571,7 +591,7 @@ class ConsoleBase(): # on board 'seaboard'. if not self.config.gdbserver: self.timeout = TIMEOUT_MS - self.p.logfile_read = self.logstream + self.logfile_read = self.logstream if self.config.use_running_system: # Send an empty command to set up the 'expect' logic. This has # the side effect of ensuring that there was no partial command @@ -620,7 +640,7 @@ class ConsoleBase(): The output produced by ensure_spawed(), as a string. """ if self.p: - return self.p.get_expect_output() + return self.get_expect_output() return None def validate_version_string_in_text(self, text): @@ -712,7 +732,7 @@ class ConsoleBase(): earliest_m = None earliest_pi = None for pi, pat in enumerate(patterns): - m = pat.search(self.p.buf) + m = pat.search(self.buf) if not m: continue if earliest_m and m.start() >= earliest_m.start(): @@ -722,10 +742,10 @@ class ConsoleBase(): if earliest_m: pos = earliest_m.start() posafter = earliest_m.end() - self.p.before = self.p.buf[:pos] - self.p.after = self.p.buf[pos:posafter] - self.p.output += self.p.buf[:posafter] - self.p.buf = self.p.buf[posafter:] + self.before = self.buf[:pos] + self.after = self.buf[pos:posafter] + self.output += self.buf[:posafter] + self.buf = self.buf[posafter:] return earliest_pi tnow_s = time.time() if self.timeout: @@ -739,13 +759,21 @@ class ConsoleBase(): if not events: raise Timeout() c = self.p.receive(1024) - if self.p.logfile_read: - self.p.logfile_read.write(c) - self.p.buf += c + if self.logfile_read: + self.logfile_read.write(c) + self.buf += c # count=0 is supposed to be the default, which indicates # unlimited substitutions, but in practice the version of # Python in Ubuntu 14.04 appears to default to count=2! - self.p.buf = self.p.re_vt100.sub('', self.p.buf, count=1000000) + self.buf = self.re_vt100.sub('', self.buf, count=1000000) finally: - if self.p.logfile_read: - self.p.logfile_read.flush() + if self.logfile_read: + self.logfile_read.flush() + + def get_expect_output(self): + """Return the output read by expect() + + Returns: + The output processed by expect(), as a string. + """ + return self.output diff --git a/test/py/console_board.py b/test/py/console_board.py index c6d5e333f03..8223668a84c 100644 --- a/test/py/console_board.py +++ b/test/py/console_board.py @@ -55,7 +55,7 @@ class ConsoleExecAttach(ConsoleBase): Returns: A spawn.Spawn object that is attached to U-Boot. """ - + super().get_spawn() args = [self.config.board_type, self.config.board_identity] s = Spawn(['u-boot-test-console'] + args) diff --git a/test/py/console_sandbox.py b/test/py/console_sandbox.py index c9a5a057bb8..53b55b1256e 100644 --- a/test/py/console_sandbox.py +++ b/test/py/console_sandbox.py @@ -35,6 +35,7 @@ class ConsoleSandbox(ConsoleBase): Returns: A spawn.Spawn object that is attached to U-Boot. """ + super().get_spawn() bcfg = self.config.buildconfig config_spl = bcfg.get('config_spl', 'n') == 'y' config_vpl = bcfg.get('config_vpl', 'n') == 'y' diff --git a/test/py/spawn.py b/test/py/spawn.py index 9fe7f15f0ec..85256b8aaac 100644 --- a/test/py/spawn.py +++ b/test/py/spawn.py @@ -24,7 +24,6 @@ perhaps short for 'process'. import io import os -import re import pty import signal import select @@ -40,9 +39,6 @@ EXIT_CHAR = 0x1d # FS (Ctrl + ]) class Spawn: """Represents the stdio of a freshly created sub-process. Commands may be sent to the process, and responses waited for. - - Members: - output: accumulated output from expect() """ def __init__(self, args, cwd=None, decode_signal=False): @@ -63,13 +59,6 @@ class Spawn: self.waited = False self.exit_code = 0 self.exit_info = '' - self.buf = '' - self.output = '' - self.logfile_read = None - self.before = '' - self.after = '' - # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escap... - self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I) (self.pid, self.fd) = pty.fork() if self.pid == 0: @@ -221,11 +210,3 @@ class Spawn: time.sleep(0.1) return 'timeout' - - def get_expect_output(self): - """Return the output read by expect() - - Returns: - The output processed by expect(), as a string. - """ - return self.output -- 2.43.0

From: Simon Glass <sjg@chromium.org> The banner-detection code is quite large and is not needed in lab mode. Move it into its own function. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 59 ++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index b0c476aa99e..cbfd10fdf3d 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -311,33 +311,9 @@ class ConsoleBase(): """ try: self.log.info('Waiting for U-Boot to be ready') - bcfg = self.config.buildconfig - config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y' - env_spl_skipped = self.config.env.get('env__spl_skipped', False) - env_spl_banner_times = self.config.env.get('env__spl_banner_times', 1) - - while not self.lab_mode and loop_num > 0: - loop_num -= 1 - while config_spl_serial and not env_spl_skipped and env_spl_banner_times > 0: - m = self.expect([pattern_u_boot_spl_signon, - pattern_lab_mode] + self.bad_patterns) - if m == 1: - self.set_lab_mode() - break - if m != 0: - raise BootFail('Bad pattern found on SPL console: ' + - self.bad_pattern_ids[m - 1]) - env_spl_banner_times -= 1 - if not self.lab_mode: - m = self.expect([pattern_u_boot_main_signon, - pattern_lab_mode] + self.bad_patterns) - if m == 1: - self.set_lab_mode() - elif m != 0: - raise BootFail('Bad pattern found on console: ' + - self.bad_pattern_ids[m - 1]) if not self.lab_mode: + self._wait_for_banner(loop_num) self.u_boot_version_string = self.after while True: m = self.expect([self.prompt_compiled, pattern_ready_prompt, @@ -363,6 +339,39 @@ class ConsoleBase(): finally: self.log.timestamp() + def _wait_for_banner(self, loop_num): + """Wait for a U-Boot banner to appear on the console + + Args: + loop_num (int): Number of times to expect a banner (used for when + U-Boot is expected to start up and then reset itself) + """ + bcfg = self.config.buildconfig + config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y' + env_spl_skipped = self.config.env.get('env__spl_skipped', False) + env_spl_banner_times = self.config.env.get('env__spl_banner_times', 1) + while loop_num > 0: + loop_num -= 1 + while config_spl_serial and not env_spl_skipped and env_spl_banner_times > 0: + m = self.expect([pattern_u_boot_spl_signon, + pattern_lab_mode] + self.bad_patterns) + if m == 1: + self.set_lab_mode() + break + if m != 0: + raise BootFail('Bad pattern found on SPL console: ' + + self.bad_pattern_ids[m - 1]) + env_spl_banner_times -= 1 + + if not self.lab_mode: + m = self.expect([pattern_u_boot_main_signon, + pattern_lab_mode] + self.bad_patterns) + if m == 1: + self.set_lab_mode() + elif m != 0: + raise BootFail('Bad pattern found on console: ' + + self.bad_pattern_ids[m - 1]) + def run_command(self, cmd, wait_for_echo=True, send_nl=True, wait_for_prompt=True, wait_for_reboot=False): """Execute a command via the U-Boot console. -- 2.43.0

From: Simon Glass <sjg@chromium.org> Some tests boot out of U-Boot or perform such other action that it can no-longer be used for tests. Provide a function to handle this, rather than having tests do it themselves. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 10 ++++++++++ test/py/tests/test_net_boot.py | 12 ++++-------- test/py/tests/test_zynqmp_rpu.py | 3 +-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index cbfd10fdf3d..3fbde86996c 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -637,6 +637,16 @@ class ConsoleBase(): pass self.p = None + def shutdown_required(self): + """Called to shut down the running U-Boot + + Some tests make changes to U-Boot which cannot be undone within the + test, such as booting an operating system. This function shuts down + U-Boot so that a new one will be started for any future tests + """ + self.drain_console() + self.cleanup_spawn() + def restart_uboot(self, expect_reset=False): """Shut down and restart U-Boot.""" self.cleanup_spawn() diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py index 93ef1edf033..34fac8e3b94 100644 --- a/test/py/tests/test_net_boot.py +++ b/test/py/tests/test_net_boot.py @@ -194,8 +194,7 @@ def test_net_tftpboot_boot(ubman): # This forces the console object to be shutdown, so any subsequent # test will reset the board back into U-Boot. We want to force this # no matter whether the kernel boot passed or failed. - ubman.drain_console() - ubman.cleanup_spawn() + ubman.shutdown_required() def setup_pxe_boot(ubman): f = ubman.config.env.get('env__net_pxe_bootable_file', None) @@ -257,8 +256,7 @@ def test_net_pxe_boot(ubman): ubman.run_command(pxe_boot_cmd, wait_for_prompt=False) ubman.wait_for(pattern) finally: - ubman.drain_console() - ubman.cleanup_spawn() + ubman.shutdown_required() @pytest.mark.buildconfigspec('cmd_pxe') def test_net_pxe_boot_config(ubman): @@ -336,8 +334,7 @@ def test_net_pxe_boot_config(ubman): ubman.wait_for(pattern) finally: - ubman.drain_console() - ubman.cleanup_spawn() + ubman.shutdown_required() @pytest.mark.buildconfigspec('cmd_pxe') def test_net_pxe_boot_config_invalid(ubman): @@ -396,5 +393,4 @@ def test_net_pxe_boot_config_invalid(ubman): ubman.wait_for(pattern) finally: - ubman.drain_console() - ubman.cleanup_spawn() + ubman.shutdown_required() diff --git a/test/py/tests/test_zynqmp_rpu.py b/test/py/tests/test_zynqmp_rpu.py index cda8c9203b7..479dca83d6d 100644 --- a/test/py/tests/test_zynqmp_rpu.py +++ b/test/py/tests/test_zynqmp_rpu.py @@ -218,5 +218,4 @@ def test_zynqmp_rpu_app_load_negative(ubman): disable_cpus(ubman, cpu_nums) # This forces the console object to be shutdown, so any subsequent test # will reset the board back into U-Boot. - ubman.drain_console() - ubman.cleanup_spawn() + ubman.shutdown_required() -- 2.43.0
participants (1)
-
Simon Glass