summaryrefslogtreecommitdiff
path: root/test/py
diff options
context:
space:
mode:
Diffstat (limited to 'test/py')
-rw-r--r--test/py/conftest.py73
-rw-r--r--test/py/tests/test_spi.py20
-rw-r--r--test/py/u_boot_console_base.py123
-rw-r--r--test/py/u_boot_console_exec_attach.py31
-rw-r--r--test/py/u_boot_spawn.py44
5 files changed, 228 insertions, 63 deletions
diff --git a/test/py/conftest.py b/test/py/conftest.py
index 46a410cf268..d9f074f3817 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -23,6 +23,7 @@ from pathlib import Path
import pytest
import re
from _pytest.runner import runtestprotocol
+import subprocess
import sys
from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
@@ -65,12 +66,16 @@ def pytest_addoption(parser):
parser.addoption('--build-dir', default=None,
help='U-Boot build directory (O=)')
+ parser.addoption('--build-dir-extra', default=None,
+ help='U-Boot build directory for extra build (O=)')
parser.addoption('--result-dir', default=None,
help='U-Boot test result/tmp directory')
parser.addoption('--persistent-data-dir', default=None,
help='U-Boot test persistent generated data directory')
parser.addoption('--board-type', '--bd', '-B', default='sandbox',
help='U-Boot board type')
+ parser.addoption('--board-type-extra', '--bde', default='sandbox',
+ help='U-Boot extra board type')
parser.addoption('--board-identity', '--id', default='na',
help='U-Boot board identity/instance')
parser.addoption('--build', default=False, action='store_true',
@@ -80,6 +85,9 @@ def pytest_addoption(parser):
parser.addoption('--gdbserver', default=None,
help='Run sandbox under gdbserver. The argument is the channel '+
'over which gdbserver should communicate, e.g. localhost:1234')
+ parser.addoption('--role', help='U-Boot board role (for Labgrid-sjg)')
+ parser.addoption('--use-running-system', default=False, action='store_true',
+ help="Assume that U-Boot is ready and don't wait for a prompt")
def run_build(config, source_dir, build_dir, board_type, log):
"""run_build: Build U-Boot
@@ -125,26 +133,71 @@ def get_details(config):
Returns:
tuple:
str: Board type (U-Boot build name)
+ str: Extra board type (where two U-Boot builds are needed)
str: Identity for the lab board
str: Build directory
+ str: Extra build directory (where two U-Boot builds are needed)
str: Source directory
"""
- board_type = config.getoption('board_type')
- board_identity = config.getoption('board_identity')
+ role = config.getoption('role')
+
+ # Get a few provided parameters
build_dir = config.getoption('build_dir')
+ build_dir_extra = config.getoption('build_dir_extra')
+ if role:
+ # When using a role, build_dir and build_dir_extra are normally not set,
+ # since they are picked up from Labgrid-sjg via the u-boot-test-getrole
+ # script
+ board_identity = role
+ cmd = ['u-boot-test-getrole', role, '--configure']
+ env = os.environ.copy()
+ if build_dir:
+ env['U_BOOT_BUILD_DIR'] = build_dir
+ if build_dir_extra:
+ env['U_BOOT_BUILD_DIR_EXTRA'] = build_dir_extra
+ proc = subprocess.run(cmd, capture_output=True, encoding='utf-8',
+ env=env)
+ if proc.returncode:
+ raise ValueError(proc.stderr)
+ # For debugging
+ # print('conftest: lab:', proc.stdout)
+ vals = {}
+ for line in proc.stdout.splitlines():
+ item, value = line.split(' ', maxsplit=1)
+ k = item.split(':')[-1]
+ vals[k] = value
+ # For debugging
+ # print('conftest: lab info:', vals)
+
+ # Read the build directories here, in case none were provided in the
+ # command-line arguments
+ (board_type, board_type_extra, default_build_dir,
+ default_build_dir_extra, source_dir) = (vals['board'],
+ vals['board_extra'], vals['build_dir'], vals['build_dir_extra'],
+ vals['source_dir'])
+ else:
+ board_type = config.getoption('board_type')
+ board_type_extra = config.getoption('board_type_extra')
+ board_identity = config.getoption('board_identity')
+
+ source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
+ default_build_dir = source_dir + '/build-' + board_type
+ default_build_dir_extra = source_dir + '/build-' + board_type_extra
- source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
- default_build_dir = source_dir + '/build-' + board_type
+ # Use the provided command-line arguments if present, else fall back to
if not build_dir:
build_dir = default_build_dir
+ if not build_dir_extra:
+ build_dir_extra = default_build_dir_extra
- return board_type, board_identity, build_dir, source_dir
+ return (board_type, board_type_extra, board_identity, build_dir,
+ build_dir_extra, source_dir)
def pytest_xdist_setupnodes(config, specs):
"""Clear out any 'done' file from a previous build"""
global build_done_file
- build_dir = get_details(config)[2]
+ build_dir = get_details(config)[3]
build_done_file = Path(build_dir) / 'build.done'
if build_done_file.exists():
@@ -184,7 +237,8 @@ def pytest_configure(config):
global console
global ubconfig
- board_type, board_identity, build_dir, source_dir = get_details(config)
+ (board_type, board_type_extra, board_identity, build_dir, build_dir_extra,
+ source_dir) = get_details(config)
board_type_filename = board_type.replace('-', '_')
board_identity_filename = board_identity.replace('-', '_')
@@ -249,20 +303,25 @@ def pytest_configure(config):
ubconfig.test_py_dir = TEST_PY_DIR
ubconfig.source_dir = source_dir
ubconfig.build_dir = build_dir
+ ubconfig.build_dir_extra = build_dir_extra
ubconfig.result_dir = result_dir
ubconfig.persistent_data_dir = persistent_data_dir
ubconfig.board_type = board_type
+ ubconfig.board_type_extra = board_type_extra
ubconfig.board_identity = board_identity
ubconfig.gdbserver = gdbserver
+ ubconfig.use_running_system = config.getoption('use_running_system')
ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
ubconfig.connection_ok = True
env_vars = (
'board_type',
+ 'board_type_extra',
'board_identity',
'source_dir',
'test_py_dir',
'build_dir',
+ 'build_dir_extra',
'result_dir',
'persistent_data_dir',
)
diff --git a/test/py/tests/test_spi.py b/test/py/tests/test_spi.py
index caca9303271..0abdfa78b76 100644
--- a/test/py/tests/test_spi.py
+++ b/test/py/tests/test_spi.py
@@ -75,7 +75,7 @@ def get_params_spi(u_boot_console):
''' Get SPI device test parameters from boardenv file '''
f = u_boot_console.config.env.get('env__spi_device_test', None)
if not f:
- pytest.skip('No env file to read for SPI family device test')
+ pytest.skip('No SPI test device configured')
bus = f.get('bus', 0)
cs = f.get('chip_select', 0)
@@ -84,7 +84,7 @@ def get_params_spi(u_boot_console):
timeout = f.get('timeout', None)
if not part_name:
- pytest.skip('No env file to read SPI family flash part name')
+ pytest.skip('No SPI test device configured')
return bus, cs, mode, part_name, timeout
@@ -92,7 +92,7 @@ def spi_find_freq_range(u_boot_console):
'''Find out minimum and maximum frequnecies that SPI device can operate'''
f = u_boot_console.config.env.get('env__spi_device_test', None)
if not f:
- pytest.skip('No env file to read for SPI family device test')
+ pytest.skip('No SPI test device configured')
min_f = f.get('min_freq', None)
max_f = f.get('max_freq', None)
@@ -116,21 +116,21 @@ def spi_pre_commands(u_boot_console, freq):
pytest.fail('No SPI device available')
if not part_name in output:
- pytest.fail('SPI flash part name not recognized')
+ pytest.fail('Not recognized the SPI flash part name')
m = re.search('page size (.+?) Bytes', output)
if m:
try:
page_size = int(m.group(1))
except ValueError:
- pytest.fail('SPI page size not recognized')
+ pytest.fail('Not recognized the SPI page size')
m = re.search('erase size (.+?) KiB', output)
if m:
try:
erase_size = int(m.group(1))
except ValueError:
- pytest.fail('SPI erase size not recognized')
+ pytest.fail('Not recognized the SPI erase size')
erase_size *= 1024
@@ -139,7 +139,7 @@ def spi_pre_commands(u_boot_console, freq):
try:
total_size = int(m.group(1))
except ValueError:
- pytest.fail('SPI total size not recognized')
+ pytest.fail('Not recognized the SPI total size')
total_size *= 1024 * 1024
@@ -149,7 +149,7 @@ def spi_pre_commands(u_boot_console, freq):
flash_part = m.group(1)
assert flash_part == part_name
except ValueError:
- pytest.fail('SPI flash part not recognized')
+ pytest.fail('Not recognized the SPI flash part')
global SPI_DATA
SPI_DATA = {
@@ -574,7 +574,7 @@ def test_spi_lock_unlock(u_boot_console):
min_f, max_f, loop = spi_find_freq_range(u_boot_console)
flashes = u_boot_console.config.env.get('env__spi_lock_unlock', False)
if not flashes:
- pytest.skip('No supported flash list for lock/unlock provided')
+ pytest.skip('No SPI test device configured for lock/unlock')
i = 0
while i < loop:
@@ -695,7 +695,7 @@ def test_spi_negative(u_boot_console):
# Read to relocation address
output = u_boot_console.run_command('bdinfo')
- m = re.search('relocaddr\s*= (.+)', output)
+ m = re.search(r'relocaddr\s*= (.+)', output)
res_area = int(m.group(1), 16)
start = 0
diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
index d8d0bdf9fd4..fa9cd57b04b 100644
--- a/test/py/u_boot_console_base.py
+++ b/test/py/u_boot_console_base.py
@@ -23,12 +23,22 @@ pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
pattern_error_notification = re.compile('## Error: ')
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
+TIMEOUT_MS = 30000 # Standard timeout
+TIMEOUT_CMD_MS = 10000 # Command-echo timeout
+
+# Timeout for board preparation in lab mode. This needs to be enough to build
+# U-Boot, write it to the board and then boot the board. Since this process is
+# under the control of another program (e.g. Labgrid), it will failure sooner
+# if something goes way. So use a very long timeout here to cover all possible
+# situations.
+TIMEOUT_PREPARE_MS = 3 * 60 * 1000
bad_pattern_defs = (
('spl_signon', pattern_u_boot_spl_signon),
@@ -142,6 +152,7 @@ class ConsoleBase(object):
self.at_prompt = False
self.at_prompt_logevt = None
+ self.lab_mode = False
def get_spawn(self):
# This is not called, ssubclass must define this.
@@ -172,43 +183,75 @@ class ConsoleBase(object):
"""
if self.p:
- self.p.close()
+ self.log.start_section('Stopping U-Boot')
+ close_type = self.p.close()
+ self.log.info(f'Close type: {close_type}')
+ self.log.end_section('Stopping U-Boot')
self.logstream.close()
+ def set_lab_mode(self):
+ """Select lab mode
+
+ 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.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.
"""
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 loop_num > 0:
+ 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] +
- self.bad_patterns)
- if m != 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])
+ self.bad_pattern_ids[m - 1])
env_spl_banner_times -= 1
- m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
- if m != 0:
- raise BootFail('Bad pattern found on console: ' +
- self.bad_pattern_ids[m - 1])
- self.u_boot_version_string = self.p.after
+ 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
while True:
- m = self.p.expect([self.prompt_compiled,
+ m = self.p.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
- if m == 1:
+ elif 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.p.timeout = TIMEOUT_MS
+ break
+ if m == 2:
+ self.log.info(f'Found autoboot prompt {m}')
self.p.send(' ')
continue
- raise BootFail('Bad pattern found on console: ' +
- self.bad_pattern_ids[m - 2])
+ 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')
finally:
self.log.timestamp()
@@ -261,22 +304,28 @@ class ConsoleBase(object):
try:
self.at_prompt = False
+ if not self.p:
+ raise BootFail(
+ f"Lab failure: Connection lost when sending command '{cmd}'")
+
if send_nl:
cmd += '\n'
- while cmd:
- # Limit max outstanding data, so UART FIFOs don't overflow
- chunk = cmd[:self.max_fifo_fill]
- cmd = cmd[self.max_fifo_fill:]
- self.p.send(chunk)
- if not wait_for_echo:
- continue
- chunk = re.escape(chunk)
- chunk = chunk.replace('\\\n', '[\r\n]')
- m = self.p.expect([chunk] + self.bad_patterns)
- if m != 0:
- self.at_prompt = False
- raise BootFail('Bad pattern found on console: ' +
- self.bad_pattern_ids[m - 1])
+ rem = cmd # Remaining to be sent
+ with self.temporary_timeout(TIMEOUT_CMD_MS):
+ while rem:
+ # Limit max outstanding data, so UART FIFOs don't overflow
+ chunk = rem[:self.max_fifo_fill]
+ rem = rem[self.max_fifo_fill:]
+ self.p.send(chunk)
+ if not wait_for_echo:
+ continue
+ chunk = re.escape(chunk)
+ chunk = chunk.replace('\\\n', '[\r\n]')
+ 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])
if not wait_for_prompt:
return
if wait_for_reboot:
@@ -440,11 +489,17 @@ class ConsoleBase(object):
if not self.config.gdbserver:
self.p.timeout = TIMEOUT_MS
self.p.logfile_read = self.logstream
- if expect_reset:
- loop_num = 2
+ 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
+ # line entered
+ self.run_command(' ')
else:
- loop_num = 1
- self.wait_for_boot_prompt(loop_num = loop_num)
+ if expect_reset:
+ loop_num = 2
+ else:
+ loop_num = 1
+ 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:
diff --git a/test/py/u_boot_console_exec_attach.py b/test/py/u_boot_console_exec_attach.py
index 8dd8cc1230c..8b253b4451d 100644
--- a/test/py/u_boot_console_exec_attach.py
+++ b/test/py/u_boot_console_exec_attach.py
@@ -59,14 +59,27 @@ class ConsoleExecAttach(ConsoleBase):
args = [self.config.board_type, self.config.board_identity]
s = Spawn(['u-boot-test-console'] + args)
- try:
- self.log.action('Resetting board')
- cmd = ['u-boot-test-reset'] + args
- runner = self.log.get_runner(cmd[0], sys.stdout)
- runner.run(cmd)
- runner.close()
- except:
- s.close()
- raise
+ if self.config.use_running_system:
+ self.log.action('Connecting to board without reset')
+ else:
+ try:
+ self.log.action('Resetting board')
+ cmd = ['u-boot-test-reset'] + args
+ runner = self.log.get_runner(cmd[0], sys.stdout)
+ runner.run(cmd)
+ runner.close()
+ except:
+ s.close()
+ raise
return s
+
+ def close(self):
+ super().close()
+
+ self.log.action('Releasing board')
+ args = [self.config.board_type, self.config.board_identity]
+ cmd = ['u-boot-test-release'] + args
+ runner = self.log.get_runner(cmd[0], sys.stdout)
+ runner.run(cmd)
+ runner.close()
diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py
index 24d369035e5..c703454389d 100644
--- a/test/py/u_boot_spawn.py
+++ b/test/py/u_boot_spawn.py
@@ -5,15 +5,21 @@
Logic to spawn a sub-process and interact with its stdio.
"""
+import io
import os
import re
import pty
import pytest
import signal
import select
+import sys
+import termios
import time
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."""
@@ -115,11 +121,30 @@ class Spawn:
finally:
os._exit(255)
+ old = None
try:
+ isatty = False
+ try:
+ isatty = os.isatty(sys.stdout.fileno())
+
+ # with --capture=tee-sys we cannot call fileno()
+ except io.UnsupportedOperation as exc:
+ pass
+ if isatty:
+ new = termios.tcgetattr(self.fd)
+ old = new
+ new[3] = new[3] & ~(termios.ICANON | termios.ISIG)
+ new[3] = new[3] & ~termios.ECHO
+ new[6][termios.VMIN] = 0
+ new[6][termios.VTIME] = 0
+ termios.tcsetattr(self.fd, termios.TCSANOW, new)
+
self.poll = select.poll()
self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR |
select.POLLHUP | select.POLLNVAL)
except:
+ if old:
+ termios.tcsetattr(self.fd, termios.TCSANOW, old)
self.close()
raise
@@ -289,15 +314,28 @@ class Spawn:
None.
Returns:
- Nothing.
+ str: Type of closure completed
"""
-
+ # For Labgrid-sjg, ask it is exit gracefully, so it can transition the
+ # board to the final state (like 'off') before exiting.
+ if os.environ.get('USE_LABGRID_SJG'):
+ self.send(chr(EXIT_CHAR) * 2)
+
+ # Wait about 10 seconds for Labgrid to close and power off the board
+ for _ in range(100):
+ if not self.isalive():
+ return 'normal'
+ time.sleep(0.1)
+
+ # That didn't work, so try closing the PTY
os.close(self.fd)
for _ in range(100):
if not self.isalive():
- break
+ return 'break'
time.sleep(0.1)
+ return 'timeout'
+
def get_expect_output(self):
"""Return the output read by expect()