Compare commits

...

16 Commits
cia ... tesc

Author SHA1 Message Date
Simon Glass
4bae88d177 sandbox: Fix pinctrl warning on startup
With the test devicetree, the following warning is shown on startup:

pinctrl_select_state_full() sandbox_serial serial:
  pinctrl_select_state_full: uclass_get_device_by_phandle_id: err=-19

Add the required bootph tags so that the pinctrl-single driver is set
up before relocation, to avoid this warning.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:17:08 -06:00
Simon Glass
a636969d81 test/py: Add a proper function to shut down U-Boot
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>
Series-to: u-boot
Cover-letter:
test/py: Some code tidy-updates
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
END
2025-07-23 13:17:07 -06:00
Simon Glass
d1640335e1 test/py: Move timeouts to console_base
These timeouts are all handled in console_base now, so drop the
self.p.timeout and just use self.timeout

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:13:25 -06:00
Simon Glass
c30ea35d4e test/py: Split up _wait_for_boot_prompt
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>
2025-07-23 13:13:25 -06:00
Simon Glass
e314a5583c test/py: Move expect() function into console_base
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

Move the exception handling as well, since it is all related to
expect(). Also move variables which relate to the 'expect'
functionality.

Tidy up the imports while we are here.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:13:25 -06:00
Simon Glass
4a63f49f2f test/py: Provide an expect() function for use by tests
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>
2025-07-23 13:13:25 -06:00
Simon Glass
950b4b4b54 test/py: Maintain a list of available patterns
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>
2025-07-23 13:13:25 -06:00
Simon Glass
ffa2c74a90 test/py: Rename bad_pattern_defs to PATTERNS
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>
2025-07-23 13:13:25 -06:00
Simon Glass
06d51b3ea3 test/py: Create a named tuple for the pattern list
It is a bit clumsy to have to subscript a list. Use a named tuple
instead.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:13:25 -06:00
Simon Glass
b1d49b9d8f test/py: Tidy up pylint warnings in spawn
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>
2025-07-23 13:13:25 -06:00
Simon Glass
3c099ee898 test/py: Tidy up pylint warnings in console_sandbox
There are quite a few warnings which makes it confusing when editing
this file. Resolve them.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:13:25 -06:00
Simon Glass
0365f1afa4 test/py: Tidy up pylint warnings in console_sandbox
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>
2025-07-23 13:13:25 -06:00
Simon Glass
92a8b5ab4a test/py: Tidy up comments in ConsoleBase()
Some of the attributes are missing comments. Add these and tidy up a
few existing ones.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-23 13:13:25 -06:00
Simon Glass
03d4507bcb test/py: Tidy up pylint warnings in console_base
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>
2025-07-23 13:13:25 -06:00
Simon Glass
1c2d31d4ee test/py: Log the PYTHONPATH on error
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>
2025-07-23 13:13:25 -06:00
Simon Glass
d48881ed22 tools: Move to version 0.0.7
Update all tools to the new version, since there have been quite a lot
of changes recently.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-06-22 15:33:21 -06:00
19 changed files with 444 additions and 421 deletions

View File

@@ -1780,6 +1780,7 @@
pinctrl-single-pins {
compatible = "pinctrl-single";
bootph-some-ram;
reg = <0x0000 0x238>;
#pinctrl-cells = <1>;
pinctrl-single,register-width = <32>;
@@ -1799,6 +1800,7 @@
};
pinmux_uart0_pins: pinmux_uart0_pins {
bootph-some-ram;
pinctrl-single,pins = <
0x70 0x30
0x74 0x00

View File

@@ -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
@@ -308,6 +308,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()

View File

@@ -2,19 +2,21 @@
# 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.
"""Common logic to interact with U-Boot via the console.
import multiplexed_log
import os
import pytest
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.
"""
from collections import namedtuple
import re
import sys
import time
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]*\\))')
@@ -26,9 +28,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,16 +39,77 @@ TIMEOUT_CMD_MS = 10000 # Command-echo timeout
# situations.
TIMEOUT_PREPARE_MS = 3 * 60 * 1000
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),
# 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
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),
NamedPattern('unknown_command', pattern_unknown_command),
NamedPattern('error_notification', pattern_error_notification),
NamedPattern('error_please_reset', pattern_error_please_reset),
)
class ConsoleDisableCheck(object):
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
executing a command that is known to trigger one of the error checks, in
@@ -63,13 +123,14 @@ class ConsoleDisableCheck(object):
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(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,37 +142,40 @@ class ConsoleEnableCheck(object):
self.console = console
self.check_type = check_type
self.check_pattern = check_pattern
self.default_bad_patterns = None
def __enter__(self):
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.eval_bad_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):
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()
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(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."""
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(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
@@ -123,20 +187,49 @@ class ConsoleBase(object):
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.
Returns:
Nothing.
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
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
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
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
output: accumulated output from expect()
"""
self.log = log
self.config = config
self.max_fifo_fill = max_fifo_fill
@@ -147,26 +240,38 @@ class ConsoleBase(object):
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.eval_bad_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
self.u_boot_version_string = None
self.buf = ''
self.output = ''
self.before = ''
self.after = ''
self.timeout = None
self.logfile_read = None
# http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
self.eval_patterns()
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)
"""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):
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]
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 self.avail_patterns
if not self.disable_check_count[pat.name]]
self.bad_pattern_ids = [pat.name for pat in self.avail_patterns
if not self.disable_check_count[pat.name]]
def close(self):
"""Terminate the connection to the U-Boot console.
@@ -177,11 +282,7 @@ class ConsoleBase(object):
Args:
None.
Returns:
Nothing.
"""
if self.p:
self.log.start_section('Stopping U-Boot')
close_type = self.p.close()
@@ -195,54 +296,32 @@ 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.p.timeout = TIMEOUT_PREPARE_MS
self.log.info('test.py: Lab mode is active')
self.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')
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.p.expect([pattern_u_boot_spl_signon,
pattern_lab_mode] + self.bad_patterns)
if m == 1:
self.set_lab_mode()
break
elif 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.p.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.u_boot_version_string = self.p.after
self._wait_for_banner(loop_num)
self.u_boot_version_string = self.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}')
break
elif m == 1:
m = pattern_ready_prompt.search(self.p.after)
if m == 1:
m = pattern_ready_prompt.search(self.after)
self.u_boot_version_string = m.group(2)
self.log.info(f'Lab: Board is ready')
self.p.timeout = TIMEOUT_MS
self.log.info('Lab: Board is ready')
self.timeout = TIMEOUT_MS
break
if m == 2:
self.log.info(f'Found autoboot prompt {m}')
@@ -251,13 +330,46 @@ 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 _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):
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 +389,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)
@@ -321,17 +431,18 @@ class ConsoleBase(object):
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(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)
m = self.expect([self.prompt_compiled] + self.bad_patterns)
if m != 0:
self.at_prompt = False
raise BootFail('Missing prompt on console: ' +
@@ -340,7 +451,7 @@ class ConsoleBase(object):
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)
@@ -352,6 +463,7 @@ class ConsoleBase(object):
raise
finally:
self.log.timestamp()
return ''
def run_command_list(self, cmds):
"""Run a list of commands.
@@ -360,7 +472,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,16 +515,12 @@ 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)
m = self.expect([text] + self.bad_patterns)
if m != 0:
raise Unexpected(
"Unexpected pattern found on console (exp '{text}': " +
@@ -428,14 +536,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
@@ -443,13 +544,13 @@ class ConsoleBase(object):
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.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.
@@ -463,7 +564,7 @@ class ConsoleBase(object):
# 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.
@@ -474,19 +575,15 @@ 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
if not self.config.gdbserver:
self.p.timeout = TIMEOUT_MS
self.timeout = TIMEOUT_MS
return
try:
self.log.start_section('Starting U-Boot')
@@ -497,8 +594,8 @@ class ConsoleBase(object):
# 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.p.logfile_read = self.logstream
self.timeout = TIMEOUT_MS
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
@@ -509,7 +606,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 +624,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()
@@ -542,6 +632,16 @@ class ConsoleBase(object):
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()
@@ -554,7 +654,7 @@ class ConsoleBase(object):
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):
@@ -564,13 +664,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 +678,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 +694,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 +711,83 @@ 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)
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.p.poll.poll(poll_maxwait)
if not events:
raise Timeout()
c = self.p.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 get_expect_output(self):
"""Return the output read by expect()
Returns:
The output processed by expect(), as a string.
"""
return self.output

View File

@@ -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')

View File

@@ -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,16 +57,16 @@ class ConsoleSandbox(ConsoleBase):
"""Run U-Boot with the given command-line flags
Args:
flags: List of flags to pass, each a string
expect_reset: Boolean indication whether this boot is expected
flags (list of str): List of flags to pass
expect_reset (bool): Indication whether this boot is expected
to be reset while the 1st boot process after main boot before
prompt. False by default.
use_dtb: True to use a device tree file, False to run without one
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
@@ -86,13 +79,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):
@@ -101,16 +90,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

View File

@@ -3,13 +3,29 @@
"""
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
@@ -20,72 +36,20 @@ import traceback
# 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
sent to the process, and responses waited for.
Members:
output: accumulated output from expect()
"""
def __init__(self, args, cwd=None, decode_signal=False):
"""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
@@ -96,14 +60,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 = ''
self.timeout = None
# http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
(self.pid, self.fd) = pty.fork()
if self.pid == 0:
@@ -128,7 +84,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 +108,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 +121,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
@@ -180,22 +131,19 @@ 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
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 +151,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,78 +177,10 @@ 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
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: A list of strings or regex objects that we expect to
see in the sub-process' stdout.
Returns:
The 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])
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)
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.
@@ -335,11 +211,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

View File

@@ -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

View File

@@ -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,14 +48,14 @@ 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()

View File

@@ -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()

View File

@@ -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')

View File

@@ -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):
@@ -318,7 +316,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,15 +327,14 @@ 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!'
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):
@@ -389,12 +386,11 @@ 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!'
ubman.wait_for(pattern)
finally:
ubman.drain_console()
ubman.cleanup_spawn()
ubman.shutdown_required()

View File

@@ -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()

View File

@@ -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')

View File

@@ -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()

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "binary-manager"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Simon Glass", email="sjg@chromium.org" },
]

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "buildman"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Simon Glass", email="sjg@chromium.org" },
]

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "dtoc"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Simon Glass", email="sjg@chromium.org" },
]

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "patch-manager"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Simon Glass", email="sjg@chromium.org" },
]

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "u_boot_pylib"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Simon Glass", email="sjg@chromium.org" },
]