mirror of
https://github.com/revyos/th1520-vendor-uboot.git
synced 2026-09-16 20:32:09 +02:00
Linux_SDK_V0.9.5
This commit is contained in:
1
test/py/.gitignore
vendored
Normal file
1
test/py/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
370
test/py/README.md
Normal file
370
test/py/README.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# U-Boot pytest suite
|
||||
|
||||
## Introduction
|
||||
|
||||
This tool aims to test U-Boot by executing U-Boot shell commands using the
|
||||
console interface. A single top-level script exists to execute or attach to the
|
||||
U-Boot console, run the entire script of tests against it, and summarize the
|
||||
results. Advantages of this approach are:
|
||||
|
||||
- Testing is performed in the same way a user or script would interact with
|
||||
U-Boot; there can be no disconnect.
|
||||
- There is no need to write or embed test-related code into U-Boot itself.
|
||||
It is asserted that writing test-related code in Python is simpler and more
|
||||
flexible than writing it all in C.
|
||||
- It is reasonably simple to interact with U-Boot in this way.
|
||||
|
||||
## Requirements
|
||||
|
||||
The test suite is implemented using pytest. Interaction with the U-Boot console
|
||||
involves executing some binary and interacting with its stdin/stdout. You will
|
||||
need to implement various "hook" scripts that are called by the test suite at
|
||||
the appropriate time.
|
||||
|
||||
In order to run the testsuite at a minimum we require that both python3 and
|
||||
pip for python3 be installed. All of the required python modules are
|
||||
described in the requirements.txt file in this directory and can be installed
|
||||
with the command ```pip install -r requirements.txt```
|
||||
|
||||
In order to execute certain tests on their supported platforms other tools
|
||||
will be required. The following is an incomplete list:
|
||||
|
||||
| Package |
|
||||
| -------------- |
|
||||
| gdisk |
|
||||
| dfu-util |
|
||||
| dtc |
|
||||
| openssl |
|
||||
| sudo OR guestmount |
|
||||
| e2fsprogs |
|
||||
| dosfstools |
|
||||
|
||||
Please use the apporirate commands for your distribution to match these tools
|
||||
up with the package that provides them.
|
||||
|
||||
The test script supports either:
|
||||
|
||||
- Executing a sandbox port of U-Boot on the local machine as a sub-process,
|
||||
and interacting with it over stdin/stdout.
|
||||
- Executing an external "hook" scripts to flash a U-Boot binary onto a
|
||||
physical board, attach to the board's console stream, and reset the board.
|
||||
Further details are described later.
|
||||
|
||||
### Using `virtualenv` to provide requirements
|
||||
|
||||
The recommended way to run the test suite, in order to ensure reproducibility
|
||||
is to use `virtualenv` to set up the necessary environment. This can be done
|
||||
via the following commands:
|
||||
|
||||
```bash
|
||||
$ cd /path/to/u-boot
|
||||
$ sudo apt-get install python3 python3-virtualenv
|
||||
$ virtualenv -p /usr/bin/python3 venv
|
||||
$ . ./venv/bin/activate
|
||||
$ pip install -r test/py/requirements.txt
|
||||
```
|
||||
|
||||
## Testing sandbox
|
||||
|
||||
To run the testsuite on the sandbox port (U-Boot built as a native user-space
|
||||
application), simply execute:
|
||||
|
||||
```
|
||||
./test/py/test.py --bd sandbox --build
|
||||
```
|
||||
|
||||
The `--bd` option tells the test suite which board type is being tested. This
|
||||
lets the test suite know which features the board has, and hence exactly what
|
||||
can be tested.
|
||||
|
||||
The `--build` option tells U-Boot to compile U-Boot. Alternatively, you may
|
||||
omit this option and build U-Boot yourself, in whatever way you choose, before
|
||||
running the test script.
|
||||
|
||||
The test script will attach to U-Boot, execute all valid tests for the board,
|
||||
then print a summary of the test process. A complete log of the test session
|
||||
will be written to `${build_dir}/test-log.html`. This is best viewed in a web
|
||||
browser, but may be read directly as plain text, perhaps with the aid of the
|
||||
`html2text` utility.
|
||||
|
||||
### Testing under a debugger
|
||||
|
||||
If you need to run sandbox under a debugger, you may pass the command-line
|
||||
option `--gdbserver COMM`. This causes two things to happens:
|
||||
|
||||
- Instead of running U-Boot directly, it will be run under gdbserver, with
|
||||
debug communication via the channel `COMM`. You can attach a debugger to the
|
||||
sandbox process in order to debug it. See `man gdbserver` and the example
|
||||
below for details of valid values for `COMM`.
|
||||
- All timeouts in tests are disabled, allowing U-Boot an arbitrary amount of
|
||||
time to execute commands. This is useful if U-Boot is stopped at a breakpoint
|
||||
during debugging.
|
||||
|
||||
A usage example is:
|
||||
|
||||
Window 1:
|
||||
```shell
|
||||
./test/py/test.py --bd sandbox --gdbserver localhost:1234
|
||||
```
|
||||
|
||||
Window 2:
|
||||
```shell
|
||||
gdb ./build-sandbox/u-boot -ex 'target remote localhost:1234'
|
||||
```
|
||||
|
||||
Alternatively, you could leave off the `-ex` option and type the command
|
||||
manually into gdb once it starts.
|
||||
|
||||
You can use any debugger you wish, so long as it speaks the gdb remote
|
||||
protocol, or any graphical wrapper around gdb.
|
||||
|
||||
Some tests deliberately cause the sandbox process to exit, e.g. to test the
|
||||
reset command, or sandbox's CTRL-C handling. When this happens, you will need
|
||||
to attach the debugger to the new sandbox instance. If these tests are not
|
||||
relevant to your debugging session, you can skip them using pytest's -k
|
||||
command-line option; see the next section.
|
||||
|
||||
## Command-line options
|
||||
|
||||
- `--board-type`, `--bd`, `-B` set the type of the board to be tested. For
|
||||
example, `sandbox` or `seaboard`.
|
||||
- `--board-identity`, `--id` set the identity of the board to be tested.
|
||||
This allows differentiation between multiple instances of the same type of
|
||||
physical board that are attached to the same host machine. This parameter is
|
||||
not interpreted by the test script in any way, but rather is simply passed
|
||||
to the hook scripts described below, and may be used in any site-specific
|
||||
way deemed necessary.
|
||||
- `--build` indicates that the test script should compile U-Boot itself
|
||||
before running the tests. If using this option, make sure that any
|
||||
environment variables required by the build process are already set, such as
|
||||
`$CROSS_COMPILE`.
|
||||
- `--build-dir` sets the directory containing the compiled U-Boot binaries.
|
||||
If omitted, this is `${source_dir}/build-${board_type}`.
|
||||
- `--result-dir` sets the directory to write results, such as log files,
|
||||
into. If omitted, the build directory is used.
|
||||
- `--persistent-data-dir` sets the directory used to store persistent test
|
||||
data. This is test data that may be re-used across test runs, such as file-
|
||||
system images.
|
||||
|
||||
`pytest` also implements a number of its own command-line options. Commonly used
|
||||
options are mentioned below. Please see `pytest` documentation for complete
|
||||
details. Execute `py.test --version` for a brief summary. Note that U-Boot's
|
||||
test.py script passes all command-line arguments directly to `pytest` for
|
||||
processing.
|
||||
|
||||
- `-k` selects which tests to run. The default is to run all known tests. This
|
||||
option takes a single argument which is used to filter test names. Simple
|
||||
logical operators are supported. For example:
|
||||
- `'ums'` runs only tests with "ums" in their name.
|
||||
- `'ut_dm'` runs only tests with "ut_dm" in their name. Note that in this
|
||||
case, "ut_dm" is a parameter to a test rather than the test name. The full
|
||||
test name is e.g. "test_ut[ut_dm_leak]".
|
||||
- `'not reset'` runs everything except tests with "reset" in their name.
|
||||
- `'ut or hush'` runs only tests with "ut" or "hush" in their name.
|
||||
- `'not (ut or hush)'` runs everything except tests with "ut" or "hush" in
|
||||
their name.
|
||||
- `-s` prevents pytest from hiding a test's stdout. This allows you to see
|
||||
U-Boot's console log in real time on pytest's stdout.
|
||||
|
||||
## Testing real hardware
|
||||
|
||||
The tools and techniques used to interact with real hardware will vary
|
||||
radically between different host and target systems, and the whims of the user.
|
||||
For this reason, the test suite does not attempt to directly interact with real
|
||||
hardware in any way. Rather, it executes a standardized set of "hook" scripts
|
||||
via `$PATH`. These scripts implement certain actions on behalf of the test
|
||||
suite. This keeps the test suite simple and isolated from system variances
|
||||
unrelated to U-Boot features.
|
||||
|
||||
### Hook scripts
|
||||
|
||||
#### Environment variables
|
||||
|
||||
The following environment variables are set when running hook scripts:
|
||||
|
||||
- `UBOOT_BOARD_TYPE` the board type being tested.
|
||||
- `UBOOT_BOARD_IDENTITY` the board identity being tested, or `na` if none was
|
||||
specified.
|
||||
- `UBOOT_SOURCE_DIR` the U-Boot source directory.
|
||||
- `UBOOT_TEST_PY_DIR` the full path to `test/py/` in the source directory.
|
||||
- `UBOOT_BUILD_DIR` the U-Boot build directory.
|
||||
- `UBOOT_RESULT_DIR` the test result directory.
|
||||
- `UBOOT_PERSISTENT_DATA_DIR` the test persistent data directory.
|
||||
|
||||
#### `u-boot-test-console`
|
||||
|
||||
This script provides access to the U-Boot console. The script's stdin/stdout
|
||||
should be connected to the board's console. This process should continue to run
|
||||
indefinitely, until killed. The test suite will run this script in parallel
|
||||
with all other hooks.
|
||||
|
||||
This script may be implemented e.g. by exec()ing `cu`, `kermit`, `conmux`, etc.
|
||||
|
||||
If you are able to run U-Boot under a hardware simulator such as qemu, then
|
||||
you would likely spawn that simulator from this script. However, note that
|
||||
`u-boot-test-reset` may be called multiple times per test script run, and must
|
||||
cause U-Boot to start execution from scratch each time. Hopefully your
|
||||
simulator includes a virtual reset button! If not, you can launch the
|
||||
simulator from `u-boot-test-reset` instead, while arranging for this console
|
||||
process to always communicate with the current simulator instance.
|
||||
|
||||
#### `u-boot-test-flash`
|
||||
|
||||
Prior to running the test suite against a board, some arrangement must be made
|
||||
so that the board executes the particular U-Boot binary to be tested. Often,
|
||||
this involves writing the U-Boot binary to the board's flash ROM. The test
|
||||
suite calls this hook script for that purpose.
|
||||
|
||||
This script should perform the entire flashing process synchronously; the
|
||||
script should only exit once flashing is complete, and a board reset will
|
||||
cause the newly flashed U-Boot binary to be executed.
|
||||
|
||||
It is conceivable that this script will do nothing. This might be useful in
|
||||
the following cases:
|
||||
|
||||
- Some other process has already written the desired U-Boot binary into the
|
||||
board's flash prior to running the test suite.
|
||||
- The board allows U-Boot to be downloaded directly into RAM, and executed
|
||||
from there. Use of this feature will reduce wear on the board's flash, so
|
||||
may be preferable if available, and if cold boot testing of U-Boot is not
|
||||
required. If this feature is used, the `u-boot-test-reset` script should
|
||||
perform this download, since the board could conceivably be reset multiple
|
||||
times in a single test run.
|
||||
|
||||
It is up to the user to determine if those situations exist, and to code this
|
||||
hook script appropriately.
|
||||
|
||||
This script will typically be implemented by calling out to some SoC- or
|
||||
board-specific vendor flashing utility.
|
||||
|
||||
#### `u-boot-test-reset`
|
||||
|
||||
Whenever the test suite needs to reset the target board, this script is
|
||||
executed. This is guaranteed to happen at least once, prior to executing the
|
||||
first test function. If any test fails, the test infra-structure will execute
|
||||
this script again to restore U-Boot to an operational state before running the
|
||||
next test function.
|
||||
|
||||
This script will likely be implemented by communicating with some form of
|
||||
relay or electronic switch attached to the board's reset signal.
|
||||
|
||||
The semantics of this script require that when it is executed, U-Boot will
|
||||
start running from scratch. If the U-Boot binary to be tested has been written
|
||||
to flash, pulsing the board's reset signal is likely all this script need do.
|
||||
However, in some scenarios, this script may perform other actions. For
|
||||
example, it may call out to some SoC- or board-specific vendor utility in order
|
||||
to download the U-Boot binary directly into RAM and execute it. This would
|
||||
avoid the need for `u-boot-test-flash` to actually write U-Boot to flash, thus
|
||||
saving wear on the flash chip(s).
|
||||
|
||||
#### Examples
|
||||
|
||||
https://github.com/swarren/uboot-test-hooks contains some working example hook
|
||||
scripts, and may be useful as a reference when implementing hook scripts for
|
||||
your platform. These scripts are not considered part of U-Boot itself.
|
||||
|
||||
### Board-type-specific configuration
|
||||
|
||||
Each board has a different configuration and behaviour. Many of these
|
||||
differences can be automatically detected by parsing the `.config` file in the
|
||||
build directory. However, some differences can't yet be handled automatically.
|
||||
|
||||
For each board, an optional Python module `u_boot_board_${board_type}` may exist
|
||||
to provide board-specific information to the test script. Any global value
|
||||
defined in these modules is available for use by any test function. The data
|
||||
contained in these scripts must be purely derived from U-Boot source code.
|
||||
Hence, these configuration files are part of the U-Boot source tree too.
|
||||
|
||||
### Execution environment configuration
|
||||
|
||||
Each user's hardware setup may enable testing different subsets of the features
|
||||
implemented by a particular board's configuration of U-Boot. For example, a
|
||||
U-Boot configuration may support USB device mode and USB Mass Storage, but this
|
||||
can only be tested if a USB cable is connected between the board and the host
|
||||
machine running the test script.
|
||||
|
||||
For each board, optional Python modules `u_boot_boardenv_${board_type}` and
|
||||
`u_boot_boardenv_${board_type}_${board_identity}` may exist to provide
|
||||
board-specific and board-identity-specific information to the test script. Any
|
||||
global value defined in these modules is available for use by any test
|
||||
function. The data contained in these is specific to a particular user's
|
||||
hardware configuration. Hence, these configuration files are not part of the
|
||||
U-Boot source tree, and should be installed outside of the source tree. Users
|
||||
should set `$PYTHONPATH` prior to running the test script to allow these
|
||||
modules to be loaded.
|
||||
|
||||
### Board module parameter usage
|
||||
|
||||
The test scripts rely on the following variables being defined by the board
|
||||
module:
|
||||
|
||||
- None at present.
|
||||
|
||||
### U-Boot `.config` feature usage
|
||||
|
||||
The test scripts rely on various U-Boot `.config` features, either directly in
|
||||
order to test those features, or indirectly in order to query information from
|
||||
the running U-Boot instance in order to test other features.
|
||||
|
||||
One example is that testing of the `md` command requires knowledge of a RAM
|
||||
address to use for the test. This data is parsed from the output of the
|
||||
`bdinfo` command, and hence relies on CONFIG_CMD_BDI being enabled.
|
||||
|
||||
For a complete list of dependencies, please search the test scripts for
|
||||
instances of:
|
||||
|
||||
- `buildconfig.get(...`
|
||||
- `@pytest.mark.buildconfigspec(...`
|
||||
- `@pytest.mark.notbuildconfigspec(...`
|
||||
|
||||
### Complete invocation example
|
||||
|
||||
Assuming that you have installed the hook scripts into $HOME/ubtest/bin, and
|
||||
any required environment configuration Python modules into $HOME/ubtest/py,
|
||||
then you would likely invoke the test script as follows:
|
||||
|
||||
If U-Boot has already been built:
|
||||
|
||||
```bash
|
||||
PATH=$HOME/ubtest/bin:$PATH \
|
||||
PYTHONPATH=${HOME}/ubtest/py/${HOSTNAME}:${PYTHONPATH} \
|
||||
./test/py/test.py --bd seaboard
|
||||
```
|
||||
|
||||
If you want the test script to compile U-Boot for you too, then you likely
|
||||
need to set `$CROSS_COMPILE` to allow this, and invoke the test script as
|
||||
follow:
|
||||
|
||||
```bash
|
||||
CROSS_COMPILE=arm-none-eabi- \
|
||||
PATH=$HOME/ubtest/bin:$PATH \
|
||||
PYTHONPATH=${HOME}/ubtest/py/${HOSTNAME}:${PYTHONPATH} \
|
||||
./test/py/test.py --bd seaboard --build
|
||||
```
|
||||
|
||||
## Writing tests
|
||||
|
||||
Please refer to the pytest documentation for details of writing pytest tests.
|
||||
Details specific to the U-Boot test suite are described below.
|
||||
|
||||
A test fixture named `u_boot_console` should be used by each test function. This
|
||||
provides the means to interact with the U-Boot console, and retrieve board and
|
||||
environment configuration information.
|
||||
|
||||
The function `u_boot_console.run_command()` executes a shell command on the
|
||||
U-Boot console, and returns all output from that command. This allows
|
||||
validation or interpretation of the command output. This function validates
|
||||
that certain strings are not seen on the U-Boot console. These include shell
|
||||
error messages and the U-Boot sign-on message (in order to detect unexpected
|
||||
board resets). See the source of `u_boot_console_base.py` for a complete list of
|
||||
"bad" strings. Some test scenarios are expected to trigger these strings. Use
|
||||
`u_boot_console.disable_check()` to temporarily disable checking for specific
|
||||
strings. See `test_unknown_cmd.py` for an example.
|
||||
|
||||
Board- and board-environment configuration values may be accessed as sub-fields
|
||||
of the `u_boot_console.config` object, for example
|
||||
`u_boot_console.config.ram_base`.
|
||||
|
||||
Build configuration values (from `.config`) may be accessed via the dictionary
|
||||
`u_boot_console.config.buildconfig`, with keys equal to the Kconfig variable
|
||||
names.
|
||||
596
test/py/conftest.py
Normal file
596
test/py/conftest.py
Normal file
@@ -0,0 +1,596 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Implementation of pytest run-time hook functions. These are invoked by
|
||||
# pytest at certain points during operation, e.g. startup, for each executed
|
||||
# test, at shutdown etc. These hooks perform functions such as:
|
||||
# - Parsing custom command-line options.
|
||||
# - Pullilng in user-specified board configuration.
|
||||
# - Creating the U-Boot console test fixture.
|
||||
# - Creating the HTML log file.
|
||||
# - Monitoring each test's results.
|
||||
# - Implementing custom pytest markers.
|
||||
|
||||
import atexit
|
||||
import configparser
|
||||
import errno
|
||||
import io
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
import re
|
||||
from _pytest.runner import runtestprotocol
|
||||
import sys
|
||||
|
||||
# Globals: The HTML log file, and the connection to the U-Boot console.
|
||||
log = None
|
||||
console = None
|
||||
|
||||
def mkdir_p(path):
|
||||
"""Create a directory path.
|
||||
|
||||
This includes creating any intermediate/parent directories. Any errors
|
||||
caused due to already extant directories are ignored.
|
||||
|
||||
Args:
|
||||
path: The directory path to create.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""pytest hook: Add custom command-line options to the cmdline parser.
|
||||
|
||||
Args:
|
||||
parser: The pytest command-line parser.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
parser.addoption('--build-dir', default=None,
|
||||
help='U-Boot build directory (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-identity', '--id', default='na',
|
||||
help='U-Boot board identity/instance')
|
||||
parser.addoption('--build', default=False, action='store_true',
|
||||
help='Compile U-Boot before running tests')
|
||||
parser.addoption('--gdbserver', default=None,
|
||||
help='Run sandbox under gdbserver. The argument is the channel '+
|
||||
'over which gdbserver should communicate, e.g. localhost:1234')
|
||||
|
||||
def pytest_configure(config):
|
||||
"""pytest hook: Perform custom initialization at startup time.
|
||||
|
||||
Args:
|
||||
config: The pytest configuration.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
global log
|
||||
global console
|
||||
global ubconfig
|
||||
|
||||
test_py_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
source_dir = os.path.dirname(os.path.dirname(test_py_dir))
|
||||
|
||||
board_type = config.getoption('board_type')
|
||||
board_type_filename = board_type.replace('-', '_')
|
||||
|
||||
board_identity = config.getoption('board_identity')
|
||||
board_identity_filename = board_identity.replace('-', '_')
|
||||
|
||||
build_dir = config.getoption('build_dir')
|
||||
if not build_dir:
|
||||
build_dir = source_dir + '/build-' + board_type
|
||||
mkdir_p(build_dir)
|
||||
|
||||
result_dir = config.getoption('result_dir')
|
||||
if not result_dir:
|
||||
result_dir = build_dir
|
||||
mkdir_p(result_dir)
|
||||
|
||||
persistent_data_dir = config.getoption('persistent_data_dir')
|
||||
if not persistent_data_dir:
|
||||
persistent_data_dir = build_dir + '/persistent-data'
|
||||
mkdir_p(persistent_data_dir)
|
||||
|
||||
gdbserver = config.getoption('gdbserver')
|
||||
if gdbserver and not board_type.startswith('sandbox'):
|
||||
raise Exception('--gdbserver only supported with sandbox targets')
|
||||
|
||||
import multiplexed_log
|
||||
log = multiplexed_log.Logfile(result_dir + '/test-log.html')
|
||||
|
||||
if config.getoption('build'):
|
||||
if build_dir != source_dir:
|
||||
o_opt = 'O=%s' % build_dir
|
||||
else:
|
||||
o_opt = ''
|
||||
cmds = (
|
||||
['make', o_opt, '-s', board_type + '_defconfig'],
|
||||
['make', o_opt, '-s', '-j8'],
|
||||
)
|
||||
with log.section('make'):
|
||||
runner = log.get_runner('make', sys.stdout)
|
||||
for cmd in cmds:
|
||||
runner.run(cmd, cwd=source_dir)
|
||||
runner.close()
|
||||
log.status_pass('OK')
|
||||
|
||||
class ArbitraryAttributeContainer(object):
|
||||
pass
|
||||
|
||||
ubconfig = ArbitraryAttributeContainer()
|
||||
ubconfig.brd = dict()
|
||||
ubconfig.env = dict()
|
||||
|
||||
modules = [
|
||||
(ubconfig.brd, 'u_boot_board_' + board_type_filename),
|
||||
(ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
|
||||
(ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
|
||||
board_identity_filename),
|
||||
]
|
||||
for (dict_to_fill, module_name) in modules:
|
||||
try:
|
||||
module = __import__(module_name)
|
||||
except ImportError:
|
||||
continue
|
||||
dict_to_fill.update(module.__dict__)
|
||||
|
||||
ubconfig.buildconfig = dict()
|
||||
|
||||
for conf_file in ('.config', 'include/autoconf.mk'):
|
||||
dot_config = build_dir + '/' + conf_file
|
||||
if not os.path.exists(dot_config):
|
||||
raise Exception(conf_file + ' does not exist; ' +
|
||||
'try passing --build option?')
|
||||
|
||||
with open(dot_config, 'rt') as f:
|
||||
ini_str = '[root]\n' + f.read()
|
||||
ini_sio = io.StringIO(ini_str)
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.read_file(ini_sio)
|
||||
ubconfig.buildconfig.update(parser.items('root'))
|
||||
|
||||
ubconfig.test_py_dir = test_py_dir
|
||||
ubconfig.source_dir = source_dir
|
||||
ubconfig.build_dir = build_dir
|
||||
ubconfig.result_dir = result_dir
|
||||
ubconfig.persistent_data_dir = persistent_data_dir
|
||||
ubconfig.board_type = board_type
|
||||
ubconfig.board_identity = board_identity
|
||||
ubconfig.gdbserver = gdbserver
|
||||
ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
|
||||
|
||||
env_vars = (
|
||||
'board_type',
|
||||
'board_identity',
|
||||
'source_dir',
|
||||
'test_py_dir',
|
||||
'build_dir',
|
||||
'result_dir',
|
||||
'persistent_data_dir',
|
||||
)
|
||||
for v in env_vars:
|
||||
os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
|
||||
|
||||
if board_type.startswith('sandbox'):
|
||||
import u_boot_console_sandbox
|
||||
console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
|
||||
else:
|
||||
import u_boot_console_exec_attach
|
||||
console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
|
||||
|
||||
re_ut_test_list = re.compile(r'_u_boot_list_2_(.*)_test_2_\1_test_(.*)\s*$')
|
||||
def generate_ut_subtest(metafunc, fixture_name):
|
||||
"""Provide parametrization for a ut_subtest fixture.
|
||||
|
||||
Determines the set of unit tests built into a U-Boot binary by parsing the
|
||||
list of symbols generated by the build process. Provides this information
|
||||
to test functions by parameterizing their ut_subtest fixture parameter.
|
||||
|
||||
Args:
|
||||
metafunc: The pytest test function.
|
||||
fixture_name: The fixture name to test.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
fn = console.config.build_dir + '/u-boot.sym'
|
||||
try:
|
||||
with open(fn, 'rt') as f:
|
||||
lines = f.readlines()
|
||||
except:
|
||||
lines = []
|
||||
lines.sort()
|
||||
|
||||
vals = []
|
||||
for l in lines:
|
||||
m = re_ut_test_list.search(l)
|
||||
if not m:
|
||||
continue
|
||||
vals.append(m.group(1) + ' ' + m.group(2))
|
||||
|
||||
ids = ['ut_' + s.replace(' ', '_') for s in vals]
|
||||
metafunc.parametrize(fixture_name, vals, ids=ids)
|
||||
|
||||
def generate_config(metafunc, fixture_name):
|
||||
"""Provide parametrization for {env,brd}__ fixtures.
|
||||
|
||||
If a test function takes parameter(s) (fixture names) of the form brd__xxx
|
||||
or env__xxx, the brd and env configuration dictionaries are consulted to
|
||||
find the list of values to use for those parameters, and the test is
|
||||
parametrized so that it runs once for each combination of values.
|
||||
|
||||
Args:
|
||||
metafunc: The pytest test function.
|
||||
fixture_name: The fixture name to test.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
subconfigs = {
|
||||
'brd': console.config.brd,
|
||||
'env': console.config.env,
|
||||
}
|
||||
parts = fixture_name.split('__')
|
||||
if len(parts) < 2:
|
||||
return
|
||||
if parts[0] not in subconfigs:
|
||||
return
|
||||
subconfig = subconfigs[parts[0]]
|
||||
vals = []
|
||||
val = subconfig.get(fixture_name, [])
|
||||
# If that exact name is a key in the data source:
|
||||
if val:
|
||||
# ... use the dict value as a single parameter value.
|
||||
vals = (val, )
|
||||
else:
|
||||
# ... otherwise, see if there's a key that contains a list of
|
||||
# values to use instead.
|
||||
vals = subconfig.get(fixture_name+ 's', [])
|
||||
def fixture_id(index, val):
|
||||
try:
|
||||
return val['fixture_id']
|
||||
except:
|
||||
return fixture_name + str(index)
|
||||
ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
|
||||
metafunc.parametrize(fixture_name, vals, ids=ids)
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
"""pytest hook: parameterize test functions based on custom rules.
|
||||
|
||||
Check each test function parameter (fixture name) to see if it is one of
|
||||
our custom names, and if so, provide the correct parametrization for that
|
||||
parameter.
|
||||
|
||||
Args:
|
||||
metafunc: The pytest test function.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
for fn in metafunc.fixturenames:
|
||||
if fn == 'ut_subtest':
|
||||
generate_ut_subtest(metafunc, fn)
|
||||
continue
|
||||
generate_config(metafunc, fn)
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def u_boot_log(request):
|
||||
"""Generate the value of a test's log fixture.
|
||||
|
||||
Args:
|
||||
request: The pytest request.
|
||||
|
||||
Returns:
|
||||
The fixture value.
|
||||
"""
|
||||
|
||||
return console.log
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def u_boot_config(request):
|
||||
"""Generate the value of a test's u_boot_config fixture.
|
||||
|
||||
Args:
|
||||
request: The pytest request.
|
||||
|
||||
Returns:
|
||||
The fixture value.
|
||||
"""
|
||||
|
||||
return console.config
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def u_boot_console(request):
|
||||
"""Generate the value of a test's u_boot_console fixture.
|
||||
|
||||
Args:
|
||||
request: The pytest request.
|
||||
|
||||
Returns:
|
||||
The fixture value.
|
||||
"""
|
||||
|
||||
console.ensure_spawned()
|
||||
return console
|
||||
|
||||
anchors = {}
|
||||
tests_not_run = []
|
||||
tests_failed = []
|
||||
tests_xpassed = []
|
||||
tests_xfailed = []
|
||||
tests_skipped = []
|
||||
tests_warning = []
|
||||
tests_passed = []
|
||||
|
||||
def pytest_itemcollected(item):
|
||||
"""pytest hook: Called once for each test found during collection.
|
||||
|
||||
This enables our custom result analysis code to see the list of all tests
|
||||
that should eventually be run.
|
||||
|
||||
Args:
|
||||
item: The item that was collected.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
tests_not_run.append(item.name)
|
||||
|
||||
def cleanup():
|
||||
"""Clean up all global state.
|
||||
|
||||
Executed (via atexit) once the entire test process is complete. This
|
||||
includes logging the status of all tests, and the identity of any failed
|
||||
or skipped tests.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if console:
|
||||
console.close()
|
||||
if log:
|
||||
with log.section('Status Report', 'status_report'):
|
||||
log.status_pass('%d passed' % len(tests_passed))
|
||||
if tests_warning:
|
||||
log.status_warning('%d passed with warning' % len(tests_warning))
|
||||
for test in tests_warning:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_warning('... ' + test, anchor)
|
||||
if tests_skipped:
|
||||
log.status_skipped('%d skipped' % len(tests_skipped))
|
||||
for test in tests_skipped:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_skipped('... ' + test, anchor)
|
||||
if tests_xpassed:
|
||||
log.status_xpass('%d xpass' % len(tests_xpassed))
|
||||
for test in tests_xpassed:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_xpass('... ' + test, anchor)
|
||||
if tests_xfailed:
|
||||
log.status_xfail('%d xfail' % len(tests_xfailed))
|
||||
for test in tests_xfailed:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_xfail('... ' + test, anchor)
|
||||
if tests_failed:
|
||||
log.status_fail('%d failed' % len(tests_failed))
|
||||
for test in tests_failed:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_fail('... ' + test, anchor)
|
||||
if tests_not_run:
|
||||
log.status_fail('%d not run' % len(tests_not_run))
|
||||
for test in tests_not_run:
|
||||
anchor = anchors.get(test, None)
|
||||
log.status_fail('... ' + test, anchor)
|
||||
log.close()
|
||||
atexit.register(cleanup)
|
||||
|
||||
def setup_boardspec(item):
|
||||
"""Process any 'boardspec' marker for a test.
|
||||
|
||||
Such a marker lists the set of board types that a test does/doesn't
|
||||
support. If tests are being executed on an unsupported board, the test is
|
||||
marked to be skipped.
|
||||
|
||||
Args:
|
||||
item: The pytest test item.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
required_boards = []
|
||||
for boards in item.iter_markers('boardspec'):
|
||||
board = boards.args[0]
|
||||
if board.startswith('!'):
|
||||
if ubconfig.board_type == board[1:]:
|
||||
pytest.skip('board "%s" not supported' % ubconfig.board_type)
|
||||
return
|
||||
else:
|
||||
required_boards.append(board)
|
||||
if required_boards and ubconfig.board_type not in required_boards:
|
||||
pytest.skip('board "%s" not supported' % ubconfig.board_type)
|
||||
|
||||
def setup_buildconfigspec(item):
|
||||
"""Process any 'buildconfigspec' marker for a test.
|
||||
|
||||
Such a marker lists some U-Boot configuration feature that the test
|
||||
requires. If tests are being executed on an U-Boot build that doesn't
|
||||
have the required feature, the test is marked to be skipped.
|
||||
|
||||
Args:
|
||||
item: The pytest test item.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
for options in item.iter_markers('buildconfigspec'):
|
||||
option = options.args[0]
|
||||
if not ubconfig.buildconfig.get('config_' + option.lower(), None):
|
||||
pytest.skip('.config feature "%s" not enabled' % option.lower())
|
||||
for option in item.iter_markers('notbuildconfigspec'):
|
||||
option = options.args[0]
|
||||
if ubconfig.buildconfig.get('config_' + option.lower(), None):
|
||||
pytest.skip('.config feature "%s" enabled' % option.lower())
|
||||
|
||||
def tool_is_in_path(tool):
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
fn = os.path.join(path, tool)
|
||||
if os.path.isfile(fn) and os.access(fn, os.X_OK):
|
||||
return True
|
||||
return False
|
||||
|
||||
def setup_requiredtool(item):
|
||||
"""Process any 'requiredtool' marker for a test.
|
||||
|
||||
Such a marker lists some external tool (binary, executable, application)
|
||||
that the test requires. If tests are being executed on a system that
|
||||
doesn't have the required tool, the test is marked to be skipped.
|
||||
|
||||
Args:
|
||||
item: The pytest test item.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
for tools in item.iter_markers('requiredtool'):
|
||||
tool = tools.args[0]
|
||||
if not tool_is_in_path(tool):
|
||||
pytest.skip('tool "%s" not in $PATH' % tool)
|
||||
|
||||
def start_test_section(item):
|
||||
anchors[item.name] = log.start_section(item.name)
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
"""pytest hook: Configure (set up) a test item.
|
||||
|
||||
Called once for each test to perform any custom configuration. This hook
|
||||
is used to skip the test if certain conditions apply.
|
||||
|
||||
Args:
|
||||
item: The pytest test item.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
start_test_section(item)
|
||||
setup_boardspec(item)
|
||||
setup_buildconfigspec(item)
|
||||
setup_requiredtool(item)
|
||||
|
||||
def pytest_runtest_protocol(item, nextitem):
|
||||
"""pytest hook: Called to execute a test.
|
||||
|
||||
This hook wraps the standard pytest runtestprotocol() function in order
|
||||
to acquire visibility into, and record, each test function's result.
|
||||
|
||||
Args:
|
||||
item: The pytest test item to execute.
|
||||
nextitem: The pytest test item that will be executed after this one.
|
||||
|
||||
Returns:
|
||||
A list of pytest reports (test result data).
|
||||
"""
|
||||
|
||||
log.get_and_reset_warning()
|
||||
reports = runtestprotocol(item, nextitem=nextitem)
|
||||
was_warning = log.get_and_reset_warning()
|
||||
|
||||
# In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if
|
||||
# the test is skipped. That call is required to create the test's section
|
||||
# in the log file. The call to log.end_section() requires that the log
|
||||
# contain a section for this test. Create a section for the test if it
|
||||
# doesn't already exist.
|
||||
if not item.name in anchors:
|
||||
start_test_section(item)
|
||||
|
||||
failure_cleanup = False
|
||||
if not was_warning:
|
||||
test_list = tests_passed
|
||||
msg = 'OK'
|
||||
msg_log = log.status_pass
|
||||
else:
|
||||
test_list = tests_warning
|
||||
msg = 'OK (with warning)'
|
||||
msg_log = log.status_warning
|
||||
for report in reports:
|
||||
if report.outcome == 'failed':
|
||||
if hasattr(report, 'wasxfail'):
|
||||
test_list = tests_xpassed
|
||||
msg = 'XPASSED'
|
||||
msg_log = log.status_xpass
|
||||
else:
|
||||
failure_cleanup = True
|
||||
test_list = tests_failed
|
||||
msg = 'FAILED:\n' + str(report.longrepr)
|
||||
msg_log = log.status_fail
|
||||
break
|
||||
if report.outcome == 'skipped':
|
||||
if hasattr(report, 'wasxfail'):
|
||||
failure_cleanup = True
|
||||
test_list = tests_xfailed
|
||||
msg = 'XFAILED:\n' + str(report.longrepr)
|
||||
msg_log = log.status_xfail
|
||||
break
|
||||
test_list = tests_skipped
|
||||
msg = 'SKIPPED:\n' + str(report.longrepr)
|
||||
msg_log = log.status_skipped
|
||||
|
||||
if failure_cleanup:
|
||||
console.drain_console()
|
||||
|
||||
test_list.append(item.name)
|
||||
tests_not_run.remove(item.name)
|
||||
|
||||
try:
|
||||
msg_log(msg)
|
||||
except:
|
||||
# If something went wrong with logging, it's better to let the test
|
||||
# process continue, which may report other exceptions that triggered
|
||||
# the logging issue (e.g. console.log wasn't created). Hence, just
|
||||
# squash the exception. If the test setup failed due to e.g. syntax
|
||||
# error somewhere else, this won't be seen. However, once that issue
|
||||
# is fixed, if this exception still exists, it will then be logged as
|
||||
# part of the test's stdout.
|
||||
import traceback
|
||||
print('Exception occurred while logging runtest status:')
|
||||
traceback.print_exc()
|
||||
# FIXME: Can we force a test failure here?
|
||||
|
||||
log.end_section(item.name)
|
||||
|
||||
if failure_cleanup:
|
||||
console.cleanup_spawn()
|
||||
|
||||
return reports
|
||||
108
test/py/multiplexed_log.css
Normal file
108
test/py/multiplexed_log.css
Normal file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2015 Stephen Warren
|
||||
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This provides pretty formatting of the HTML log file, e.g.
|
||||
* - colored bars beside/above log sections for easily parsed delineation.
|
||||
* - color highlighting of various messages.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.implicit {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.block {
|
||||
border-style: solid;
|
||||
border-color: #303030;
|
||||
border-width: 0px 0px 0px 5px;
|
||||
padding-left: 5px
|
||||
}
|
||||
|
||||
.block-header {
|
||||
background-color: #303030;
|
||||
margin-left: -5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.block-header:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.block-trailer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff0000
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #ffff00
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #808080
|
||||
}
|
||||
|
||||
.action {
|
||||
color: #8080ff
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: #8080ff
|
||||
}
|
||||
|
||||
.status-pass {
|
||||
color: #00ff00
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
color: #ffff00
|
||||
}
|
||||
|
||||
.status-skipped {
|
||||
color: #ffff00
|
||||
}
|
||||
|
||||
.status-xfail {
|
||||
color: #ff7f00
|
||||
}
|
||||
|
||||
.status-xpass {
|
||||
color: #ff7f00
|
||||
}
|
||||
|
||||
.status-fail {
|
||||
color: #ff0000
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a:link {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
709
test/py/multiplexed_log.py
Normal file
709
test/py/multiplexed_log.py
Normal file
@@ -0,0 +1,709 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Generate an HTML-formatted log file containing multiple streams of data,
|
||||
# each represented in a well-delineated/-structured fashion.
|
||||
|
||||
import datetime
|
||||
import html
|
||||
import os.path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
mod_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class LogfileStream(object):
|
||||
"""A file-like object used to write a single logical stream of data into
|
||||
a multiplexed log file. Objects of this type should be created by factory
|
||||
functions in the Logfile class rather than directly."""
|
||||
|
||||
def __init__(self, logfile, name, chained_file):
|
||||
"""Initialize a new object.
|
||||
|
||||
Args:
|
||||
logfile: The Logfile object to log to.
|
||||
name: The name of this log stream.
|
||||
chained_file: The file-like object to which all stream data should be
|
||||
logged to in addition to logfile. Can be None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.logfile = logfile
|
||||
self.name = name
|
||||
self.chained_file = chained_file
|
||||
|
||||
def close(self):
|
||||
"""Dummy function so that this class is "file-like".
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def write(self, data, implicit=False):
|
||||
"""Write data to the log stream.
|
||||
|
||||
Args:
|
||||
data: The data to write to the file.
|
||||
implicit: Boolean indicating whether data actually appeared in the
|
||||
stream, or was implicitly generated. A valid use-case is to
|
||||
repeat a shell prompt at the start of each separate log
|
||||
section, which makes the log sections more readable in
|
||||
isolation.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.logfile.write(self, data, implicit)
|
||||
if self.chained_file:
|
||||
# Chained file is console, convert things a little
|
||||
self.chained_file.write((data.encode('ascii', 'replace')).decode())
|
||||
|
||||
def flush(self):
|
||||
"""Flush the log stream, to ensure correct log interleaving.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.logfile.flush()
|
||||
if self.chained_file:
|
||||
self.chained_file.flush()
|
||||
|
||||
class RunAndLog(object):
|
||||
"""A utility object used to execute sub-processes and log their output to
|
||||
a multiplexed log file. Objects of this type should be created by factory
|
||||
functions in the Logfile class rather than directly."""
|
||||
|
||||
def __init__(self, logfile, name, chained_file):
|
||||
"""Initialize a new object.
|
||||
|
||||
Args:
|
||||
logfile: The Logfile object to log to.
|
||||
name: The name of this log stream or sub-process.
|
||||
chained_file: The file-like object to which all stream data should
|
||||
be logged to in addition to logfile. Can be None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.logfile = logfile
|
||||
self.name = name
|
||||
self.chained_file = chained_file
|
||||
self.output = None
|
||||
self.exit_status = None
|
||||
|
||||
def close(self):
|
||||
"""Clean up any resources managed by this object."""
|
||||
pass
|
||||
|
||||
def run(self, cmd, cwd=None, ignore_errors=False):
|
||||
"""Run a command as a sub-process, and log the results.
|
||||
|
||||
The output is available at self.output which can be useful if there is
|
||||
an exception.
|
||||
|
||||
Args:
|
||||
cmd: The command to execute.
|
||||
cwd: The directory to run the command in. Can be None to use the
|
||||
current directory.
|
||||
ignore_errors: Indicate whether to ignore errors. If True, the
|
||||
function will simply return if the command cannot be executed
|
||||
or exits with an error code, otherwise an exception will be
|
||||
raised if such problems occur.
|
||||
|
||||
Returns:
|
||||
The output as a string.
|
||||
"""
|
||||
|
||||
msg = '+' + ' '.join(cmd) + '\n'
|
||||
if self.chained_file:
|
||||
self.chained_file.write(msg)
|
||||
self.logfile.write(self, msg)
|
||||
|
||||
try:
|
||||
p = subprocess.Popen(cmd, cwd=cwd,
|
||||
stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
(stdout, stderr) = p.communicate()
|
||||
if stdout is not None:
|
||||
stdout = stdout.decode('utf-8')
|
||||
if stderr is not None:
|
||||
stderr = stderr.decode('utf-8')
|
||||
output = ''
|
||||
if stdout:
|
||||
if stderr:
|
||||
output += 'stdout:\n'
|
||||
output += stdout
|
||||
if stderr:
|
||||
if stdout:
|
||||
output += 'stderr:\n'
|
||||
output += stderr
|
||||
exit_status = p.returncode
|
||||
exception = None
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
output = cpe.output
|
||||
exit_status = cpe.returncode
|
||||
exception = cpe
|
||||
except Exception as e:
|
||||
output = ''
|
||||
exit_status = 0
|
||||
exception = e
|
||||
if output and not output.endswith('\n'):
|
||||
output += '\n'
|
||||
if exit_status and not exception and not ignore_errors:
|
||||
exception = Exception('Exit code: ' + str(exit_status))
|
||||
if exception:
|
||||
output += str(exception) + '\n'
|
||||
self.logfile.write(self, output)
|
||||
if self.chained_file:
|
||||
self.chained_file.write(output)
|
||||
self.logfile.timestamp()
|
||||
|
||||
# Store the output so it can be accessed if we raise an exception.
|
||||
self.output = output
|
||||
self.exit_status = exit_status
|
||||
if exception:
|
||||
raise exception
|
||||
return output
|
||||
|
||||
class SectionCtxMgr(object):
|
||||
"""A context manager for Python's "with" statement, which allows a certain
|
||||
portion of test code to be logged to a separate section of the log file.
|
||||
Objects of this type should be created by factory functions in the Logfile
|
||||
class rather than directly."""
|
||||
|
||||
def __init__(self, log, marker, anchor):
|
||||
"""Initialize a new object.
|
||||
|
||||
Args:
|
||||
log: The Logfile object to log to.
|
||||
marker: The name of the nested log section.
|
||||
anchor: The anchor value to pass to start_section().
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.log = log
|
||||
self.marker = marker
|
||||
self.anchor = anchor
|
||||
|
||||
def __enter__(self):
|
||||
self.anchor = self.log.start_section(self.marker, self.anchor)
|
||||
|
||||
def __exit__(self, extype, value, traceback):
|
||||
self.log.end_section(self.marker)
|
||||
|
||||
class Logfile(object):
|
||||
"""Generates an HTML-formatted log file containing multiple streams of
|
||||
data, each represented in a well-delineated/-structured fashion."""
|
||||
|
||||
def __init__(self, fn):
|
||||
"""Initialize a new object.
|
||||
|
||||
Args:
|
||||
fn: The filename to write to.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.f = open(fn, 'wt', encoding='utf-8')
|
||||
self.last_stream = None
|
||||
self.blocks = []
|
||||
self.cur_evt = 1
|
||||
self.anchor = 0
|
||||
self.timestamp_start = self._get_time()
|
||||
self.timestamp_prev = self.timestamp_start
|
||||
self.timestamp_blocks = []
|
||||
self.seen_warning = False
|
||||
|
||||
shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
|
||||
self.f.write('''\
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
|
||||
<script src="http://code.jquery.com/jquery.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// Copy status report HTML to start of log for easy access
|
||||
sts = $(".block#status_report")[0].outerHTML;
|
||||
$("tt").prepend(sts);
|
||||
|
||||
// Add expand/contract buttons to all block headers
|
||||
btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
|
||||
"<span class=\\\"block-contract\\\">[-] </span>";
|
||||
$(".block-header").prepend(btns);
|
||||
|
||||
// Pre-contract all blocks which passed, leaving only problem cases
|
||||
// expanded, to highlight issues the user should look at.
|
||||
// Only top-level blocks (sections) should have any status
|
||||
passed_bcs = $(".block-content:has(.status-pass)");
|
||||
// Some blocks might have multiple status entries (e.g. the status
|
||||
// report), so take care not to hide blocks with partial success.
|
||||
passed_bcs = passed_bcs.not(":has(.status-fail)");
|
||||
passed_bcs = passed_bcs.not(":has(.status-xfail)");
|
||||
passed_bcs = passed_bcs.not(":has(.status-xpass)");
|
||||
passed_bcs = passed_bcs.not(":has(.status-skipped)");
|
||||
passed_bcs = passed_bcs.not(":has(.status-warning)");
|
||||
// Hide the passed blocks
|
||||
passed_bcs.addClass("hidden");
|
||||
// Flip the expand/contract button hiding for those blocks.
|
||||
bhs = passed_bcs.parent().children(".block-header")
|
||||
bhs.children(".block-expand").removeClass("hidden");
|
||||
bhs.children(".block-contract").addClass("hidden");
|
||||
|
||||
// Add click handler to block headers.
|
||||
// The handler expands/contracts the block.
|
||||
$(".block-header").on("click", function (e) {
|
||||
var header = $(this);
|
||||
var content = header.next(".block-content");
|
||||
var expanded = !content.hasClass("hidden");
|
||||
if (expanded) {
|
||||
content.addClass("hidden");
|
||||
header.children(".block-expand").first().removeClass("hidden");
|
||||
header.children(".block-contract").first().addClass("hidden");
|
||||
} else {
|
||||
header.children(".block-contract").first().removeClass("hidden");
|
||||
header.children(".block-expand").first().addClass("hidden");
|
||||
content.removeClass("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
// When clicking on a link, expand the target block
|
||||
$("a").on("click", function (e) {
|
||||
var block = $($(this).attr("href"));
|
||||
var header = block.children(".block-header");
|
||||
var content = block.children(".block-content").first();
|
||||
header.children(".block-contract").first().removeClass("hidden");
|
||||
header.children(".block-expand").first().addClass("hidden");
|
||||
content.removeClass("hidden");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<tt>
|
||||
''')
|
||||
|
||||
def close(self):
|
||||
"""Close the log file.
|
||||
|
||||
After calling this function, no more data may be written to the log.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.f.write('''\
|
||||
</tt>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
self.f.close()
|
||||
|
||||
# The set of characters that should be represented as hexadecimal codes in
|
||||
# the log file.
|
||||
_nonprint = {ord('%')}
|
||||
_nonprint.update({c for c in range(0, 32) if c not in (9, 10)})
|
||||
_nonprint.update({c for c in range(127, 256)})
|
||||
|
||||
def _escape(self, data):
|
||||
"""Render data format suitable for inclusion in an HTML document.
|
||||
|
||||
This includes HTML-escaping certain characters, and translating
|
||||
control characters to a hexadecimal representation.
|
||||
|
||||
Args:
|
||||
data: The raw string data to be escaped.
|
||||
|
||||
Returns:
|
||||
An escaped version of the data.
|
||||
"""
|
||||
|
||||
data = data.replace(chr(13), '')
|
||||
data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
|
||||
c for c in data)
|
||||
data = html.escape(data)
|
||||
return data
|
||||
|
||||
def _terminate_stream(self):
|
||||
"""Write HTML to the log file to terminate the current stream's data.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.cur_evt += 1
|
||||
if not self.last_stream:
|
||||
return
|
||||
self.f.write('</pre>\n')
|
||||
self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
|
||||
self.last_stream.name + '</div>\n')
|
||||
self.f.write('</div>\n')
|
||||
self.f.write('</div>\n')
|
||||
self.last_stream = None
|
||||
|
||||
def _note(self, note_type, msg, anchor=None):
|
||||
"""Write a note or one-off message to the log file.
|
||||
|
||||
Args:
|
||||
note_type: The type of note. This must be a value supported by the
|
||||
accompanying multiplexed_log.css.
|
||||
msg: The note/message to log.
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._terminate_stream()
|
||||
self.f.write('<div class="' + note_type + '">\n')
|
||||
self.f.write('<pre>')
|
||||
if anchor:
|
||||
self.f.write('<a href="#%s">' % anchor)
|
||||
self.f.write(self._escape(msg))
|
||||
if anchor:
|
||||
self.f.write('</a>')
|
||||
self.f.write('\n</pre>\n')
|
||||
self.f.write('</div>\n')
|
||||
|
||||
def start_section(self, marker, anchor=None):
|
||||
"""Begin a new nested section in the log file.
|
||||
|
||||
Args:
|
||||
marker: The name of the section that is starting.
|
||||
anchor: The value to use for the anchor. If None, a unique value
|
||||
will be calculated and used
|
||||
|
||||
Returns:
|
||||
Name of the HTML anchor emitted before section.
|
||||
"""
|
||||
|
||||
self._terminate_stream()
|
||||
self.blocks.append(marker)
|
||||
self.timestamp_blocks.append(self._get_time())
|
||||
if not anchor:
|
||||
self.anchor += 1
|
||||
anchor = str(self.anchor)
|
||||
blk_path = '/'.join(self.blocks)
|
||||
self.f.write('<div class="section block" id="' + anchor + '">\n')
|
||||
self.f.write('<div class="section-header block-header">Section: ' +
|
||||
blk_path + '</div>\n')
|
||||
self.f.write('<div class="section-content block-content">\n')
|
||||
self.timestamp()
|
||||
|
||||
return anchor
|
||||
|
||||
def end_section(self, marker):
|
||||
"""Terminate the current nested section in the log file.
|
||||
|
||||
This function validates proper nesting of start_section() and
|
||||
end_section() calls. If a mismatch is found, an exception is raised.
|
||||
|
||||
Args:
|
||||
marker: The name of the section that is ending.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if (not self.blocks) or (marker != self.blocks[-1]):
|
||||
raise Exception('Block nesting mismatch: "%s" "%s"' %
|
||||
(marker, '/'.join(self.blocks)))
|
||||
self._terminate_stream()
|
||||
timestamp_now = self._get_time()
|
||||
timestamp_section_start = self.timestamp_blocks.pop()
|
||||
delta_section = timestamp_now - timestamp_section_start
|
||||
self._note("timestamp",
|
||||
"TIME: SINCE-SECTION: " + str(delta_section))
|
||||
blk_path = '/'.join(self.blocks)
|
||||
self.f.write('<div class="section-trailer block-trailer">' +
|
||||
'End section: ' + blk_path + '</div>\n')
|
||||
self.f.write('</div>\n')
|
||||
self.f.write('</div>\n')
|
||||
self.blocks.pop()
|
||||
|
||||
def section(self, marker, anchor=None):
|
||||
"""Create a temporary section in the log file.
|
||||
|
||||
This function creates a context manager for Python's "with" statement,
|
||||
which allows a certain portion of test code to be logged to a separate
|
||||
section of the log file.
|
||||
|
||||
Usage:
|
||||
with log.section("somename"):
|
||||
some test code
|
||||
|
||||
Args:
|
||||
marker: The name of the nested section.
|
||||
anchor: The anchor value to pass to start_section().
|
||||
|
||||
Returns:
|
||||
A context manager object.
|
||||
"""
|
||||
|
||||
return SectionCtxMgr(self, marker, anchor)
|
||||
|
||||
def error(self, msg):
|
||||
"""Write an error note to the log file.
|
||||
|
||||
Args:
|
||||
msg: A message describing the error.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("error", msg)
|
||||
|
||||
def warning(self, msg):
|
||||
"""Write an warning note to the log file.
|
||||
|
||||
Args:
|
||||
msg: A message describing the warning.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.seen_warning = True
|
||||
self._note("warning", msg)
|
||||
|
||||
def get_and_reset_warning(self):
|
||||
"""Get and reset the log warning flag.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
Whether a warning was seen since the last call.
|
||||
"""
|
||||
|
||||
ret = self.seen_warning
|
||||
self.seen_warning = False
|
||||
return ret
|
||||
|
||||
def info(self, msg):
|
||||
"""Write an informational note to the log file.
|
||||
|
||||
Args:
|
||||
msg: An informational message.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("info", msg)
|
||||
|
||||
def action(self, msg):
|
||||
"""Write an action note to the log file.
|
||||
|
||||
Args:
|
||||
msg: A message describing the action that is being logged.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("action", msg)
|
||||
|
||||
def _get_time(self):
|
||||
return datetime.datetime.now()
|
||||
|
||||
def timestamp(self):
|
||||
"""Write a timestamp to the log file.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
timestamp_now = self._get_time()
|
||||
delta_prev = timestamp_now - self.timestamp_prev
|
||||
delta_start = timestamp_now - self.timestamp_start
|
||||
self.timestamp_prev = timestamp_now
|
||||
|
||||
self._note("timestamp",
|
||||
"TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
|
||||
self._note("timestamp",
|
||||
"TIME: SINCE-PREV: " + str(delta_prev))
|
||||
self._note("timestamp",
|
||||
"TIME: SINCE-START: " + str(delta_start))
|
||||
|
||||
def status_pass(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing test(s) which passed.
|
||||
|
||||
Args:
|
||||
msg: A message describing the passed test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-pass", msg, anchor)
|
||||
|
||||
def status_warning(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing test(s) which passed.
|
||||
|
||||
Args:
|
||||
msg: A message describing the passed test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-warning", msg, anchor)
|
||||
|
||||
def status_skipped(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing skipped test(s).
|
||||
|
||||
Args:
|
||||
msg: A message describing the skipped test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-skipped", msg, anchor)
|
||||
|
||||
def status_xfail(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing xfailed test(s).
|
||||
|
||||
Args:
|
||||
msg: A message describing the xfailed test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-xfail", msg, anchor)
|
||||
|
||||
def status_xpass(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing xpassed test(s).
|
||||
|
||||
Args:
|
||||
msg: A message describing the xpassed test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-xpass", msg, anchor)
|
||||
|
||||
def status_fail(self, msg, anchor=None):
|
||||
"""Write a note to the log file describing failed test(s).
|
||||
|
||||
Args:
|
||||
msg: A message describing the failed test(s).
|
||||
anchor: Optional internal link target.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self._note("status-fail", msg, anchor)
|
||||
|
||||
def get_stream(self, name, chained_file=None):
|
||||
"""Create an object to log a single stream's data into the log file.
|
||||
|
||||
This creates a "file-like" object that can be written to in order to
|
||||
write a single stream's data to the log file. The implementation will
|
||||
handle any required interleaving of data (from multiple streams) in
|
||||
the log, in a way that makes it obvious which stream each bit of data
|
||||
came from.
|
||||
|
||||
Args:
|
||||
name: The name of the stream.
|
||||
chained_file: The file-like object to which all stream data should
|
||||
be logged to in addition to this log. Can be None.
|
||||
|
||||
Returns:
|
||||
A file-like object.
|
||||
"""
|
||||
|
||||
return LogfileStream(self, name, chained_file)
|
||||
|
||||
def get_runner(self, name, chained_file=None):
|
||||
"""Create an object that executes processes and logs their output.
|
||||
|
||||
Args:
|
||||
name: The name of this sub-process.
|
||||
chained_file: The file-like object to which all stream data should
|
||||
be logged to in addition to logfile. Can be None.
|
||||
|
||||
Returns:
|
||||
A RunAndLog object.
|
||||
"""
|
||||
|
||||
return RunAndLog(self, name, chained_file)
|
||||
|
||||
def write(self, stream, data, implicit=False):
|
||||
"""Write stream data into the log file.
|
||||
|
||||
This function should only be used by instances of LogfileStream or
|
||||
RunAndLog.
|
||||
|
||||
Args:
|
||||
stream: The stream whose data is being logged.
|
||||
data: The data to log.
|
||||
implicit: Boolean indicating whether data actually appeared in the
|
||||
stream, or was implicitly generated. A valid use-case is to
|
||||
repeat a shell prompt at the start of each separate log
|
||||
section, which makes the log sections more readable in
|
||||
isolation.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if stream != self.last_stream:
|
||||
self._terminate_stream()
|
||||
self.f.write('<div class="stream block">\n')
|
||||
self.f.write('<div class="stream-header block-header">Stream: ' +
|
||||
stream.name + '</div>\n')
|
||||
self.f.write('<div class="stream-content block-content">\n')
|
||||
self.f.write('<pre>')
|
||||
if implicit:
|
||||
self.f.write('<span class="implicit">')
|
||||
self.f.write(self._escape(data))
|
||||
if implicit:
|
||||
self.f.write('</span>')
|
||||
self.last_stream = stream
|
||||
|
||||
def flush(self):
|
||||
"""Flush the log stream, to ensure correct log interleaving.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.f.flush()
|
||||
13
test/py/pytest.ini
Normal file
13
test/py/pytest.ini
Normal file
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Static configuration data for pytest. pytest reads this at startup time.
|
||||
|
||||
[pytest]
|
||||
markers =
|
||||
boardspec: U-Boot: Describes the set of boards a test can/can't run on.
|
||||
buildconfigspec: U-Boot: Describes Kconfig/config-header constraints.
|
||||
notbuildconfigspec: U-Boot: Describes required disabled Kconfig options.
|
||||
requiredtool: U-Boot: Required host tools for a test.
|
||||
slow: U-Boot: Specific test will run slowly.
|
||||
22
test/py/requirements.txt
Normal file
22
test/py/requirements.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
atomicwrites==1.3.0
|
||||
attrs==19.3.0
|
||||
coverage==4.5.4
|
||||
extras==1.0.0
|
||||
fixtures==3.0.0
|
||||
importlib-metadata==0.23
|
||||
linecache2==1.0.0
|
||||
more-itertools==7.2.0
|
||||
packaging==19.2
|
||||
pbr==5.4.3
|
||||
pluggy==0.13.0
|
||||
py==1.8.0
|
||||
pyparsing==2.4.2
|
||||
pytest==5.2.1
|
||||
python-mimeparse==1.6.0
|
||||
python-subunit==1.3.0
|
||||
six==1.12.0
|
||||
testtools==2.3.0
|
||||
traceback2==1.4.0
|
||||
unittest2==1.1.0
|
||||
wcwidth==0.1.7
|
||||
zipp==0.6.0
|
||||
20
test/py/test.py
Executable file
20
test/py/test.py
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Wrapper script to invoke pytest with the directory name that contains the
|
||||
# U-Boot tests.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
# argv; py.test test_directory_name user-supplied-arguments
|
||||
args = [os.path.dirname(__file__) + '/tests']
|
||||
args.extend(sys.argv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(load_entry_point('pytest', 'console_scripts', 'pytest')(args))
|
||||
19
test/py/tests/test_000_version.py
Normal file
19
test/py/tests/test_000_version.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# pytest runs tests the order of their module path, which is related to the
|
||||
# filename containing the test. This file is named such that it is sorted
|
||||
# first, simply as a very basic sanity check of the functionality of the U-Boot
|
||||
# command prompt.
|
||||
|
||||
def test_version(u_boot_console):
|
||||
"""Test that the "version" command prints the U-Boot version."""
|
||||
|
||||
# "version" prints the U-Boot sign-on message. This is usually considered
|
||||
# an error, so that any unexpected reboot causes an error. Here, this
|
||||
# error detection is disabled since the sign-on message is expected.
|
||||
with u_boot_console.disable_check('main_signon'):
|
||||
response = u_boot_console.run_command('version')
|
||||
# Ensure "version" printed what we expected.
|
||||
u_boot_console.validate_version_string_in_text(response)
|
||||
75
test/py/tests/test_android/test_ab.py
Normal file
75
test/py/tests/test_android/test_ab.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# (C) Copyright 2018 Texas Instruments, <www.ti.com>
|
||||
|
||||
# Test A/B update commands.
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
class ABTestDiskImage(object):
|
||||
"""Disk Image used by the A/B tests."""
|
||||
|
||||
def __init__(self, u_boot_console):
|
||||
"""Initialize a new ABTestDiskImage object.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
filename = 'test_ab_disk_image.bin'
|
||||
|
||||
persistent = u_boot_console.config.persistent_data_dir + '/' + filename
|
||||
self.path = u_boot_console.config.result_dir + '/' + filename
|
||||
|
||||
with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
|
||||
if os.path.exists(persistent):
|
||||
u_boot_console.log.action('Disk image file ' + persistent +
|
||||
' already exists')
|
||||
else:
|
||||
u_boot_console.log.action('Generating ' + persistent)
|
||||
fd = os.open(persistent, os.O_RDWR | os.O_CREAT)
|
||||
os.ftruncate(fd, 524288)
|
||||
os.close(fd)
|
||||
cmd = ('sgdisk', persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
cmd = ('sgdisk', '--new=1:64:512', '--change-name=1:misc',
|
||||
persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
cmd = ('sgdisk', '--load-backup=' + persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
cmd = ('cp', persistent, self.path)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
di = None
|
||||
@pytest.fixture(scope='function')
|
||||
def ab_disk_image(u_boot_console):
|
||||
global di
|
||||
if not di:
|
||||
di = ABTestDiskImage(u_boot_console)
|
||||
return di
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('android_ab')
|
||||
@pytest.mark.buildconfigspec('cmd_ab_select')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_ab(ab_disk_image, u_boot_console):
|
||||
"""Test the 'ab_select' command."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + ab_disk_image.path)
|
||||
|
||||
output = u_boot_console.run_command('ab_select slot_name host 0#misc')
|
||||
assert 're-initializing A/B metadata' in output
|
||||
assert 'Attempting slot a, tries remaining 7' in output
|
||||
output = u_boot_console.run_command('printenv slot_name')
|
||||
assert 'slot_name=a' in output
|
||||
|
||||
output = u_boot_console.run_command('ab_select slot_name host 0:1')
|
||||
assert 'Attempting slot b, tries remaining 7' in output
|
||||
output = u_boot_console.run_command('printenv slot_name')
|
||||
assert 'slot_name=b' in output
|
||||
137
test/py/tests/test_android/test_avb.py
Normal file
137
test/py/tests/test_android/test_avb.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Android Verified Boot 2.0 Test
|
||||
|
||||
"""
|
||||
This tests Android Verified Boot 2.0 support in U-boot:
|
||||
|
||||
For additional details about how to build proper vbmeta partition
|
||||
check doc/android/avb2.txt
|
||||
|
||||
For configuration verification:
|
||||
- Corrupt boot partition and check for failure
|
||||
- Corrupt vbmeta partition and check for failure
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import u_boot_utils as util
|
||||
|
||||
# defauld mmc id
|
||||
mmc_dev = 1
|
||||
temp_addr = 0x90000000
|
||||
temp_addr2 = 0x90002000
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_avb_verify(u_boot_console):
|
||||
"""Run AVB 2.0 boot verification chain with avb subset of commands
|
||||
"""
|
||||
|
||||
success_str = "Verification passed successfully"
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' %str(mmc_dev))
|
||||
assert response == ''
|
||||
response = u_boot_console.run_command('avb verify')
|
||||
assert response.find(success_str)
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_avb_mmc_uuid(u_boot_console):
|
||||
"""Check if 'avb get_uuid' works, compare results with
|
||||
'part list mmc 1' output
|
||||
"""
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
|
||||
assert response == ''
|
||||
|
||||
response = u_boot_console.run_command('mmc rescan; mmc dev %s' %
|
||||
str(mmc_dev))
|
||||
assert response.find('is current device')
|
||||
|
||||
part_lines = u_boot_console.run_command('mmc part').splitlines()
|
||||
part_list = {}
|
||||
cur_partname = ''
|
||||
|
||||
for line in part_lines:
|
||||
if '"' in line:
|
||||
start_pt = line.find('"')
|
||||
end_pt = line.find('"', start_pt + 1)
|
||||
cur_partname = line[start_pt + 1: end_pt]
|
||||
|
||||
if 'guid:' in line:
|
||||
guid_to_check = line.split('guid:\t')
|
||||
part_list[cur_partname] = guid_to_check[1]
|
||||
|
||||
# lets check all guids with avb get_guid
|
||||
for part, guid in part_list.iteritems():
|
||||
avb_guid_resp = u_boot_console.run_command('avb get_uuid %s' % part)
|
||||
assert guid == avb_guid_resp.split('UUID: ')[1]
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
def test_avb_read_rb(u_boot_console):
|
||||
"""Test reading rollback indexes
|
||||
"""
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
|
||||
assert response == ''
|
||||
|
||||
response = u_boot_console.run_command('avb read_rb 1')
|
||||
assert response == 'Rollback index: 0'
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
def test_avb_is_unlocked(u_boot_console):
|
||||
"""Test if device is in the unlocked state
|
||||
"""
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
|
||||
assert response == ''
|
||||
|
||||
response = u_boot_console.run_command('avb is_unlocked')
|
||||
assert response == 'Unlocked = 1'
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_avb_mmc_read(u_boot_console):
|
||||
"""Test mmc read operation
|
||||
"""
|
||||
|
||||
response = u_boot_console.run_command('mmc rescan; mmc dev %s 0' %
|
||||
str(mmc_dev))
|
||||
assert response.find('is current device')
|
||||
|
||||
response = u_boot_console.run_command('mmc read 0x%x 0x100 0x1' % temp_addr)
|
||||
assert response.find('read: OK')
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
|
||||
assert response == ''
|
||||
|
||||
response = u_boot_console.run_command('avb read_part xloader 0 100 0x%x' %
|
||||
temp_addr2)
|
||||
assert response.find('Read 512 bytes')
|
||||
|
||||
# Now lets compare two buffers
|
||||
response = u_boot_console.run_command('cmp 0x%x 0x%x 40' %
|
||||
(temp_addr, temp_addr2))
|
||||
assert response.find('64 word')
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_avb')
|
||||
@pytest.mark.buildconfigspec('optee_ta_avb')
|
||||
def test_avb_persistent_values(u_boot_console):
|
||||
"""Test reading/writing persistent storage to avb
|
||||
"""
|
||||
|
||||
response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
|
||||
assert response == ''
|
||||
|
||||
response = u_boot_console.run_command('avb write_pvalue test value_value')
|
||||
assert response == 'Wrote 12 bytes'
|
||||
|
||||
response = u_boot_console.run_command('avb read_pvalue test 12')
|
||||
assert response == 'Read 12 bytes, value = value_value'
|
||||
179
test/py/tests/test_bind.py
Normal file
179
test/py/tests/test_bind.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
import os.path
|
||||
import pytest
|
||||
import re
|
||||
|
||||
def in_tree(response, name, uclass, drv, depth, last_child):
|
||||
lines = [x.strip() for x in response.splitlines()]
|
||||
leaf = ' ' * 4 * depth;
|
||||
if not last_child:
|
||||
leaf = leaf + r'\|'
|
||||
else:
|
||||
leaf = leaf + '`'
|
||||
leaf = leaf + '-- ' + name
|
||||
line = (r' *{:10.10} [0-9]* \[ [ +] \] {:20.20} {}$'
|
||||
.format(uclass, drv, leaf))
|
||||
prog = re.compile(line)
|
||||
for l in lines:
|
||||
if prog.match(l):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bind')
|
||||
def test_bind_unbind_with_node(u_boot_console):
|
||||
|
||||
#bind /bind-test. Device should come up as well as its children
|
||||
response = u_boot_console.run_command('bind /bind-test generic_simple_bus')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
|
||||
#Unbind child #1. No error expected and all devices should be there except for bind-test-child1
|
||||
response = u_boot_console.run_command('unbind /bind-test/bind-test-child1')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert 'bind-test-child1' not in tree
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
|
||||
#bind child #1. No error expected and all devices should be there
|
||||
response = u_boot_console.run_command('bind /bind-test/bind-test-child1 phy_sandbox')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, False)
|
||||
|
||||
#Unbind child #2. No error expected and all devices should be there except for bind-test-child2
|
||||
response = u_boot_console.run_command('unbind /bind-test/bind-test-child2')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
|
||||
assert 'bind-test-child2' not in tree
|
||||
|
||||
|
||||
#Bind child #2. No error expected and all devices should be there
|
||||
response = u_boot_console.run_command('bind /bind-test/bind-test-child2 generic_simple_bus')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
|
||||
#Unbind parent. No error expected. All devices should be removed and unbound
|
||||
response = u_boot_console.run_command('unbind /bind-test')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert 'bind-test' not in tree
|
||||
assert 'bind-test-child1' not in tree
|
||||
assert 'bind-test-child2' not in tree
|
||||
|
||||
#try binding invalid node with valid driver
|
||||
response = u_boot_console.run_command('bind /not-a-valid-node generic_simple_bus')
|
||||
assert response != ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert 'not-a-valid-node' not in tree
|
||||
|
||||
#try binding valid node with invalid driver
|
||||
response = u_boot_console.run_command('bind /bind-test not_a_driver')
|
||||
assert response != ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert 'bind-test' not in tree
|
||||
|
||||
#bind /bind-test. Device should come up as well as its children
|
||||
response = u_boot_console.run_command('bind /bind-test generic_simple_bus')
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test', 'simple_bus', 'generic_simple_bus', 0, True)
|
||||
assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
|
||||
response = u_boot_console.run_command('unbind /bind-test')
|
||||
assert response == ''
|
||||
|
||||
def get_next_line(tree, name):
|
||||
treelines = [x.strip() for x in tree.splitlines() if x.strip()]
|
||||
child_line = ''
|
||||
for idx, line in enumerate(treelines):
|
||||
if ('-- ' + name) in line:
|
||||
try:
|
||||
child_line = treelines[idx+1]
|
||||
except:
|
||||
pass
|
||||
break
|
||||
return child_line
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bind')
|
||||
def test_bind_unbind_with_uclass(u_boot_console):
|
||||
#bind /bind-test
|
||||
response = u_boot_console.run_command('bind /bind-test generic_simple_bus')
|
||||
assert response == ''
|
||||
|
||||
#make sure bind-test-child2 is there and get its uclass/index pair
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
child2_line = [x.strip() for x in tree.splitlines() if '-- bind-test-child2' in x]
|
||||
assert len(child2_line) == 1
|
||||
|
||||
child2_uclass = child2_line[0].split()[0]
|
||||
child2_index = int(child2_line[0].split()[1])
|
||||
|
||||
#bind generic_simple_bus as a child of bind-test-child2
|
||||
response = u_boot_console.run_command('bind {} {} generic_simple_bus'.format(child2_uclass, child2_index, 'generic_simple_bus'))
|
||||
|
||||
#check that the child is there and its uclass/index pair is right
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
|
||||
child_of_child2_line = get_next_line(tree, 'bind-test-child2')
|
||||
assert child_of_child2_line
|
||||
child_of_child2_index = int(child_of_child2_line.split()[1])
|
||||
assert in_tree(tree, 'generic_simple_bus', 'simple_bus', 'generic_simple_bus', 2, True)
|
||||
assert child_of_child2_index == child2_index + 1
|
||||
|
||||
#unbind the child and check it has been removed
|
||||
response = u_boot_console.run_command('unbind simple_bus {}'.format(child_of_child2_index))
|
||||
assert response == ''
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
assert not in_tree(tree, 'generic_simple_bus', 'simple_bus', 'generic_simple_bus', 2, True)
|
||||
child_of_child2_line = get_next_line(tree, 'bind-test-child2')
|
||||
assert child_of_child2_line == ''
|
||||
|
||||
#bind generic_simple_bus as a child of bind-test-child2
|
||||
response = u_boot_console.run_command('bind {} {} generic_simple_bus'.format(child2_uclass, child2_index, 'generic_simple_bus'))
|
||||
|
||||
#check that the child is there and its uclass/index pair is right
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
treelines = [x.strip() for x in tree.splitlines() if x.strip()]
|
||||
|
||||
child_of_child2_line = get_next_line(tree, 'bind-test-child2')
|
||||
assert child_of_child2_line
|
||||
child_of_child2_index = int(child_of_child2_line.split()[1])
|
||||
assert in_tree(tree, 'generic_simple_bus', 'simple_bus', 'generic_simple_bus', 2, True)
|
||||
assert child_of_child2_index == child2_index + 1
|
||||
|
||||
#unbind the child and check it has been removed
|
||||
response = u_boot_console.run_command('unbind {} {} generic_simple_bus'.format(child2_uclass, child2_index, 'generic_simple_bus'))
|
||||
assert response == ''
|
||||
|
||||
tree = u_boot_console.run_command('dm tree')
|
||||
assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'generic_simple_bus', 1, True)
|
||||
|
||||
child_of_child2_line = get_next_line(tree, 'bind-test-child2')
|
||||
assert child_of_child2_line == ''
|
||||
|
||||
#unbind the child again and check it doesn't change the tree
|
||||
tree_old = u_boot_console.run_command('dm tree')
|
||||
response = u_boot_console.run_command('unbind {} {} generic_simple_bus'.format(child2_uclass, child2_index, 'generic_simple_bus'))
|
||||
tree_new = u_boot_console.run_command('dm tree')
|
||||
|
||||
assert response == ''
|
||||
assert tree_old == tree_new
|
||||
|
||||
response = u_boot_console.run_command('unbind /bind-test')
|
||||
assert response == ''
|
||||
320
test/py/tests/test_dfu.py
Normal file
320
test/py/tests/test_dfu.py
Normal file
@@ -0,0 +1,320 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB
|
||||
# device enumeration on the host, executes dfu-util multiple times to test
|
||||
# various transfer sizes, many of which trigger USB driver edge cases, and
|
||||
# finally aborts the "dfu" command in U-Boot.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on:
|
||||
|
||||
a) boardenv_* to contain configuration values to define which USB ports are
|
||||
available for testing. Without this, this test will be automatically skipped.
|
||||
For example:
|
||||
|
||||
env__usb_dev_ports = (
|
||||
{
|
||||
'fixture_id': 'micro_b',
|
||||
'tgt_usb_ctlr': '0',
|
||||
'host_usb_dev_node': '/dev/usbdev-p2371-2180',
|
||||
# This parameter is optional /if/ you only have a single board
|
||||
# attached to your host at a time.
|
||||
'host_usb_port_path': '3-13',
|
||||
},
|
||||
)
|
||||
|
||||
# Optional entries (required only when 'alt_id_test_file' and
|
||||
# 'alt_id_dummy_file' are specified).
|
||||
test_file_name = '/dfu_test.bin'
|
||||
dummy_file_name = '/dfu_dummy.bin'
|
||||
# Above files are used to generate proper 'alt_info' entry
|
||||
'alt_info': '/%s ext4 0 2;/%s ext4 0 2' % (test_file_name, dummy_file_name),
|
||||
|
||||
env__dfu_configs = (
|
||||
# eMMC, partition 1
|
||||
{
|
||||
'fixture_id': 'emmc',
|
||||
'alt_info': '/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1',
|
||||
'cmd_params': 'mmc 0',
|
||||
# This value is optional.
|
||||
# If present, it specified the set of transfer sizes tested.
|
||||
# If missing, a default list of sizes will be used, which covers
|
||||
# various useful corner cases.
|
||||
# Manually specifying test sizes is useful if you wish to test 4 DFU
|
||||
# configurations, but don't want to test every single transfer size
|
||||
# on each, to avoid bloating the overall time taken by testing.
|
||||
'test_sizes': (63, 64, 65),
|
||||
# This value is optional.
|
||||
# The name of the environment variable that the the dfu command reads
|
||||
# alt info from. If unspecified, this defaults to dfu_alt_info, which is
|
||||
# valid for most systems. Some systems use a different variable name.
|
||||
# One example is the Odroid XU3, which automatically generates
|
||||
# $dfu_alt_info, each time the dfu command is run, by concatenating
|
||||
# $dfu_alt_boot and $dfu_alt_system.
|
||||
'alt_info_env_name': 'dfu_alt_system',
|
||||
# This value is optional.
|
||||
# For boards which require the 'test file' alt setting number other than
|
||||
# default (0) it is possible to specify exact file name to be used as
|
||||
# this parameter.
|
||||
'alt_id_test_file': test_file_name,
|
||||
# This value is optional.
|
||||
# For boards which require the 'dummy file' alt setting number other
|
||||
# than default (1) it is possible to specify exact file name to be used
|
||||
# as this parameter.
|
||||
'alt_id_dummy_file': dummy_file_name,
|
||||
},
|
||||
)
|
||||
|
||||
b) udev rules to set permissions on devices nodes, so that sudo is not
|
||||
required. For example:
|
||||
|
||||
ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
|
||||
|
||||
(You may wish to change the group ID instead of setting the permissions wide
|
||||
open. All that matters is that the user ID running the test can access the
|
||||
device.)
|
||||
|
||||
c) An optional udev rule to give you a persistent value to use in
|
||||
host_usb_dev_node. For example:
|
||||
|
||||
IMPORT{builtin}="path_id"
|
||||
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="bus/usb/by-path/$env{ID_PATH}"
|
||||
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="bus/usb/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
|
||||
"""
|
||||
|
||||
# The set of file sizes to test. These values trigger various edge-cases such
|
||||
# as one less than, equal to, and one greater than typical USB max packet
|
||||
# sizes, and similar boundary conditions.
|
||||
test_sizes_default = (
|
||||
64 - 1,
|
||||
64,
|
||||
64 + 1,
|
||||
128 - 1,
|
||||
128,
|
||||
128 + 1,
|
||||
960 - 1,
|
||||
960,
|
||||
960 + 1,
|
||||
4096 - 1,
|
||||
4096,
|
||||
4096 + 1,
|
||||
1024 * 1024 - 1,
|
||||
1024 * 1024,
|
||||
8 * 1024 * 1024,
|
||||
)
|
||||
|
||||
first_usb_dev_port = None
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_dfu')
|
||||
@pytest.mark.requiredtool('dfu-util')
|
||||
def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
|
||||
"""Test the "dfu" command; the host system must be able to enumerate a USB
|
||||
device when "dfu" is running, various DFU transfers are tested, and the
|
||||
USB device must disappear when "dfu" is aborted.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__usb_dev_port: The single USB device-mode port specification on
|
||||
which to run the test. See the file-level comment above for
|
||||
details of the format.
|
||||
env__dfu_config: The single DFU (memory region) configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
def start_dfu():
|
||||
"""Start U-Boot's dfu shell command.
|
||||
|
||||
This also waits for the host-side USB enumeration process to complete.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
u_boot_utils.wait_until_file_open_fails(
|
||||
env__usb_dev_port['host_usb_dev_node'], True)
|
||||
fh = u_boot_utils.attempt_to_open_file(
|
||||
env__usb_dev_port['host_usb_dev_node'])
|
||||
if fh:
|
||||
fh.close()
|
||||
raise Exception('USB device present before dfu command invoked')
|
||||
|
||||
u_boot_console.log.action(
|
||||
'Starting long-running U-Boot dfu shell command')
|
||||
|
||||
dfu_alt_info_env = env__dfu_config.get('alt_info_env_name', \
|
||||
'dfu_alt_info')
|
||||
|
||||
cmd = 'setenv "%s" "%s"' % (dfu_alt_info_env,
|
||||
env__dfu_config['alt_info'])
|
||||
u_boot_console.run_command(cmd)
|
||||
|
||||
cmd = 'dfu 0 ' + env__dfu_config['cmd_params']
|
||||
u_boot_console.run_command(cmd, wait_for_prompt=False)
|
||||
u_boot_console.log.action('Waiting for DFU USB device to appear')
|
||||
fh = u_boot_utils.wait_until_open_succeeds(
|
||||
env__usb_dev_port['host_usb_dev_node'])
|
||||
fh.close()
|
||||
|
||||
def stop_dfu(ignore_errors):
|
||||
"""Stop U-Boot's dfu shell command from executing.
|
||||
|
||||
This also waits for the host-side USB de-enumeration process to
|
||||
complete.
|
||||
|
||||
Args:
|
||||
ignore_errors: Ignore any errors. This is useful if an error has
|
||||
already been detected, and the code is performing best-effort
|
||||
cleanup. In this case, we do not want to mask the original
|
||||
error by "honoring" any new errors.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
try:
|
||||
u_boot_console.log.action(
|
||||
'Stopping long-running U-Boot dfu shell command')
|
||||
u_boot_console.ctrlc()
|
||||
u_boot_console.log.action(
|
||||
'Waiting for DFU USB device to disappear')
|
||||
u_boot_utils.wait_until_file_open_fails(
|
||||
env__usb_dev_port['host_usb_dev_node'], ignore_errors)
|
||||
except:
|
||||
if not ignore_errors:
|
||||
raise
|
||||
|
||||
def run_dfu_util(alt_setting, fn, up_dn_load_arg):
|
||||
"""Invoke dfu-util on the host.
|
||||
|
||||
Args:
|
||||
alt_setting: The DFU "alternate setting" identifier to interact
|
||||
with.
|
||||
fn: The host-side file name to transfer.
|
||||
up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or
|
||||
download operation should be performed.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
cmd = ['dfu-util', '-a', alt_setting, up_dn_load_arg, fn]
|
||||
if 'host_usb_port_path' in env__usb_dev_port:
|
||||
cmd += ['-p', env__usb_dev_port['host_usb_port_path']]
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
u_boot_console.wait_for('Ctrl+C to exit ...')
|
||||
|
||||
def dfu_write(alt_setting, fn):
|
||||
"""Write a file to the target board using DFU.
|
||||
|
||||
Args:
|
||||
alt_setting: The DFU "alternate setting" identifier to interact
|
||||
with.
|
||||
fn: The host-side file name to transfer.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
run_dfu_util(alt_setting, fn, '-D')
|
||||
|
||||
def dfu_read(alt_setting, fn):
|
||||
"""Read a file from the target board using DFU.
|
||||
|
||||
Args:
|
||||
alt_setting: The DFU "alternate setting" identifier to interact
|
||||
with.
|
||||
fn: The host-side file name to transfer.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
# dfu-util fails reads/uploads if the host file already exists
|
||||
if os.path.exists(fn):
|
||||
os.remove(fn)
|
||||
run_dfu_util(alt_setting, fn, '-U')
|
||||
|
||||
def dfu_write_read_check(size):
|
||||
"""Test DFU transfers of a specific size of data
|
||||
|
||||
This function first writes data to the board then reads it back and
|
||||
compares the written and read back data. Measures are taken to avoid
|
||||
certain types of false positives.
|
||||
|
||||
Args:
|
||||
size: The data size to test.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
test_f = u_boot_utils.PersistentRandomFile(u_boot_console,
|
||||
'dfu_%d.bin' % size, size)
|
||||
readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin'
|
||||
|
||||
u_boot_console.log.action('Writing test data to DFU primary ' +
|
||||
'altsetting')
|
||||
dfu_write(alt_setting_test_file, test_f.abs_fn)
|
||||
|
||||
u_boot_console.log.action('Writing dummy data to DFU secondary ' +
|
||||
'altsetting to clear DFU buffers')
|
||||
dfu_write(alt_setting_dummy_file, dummy_f.abs_fn)
|
||||
|
||||
u_boot_console.log.action('Reading DFU primary altsetting for ' +
|
||||
'comparison')
|
||||
dfu_read(alt_setting_test_file, readback_fn)
|
||||
|
||||
u_boot_console.log.action('Comparing written and read data')
|
||||
written_hash = test_f.content_hash
|
||||
read_back_hash = u_boot_utils.md5sum_file(readback_fn, size)
|
||||
assert(written_hash == read_back_hash)
|
||||
|
||||
# This test may be executed against multiple USB ports. The test takes a
|
||||
# long time, so we don't want to do the whole thing each time. Instead,
|
||||
# execute the full test on the first USB port, and perform a very limited
|
||||
# test on other ports. In the limited case, we solely validate that the
|
||||
# host PC can enumerate the U-Boot USB device.
|
||||
global first_usb_dev_port
|
||||
if not first_usb_dev_port:
|
||||
first_usb_dev_port = env__usb_dev_port
|
||||
if env__usb_dev_port == first_usb_dev_port:
|
||||
sizes = env__dfu_config.get('test_sizes', test_sizes_default)
|
||||
else:
|
||||
sizes = []
|
||||
|
||||
dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
|
||||
'dfu_dummy.bin', 1024)
|
||||
|
||||
alt_setting_test_file = env__dfu_config.get('alt_id_test_file', '0')
|
||||
alt_setting_dummy_file = env__dfu_config.get('alt_id_dummy_file', '1')
|
||||
|
||||
ignore_cleanup_errors = True
|
||||
try:
|
||||
start_dfu()
|
||||
|
||||
u_boot_console.log.action(
|
||||
'Overwriting DFU primary altsetting with dummy data')
|
||||
dfu_write(alt_setting_test_file, dummy_f.abs_fn)
|
||||
|
||||
for size in sizes:
|
||||
with u_boot_console.log.section('Data size %d' % size):
|
||||
dfu_write_read_check(size)
|
||||
# Make the status of each sub-test obvious. If the test didn't
|
||||
# pass, an exception was thrown so this code isn't executed.
|
||||
u_boot_console.log.status_pass('OK')
|
||||
ignore_cleanup_errors = False
|
||||
finally:
|
||||
stop_dfu(ignore_cleanup_errors)
|
||||
196
test/py/tests/test_efi_loader.py
Normal file
196
test/py/tests/test_efi_loader.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
# Copyright (c) 2016, Alexander Graf <agraf@suse.de>
|
||||
#
|
||||
# based on test_net.py.
|
||||
|
||||
# Test efi loader implementation
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on boardenv_* containing configuration values to define
|
||||
which network environment is available for testing. Without this, the parts
|
||||
that rely on network will be automatically skipped.
|
||||
|
||||
For example:
|
||||
|
||||
# Boolean indicating whether the Ethernet device is attached to USB, and hence
|
||||
# USB enumeration needs to be performed prior to network tests.
|
||||
# This variable may be omitted if its value is False.
|
||||
env__net_uses_usb = False
|
||||
|
||||
# Boolean indicating whether the Ethernet device is attached to PCI, and hence
|
||||
# PCI enumeration needs to be performed prior to network tests.
|
||||
# This variable may be omitted if its value is False.
|
||||
env__net_uses_pci = True
|
||||
|
||||
# True if a DHCP server is attached to the network, and should be tested.
|
||||
# If DHCP testing is not possible or desired, this variable may be omitted or
|
||||
# set to False.
|
||||
env__net_dhcp_server = True
|
||||
|
||||
# A list of environment variables that should be set in order to configure a
|
||||
# static IP. If solely relying on DHCP, this variable may be omitted or set to
|
||||
# an empty list.
|
||||
env__net_static_env_vars = [
|
||||
('ipaddr', '10.0.0.100'),
|
||||
('netmask', '255.255.255.0'),
|
||||
('serverip', '10.0.0.1'),
|
||||
]
|
||||
|
||||
# Details regarding a file that may be read from a TFTP server. This variable
|
||||
# may be omitted or set to None if TFTP testing is not possible or desired.
|
||||
env__efi_loader_helloworld_file = {
|
||||
'fn': 'lib/efi_loader/helloworld.efi',
|
||||
'size': 5058624,
|
||||
'crc32': 'c2244b26',
|
||||
}
|
||||
"""
|
||||
|
||||
net_set_up = False
|
||||
|
||||
def test_efi_pre_commands(u_boot_console):
|
||||
"""Execute any commands required to enable network hardware.
|
||||
|
||||
These commands are provided by the boardenv_* file; see the comment at the
|
||||
beginning of this file.
|
||||
"""
|
||||
|
||||
init_usb = u_boot_console.config.env.get('env__net_uses_usb', False)
|
||||
if init_usb:
|
||||
u_boot_console.run_command('usb start')
|
||||
|
||||
init_pci = u_boot_console.config.env.get('env__net_uses_pci', False)
|
||||
if init_pci:
|
||||
u_boot_console.run_command('pci enum')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_dhcp')
|
||||
def test_efi_dhcp(u_boot_console):
|
||||
"""Test the dhcp command.
|
||||
|
||||
The boardenv_* file may be used to enable/disable this test; see the
|
||||
comment at the beginning of this file.
|
||||
"""
|
||||
|
||||
test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False)
|
||||
if not test_dhcp:
|
||||
pytest.skip('No DHCP server available')
|
||||
|
||||
u_boot_console.run_command('setenv autoload no')
|
||||
output = u_boot_console.run_command('dhcp')
|
||||
assert 'DHCP client bound to address ' in output
|
||||
|
||||
global net_set_up
|
||||
net_set_up = True
|
||||
|
||||
@pytest.mark.buildconfigspec('net')
|
||||
def test_efi_setup_static(u_boot_console):
|
||||
"""Set up a static IP configuration.
|
||||
|
||||
The configuration is provided by the boardenv_* file; see the comment at
|
||||
the beginning of this file.
|
||||
"""
|
||||
|
||||
env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None)
|
||||
if not env_vars:
|
||||
pytest.skip('No static network configuration is defined')
|
||||
|
||||
for (var, val) in env_vars:
|
||||
u_boot_console.run_command('setenv %s %s' % (var, val))
|
||||
|
||||
global net_set_up
|
||||
net_set_up = True
|
||||
|
||||
def fetch_tftp_file(u_boot_console, env_conf):
|
||||
"""Grab an env described file via TFTP and return its address
|
||||
|
||||
A file as described by an env config <env_conf> is downloaded from the TFTP
|
||||
server. The address to that file is returned.
|
||||
"""
|
||||
if not net_set_up:
|
||||
pytest.skip('Network not initialized')
|
||||
|
||||
f = u_boot_console.config.env.get(env_conf, None)
|
||||
if not f:
|
||||
pytest.skip('No %s binary specified in environment' % env_conf)
|
||||
|
||||
addr = f.get('addr', None)
|
||||
if not addr:
|
||||
addr = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
fn = f['fn']
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
|
||||
expected_text = 'Bytes transferred = '
|
||||
sz = f.get('size', None)
|
||||
if sz:
|
||||
expected_text += '%d' % sz
|
||||
assert expected_text in output
|
||||
|
||||
expected_crc = f.get('crc32', None)
|
||||
if not expected_crc:
|
||||
return addr
|
||||
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
|
||||
return addr
|
||||
|
||||
output = u_boot_console.run_command('crc32 %x $filesize' % addr)
|
||||
assert expected_crc in output
|
||||
|
||||
return addr
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
|
||||
def test_efi_helloworld_net(u_boot_console):
|
||||
"""Run the helloworld.efi binary via TFTP.
|
||||
|
||||
The helloworld.efi file is downloaded from the TFTP server and gets
|
||||
executed.
|
||||
"""
|
||||
|
||||
addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_helloworld_file')
|
||||
|
||||
output = u_boot_console.run_command('bootefi %x' % addr)
|
||||
expected_text = 'Hello, world'
|
||||
assert expected_text in output
|
||||
expected_text = '## Application terminated, r = 0'
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_hello')
|
||||
def test_efi_helloworld_builtin(u_boot_console):
|
||||
"""Run the builtin helloworld.efi binary.
|
||||
|
||||
The helloworld.efi file is included in U-Boot, execute it using the
|
||||
special "bootefi hello" command.
|
||||
"""
|
||||
|
||||
output = u_boot_console.run_command('bootefi hello')
|
||||
expected_text = 'Hello, world'
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi')
|
||||
def test_efi_grub_net(u_boot_console):
|
||||
"""Run the grub.efi binary via TFTP.
|
||||
|
||||
The grub.efi file is downloaded from the TFTP server and gets
|
||||
executed.
|
||||
"""
|
||||
|
||||
addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_grub_file')
|
||||
|
||||
u_boot_console.run_command('bootefi %x' % addr, wait_for_prompt=False)
|
||||
|
||||
# Verify that we have an SMBIOS table
|
||||
check_smbios = u_boot_console.config.env.get('env__efi_loader_check_smbios', False)
|
||||
if check_smbios:
|
||||
u_boot_console.wait_for('grub>')
|
||||
output = u_boot_console.run_command('lsefisystab', wait_for_prompt=False, wait_for_echo=False)
|
||||
u_boot_console.wait_for('SMBIOS')
|
||||
|
||||
# Then exit cleanly
|
||||
u_boot_console.wait_for('grub>')
|
||||
output = u_boot_console.run_command('exit', wait_for_prompt=False, wait_for_echo=False)
|
||||
u_boot_console.wait_for('r = 0')
|
||||
|
||||
# And give us our U-Boot prompt back
|
||||
u_boot_console.run_command('')
|
||||
198
test/py/tests/test_efi_selftest.py
Normal file
198
test/py/tests/test_efi_selftest.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2017, Heinrich Schuchardt <xypron.glpk@gmx.de>
|
||||
|
||||
# Test efi API implementation
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
|
||||
def test_efi_selftest(u_boot_console):
|
||||
"""Test the UEFI implementation
|
||||
|
||||
:param u_boot_console: U-Boot console
|
||||
|
||||
This function executes all selftests that are not marked as on request.
|
||||
"""
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest')
|
||||
u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
|
||||
if m != 0:
|
||||
raise Exception('Failures occurred during the EFI selftest')
|
||||
u_boot_console.restart_uboot();
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
|
||||
@pytest.mark.buildconfigspec('of_control')
|
||||
@pytest.mark.notbuildconfigspec('generate_acpi_table')
|
||||
def test_efi_selftest_device_tree(u_boot_console):
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest list')
|
||||
output = u_boot_console.run_command('bootefi selftest')
|
||||
assert '\'device tree\'' in output
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest device tree')
|
||||
u_boot_console.run_command(cmd='setenv -f serial# Testing DT')
|
||||
u_boot_console.run_command(cmd='bootefi selftest ${fdtcontroladdr}', wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(['serial-number: Testing DT', 'U-Boot'])
|
||||
if m != 0:
|
||||
raise Exception('serial-number missing in device tree')
|
||||
u_boot_console.restart_uboot();
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
|
||||
def test_efi_selftest_watchdog_reboot(u_boot_console):
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest list')
|
||||
output = u_boot_console.run_command('bootefi selftest')
|
||||
assert '\'watchdog reboot\'' in output
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest watchdog reboot')
|
||||
u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(['resetting', 'U-Boot'])
|
||||
if m != 0:
|
||||
raise Exception('Reset failed in \'watchdog reboot\' test')
|
||||
u_boot_console.restart_uboot();
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
|
||||
def test_efi_selftest_text_input(u_boot_console):
|
||||
"""Test the EFI_SIMPLE_TEXT_INPUT_PROTOCOL
|
||||
|
||||
:param u_boot_console: U-Boot console
|
||||
|
||||
This function calls the text input EFI selftest.
|
||||
"""
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest text input')
|
||||
output = u_boot_console.run_command(cmd='bootefi selftest',
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect([r'To terminate type \'x\''])
|
||||
if m != 0:
|
||||
raise Exception('No prompt for \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
u_boot_console.p.timeout = 500
|
||||
# EOT
|
||||
u_boot_console.run_command(cmd=chr(4), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 4 \(unknown\), scan code 0 \(Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('EOT failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# BS
|
||||
u_boot_console.run_command(cmd=chr(8), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 8 \(BS\), scan code 0 \(Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('BS failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# TAB
|
||||
u_boot_console.run_command(cmd=chr(9), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 9 \(TAB\), scan code 0 \(Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('BS failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# a
|
||||
u_boot_console.run_command(cmd='a', wait_for_echo=False, send_nl=False,
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('\'a\' failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# UP escape sequence
|
||||
u_boot_console.run_command(cmd=chr(27) + '[A', wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 0 \(Null\), scan code 1 \(Up\)'])
|
||||
if m != 0:
|
||||
raise Exception('UP failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# Euro sign
|
||||
u_boot_console.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect([r'Unicode char 8364 \(\''])
|
||||
if m != 0:
|
||||
raise Exception('Euro sign failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
u_boot_console.run_command(cmd='x', wait_for_echo=False, send_nl=False,
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
|
||||
if m != 0:
|
||||
raise Exception('Failures occurred during the EFI selftest')
|
||||
u_boot_console.restart_uboot();
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
|
||||
def test_efi_selftest_text_input_ex(u_boot_console):
|
||||
"""Test the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
|
||||
|
||||
:param u_boot_console: U-Boot console
|
||||
|
||||
This function calls the extended text input EFI selftest.
|
||||
"""
|
||||
u_boot_console.run_command(cmd='setenv efi_selftest extended text input')
|
||||
output = u_boot_console.run_command(cmd='bootefi selftest',
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect([r'To terminate type \'CTRL\+x\''])
|
||||
if m != 0:
|
||||
raise Exception('No prompt for \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
u_boot_console.p.timeout = 500
|
||||
# EOT
|
||||
u_boot_console.run_command(cmd=chr(4), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 100 \(\'d\'\), scan code 0 \(CTRL\+Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('EOT failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# BS
|
||||
u_boot_console.run_command(cmd=chr(8), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 8 \(BS\), scan code 0 \(\+Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('BS failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# TAB
|
||||
u_boot_console.run_command(cmd=chr(9), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 9 \(TAB\), scan code 0 \(\+Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('TAB failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# a
|
||||
u_boot_console.run_command(cmd='a', wait_for_echo=False, send_nl=False,
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)'])
|
||||
if m != 0:
|
||||
raise Exception('\'a\' failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# UP escape sequence
|
||||
u_boot_console.run_command(cmd=chr(27) + '[A', wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 0 \(Null\), scan code 1 \(\+Up\)'])
|
||||
if m != 0:
|
||||
raise Exception('UP failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# Euro sign
|
||||
u_boot_console.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False,
|
||||
send_nl=False, wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect([r'Unicode char 8364 \(\''])
|
||||
if m != 0:
|
||||
raise Exception('Euro sign failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
# SHIFT+ALT+FN 5
|
||||
u_boot_console.run_command(cmd=b'\x1b\x5b\x31\x35\x3b\x34\x7e'.decode(),
|
||||
wait_for_echo=False, send_nl=False,
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(
|
||||
[r'Unicode char 0 \(Null\), scan code 15 \(SHIFT\+ALT\+FN 5\)'])
|
||||
if m != 0:
|
||||
raise Exception('SHIFT+ALT+FN 5 failed in \'text input\' test')
|
||||
u_boot_console.drain_console()
|
||||
u_boot_console.run_command(cmd=chr(24), wait_for_echo=False, send_nl=False,
|
||||
wait_for_prompt=False)
|
||||
m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
|
||||
if m != 0:
|
||||
raise Exception('Failures occurred during the EFI selftest')
|
||||
u_boot_console.restart_uboot();
|
||||
338
test/py/tests/test_env.py
Normal file
338
test/py/tests/test_env.py
Normal file
@@ -0,0 +1,338 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test operation of shell commands relating to environment variables.
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
# FIXME: This might be useful for other tests;
|
||||
# perhaps refactor it into ConsoleBase or some other state object?
|
||||
class StateTestEnv(object):
|
||||
"""Container that represents the state of all U-Boot environment variables.
|
||||
This enables quick determination of existant/non-existant variable
|
||||
names.
|
||||
"""
|
||||
|
||||
def __init__(self, u_boot_console):
|
||||
"""Initialize a new StateTestEnv object.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.u_boot_console = u_boot_console
|
||||
self.get_env()
|
||||
self.set_var = self.get_non_existent_var()
|
||||
|
||||
def get_env(self):
|
||||
"""Read all current environment variables from U-Boot.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if self.u_boot_console.config.buildconfig.get(
|
||||
'config_version_variable', 'n') == 'y':
|
||||
with self.u_boot_console.disable_check('main_signon'):
|
||||
response = self.u_boot_console.run_command('printenv')
|
||||
else:
|
||||
response = self.u_boot_console.run_command('printenv')
|
||||
self.env = {}
|
||||
for l in response.splitlines():
|
||||
if not '=' in l:
|
||||
continue
|
||||
(var, value) = l.split('=', 1)
|
||||
self.env[var] = value
|
||||
|
||||
def get_existent_var(self):
|
||||
"""Return the name of an environment variable that exists.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
The name of an environment variable.
|
||||
"""
|
||||
|
||||
for var in self.env:
|
||||
return var
|
||||
|
||||
def get_non_existent_var(self):
|
||||
"""Return the name of an environment variable that does not exist.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
The name of an environment variable.
|
||||
"""
|
||||
|
||||
n = 0
|
||||
while True:
|
||||
var = 'test_env_' + str(n)
|
||||
if var not in self.env:
|
||||
return var
|
||||
n += 1
|
||||
|
||||
ste = None
|
||||
@pytest.fixture(scope='function')
|
||||
def state_test_env(u_boot_console):
|
||||
"""pytest fixture to provide a StateTestEnv object to tests."""
|
||||
|
||||
global ste
|
||||
if not ste:
|
||||
ste = StateTestEnv(u_boot_console)
|
||||
return ste
|
||||
|
||||
def unset_var(state_test_env, var):
|
||||
"""Unset an environment variable.
|
||||
|
||||
This both executes a U-Boot shell command and updates a StateTestEnv
|
||||
object.
|
||||
|
||||
Args:
|
||||
state_test_env: The StateTestEnv object to update.
|
||||
var: The variable name to unset.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
state_test_env.u_boot_console.run_command('setenv %s' % var)
|
||||
if var in state_test_env.env:
|
||||
del state_test_env.env[var]
|
||||
|
||||
def set_var(state_test_env, var, value):
|
||||
"""Set an environment variable.
|
||||
|
||||
This both executes a U-Boot shell command and updates a StateTestEnv
|
||||
object.
|
||||
|
||||
Args:
|
||||
state_test_env: The StateTestEnv object to update.
|
||||
var: The variable name to set.
|
||||
value: The value to set the variable to.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
bc = state_test_env.u_boot_console.config.buildconfig
|
||||
if bc.get('config_hush_parser', None):
|
||||
quote = '"'
|
||||
else:
|
||||
quote = ''
|
||||
if ' ' in value:
|
||||
pytest.skip('Space in variable value on non-Hush shell')
|
||||
|
||||
state_test_env.u_boot_console.run_command(
|
||||
'setenv %s %s%s%s' % (var, quote, value, quote))
|
||||
state_test_env.env[var] = value
|
||||
|
||||
def validate_empty(state_test_env, var):
|
||||
"""Validate that a variable is not set, using U-Boot shell commands.
|
||||
|
||||
Args:
|
||||
var: The variable name to test.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
response = state_test_env.u_boot_console.run_command('echo $%s' % var)
|
||||
assert response == ''
|
||||
|
||||
def validate_set(state_test_env, var, value):
|
||||
"""Validate that a variable is set, using U-Boot shell commands.
|
||||
|
||||
Args:
|
||||
var: The variable name to test.
|
||||
value: The value the variable is expected to have.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
# echo does not preserve leading, internal, or trailing whitespace in the
|
||||
# value. printenv does, and hence allows more complete testing.
|
||||
response = state_test_env.u_boot_console.run_command('printenv %s' % var)
|
||||
assert response == ('%s=%s' % (var, value))
|
||||
|
||||
def test_env_echo_exists(state_test_env):
|
||||
"""Test echoing a variable that exists."""
|
||||
|
||||
var = state_test_env.get_existent_var()
|
||||
value = state_test_env.env[var]
|
||||
validate_set(state_test_env, var, value)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_env_echo_non_existent(state_test_env):
|
||||
"""Test echoing a variable that doesn't exist."""
|
||||
|
||||
var = state_test_env.set_var
|
||||
validate_empty(state_test_env, var)
|
||||
|
||||
def test_env_printenv_non_existent(state_test_env):
|
||||
"""Test printenv error message for non-existant variables."""
|
||||
|
||||
var = state_test_env.set_var
|
||||
c = state_test_env.u_boot_console
|
||||
with c.disable_check('error_notification'):
|
||||
response = c.run_command('printenv %s' % var)
|
||||
assert(response == '## Error: "%s" not defined' % var)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_env_unset_non_existent(state_test_env):
|
||||
"""Test unsetting a nonexistent variable."""
|
||||
|
||||
var = state_test_env.get_non_existent_var()
|
||||
unset_var(state_test_env, var)
|
||||
validate_empty(state_test_env, var)
|
||||
|
||||
def test_env_set_non_existent(state_test_env):
|
||||
"""Test set a non-existant variable."""
|
||||
|
||||
var = state_test_env.set_var
|
||||
value = 'foo'
|
||||
set_var(state_test_env, var, value)
|
||||
validate_set(state_test_env, var, value)
|
||||
|
||||
def test_env_set_existing(state_test_env):
|
||||
"""Test setting an existant variable."""
|
||||
|
||||
var = state_test_env.set_var
|
||||
value = 'bar'
|
||||
set_var(state_test_env, var, value)
|
||||
validate_set(state_test_env, var, value)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_env_unset_existing(state_test_env):
|
||||
"""Test unsetting a variable."""
|
||||
|
||||
var = state_test_env.set_var
|
||||
unset_var(state_test_env, var)
|
||||
validate_empty(state_test_env, var)
|
||||
|
||||
def test_env_expansion_spaces(state_test_env):
|
||||
"""Test expanding a variable that contains a space in its value."""
|
||||
|
||||
var_space = None
|
||||
var_test = None
|
||||
try:
|
||||
var_space = state_test_env.get_non_existent_var()
|
||||
set_var(state_test_env, var_space, ' ')
|
||||
|
||||
var_test = state_test_env.get_non_existent_var()
|
||||
value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals()
|
||||
set_var(state_test_env, var_test, value)
|
||||
value = ' 1 2 '
|
||||
validate_set(state_test_env, var_test, value)
|
||||
finally:
|
||||
if var_space:
|
||||
unset_var(state_test_env, var_space)
|
||||
if var_test:
|
||||
unset_var(state_test_env, var_test)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_importenv')
|
||||
def test_env_import_checksum_no_size(state_test_env):
|
||||
"""Test that omitted ('-') size parameter with checksum validation fails the
|
||||
env import function.
|
||||
"""
|
||||
c = state_test_env.u_boot_console
|
||||
ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
|
||||
addr = '%08x' % ram_base
|
||||
|
||||
with c.disable_check('error_notification'):
|
||||
response = c.run_command('env import -c %s -' % addr)
|
||||
assert(response == '## Error: external checksum format must pass size')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_importenv')
|
||||
def test_env_import_whitelist_checksum_no_size(state_test_env):
|
||||
"""Test that omitted ('-') size parameter with checksum validation fails the
|
||||
env import function when variables are passed as parameters.
|
||||
"""
|
||||
c = state_test_env.u_boot_console
|
||||
ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
|
||||
addr = '%08x' % ram_base
|
||||
|
||||
with c.disable_check('error_notification'):
|
||||
response = c.run_command('env import -c %s - foo1 foo2 foo4' % addr)
|
||||
assert(response == '## Error: external checksum format must pass size')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_exportenv')
|
||||
@pytest.mark.buildconfigspec('cmd_importenv')
|
||||
def test_env_import_whitelist(state_test_env):
|
||||
"""Test importing only a handful of env variables from an environment."""
|
||||
c = state_test_env.u_boot_console
|
||||
ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
|
||||
addr = '%08x' % ram_base
|
||||
|
||||
set_var(state_test_env, 'foo1', 'bar1')
|
||||
set_var(state_test_env, 'foo2', 'bar2')
|
||||
set_var(state_test_env, 'foo3', 'bar3')
|
||||
|
||||
c.run_command('env export %s' % addr)
|
||||
|
||||
unset_var(state_test_env, 'foo1')
|
||||
set_var(state_test_env, 'foo2', 'test2')
|
||||
set_var(state_test_env, 'foo4', 'bar4')
|
||||
|
||||
# no foo1 in current env, foo2 overridden, foo3 should be of the value
|
||||
# before exporting and foo4 should be of the value before importing.
|
||||
c.run_command('env import %s - foo1 foo2 foo4' % addr)
|
||||
|
||||
validate_set(state_test_env, 'foo1', 'bar1')
|
||||
validate_set(state_test_env, 'foo2', 'bar2')
|
||||
validate_set(state_test_env, 'foo3', 'bar3')
|
||||
validate_set(state_test_env, 'foo4', 'bar4')
|
||||
|
||||
# Cleanup test environment
|
||||
unset_var(state_test_env, 'foo1')
|
||||
unset_var(state_test_env, 'foo2')
|
||||
unset_var(state_test_env, 'foo3')
|
||||
unset_var(state_test_env, 'foo4')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_exportenv')
|
||||
@pytest.mark.buildconfigspec('cmd_importenv')
|
||||
def test_env_import_whitelist_delete(state_test_env):
|
||||
|
||||
"""Test importing only a handful of env variables from an environment, with.
|
||||
deletion if a var A that is passed to env import is not in the
|
||||
environment to be imported.
|
||||
"""
|
||||
c = state_test_env.u_boot_console
|
||||
ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
|
||||
addr = '%08x' % ram_base
|
||||
|
||||
set_var(state_test_env, 'foo1', 'bar1')
|
||||
set_var(state_test_env, 'foo2', 'bar2')
|
||||
set_var(state_test_env, 'foo3', 'bar3')
|
||||
|
||||
c.run_command('env export %s' % addr)
|
||||
|
||||
unset_var(state_test_env, 'foo1')
|
||||
set_var(state_test_env, 'foo2', 'test2')
|
||||
set_var(state_test_env, 'foo4', 'bar4')
|
||||
|
||||
# no foo1 in current env, foo2 overridden, foo3 should be of the value
|
||||
# before exporting and foo4 should be empty.
|
||||
c.run_command('env import -d %s - foo1 foo2 foo4' % addr)
|
||||
|
||||
validate_set(state_test_env, 'foo1', 'bar1')
|
||||
validate_set(state_test_env, 'foo2', 'bar2')
|
||||
validate_set(state_test_env, 'foo3', 'bar3')
|
||||
validate_empty(state_test_env, 'foo4')
|
||||
|
||||
# Cleanup test environment
|
||||
unset_var(state_test_env, 'foo1')
|
||||
unset_var(state_test_env, 'foo2')
|
||||
unset_var(state_test_env, 'foo3')
|
||||
unset_var(state_test_env, 'foo4')
|
||||
457
test/py/tests/test_fit.py
Executable file
457
test/py/tests/test_fit.py
Executable file
@@ -0,0 +1,457 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2013, Google Inc.
|
||||
#
|
||||
# Sanity check of the FIT handling in U-Boot
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import struct
|
||||
import u_boot_utils as util
|
||||
|
||||
# Define a base ITS which we can adjust using % and a dictionary
|
||||
base_its = '''
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel@1 {
|
||||
data = /incbin/("%(kernel)s");
|
||||
type = "kernel";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "%(compression)s";
|
||||
load = <0x40000>;
|
||||
entry = <0x8>;
|
||||
};
|
||||
kernel@2 {
|
||||
data = /incbin/("%(loadables1)s");
|
||||
type = "kernel";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
%(loadables1_load)s
|
||||
entry = <0x0>;
|
||||
};
|
||||
fdt@1 {
|
||||
description = "snow";
|
||||
data = /incbin/("%(fdt)s");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
%(fdt_load)s
|
||||
compression = "%(compression)s";
|
||||
signature@1 {
|
||||
algo = "sha1,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
ramdisk@1 {
|
||||
description = "snow";
|
||||
data = /incbin/("%(ramdisk)s");
|
||||
type = "ramdisk";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
%(ramdisk_load)s
|
||||
compression = "%(compression)s";
|
||||
};
|
||||
ramdisk@2 {
|
||||
description = "snow";
|
||||
data = /incbin/("%(loadables2)s");
|
||||
type = "ramdisk";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
%(loadables2_load)s
|
||||
compression = "none";
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf@1";
|
||||
conf@1 {
|
||||
kernel = "kernel@1";
|
||||
fdt = "fdt@1";
|
||||
%(ramdisk_config)s
|
||||
%(loadables_config)s
|
||||
};
|
||||
};
|
||||
};
|
||||
'''
|
||||
|
||||
# Define a base FDT - currently we don't use anything in this
|
||||
base_fdt = '''
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
model = "Sandbox Verified Boot Test";
|
||||
compatible = "sandbox";
|
||||
|
||||
reset@0 {
|
||||
compatible = "sandbox,reset";
|
||||
};
|
||||
|
||||
};
|
||||
'''
|
||||
|
||||
# This is the U-Boot script that is run for each test. First load the FIT,
|
||||
# then run the 'bootm' command, then save out memory from the places where
|
||||
# we expect 'bootm' to write things. Then quit.
|
||||
base_script = '''
|
||||
host load hostfs 0 %(fit_addr)x %(fit)s
|
||||
fdt addr %(fit_addr)x
|
||||
bootm start %(fit_addr)x
|
||||
bootm loados
|
||||
host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
|
||||
host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
|
||||
host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
|
||||
host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
|
||||
host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
|
||||
'''
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('fit_signature')
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
def test_fit(u_boot_console):
|
||||
def make_fname(leaf):
|
||||
"""Make a temporary filename
|
||||
|
||||
Args:
|
||||
leaf: Leaf name of file to create (within temporary directory)
|
||||
Return:
|
||||
Temporary filename
|
||||
"""
|
||||
|
||||
return os.path.join(cons.config.build_dir, leaf)
|
||||
|
||||
def filesize(fname):
|
||||
"""Get the size of a file
|
||||
|
||||
Args:
|
||||
fname: Filename to check
|
||||
Return:
|
||||
Size of file in bytes
|
||||
"""
|
||||
return os.stat(fname).st_size
|
||||
|
||||
def read_file(fname):
|
||||
"""Read the contents of a file
|
||||
|
||||
Args:
|
||||
fname: Filename to read
|
||||
Returns:
|
||||
Contents of file as a string
|
||||
"""
|
||||
with open(fname, 'rb') as fd:
|
||||
return fd.read()
|
||||
|
||||
def make_dtb():
|
||||
"""Make a sample .dts file and compile it to a .dtb
|
||||
|
||||
Returns:
|
||||
Filename of .dtb file created
|
||||
"""
|
||||
src = make_fname('u-boot.dts')
|
||||
dtb = make_fname('u-boot.dtb')
|
||||
with open(src, 'w') as fd:
|
||||
fd.write(base_fdt)
|
||||
util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
|
||||
return dtb
|
||||
|
||||
def make_its(params):
|
||||
"""Make a sample .its file with parameters embedded
|
||||
|
||||
Args:
|
||||
params: Dictionary containing parameters to embed in the %() strings
|
||||
Returns:
|
||||
Filename of .its file created
|
||||
"""
|
||||
its = make_fname('test.its')
|
||||
with open(its, 'w') as fd:
|
||||
print(base_its % params, file=fd)
|
||||
return its
|
||||
|
||||
def make_fit(mkimage, params):
|
||||
"""Make a sample .fit file ready for loading
|
||||
|
||||
This creates a .its script with the selected parameters and uses mkimage to
|
||||
turn this into a .fit image.
|
||||
|
||||
Args:
|
||||
mkimage: Filename of 'mkimage' utility
|
||||
params: Dictionary containing parameters to embed in the %() strings
|
||||
Return:
|
||||
Filename of .fit file created
|
||||
"""
|
||||
fit = make_fname('test.fit')
|
||||
its = make_its(params)
|
||||
util.run_and_log(cons, [mkimage, '-f', its, fit])
|
||||
with open(make_fname('u-boot.dts'), 'w') as fd:
|
||||
fd.write(base_fdt)
|
||||
return fit
|
||||
|
||||
def make_kernel(filename, text):
|
||||
"""Make a sample kernel with test data
|
||||
|
||||
Args:
|
||||
filename: the name of the file you want to create
|
||||
Returns:
|
||||
Full path and filename of the kernel it created
|
||||
"""
|
||||
fname = make_fname(filename)
|
||||
data = ''
|
||||
for i in range(100):
|
||||
data += 'this %s %d is unlikely to boot\n' % (text, i)
|
||||
with open(fname, 'w') as fd:
|
||||
print(data, file=fd)
|
||||
return fname
|
||||
|
||||
def make_ramdisk(filename, text):
|
||||
"""Make a sample ramdisk with test data
|
||||
|
||||
Returns:
|
||||
Filename of ramdisk created
|
||||
"""
|
||||
fname = make_fname(filename)
|
||||
data = ''
|
||||
for i in range(100):
|
||||
data += '%s %d was seldom used in the middle ages\n' % (text, i)
|
||||
with open(fname, 'w') as fd:
|
||||
print(data, file=fd)
|
||||
return fname
|
||||
|
||||
def make_compressed(filename):
|
||||
util.run_and_log(cons, ['gzip', '-f', '-k', filename])
|
||||
return filename + '.gz'
|
||||
|
||||
def find_matching(text, match):
|
||||
"""Find a match in a line of text, and return the unmatched line portion
|
||||
|
||||
This is used to extract a part of a line from some text. The match string
|
||||
is used to locate the line - we use the first line that contains that
|
||||
match text.
|
||||
|
||||
Once we find a match, we discard the match string itself from the line,
|
||||
and return what remains.
|
||||
|
||||
TODO: If this function becomes more generally useful, we could change it
|
||||
to use regex and return groups.
|
||||
|
||||
Args:
|
||||
text: Text to check (list of strings, one for each command issued)
|
||||
match: String to search for
|
||||
Return:
|
||||
String containing unmatched portion of line
|
||||
Exceptions:
|
||||
ValueError: If match is not found
|
||||
|
||||
>>> find_matching(['first line:10', 'second_line:20'], 'first line:')
|
||||
'10'
|
||||
>>> find_matching(['first line:10', 'second_line:20'], 'second line')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Test aborted
|
||||
>>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
|
||||
'20'
|
||||
>>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
|
||||
'third_line:')
|
||||
'30'
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
for line in '\n'.join(text).splitlines():
|
||||
pos = line.find(match)
|
||||
if pos != -1:
|
||||
return line[:pos] + line[pos + len(match):]
|
||||
|
||||
pytest.fail("Expected '%s' but not found in output")
|
||||
|
||||
def check_equal(expected_fname, actual_fname, failure_msg):
|
||||
"""Check that a file matches its expected contents
|
||||
|
||||
This is always used on out-buffers whose size is decided by the test
|
||||
script anyway, which in some cases may be larger than what we're
|
||||
actually looking for. So it's safe to truncate it to the size of the
|
||||
expected data.
|
||||
|
||||
Args:
|
||||
expected_fname: Filename containing expected contents
|
||||
actual_fname: Filename containing actual contents
|
||||
failure_msg: Message to print on failure
|
||||
"""
|
||||
expected_data = read_file(expected_fname)
|
||||
actual_data = read_file(actual_fname)
|
||||
if len(expected_data) < len(actual_data):
|
||||
actual_data = actual_data[:len(expected_data)]
|
||||
assert expected_data == actual_data, failure_msg
|
||||
|
||||
def check_not_equal(expected_fname, actual_fname, failure_msg):
|
||||
"""Check that a file does not match its expected contents
|
||||
|
||||
Args:
|
||||
expected_fname: Filename containing expected contents
|
||||
actual_fname: Filename containing actual contents
|
||||
failure_msg: Message to print on failure
|
||||
"""
|
||||
expected_data = read_file(expected_fname)
|
||||
actual_data = read_file(actual_fname)
|
||||
assert expected_data != actual_data, failure_msg
|
||||
|
||||
def run_fit_test(mkimage):
|
||||
"""Basic sanity check of FIT loading in U-Boot
|
||||
|
||||
TODO: Almost everything:
|
||||
- hash algorithms - invalid hash/contents should be detected
|
||||
- signature algorithms - invalid sig/contents should be detected
|
||||
- compression
|
||||
- checking that errors are detected like:
|
||||
- image overwriting
|
||||
- missing images
|
||||
- invalid configurations
|
||||
- incorrect os/arch/type fields
|
||||
- empty data
|
||||
- images too large/small
|
||||
- invalid FDT (e.g. putting a random binary in instead)
|
||||
- default configuration selection
|
||||
- bootm command line parameters should have desired effect
|
||||
- run code coverage to make sure we are testing all the code
|
||||
"""
|
||||
# Set up invariant files
|
||||
control_dtb = make_dtb()
|
||||
kernel = make_kernel('test-kernel.bin', 'kernel')
|
||||
ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
|
||||
loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
|
||||
loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
|
||||
kernel_out = make_fname('kernel-out.bin')
|
||||
fdt = make_fname('u-boot.dtb')
|
||||
fdt_out = make_fname('fdt-out.dtb')
|
||||
ramdisk_out = make_fname('ramdisk-out.bin')
|
||||
loadables1_out = make_fname('loadables1-out.bin')
|
||||
loadables2_out = make_fname('loadables2-out.bin')
|
||||
|
||||
# Set up basic parameters with default values
|
||||
params = {
|
||||
'fit_addr' : 0x1000,
|
||||
|
||||
'kernel' : kernel,
|
||||
'kernel_out' : kernel_out,
|
||||
'kernel_addr' : 0x40000,
|
||||
'kernel_size' : filesize(kernel),
|
||||
|
||||
'fdt' : fdt,
|
||||
'fdt_out' : fdt_out,
|
||||
'fdt_addr' : 0x80000,
|
||||
'fdt_size' : filesize(control_dtb),
|
||||
'fdt_load' : '',
|
||||
|
||||
'ramdisk' : ramdisk,
|
||||
'ramdisk_out' : ramdisk_out,
|
||||
'ramdisk_addr' : 0xc0000,
|
||||
'ramdisk_size' : filesize(ramdisk),
|
||||
'ramdisk_load' : '',
|
||||
'ramdisk_config' : '',
|
||||
|
||||
'loadables1' : loadables1,
|
||||
'loadables1_out' : loadables1_out,
|
||||
'loadables1_addr' : 0x100000,
|
||||
'loadables1_size' : filesize(loadables1),
|
||||
'loadables1_load' : '',
|
||||
|
||||
'loadables2' : loadables2,
|
||||
'loadables2_out' : loadables2_out,
|
||||
'loadables2_addr' : 0x140000,
|
||||
'loadables2_size' : filesize(loadables2),
|
||||
'loadables2_load' : '',
|
||||
|
||||
'loadables_config' : '',
|
||||
'compression' : 'none',
|
||||
}
|
||||
|
||||
# Make a basic FIT and a script to load it
|
||||
fit = make_fit(mkimage, params)
|
||||
params['fit'] = fit
|
||||
cmd = base_script % params
|
||||
|
||||
# First check that we can load a kernel
|
||||
# We could perhaps reduce duplication with some loss of readability
|
||||
cons.config.dtb = control_dtb
|
||||
cons.restart_uboot()
|
||||
with cons.log.section('Kernel load'):
|
||||
output = cons.run_command_list(cmd.splitlines())
|
||||
check_equal(kernel, kernel_out, 'Kernel not loaded')
|
||||
check_not_equal(control_dtb, fdt_out,
|
||||
'FDT loaded but should be ignored')
|
||||
check_not_equal(ramdisk, ramdisk_out,
|
||||
'Ramdisk loaded but should not be')
|
||||
|
||||
# Find out the offset in the FIT where U-Boot has found the FDT
|
||||
line = find_matching(output, 'Booting using the fdt blob at ')
|
||||
fit_offset = int(line, 16) - params['fit_addr']
|
||||
fdt_magic = struct.pack('>L', 0xd00dfeed)
|
||||
data = read_file(fit)
|
||||
|
||||
# Now find where it actually is in the FIT (skip the first word)
|
||||
real_fit_offset = data.find(fdt_magic, 4)
|
||||
assert fit_offset == real_fit_offset, (
|
||||
'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
|
||||
(fit_offset, real_fit_offset))
|
||||
|
||||
# Now a kernel and an FDT
|
||||
with cons.log.section('Kernel + FDT load'):
|
||||
params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
|
||||
fit = make_fit(mkimage, params)
|
||||
cons.restart_uboot()
|
||||
output = cons.run_command_list(cmd.splitlines())
|
||||
check_equal(kernel, kernel_out, 'Kernel not loaded')
|
||||
check_equal(control_dtb, fdt_out, 'FDT not loaded')
|
||||
check_not_equal(ramdisk, ramdisk_out,
|
||||
'Ramdisk loaded but should not be')
|
||||
|
||||
# Try a ramdisk
|
||||
with cons.log.section('Kernel + FDT + Ramdisk load'):
|
||||
params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
|
||||
params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
|
||||
fit = make_fit(mkimage, params)
|
||||
cons.restart_uboot()
|
||||
output = cons.run_command_list(cmd.splitlines())
|
||||
check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
|
||||
|
||||
# Configuration with some Loadables
|
||||
with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
|
||||
params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
|
||||
params['loadables1_load'] = ('load = <%#x>;' %
|
||||
params['loadables1_addr'])
|
||||
params['loadables2_load'] = ('load = <%#x>;' %
|
||||
params['loadables2_addr'])
|
||||
fit = make_fit(mkimage, params)
|
||||
cons.restart_uboot()
|
||||
output = cons.run_command_list(cmd.splitlines())
|
||||
check_equal(loadables1, loadables1_out,
|
||||
'Loadables1 (kernel) not loaded')
|
||||
check_equal(loadables2, loadables2_out,
|
||||
'Loadables2 (ramdisk) not loaded')
|
||||
|
||||
# Kernel, FDT and Ramdisk all compressed
|
||||
with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
|
||||
params['compression'] = 'gzip'
|
||||
params['kernel'] = make_compressed(kernel)
|
||||
params['fdt'] = make_compressed(fdt)
|
||||
params['ramdisk'] = make_compressed(ramdisk)
|
||||
fit = make_fit(mkimage, params)
|
||||
cons.restart_uboot()
|
||||
output = cons.run_command_list(cmd.splitlines())
|
||||
check_equal(kernel, kernel_out, 'Kernel not loaded')
|
||||
check_equal(control_dtb, fdt_out, 'FDT not loaded')
|
||||
check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
|
||||
check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
|
||||
|
||||
|
||||
cons = u_boot_console
|
||||
try:
|
||||
# We need to use our own device tree file. Remember to restore it
|
||||
# afterwards.
|
||||
old_dtb = cons.config.dtb
|
||||
mkimage = cons.config.build_dir + '/tools/mkimage'
|
||||
run_fit_test(mkimage)
|
||||
finally:
|
||||
# Go back to the original U-Boot with the correct dtb.
|
||||
cons.config.dtb = old_dtb
|
||||
cons.restart_uboot()
|
||||
565
test/py/tests/test_fpga.py
Normal file
565
test/py/tests/test_fpga.py
Normal file
@@ -0,0 +1,565 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2018, Xilinx Inc.
|
||||
#
|
||||
# Michal Simek
|
||||
# Siva Durga Prasad Paladugu
|
||||
|
||||
import pytest
|
||||
import re
|
||||
import random
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on boardenv_* containing configuration values to define
|
||||
the network available and files to be used for testing. Without this, this test
|
||||
will be automatically skipped.
|
||||
|
||||
For example:
|
||||
|
||||
# True if a DHCP server is attached to the network, and should be tested.
|
||||
env__net_dhcp_server = True
|
||||
|
||||
# A list of environment variables that should be set in order to configure a
|
||||
# static IP. In this test case we atleast need serverip for performing tftpb
|
||||
# to get required files.
|
||||
env__net_static_env_vars = [
|
||||
('ipaddr', '10.0.0.100'),
|
||||
('netmask', '255.255.255.0'),
|
||||
('serverip', '10.0.0.1'),
|
||||
]
|
||||
|
||||
# Details regarding the files that may be read from a TFTP server. .
|
||||
env__fpga_secure_readable_file = {
|
||||
'fn': 'auth_bhdr_ppk1_bit.bin',
|
||||
'enckupfn': 'auth_bhdr_enc_kup_load_bit.bin',
|
||||
'addr': 0x1000000,
|
||||
'keyaddr': 0x100000,
|
||||
'keyfn': 'key.txt',
|
||||
}
|
||||
|
||||
env__fpga_under_test = {
|
||||
'dev': 0,
|
||||
'addr' : 0x1000000,
|
||||
'bitstream_load': 'compress.bin',
|
||||
'bitstream_load_size': 1831960,
|
||||
'bitstream_loadp': 'compress_pr.bin',
|
||||
'bitstream_loadp_size': 423352,
|
||||
'bitstream_loadb': 'compress.bit',
|
||||
'bitstream_loadb_size': 1832086,
|
||||
'bitstream_loadbp': 'compress_pr.bit',
|
||||
'bitstream_loadbp_size': 423491,
|
||||
'mkimage_legacy': 'download.ub',
|
||||
'mkimage_legacy_size': 13321468,
|
||||
'mkimage_legacy_gz': 'download.gz.ub',
|
||||
'mkimage_legacy_gz_size': 53632,
|
||||
'mkimage_fit': 'download-fit.ub',
|
||||
'mkimage_fit_size': 13322784,
|
||||
'loadfs': 'mmc 0 compress.bin',
|
||||
'loadfs_size': 1831960,
|
||||
'loadfs_block_size': 0x10000,
|
||||
}
|
||||
"""
|
||||
|
||||
import test_net
|
||||
|
||||
def check_dev(u_boot_console):
|
||||
f = u_boot_console.config.env.get('env__fpga_under_test', None)
|
||||
if not f:
|
||||
pytest.skip('No FPGA to test')
|
||||
|
||||
dev = f.get('dev', -1)
|
||||
if dev < 0:
|
||||
pytest.fail('No dev specified via env__fpga_under_test')
|
||||
|
||||
return dev, f
|
||||
|
||||
def load_file_from_var(u_boot_console, name):
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
addr = f.get('addr', -1)
|
||||
if addr < 0:
|
||||
pytest.fail('No address specified via env__fpga_under_test')
|
||||
|
||||
test_net.test_net_dhcp(u_boot_console)
|
||||
test_net.test_net_setup_static(u_boot_console)
|
||||
bit = f['%s' % (name)]
|
||||
bit_size = f['%s_size' % (name)]
|
||||
|
||||
expected_tftp = 'Bytes transferred = %d' % bit_size
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (addr, bit))
|
||||
assert expected_tftp in output
|
||||
|
||||
return f, dev, addr, bit, bit_size
|
||||
|
||||
###### FPGA FAIL test ######
|
||||
expected_usage = 'fpga - loadable FPGA image support'
|
||||
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_fail(u_boot_console):
|
||||
# Test non valid fpga subcommand
|
||||
expected = 'fpga: non existing command'
|
||||
output = u_boot_console.run_command('fpga broken 0')
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_help(u_boot_console):
|
||||
# Just show help
|
||||
output = u_boot_console.run_command('fpga')
|
||||
assert expected_usage in output
|
||||
|
||||
|
||||
###### FPGA DUMP tests ######
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_dump(u_boot_console):
|
||||
pytest.skip('Not implemented now')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_dump_variable(u_boot_console):
|
||||
# Same as above but via "fpga" variable
|
||||
pytest.skip('Not implemented now')
|
||||
|
||||
###### FPGA INFO tests ######
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_info_fail(u_boot_console):
|
||||
# Maybe this can be skipped completely
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
# Multiple parameters to fpga info should fail
|
||||
expected = 'fpga: more parameters passed'
|
||||
output = u_boot_console.run_command('fpga info 0 0')
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_info_list(u_boot_console):
|
||||
# Maybe this can be skipped completely
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
# Code is design in a way that if fpga dev is not passed it should
|
||||
# return list of all fpga devices in the system
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
output = u_boot_console.run_command('fpga info')
|
||||
assert expected_usage not in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_info(u_boot_console):
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
output = u_boot_console.run_command('fpga info %x' % (dev))
|
||||
assert expected_usage not in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_info_variable(u_boot_console):
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
#
|
||||
# fpga variable is storing device number which doesn't need to be passed
|
||||
#
|
||||
u_boot_console.run_command('setenv fpga %x' % (dev))
|
||||
|
||||
output = u_boot_console.run_command('fpga info')
|
||||
# Variable cleanup
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
assert expected_usage not in output
|
||||
|
||||
###### FPGA LOAD tests ######
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_load_fail(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
|
||||
|
||||
for cmd in ['dump', 'load', 'loadb']:
|
||||
# missing dev parameter
|
||||
expected = 'fpga: incorrect parameters passed'
|
||||
output = u_boot_console.run_command('fpga %s %x $filesize' % (cmd, addr))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# more parameters - 0 at the end
|
||||
expected = 'fpga: more parameters passed'
|
||||
output = u_boot_console.run_command('fpga %s %x %x $filesize 0' % (cmd, dev, addr))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# 0 address
|
||||
expected = 'fpga: zero fpga_data address'
|
||||
output = u_boot_console.run_command('fpga %s %x 0 $filesize' % (cmd, dev))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# 0 filesize
|
||||
expected = 'fpga: zero size'
|
||||
output = u_boot_console.run_command('fpga %s %x %x 0' % (cmd, dev, addr))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_load(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga load %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadp')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadp(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga load %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
# And load also partial bistream
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadp')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadp %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadb(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadb')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadb %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadbp')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadbp(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadb')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadb %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
# And load also partial bistream in bit format
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadbp')
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadbp %x %x $filesize && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
###### FPGA LOADMK tests ######
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
def test_fpga_loadmk_fail(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
# load image but pass incorrect address to show error message
|
||||
expected = 'Unknown image type'
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x' % (dev, addr + 0x10))
|
||||
assert expected in output
|
||||
|
||||
# Pass more parameters then command expects - 0 at the end
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x 0' % (dev, addr))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
def test_fpga_loadmk_legacy(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
def test_fpga_loadmk_legacy_variable_fpga(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
u_boot_console.run_command('setenv fpga %x' % (dev))
|
||||
|
||||
# this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (addr, expected_text))
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
def test_fpga_loadmk_legacy_variable_fpgadata(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
u_boot_console.run_command('setenv fpgadata %x' % (addr))
|
||||
|
||||
# this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (dev, expected_text))
|
||||
u_boot_console.run_command('setenv fpgadata')
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
def test_fpga_loadmk_legacy_variable(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
u_boot_console.run_command('setenv fpga %x' % (dev))
|
||||
u_boot_console.run_command('setenv fpgadata %x' % (addr))
|
||||
|
||||
# this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk && echo %s' % (expected_text))
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
u_boot_console.run_command('setenv fpgadata')
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('image_format_legacy')
|
||||
@pytest.mark.buildconfigspec('gzip')
|
||||
def test_fpga_loadmk_legacy_gz(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy_gz')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadmk_fit_external(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit_external')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x:fpga && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadmk_fit(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x %x:fpga && echo %s' % (dev, addr, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadmk_fit_variable_fpga(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
# FIXME this should fail - broken support in past
|
||||
u_boot_console.run_command('setenv fpga %x' % (dev))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x:fpga && echo %s' % (addr, expected_text))
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadmk_fit_variable_fpgadata(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
# FIXME this should fail - broken support in past
|
||||
u_boot_console.run_command('setenv fpgadata %x:fpga' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (dev, expected_text))
|
||||
u_boot_console.run_command('setenv fpgadata')
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadmk_fit_variable(u_boot_console):
|
||||
f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
|
||||
|
||||
u_boot_console.run_command('imi %x' % (addr))
|
||||
|
||||
u_boot_console.run_command('setenv fpga %x' % (dev))
|
||||
u_boot_console.run_command('setenv fpgadata %x:fpga' % (addr))
|
||||
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadmk && echo %s' % (expected_text))
|
||||
u_boot_console.run_command('setenv fpga')
|
||||
u_boot_console.run_command('setenv fpgadata')
|
||||
assert expected_text in output
|
||||
|
||||
###### FPGA LOAD tests ######
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
def test_fpga_loadfs_fail(u_boot_console):
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
addr = f.get('addr', -1)
|
||||
if addr < 0:
|
||||
pytest.fail('No address specified via env__fpga_under_test')
|
||||
|
||||
bit = f['loadfs']
|
||||
bit_size = f['loadfs_size']
|
||||
block_size = f['loadfs_block_size']
|
||||
|
||||
# less params - dev number removed
|
||||
expected = 'fpga: incorrect parameters passed'
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %s' % (addr, bit_size, block_size, bit))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# one more param - 0 at the end
|
||||
# This is the longest command that's why there is no message from cmd/fpga.c
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s 0' % (dev, addr, bit_size, block_size, bit))
|
||||
assert expected_usage in output
|
||||
|
||||
# zero address 0
|
||||
expected = 'fpga: zero fpga_data address'
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, 0, bit_size, block_size, bit))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# bit_size 0
|
||||
expected = 'fpga: zero size'
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, addr, 0, block_size, bit))
|
||||
#assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# block size 0
|
||||
# FIXME this should pass but it failing too
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, addr, bit_size, 0, bit))
|
||||
assert expected_usage in output
|
||||
|
||||
# non existing bitstream name
|
||||
expected = 'Unable to read file noname'
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x mmc 0 noname' % (dev, addr, bit_size, block_size))
|
||||
assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
# -1 dev number
|
||||
expected = 'fpga_fsload: Invalid device number -1'
|
||||
output = u_boot_console.run_command('fpga loadfs %d %x %x %x mmc 0 noname' % (-1, addr, bit_size, block_size))
|
||||
assert expected in output
|
||||
assert expected_usage in output
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
def test_fpga_loadfs(u_boot_console):
|
||||
dev, f = check_dev(u_boot_console)
|
||||
|
||||
addr = f.get('addr', -1)
|
||||
if addr < 0:
|
||||
pytest.fail('No address specified via env__fpga_under_test')
|
||||
|
||||
bit = f['loadfs']
|
||||
bit_size = f['loadfs_size']
|
||||
block_size = f['loadfs_block_size']
|
||||
|
||||
# This should be done better
|
||||
expected_text = 'FPGA loaded successfully'
|
||||
output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s && echo %s' % (dev, addr, bit_size, block_size, bit, expected_text))
|
||||
assert expected_text in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_load_secure')
|
||||
@pytest.mark.buildconfigspec('cmd_net')
|
||||
@pytest.mark.buildconfigspec('cmd_dhcp')
|
||||
@pytest.mark.buildconfigspec('net')
|
||||
def test_fpga_secure_bit_auth(u_boot_console):
|
||||
|
||||
test_net.test_net_dhcp(u_boot_console)
|
||||
test_net.test_net_setup_static(u_boot_console)
|
||||
|
||||
f = u_boot_console.config.env.get('env__fpga_secure_readable_file', None)
|
||||
if not f:
|
||||
pytest.skip('No TFTP readable file to read')
|
||||
|
||||
addr = f.get('addr', None)
|
||||
if not addr:
|
||||
addr = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
expected_tftp = 'Bytes transferred = '
|
||||
fn = f['fn']
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
|
||||
assert expected_tftp in output
|
||||
|
||||
expected_zynqmpsecure = 'Bitstream successfully loaded'
|
||||
output = u_boot_console.run_command('fpga loads 0 %x $filesize 0 2' % (addr))
|
||||
assert expected_zynqmpsecure in output
|
||||
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_fpga')
|
||||
@pytest.mark.buildconfigspec('cmd_fpga_load_secure')
|
||||
@pytest.mark.buildconfigspec('cmd_net')
|
||||
@pytest.mark.buildconfigspec('cmd_dhcp')
|
||||
@pytest.mark.buildconfigspec('net')
|
||||
def test_fpga_secure_bit_img_auth_kup(u_boot_console):
|
||||
|
||||
test_net.test_net_dhcp(u_boot_console)
|
||||
test_net.test_net_setup_static(u_boot_console)
|
||||
|
||||
f = u_boot_console.config.env.get('env__fpga_secure_readable_file', None)
|
||||
if not f:
|
||||
pytest.skip('No TFTP readable file to read')
|
||||
|
||||
keyaddr = f.get('keyaddr', None)
|
||||
if not keyaddr:
|
||||
addr = u_boot_utils.find_ram_base(u_boot_console)
|
||||
expected_tftp = 'Bytes transferred = '
|
||||
keyfn = f['keyfn']
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (keyaddr, keyfn))
|
||||
assert expected_tftp in output
|
||||
|
||||
addr = f.get('addr', None)
|
||||
if not addr:
|
||||
addr = u_boot_utils.find_ram_base(u_boot_console)
|
||||
expected_tftp = 'Bytes transferred = '
|
||||
fn = f['enckupfn']
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
|
||||
assert expected_tftp in output
|
||||
|
||||
expected_zynqmpsecure = 'Bitstream successfully loaded'
|
||||
output = u_boot_console.run_command('fpga loads 0 %x $filesize 0 1 %x' % (addr, keyaddr))
|
||||
assert expected_zynqmpsecure in output
|
||||
602
test/py/tests/test_fs/conftest.py
Normal file
602
test/py/tests/test_fs/conftest.py
Normal file
@@ -0,0 +1,602 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
import re
|
||||
from subprocess import call, check_call, check_output, CalledProcessError
|
||||
from fstest_defs import *
|
||||
|
||||
supported_fs_basic = ['fat16', 'fat32', 'ext4']
|
||||
supported_fs_ext = ['fat16', 'fat32']
|
||||
supported_fs_mkdir = ['fat16', 'fat32']
|
||||
supported_fs_unlink = ['fat16', 'fat32']
|
||||
supported_fs_symlink = ['ext4']
|
||||
|
||||
#
|
||||
# Filesystem test specific setup
|
||||
#
|
||||
def pytest_addoption(parser):
|
||||
"""Enable --fs-type option.
|
||||
|
||||
See pytest_configure() about how it works.
|
||||
|
||||
Args:
|
||||
parser: Pytest command-line parser.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
parser.addoption('--fs-type', action='append', default=None,
|
||||
help='Targeting Filesystem Types')
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Restrict a file system(s) to be tested.
|
||||
|
||||
A file system explicitly named with --fs-type option is selected
|
||||
if it belongs to a default supported_fs_xxx list.
|
||||
Multiple options can be specified.
|
||||
|
||||
Args:
|
||||
config: Pytest configuration.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
global supported_fs_basic
|
||||
global supported_fs_ext
|
||||
global supported_fs_mkdir
|
||||
global supported_fs_unlink
|
||||
global supported_fs_symlink
|
||||
|
||||
def intersect(listA, listB):
|
||||
return [x for x in listA if x in listB]
|
||||
|
||||
supported_fs = config.getoption('fs_type')
|
||||
if supported_fs:
|
||||
print('*** FS TYPE modified: %s' % supported_fs)
|
||||
supported_fs_basic = intersect(supported_fs, supported_fs_basic)
|
||||
supported_fs_ext = intersect(supported_fs, supported_fs_ext)
|
||||
supported_fs_mkdir = intersect(supported_fs, supported_fs_mkdir)
|
||||
supported_fs_unlink = intersect(supported_fs, supported_fs_unlink)
|
||||
supported_fs_symlink = intersect(supported_fs, supported_fs_symlink)
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
"""Parametrize fixtures, fs_obj_xxx
|
||||
|
||||
Each fixture will be parametrized with a corresponding support_fs_xxx
|
||||
list.
|
||||
|
||||
Args:
|
||||
metafunc: Pytest test function.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
if 'fs_obj_basic' in metafunc.fixturenames:
|
||||
metafunc.parametrize('fs_obj_basic', supported_fs_basic,
|
||||
indirect=True, scope='module')
|
||||
if 'fs_obj_ext' in metafunc.fixturenames:
|
||||
metafunc.parametrize('fs_obj_ext', supported_fs_ext,
|
||||
indirect=True, scope='module')
|
||||
if 'fs_obj_mkdir' in metafunc.fixturenames:
|
||||
metafunc.parametrize('fs_obj_mkdir', supported_fs_mkdir,
|
||||
indirect=True, scope='module')
|
||||
if 'fs_obj_unlink' in metafunc.fixturenames:
|
||||
metafunc.parametrize('fs_obj_unlink', supported_fs_unlink,
|
||||
indirect=True, scope='module')
|
||||
if 'fs_obj_symlink' in metafunc.fixturenames:
|
||||
metafunc.parametrize('fs_obj_symlink', supported_fs_symlink,
|
||||
indirect=True, scope='module')
|
||||
|
||||
#
|
||||
# Helper functions
|
||||
#
|
||||
def fstype_to_ubname(fs_type):
|
||||
"""Convert a file system type to an U-boot specific string
|
||||
|
||||
A generated string can be used as part of file system related commands
|
||||
or a config name in u-boot. Currently fat16 and fat32 are handled
|
||||
specifically.
|
||||
|
||||
Args:
|
||||
fs_type: File system type.
|
||||
|
||||
Return:
|
||||
A corresponding string for file system type.
|
||||
"""
|
||||
if re.match('fat', fs_type):
|
||||
return 'fat'
|
||||
else:
|
||||
return fs_type
|
||||
|
||||
def check_ubconfig(config, fs_type):
|
||||
"""Check whether a file system is enabled in u-boot configuration.
|
||||
|
||||
This function is assumed to be called in a fixture function so that
|
||||
the whole test cases will be skipped if a given file system is not
|
||||
enabled.
|
||||
|
||||
Args:
|
||||
fs_type: File system type.
|
||||
|
||||
Return:
|
||||
Nothing.
|
||||
"""
|
||||
if not config.buildconfig.get('config_cmd_%s' % fs_type, None):
|
||||
pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper())
|
||||
if not config.buildconfig.get('config_%s_write' % fs_type, None):
|
||||
pytest.skip('.config feature "%s_WRITE" not enabled'
|
||||
% fs_type.upper())
|
||||
|
||||
def mk_fs(config, fs_type, size, id):
|
||||
"""Create a file system volume.
|
||||
|
||||
Args:
|
||||
fs_type: File system type.
|
||||
size: Size of file system in MiB.
|
||||
id: Prefix string of volume's file name.
|
||||
|
||||
Return:
|
||||
Nothing.
|
||||
"""
|
||||
fs_img = '%s.%s.img' % (id, fs_type)
|
||||
fs_img = config.persistent_data_dir + '/' + fs_img
|
||||
|
||||
if fs_type == 'fat16':
|
||||
mkfs_opt = '-F 16'
|
||||
elif fs_type == 'fat32':
|
||||
mkfs_opt = '-F 32'
|
||||
elif fs_type == 'ext4':
|
||||
mkfs_opt = '-O ^metadata_csum'
|
||||
else:
|
||||
mkfs_opt = ''
|
||||
|
||||
if re.match('fat', fs_type):
|
||||
fs_lnxtype = 'vfat'
|
||||
else:
|
||||
fs_lnxtype = fs_type
|
||||
|
||||
count = (size + 1048576 - 1) / 1048576
|
||||
|
||||
try:
|
||||
check_call('rm -f %s' % fs_img, shell=True)
|
||||
check_call('dd if=/dev/zero of=%s bs=1M count=%d'
|
||||
% (fs_img, count), shell=True)
|
||||
check_call('mkfs.%s %s %s'
|
||||
% (fs_lnxtype, mkfs_opt, fs_img), shell=True)
|
||||
return fs_img
|
||||
except CalledProcessError:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
raise
|
||||
|
||||
# from test/py/conftest.py
|
||||
def tool_is_in_path(tool):
|
||||
"""Check whether a given command is available on host.
|
||||
|
||||
Args:
|
||||
tool: Command name.
|
||||
|
||||
Return:
|
||||
True if available, False if not.
|
||||
"""
|
||||
for path in os.environ['PATH'].split(os.pathsep):
|
||||
fn = os.path.join(path, tool)
|
||||
if os.path.isfile(fn) and os.access(fn, os.X_OK):
|
||||
return True
|
||||
return False
|
||||
|
||||
fuse_mounted = False
|
||||
|
||||
def mount_fs(fs_type, device, mount_point):
|
||||
"""Mount a volume.
|
||||
|
||||
Args:
|
||||
fs_type: File system type.
|
||||
device: Volume's file name.
|
||||
mount_point: Mount point.
|
||||
|
||||
Return:
|
||||
Nothing.
|
||||
"""
|
||||
global fuse_mounted
|
||||
|
||||
fuse_mounted = False
|
||||
try:
|
||||
if tool_is_in_path('guestmount'):
|
||||
fuse_mounted = True
|
||||
check_call('guestmount -a %s -m /dev/sda %s'
|
||||
% (device, mount_point), shell=True)
|
||||
else:
|
||||
mount_opt = 'loop,rw'
|
||||
if re.match('fat', fs_type):
|
||||
mount_opt += ',umask=0000'
|
||||
|
||||
check_call('sudo mount -o %s %s %s'
|
||||
% (mount_opt, device, mount_point), shell=True)
|
||||
|
||||
# may not be effective for some file systems
|
||||
check_call('sudo chmod a+rw %s' % mount_point, shell=True)
|
||||
except CalledProcessError:
|
||||
raise
|
||||
|
||||
def umount_fs(mount_point):
|
||||
"""Unmount a volume.
|
||||
|
||||
Args:
|
||||
mount_point: Mount point.
|
||||
|
||||
Return:
|
||||
Nothing.
|
||||
"""
|
||||
if fuse_mounted:
|
||||
call('sync')
|
||||
call('guestunmount %s' % mount_point, shell=True)
|
||||
else:
|
||||
call('sudo umount %s' % mount_point, shell=True)
|
||||
|
||||
#
|
||||
# Fixture for basic fs test
|
||||
# derived from test/fs/fs-test.sh
|
||||
#
|
||||
# NOTE: yield_fixture was deprecated since pytest-3.0
|
||||
@pytest.yield_fixture()
|
||||
def fs_obj_basic(request, u_boot_config):
|
||||
"""Set up a file system to be used in basic fs test.
|
||||
|
||||
Args:
|
||||
request: Pytest request object.
|
||||
u_boot_config: U-boot configuration.
|
||||
|
||||
Return:
|
||||
A fixture for basic fs test, i.e. a triplet of file system type,
|
||||
volume file name and a list of MD5 hashes.
|
||||
"""
|
||||
fs_type = request.param
|
||||
fs_img = ''
|
||||
|
||||
fs_ubtype = fstype_to_ubname(fs_type)
|
||||
check_ubconfig(u_boot_config, fs_ubtype)
|
||||
|
||||
mount_dir = u_boot_config.persistent_data_dir + '/mnt'
|
||||
|
||||
small_file = mount_dir + '/' + SMALL_FILE
|
||||
big_file = mount_dir + '/' + BIG_FILE
|
||||
|
||||
try:
|
||||
|
||||
# 3GiB volume
|
||||
fs_img = mk_fs(u_boot_config, fs_type, 0xc0000000, '3GB')
|
||||
|
||||
# Mount the image so we can populate it.
|
||||
check_call('mkdir -p %s' % mount_dir, shell=True)
|
||||
mount_fs(fs_type, fs_img, mount_dir)
|
||||
|
||||
# Create a subdirectory.
|
||||
check_call('mkdir %s/SUBDIR' % mount_dir, shell=True)
|
||||
|
||||
# Create big file in this image.
|
||||
# Note that we work only on the start 1MB, couple MBs in the 2GB range
|
||||
# and the last 1 MB of the huge 2.5GB file.
|
||||
# So, just put random values only in those areas.
|
||||
check_call('dd if=/dev/urandom of=%s bs=1M count=1'
|
||||
% big_file, shell=True)
|
||||
check_call('dd if=/dev/urandom of=%s bs=1M count=2 seek=2047'
|
||||
% big_file, shell=True)
|
||||
check_call('dd if=/dev/urandom of=%s bs=1M count=1 seek=2499'
|
||||
% big_file, shell=True)
|
||||
|
||||
# Create a small file in this image.
|
||||
check_call('dd if=/dev/urandom of=%s bs=1M count=1'
|
||||
% small_file, shell=True)
|
||||
|
||||
# Delete the small file copies which possibly are written as part of a
|
||||
# previous test.
|
||||
# check_call('rm -f "%s.w"' % MB1, shell=True)
|
||||
# check_call('rm -f "%s.w2"' % MB1, shell=True)
|
||||
|
||||
# Generate the md5sums of reads that we will test against small file
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
|
||||
% small_file, shell=True).decode()
|
||||
md5val = [ out.split()[0] ]
|
||||
|
||||
# Generate the md5sums of reads that we will test against big file
|
||||
# One from beginning of file.
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
|
||||
% big_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# One from end of file.
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=2499 count=1 2> /dev/null | md5sum'
|
||||
% big_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# One from the last 1MB chunk of 2GB
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=2047 count=1 2> /dev/null | md5sum'
|
||||
% big_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# One from the start 1MB chunk from 2GB
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=2048 count=1 2> /dev/null | md5sum'
|
||||
% big_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# One 1MB chunk crossing the 2GB boundary
|
||||
out = check_output(
|
||||
'dd if=%s bs=512K skip=4095 count=2 2> /dev/null | md5sum'
|
||||
% big_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
umount_fs(mount_dir)
|
||||
except CalledProcessError:
|
||||
pytest.skip('Setup failed for filesystem: ' + fs_type)
|
||||
return
|
||||
else:
|
||||
yield [fs_ubtype, fs_img, md5val]
|
||||
finally:
|
||||
umount_fs(mount_dir)
|
||||
call('rmdir %s' % mount_dir, shell=True)
|
||||
if fs_img:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
|
||||
#
|
||||
# Fixture for extended fs test
|
||||
#
|
||||
# NOTE: yield_fixture was deprecated since pytest-3.0
|
||||
@pytest.yield_fixture()
|
||||
def fs_obj_ext(request, u_boot_config):
|
||||
"""Set up a file system to be used in extended fs test.
|
||||
|
||||
Args:
|
||||
request: Pytest request object.
|
||||
u_boot_config: U-boot configuration.
|
||||
|
||||
Return:
|
||||
A fixture for extended fs test, i.e. a triplet of file system type,
|
||||
volume file name and a list of MD5 hashes.
|
||||
"""
|
||||
fs_type = request.param
|
||||
fs_img = ''
|
||||
|
||||
fs_ubtype = fstype_to_ubname(fs_type)
|
||||
check_ubconfig(u_boot_config, fs_ubtype)
|
||||
|
||||
mount_dir = u_boot_config.persistent_data_dir + '/mnt'
|
||||
|
||||
min_file = mount_dir + '/' + MIN_FILE
|
||||
tmp_file = mount_dir + '/tmpfile'
|
||||
|
||||
try:
|
||||
|
||||
# 128MiB volume
|
||||
fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
|
||||
|
||||
# Mount the image so we can populate it.
|
||||
check_call('mkdir -p %s' % mount_dir, shell=True)
|
||||
mount_fs(fs_type, fs_img, mount_dir)
|
||||
|
||||
# Create a test directory
|
||||
check_call('mkdir %s/dir1' % mount_dir, shell=True)
|
||||
|
||||
# Create a small file and calculate md5
|
||||
check_call('dd if=/dev/urandom of=%s bs=1K count=20'
|
||||
% min_file, shell=True)
|
||||
out = check_output(
|
||||
'dd if=%s bs=1K 2> /dev/null | md5sum'
|
||||
% min_file, shell=True).decode()
|
||||
md5val = [ out.split()[0] ]
|
||||
|
||||
# Calculate md5sum of Test Case 4
|
||||
check_call('dd if=%s of=%s bs=1K count=20'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
check_call('dd if=%s of=%s bs=1K seek=5 count=20'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
|
||||
% tmp_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# Calculate md5sum of Test Case 5
|
||||
check_call('dd if=%s of=%s bs=1K count=20'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
check_call('dd if=%s of=%s bs=1K seek=5 count=5'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
|
||||
% tmp_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
# Calculate md5sum of Test Case 7
|
||||
check_call('dd if=%s of=%s bs=1K count=20'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
check_call('dd if=%s of=%s bs=1K seek=20 count=20'
|
||||
% (min_file, tmp_file), shell=True)
|
||||
out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
|
||||
% tmp_file, shell=True).decode()
|
||||
md5val.append(out.split()[0])
|
||||
|
||||
check_call('rm %s' % tmp_file, shell=True)
|
||||
umount_fs(mount_dir)
|
||||
except CalledProcessError:
|
||||
pytest.skip('Setup failed for filesystem: ' + fs_type)
|
||||
return
|
||||
else:
|
||||
yield [fs_ubtype, fs_img, md5val]
|
||||
finally:
|
||||
umount_fs(mount_dir)
|
||||
call('rmdir %s' % mount_dir, shell=True)
|
||||
if fs_img:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
|
||||
#
|
||||
# Fixture for mkdir test
|
||||
#
|
||||
# NOTE: yield_fixture was deprecated since pytest-3.0
|
||||
@pytest.yield_fixture()
|
||||
def fs_obj_mkdir(request, u_boot_config):
|
||||
"""Set up a file system to be used in mkdir test.
|
||||
|
||||
Args:
|
||||
request: Pytest request object.
|
||||
u_boot_config: U-boot configuration.
|
||||
|
||||
Return:
|
||||
A fixture for mkdir test, i.e. a duplet of file system type and
|
||||
volume file name.
|
||||
"""
|
||||
fs_type = request.param
|
||||
fs_img = ''
|
||||
|
||||
fs_ubtype = fstype_to_ubname(fs_type)
|
||||
check_ubconfig(u_boot_config, fs_ubtype)
|
||||
|
||||
try:
|
||||
# 128MiB volume
|
||||
fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
|
||||
except:
|
||||
pytest.skip('Setup failed for filesystem: ' + fs_type)
|
||||
else:
|
||||
yield [fs_ubtype, fs_img]
|
||||
finally:
|
||||
if fs_img:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
|
||||
#
|
||||
# Fixture for unlink test
|
||||
#
|
||||
# NOTE: yield_fixture was deprecated since pytest-3.0
|
||||
@pytest.yield_fixture()
|
||||
def fs_obj_unlink(request, u_boot_config):
|
||||
"""Set up a file system to be used in unlink test.
|
||||
|
||||
Args:
|
||||
request: Pytest request object.
|
||||
u_boot_config: U-boot configuration.
|
||||
|
||||
Return:
|
||||
A fixture for unlink test, i.e. a duplet of file system type and
|
||||
volume file name.
|
||||
"""
|
||||
fs_type = request.param
|
||||
fs_img = ''
|
||||
|
||||
fs_ubtype = fstype_to_ubname(fs_type)
|
||||
check_ubconfig(u_boot_config, fs_ubtype)
|
||||
|
||||
mount_dir = u_boot_config.persistent_data_dir + '/mnt'
|
||||
|
||||
try:
|
||||
|
||||
# 128MiB volume
|
||||
fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
|
||||
|
||||
# Mount the image so we can populate it.
|
||||
check_call('mkdir -p %s' % mount_dir, shell=True)
|
||||
mount_fs(fs_type, fs_img, mount_dir)
|
||||
|
||||
# Test Case 1 & 3
|
||||
check_call('mkdir %s/dir1' % mount_dir, shell=True)
|
||||
check_call('dd if=/dev/urandom of=%s/dir1/file1 bs=1K count=1'
|
||||
% mount_dir, shell=True)
|
||||
check_call('dd if=/dev/urandom of=%s/dir1/file2 bs=1K count=1'
|
||||
% mount_dir, shell=True)
|
||||
|
||||
# Test Case 2
|
||||
check_call('mkdir %s/dir2' % mount_dir, shell=True)
|
||||
for i in range(0, 20):
|
||||
check_call('mkdir %s/dir2/0123456789abcdef%02x'
|
||||
% (mount_dir, i), shell=True)
|
||||
|
||||
# Test Case 4
|
||||
check_call('mkdir %s/dir4' % mount_dir, shell=True)
|
||||
|
||||
# Test Case 5, 6 & 7
|
||||
check_call('mkdir %s/dir5' % mount_dir, shell=True)
|
||||
check_call('dd if=/dev/urandom of=%s/dir5/file1 bs=1K count=1'
|
||||
% mount_dir, shell=True)
|
||||
|
||||
umount_fs(mount_dir)
|
||||
except CalledProcessError:
|
||||
pytest.skip('Setup failed for filesystem: ' + fs_type)
|
||||
return
|
||||
else:
|
||||
yield [fs_ubtype, fs_img]
|
||||
finally:
|
||||
umount_fs(mount_dir)
|
||||
call('rmdir %s' % mount_dir, shell=True)
|
||||
if fs_img:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
|
||||
#
|
||||
# Fixture for symlink fs test
|
||||
#
|
||||
# NOTE: yield_fixture was deprecated since pytest-3.0
|
||||
@pytest.yield_fixture()
|
||||
def fs_obj_symlink(request, u_boot_config):
|
||||
"""Set up a file system to be used in symlink fs test.
|
||||
|
||||
Args:
|
||||
request: Pytest request object.
|
||||
u_boot_config: U-boot configuration.
|
||||
|
||||
Return:
|
||||
A fixture for basic fs test, i.e. a triplet of file system type,
|
||||
volume file name and a list of MD5 hashes.
|
||||
"""
|
||||
fs_type = request.param
|
||||
fs_img = ''
|
||||
|
||||
fs_ubtype = fstype_to_ubname(fs_type)
|
||||
check_ubconfig(u_boot_config, fs_ubtype)
|
||||
|
||||
mount_dir = u_boot_config.persistent_data_dir + '/mnt'
|
||||
|
||||
small_file = mount_dir + '/' + SMALL_FILE
|
||||
medium_file = mount_dir + '/' + MEDIUM_FILE
|
||||
|
||||
try:
|
||||
|
||||
# 3GiB volume
|
||||
fs_img = mk_fs(u_boot_config, fs_type, 0x40000000, '1GB')
|
||||
|
||||
# Mount the image so we can populate it.
|
||||
check_call('mkdir -p %s' % mount_dir, shell=True)
|
||||
mount_fs(fs_type, fs_img, mount_dir)
|
||||
|
||||
# Create a subdirectory.
|
||||
check_call('mkdir %s/SUBDIR' % mount_dir, shell=True)
|
||||
|
||||
# Create a small file in this image.
|
||||
check_call('dd if=/dev/urandom of=%s bs=1M count=1'
|
||||
% small_file, shell=True)
|
||||
|
||||
# Create a medium file in this image.
|
||||
check_call('dd if=/dev/urandom of=%s bs=10M count=1'
|
||||
% medium_file, shell=True)
|
||||
|
||||
# Generate the md5sums of reads that we will test against small file
|
||||
out = check_output(
|
||||
'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
|
||||
% small_file, shell=True).decode()
|
||||
md5val = [out.split()[0]]
|
||||
out = check_output(
|
||||
'dd if=%s bs=10M skip=0 count=1 2> /dev/null | md5sum'
|
||||
% medium_file, shell=True).decode()
|
||||
md5val.extend([out.split()[0]])
|
||||
|
||||
umount_fs(mount_dir)
|
||||
except CalledProcessError:
|
||||
pytest.skip('Setup failed for filesystem: ' + fs_type)
|
||||
return
|
||||
else:
|
||||
yield [fs_ubtype, fs_img, md5val]
|
||||
finally:
|
||||
umount_fs(mount_dir)
|
||||
call('rmdir %s' % mount_dir, shell=True)
|
||||
if fs_img:
|
||||
call('rm -f %s' % fs_img, shell=True)
|
||||
16
test/py/tests/test_fs/fstest_defs.py
Normal file
16
test/py/tests/test_fs/fstest_defs.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
# $MIN_FILE is the name of the 20KB file in the file system image
|
||||
MIN_FILE='testfile'
|
||||
|
||||
# $SMALL_FILE is the name of the 1MB file in the file system image
|
||||
SMALL_FILE='1MB.file'
|
||||
|
||||
# $MEDIUM_FILE is the name of the 10MB file in the file system image
|
||||
MEDIUM_FILE='10MB.file'
|
||||
|
||||
# $BIG_FILE is the name of the 2.5GB file in the file system image
|
||||
BIG_FILE='2.5GB.file'
|
||||
|
||||
ADDR=0x01000008
|
||||
LENGTH=0x00100000
|
||||
13
test/py/tests/test_fs/fstest_helpers.py
Normal file
13
test/py/tests/test_fs/fstest_helpers.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2019, Texas Instrument
|
||||
# Author: JJ Hiblot <jjhiblot@ti.com>
|
||||
#
|
||||
|
||||
from subprocess import check_call, CalledProcessError
|
||||
|
||||
def assert_fs_integrity(fs_type, fs_img):
|
||||
try:
|
||||
if fs_type == 'ext4':
|
||||
check_call('fsck.ext4 -n -f %s' % fs_img, shell=True)
|
||||
except CalledProcessError:
|
||||
raise
|
||||
292
test/py/tests/test_fs/test_basic.py
Normal file
292
test/py/tests/test_fs/test_basic.py
Normal file
@@ -0,0 +1,292 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
|
||||
#
|
||||
# U-Boot File System:Basic Test
|
||||
|
||||
"""
|
||||
This test verifies basic read/write operation on file system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import re
|
||||
from fstest_defs import *
|
||||
from fstest_helpers import assert_fs_integrity
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.slow
|
||||
class TestFsBasic(object):
|
||||
def test_fs1(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 1 - ls command, listing a root directory and invalid directory
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 1a - ls'):
|
||||
# Test Case 1 - ls
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sls host 0:0' % fs_type])
|
||||
assert(re.search('2621440000 *%s' % BIG_FILE, ''.join(output)))
|
||||
assert(re.search('1048576 *%s' % SMALL_FILE, ''.join(output)))
|
||||
|
||||
with u_boot_console.log.section('Test Case 1b - ls (invalid dir)'):
|
||||
# In addition, test with a nonexistent directory to see if we crash.
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 invalid_d' % fs_type)
|
||||
if fs_type == 'ext4':
|
||||
assert('Can not find directory' in output)
|
||||
else:
|
||||
assert('' == output)
|
||||
|
||||
def test_fs2(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 2 - size command for a small file
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 2a - size (small)'):
|
||||
# 1MB is 0x0010 0000
|
||||
# Test Case 2a - size of small file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%ssize host 0:0 /%s' % (fs_type, SMALL_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
with u_boot_console.log.section('Test Case 2b - size (/../<file>)'):
|
||||
# Test Case 2b - size of small file via a path using '..'
|
||||
output = u_boot_console.run_command_list([
|
||||
'%ssize host 0:0 /SUBDIR/../%s' % (fs_type, SMALL_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
def test_fs3(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 3 - size command for a large file
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 3 - size (large)'):
|
||||
# 2.5GB (1024*1024*2500) is 0x9C40 0000
|
||||
# Test Case 3 - size of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%ssize host 0:0 /%s' % (fs_type, BIG_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=9c400000' in ''.join(output))
|
||||
|
||||
def test_fs4(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 4 - load a small file, 1MB
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 4 - load (small)'):
|
||||
# Test Case 4a - Read full 1MB of small file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 4b - Read full 1MB of small file
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
|
||||
def test_fs5(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 5 - load, reading first 1MB of 3GB file
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 5 - load (first 1MB)'):
|
||||
# Test Case 5a - First 1MB of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s %x 0x0' % (fs_type, ADDR, BIG_FILE, LENGTH),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 5b - First 1MB of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[1] in ''.join(output))
|
||||
|
||||
def test_fs6(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 6 - load, reading last 1MB of 3GB file
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 6 - load (last 1MB)'):
|
||||
# fails for ext as no offset support
|
||||
# Test Case 6a - Last 1MB of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s %x 0x9c300000'
|
||||
% (fs_type, ADDR, BIG_FILE, LENGTH),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 6b - Last 1MB of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[2] in ''.join(output))
|
||||
|
||||
def test_fs7(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 7 - load, 1MB from the last 1MB in 2GB
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 7 - load (last 1MB in 2GB)'):
|
||||
# fails for ext as no offset support
|
||||
# Test Case 7a - One from the last 1MB chunk of 2GB
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s %x 0x7ff00000'
|
||||
% (fs_type, ADDR, BIG_FILE, LENGTH),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 7b - One from the last 1MB chunk of 2GB
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[3] in ''.join(output))
|
||||
|
||||
def test_fs8(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 8 - load, reading first 1MB in 2GB
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 8 - load (first 1MB in 2GB)'):
|
||||
# fails for ext as no offset support
|
||||
# Test Case 8a - One from the start 1MB chunk from 2GB
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s %x 0x80000000'
|
||||
% (fs_type, ADDR, BIG_FILE, LENGTH),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 8b - One from the start 1MB chunk from 2GB
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[4] in ''.join(output))
|
||||
|
||||
def test_fs9(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 9 - load, 1MB crossing 2GB boundary
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 9 - load (crossing 2GB boundary)'):
|
||||
# fails for ext as no offset support
|
||||
# Test Case 9a - One 1MB chunk crossing the 2GB boundary
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s %x 0x7ff80000'
|
||||
% (fs_type, ADDR, BIG_FILE, LENGTH),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 9b - One 1MB chunk crossing the 2GB boundary
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[5] in ''.join(output))
|
||||
|
||||
def test_fs10(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 10 - load, reading beyond file end'):
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 10 - load (beyond file end)'):
|
||||
# Generic failure case
|
||||
# Test Case 10 - 2MB chunk from the last 1MB of big file
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s 0x00200000 0x9c300000'
|
||||
% (fs_type, ADDR, BIG_FILE),
|
||||
'printenv filesize',
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
def test_fs11(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 11 - write'
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 11 - write'):
|
||||
# Read 1MB from small file
|
||||
# Write it back to test the writes
|
||||
# Test Case 11a - Check that the write succeeded
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
|
||||
'%swrite host 0:0 %x /%s.w $filesize'
|
||||
% (fs_type, ADDR, SMALL_FILE)])
|
||||
assert('1048576 bytes written' in ''.join(output))
|
||||
|
||||
# Test Case 11b - Check md5 of written to is same
|
||||
# as the one read from
|
||||
output = u_boot_console.run_command_list([
|
||||
'%sload host 0:0 %x /%s.w' % (fs_type, ADDR, SMALL_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs12(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 12 - write to "." directory
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 12 - write (".")'):
|
||||
# Next test case checks writing a file whose dirent
|
||||
# is the first in the block, which is always true for "."
|
||||
# The write should fail, but the lookup should work
|
||||
# Test Case 12 - Check directory traversal
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%swrite host 0:0 %x /. 0x10' % (fs_type, ADDR)])
|
||||
assert('Unable to write' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs13(self, u_boot_console, fs_obj_basic):
|
||||
"""
|
||||
Test Case 13 - write to a file with "/./<filename>"
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_basic
|
||||
with u_boot_console.log.section('Test Case 13 - write ("./<file>")'):
|
||||
# Read 1MB from small file
|
||||
# Write it via "same directory", i.e. "." dirent
|
||||
# Test Case 13a - Check directory traversal
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
|
||||
'%swrite host 0:0 %x /./%s2 $filesize'
|
||||
% (fs_type, ADDR, SMALL_FILE)])
|
||||
assert('1048576 bytes written' in ''.join(output))
|
||||
|
||||
# Test Case 13b - Check md5 of written to is same
|
||||
# as the one read from
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /./%s2' % (fs_type, ADDR, SMALL_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
|
||||
# Test Case 13c - Check md5 of written to is same
|
||||
# as the one read from
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /%s2' % (fs_type, ADDR, SMALL_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
319
test/py/tests/test_fs/test_ext.py
Normal file
319
test/py/tests/test_fs/test_ext.py
Normal file
@@ -0,0 +1,319 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
|
||||
#
|
||||
# U-Boot File System:Exntented Test
|
||||
|
||||
"""
|
||||
This test verifies extended write operation on file system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import re
|
||||
from fstest_defs import *
|
||||
from fstest_helpers import assert_fs_integrity
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.slow
|
||||
class TestFsExt(object):
|
||||
def test_fs_ext1(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 1 - write a file with absolute path
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 1 - write with abs path'):
|
||||
# Test Case 1a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w1 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
assert('20480 bytes written' in ''.join(output))
|
||||
|
||||
# Test Case 1b - Check md5 of file content
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /dir1/%s.w1' % (fs_type, ADDR, MIN_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext2(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 2 - write to a file with relative path
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 2 - write with rel path'):
|
||||
# Test Case 2a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x dir1/%s.w2 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
assert('20480 bytes written' in ''.join(output))
|
||||
|
||||
# Test Case 2b - Check md5 of file content
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x dir1/%s.w2' % (fs_type, ADDR, MIN_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext3(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 3 - write to a file with invalid path
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 3 - write with invalid path'):
|
||||
# Test Case 3 - Check if command expectedly failed
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/none/%s.w3 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
assert('Unable to write "/dir1/none/' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext4(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 4 - write at non-zero offset, enlarging file size
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 4 - write at non-zero offset, enlarging file size'):
|
||||
# Test Case 4a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w4 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/%s.w4 $filesize 0x1400'
|
||||
% (fs_type, ADDR, MIN_FILE))
|
||||
assert('20480 bytes written' in output)
|
||||
|
||||
# Test Case 4b - Check size of written file
|
||||
output = u_boot_console.run_command_list([
|
||||
'%ssize host 0:0 /dir1/%s.w4' % (fs_type, MIN_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=6400' in ''.join(output))
|
||||
|
||||
# Test Case 4c - Check md5 of file content
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /dir1/%s.w4' % (fs_type, ADDR, MIN_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[1] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext5(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 5 - write at non-zero offset, shrinking file size
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 5 - write at non-zero offset, shrinking file size'):
|
||||
# Test Case 5a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w5 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/%s.w5 0x1400 0x1400'
|
||||
% (fs_type, ADDR, MIN_FILE))
|
||||
assert('5120 bytes written' in output)
|
||||
|
||||
# Test Case 5b - Check size of written file
|
||||
output = u_boot_console.run_command_list([
|
||||
'%ssize host 0:0 /dir1/%s.w5' % (fs_type, MIN_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=2800' in ''.join(output))
|
||||
|
||||
# Test Case 5c - Check md5 of file content
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /dir1/%s.w5' % (fs_type, ADDR, MIN_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[2] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext6(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 6 - write nothing at the start, truncating to zero
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 6 - write nothing at the start, truncating to zero'):
|
||||
# Test Case 6a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w6 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/%s.w6 0 0'
|
||||
% (fs_type, ADDR, MIN_FILE))
|
||||
assert('0 bytes written' in output)
|
||||
|
||||
# Test Case 6b - Check size of written file
|
||||
output = u_boot_console.run_command_list([
|
||||
'%ssize host 0:0 /dir1/%s.w6' % (fs_type, MIN_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=0' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext7(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 7 - write at the end (append)
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 7 - write at the end (append)'):
|
||||
# Test Case 7a - Check if command successfully returned
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w7 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/%s.w7 $filesize $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE))
|
||||
assert('20480 bytes written' in output)
|
||||
|
||||
# Test Case 7b - Check size of written file
|
||||
output = u_boot_console.run_command_list([
|
||||
'%ssize host 0:0 /dir1/%s.w7' % (fs_type, MIN_FILE),
|
||||
'printenv filesize',
|
||||
'setenv filesize'])
|
||||
assert('filesize=a000' in ''.join(output))
|
||||
|
||||
# Test Case 7c - Check md5 of file content
|
||||
output = u_boot_console.run_command_list([
|
||||
'mw.b %x 00 100' % ADDR,
|
||||
'%sload host 0:0 %x /dir1/%s.w7' % (fs_type, ADDR, MIN_FILE),
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[3] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext8(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 8 - write at offset beyond the end of file
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 8 - write beyond the end'):
|
||||
# Test Case 8a - Check if command expectedly failed
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w8 $filesize'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/%s.w8 0x1400 %x'
|
||||
% (fs_type, ADDR, MIN_FILE, 0x100000 + 0x1400))
|
||||
assert('Unable to write "/dir1' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext9(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
Test Case 9 - write to a non-existing file at non-zero offset
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 9 - write to non-existing file with non-zero offset'):
|
||||
# Test Case 9a - Check if command expectedly failed
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
|
||||
'%swrite host 0:0 %x /dir1/%s.w9 0x1400 0x1400'
|
||||
% (fs_type, ADDR, MIN_FILE)])
|
||||
assert('Unable to write "/dir1' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext10(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
'Test Case 10 - create/delete as many directories under root directory
|
||||
as amount of directory entries goes beyond one cluster size)'
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 10 - create/delete (many)'):
|
||||
# Test Case 10a - Create many files
|
||||
# Please note that the size of directory entry is 32 bytes.
|
||||
# So one typical cluster may holds 64 (2048/32) entries.
|
||||
output = u_boot_console.run_command(
|
||||
'host bind 0 %s' % fs_img)
|
||||
|
||||
for i in range(0, 66):
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /FILE0123456789_%02x 100'
|
||||
% (fs_type, ADDR, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
|
||||
assert('FILE0123456789_00' in output)
|
||||
assert('FILE0123456789_41' in output)
|
||||
|
||||
# Test Case 10b - Delete many files
|
||||
for i in range(0, 66):
|
||||
output = u_boot_console.run_command(
|
||||
'%srm host 0:0 /FILE0123456789_%02x'
|
||||
% (fs_type, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
|
||||
assert(not 'FILE0123456789_00' in output)
|
||||
assert(not 'FILE0123456789_41' in output)
|
||||
|
||||
# Test Case 10c - Create many files again
|
||||
# Please note no.64 and 65 are intentionally re-created
|
||||
for i in range(64, 128):
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /FILE0123456789_%02x 100'
|
||||
% (fs_type, ADDR, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
|
||||
assert('FILE0123456789_40' in output)
|
||||
assert('FILE0123456789_79' in output)
|
||||
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_fs_ext11(self, u_boot_console, fs_obj_ext):
|
||||
"""
|
||||
'Test Case 11 - create/delete as many directories under non-root
|
||||
directory as amount of directory entries goes beyond one cluster size)'
|
||||
"""
|
||||
fs_type,fs_img,md5val = fs_obj_ext
|
||||
with u_boot_console.log.section('Test Case 11 - create/delete (many)'):
|
||||
# Test Case 11a - Create many files
|
||||
# Please note that the size of directory entry is 32 bytes.
|
||||
# So one typical cluster may holds 64 (2048/32) entries.
|
||||
output = u_boot_console.run_command(
|
||||
'host bind 0 %s' % fs_img)
|
||||
|
||||
for i in range(0, 66):
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/FILE0123456789_%02x 100'
|
||||
% (fs_type, ADDR, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
|
||||
assert('FILE0123456789_00' in output)
|
||||
assert('FILE0123456789_41' in output)
|
||||
|
||||
# Test Case 11b - Delete many files
|
||||
for i in range(0, 66):
|
||||
output = u_boot_console.run_command(
|
||||
'%srm host 0:0 /dir1/FILE0123456789_%02x'
|
||||
% (fs_type, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
|
||||
assert(not 'FILE0123456789_00' in output)
|
||||
assert(not 'FILE0123456789_41' in output)
|
||||
|
||||
# Test Case 11c - Create many files again
|
||||
# Please note no.64 and 65 are intentionally re-created
|
||||
for i in range(64, 128):
|
||||
output = u_boot_console.run_command(
|
||||
'%swrite host 0:0 %x /dir1/FILE0123456789_%02x 100'
|
||||
% (fs_type, ADDR, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
|
||||
assert('FILE0123456789_40' in output)
|
||||
assert('FILE0123456789_79' in output)
|
||||
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
121
test/py/tests/test_fs/test_mkdir.py
Normal file
121
test/py/tests/test_fs/test_mkdir.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
|
||||
#
|
||||
# U-Boot File System:mkdir Test
|
||||
|
||||
"""
|
||||
This test verifies mkdir operation on file system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from fstest_helpers import assert_fs_integrity
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.slow
|
||||
class TestMkdir(object):
|
||||
def test_mkdir1(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
Test Case 1 - create a directory under a root
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 1 - mkdir'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 dir1' % fs_type,
|
||||
'%sls host 0:0 /' % fs_type])
|
||||
assert('dir1/' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir1' % fs_type)
|
||||
assert('./' in output)
|
||||
assert('../' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
|
||||
def test_mkdir2(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
Test Case 2 - create a directory under a sub-directory
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 2 - mkdir (sub-sub directory)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 dir1/dir2' % fs_type,
|
||||
'%sls host 0:0 dir1' % fs_type])
|
||||
assert('dir2/' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir1/dir2' % fs_type)
|
||||
assert('./' in output)
|
||||
assert('../' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_mkdir3(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
Test Case 3 - trying to create a directory with a non-existing
|
||||
path should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 3 - mkdir (non-existing path)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 none/dir3' % fs_type])
|
||||
assert('Unable to create a directory' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_mkdir4(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
Test Case 4 - trying to create "." should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 4 - mkdir (".")'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 .' % fs_type])
|
||||
assert('Unable to create a directory' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_mkdir5(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
Test Case 5 - trying to create ".." should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 5 - mkdir ("..")'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 ..' % fs_type])
|
||||
assert('Unable to create a directory' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_mkdir6(self, u_boot_console, fs_obj_mkdir):
|
||||
"""
|
||||
'Test Case 6 - create as many directories as amount of directory
|
||||
entries goes beyond a cluster size)'
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_mkdir
|
||||
with u_boot_console.log.section('Test Case 6 - mkdir (create many)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%smkdir host 0:0 dir6' % fs_type,
|
||||
'%sls host 0:0 /' % fs_type])
|
||||
assert('dir6/' in ''.join(output))
|
||||
|
||||
for i in range(0, 20):
|
||||
output = u_boot_console.run_command(
|
||||
'%smkdir host 0:0 dir6/0123456789abcdef%02x'
|
||||
% (fs_type, i))
|
||||
output = u_boot_console.run_command('%sls host 0:0 dir6' % fs_type)
|
||||
assert('0123456789abcdef00/' in output)
|
||||
assert('0123456789abcdef13/' in output)
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir6/0123456789abcdef13/.' % fs_type)
|
||||
assert('./' in output)
|
||||
assert('../' in output)
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir6/0123456789abcdef13/..' % fs_type)
|
||||
assert('0123456789abcdef00/' in output)
|
||||
assert('0123456789abcdef13/' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
130
test/py/tests/test_fs/test_symlink.py
Normal file
130
test/py/tests/test_fs/test_symlink.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2019, Texas Instrument
|
||||
# Author: Jean-Jacques Hiblot <jjhiblot@ti.com>
|
||||
#
|
||||
# U-Boot File System:symlink Test
|
||||
|
||||
"""
|
||||
This test verifies unlink operation (deleting a file or a directory)
|
||||
on file system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import re
|
||||
from fstest_defs import *
|
||||
from fstest_helpers import assert_fs_integrity
|
||||
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.slow
|
||||
class TestSymlink(object):
|
||||
def test_symlink1(self, u_boot_console, fs_obj_symlink):
|
||||
"""
|
||||
Test Case 1 - create a link. and follow it when reading
|
||||
"""
|
||||
fs_type, fs_img, md5val = fs_obj_symlink
|
||||
with u_boot_console.log.section('Test Case 1 - create link and read'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'setenv filesize',
|
||||
'ln host 0:0 %s /%s.link ' % (SMALL_FILE, SMALL_FILE),
|
||||
])
|
||||
assert('' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'%sload host 0:0 %x /%s.link' % (fs_type, ADDR, SMALL_FILE),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 4b - Read full 1MB of small file
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_symlink2(self, u_boot_console, fs_obj_symlink):
|
||||
"""
|
||||
Test Case 2 - create chained links
|
||||
"""
|
||||
fs_type, fs_img, md5val = fs_obj_symlink
|
||||
with u_boot_console.log.section('Test Case 2 - create chained links'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'setenv filesize',
|
||||
'ln host 0:0 %s /%s.link1 ' % (SMALL_FILE, SMALL_FILE),
|
||||
'ln host 0:0 /%s.link1 /SUBDIR/%s.link2' % (
|
||||
SMALL_FILE, SMALL_FILE),
|
||||
'ln host 0:0 SUBDIR/%s.link2 /%s.link3' % (
|
||||
SMALL_FILE, SMALL_FILE),
|
||||
])
|
||||
assert('' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'%sload host 0:0 %x /%s.link3' % (fs_type, ADDR, SMALL_FILE),
|
||||
'printenv filesize'])
|
||||
assert('filesize=100000' in ''.join(output))
|
||||
|
||||
# Test Case 4b - Read full 1MB of small file
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[0] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_symlink3(self, u_boot_console, fs_obj_symlink):
|
||||
"""
|
||||
Test Case 3 - replace file/link with link
|
||||
"""
|
||||
fs_type, fs_img, md5val = fs_obj_symlink
|
||||
with u_boot_console.log.section('Test Case 1 - create link and read'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'setenv filesize',
|
||||
'ln host 0:0 %s /%s ' % (MEDIUM_FILE, SMALL_FILE),
|
||||
'ln host 0:0 %s /%s.link ' % (MEDIUM_FILE, MEDIUM_FILE),
|
||||
])
|
||||
assert('' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
|
||||
'printenv filesize'])
|
||||
assert('filesize=a00000' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[1] in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'ln host 0:0 %s.link /%s ' % (MEDIUM_FILE, SMALL_FILE),
|
||||
'%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
|
||||
'printenv filesize'])
|
||||
assert('filesize=a00000' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'md5sum %x $filesize' % ADDR,
|
||||
'setenv filesize'])
|
||||
assert(md5val[1] in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_symlink4(self, u_boot_console, fs_obj_symlink):
|
||||
"""
|
||||
Test Case 4 - create a broken link
|
||||
"""
|
||||
fs_type, fs_img, md5val = fs_obj_symlink
|
||||
with u_boot_console.log.section('Test Case 1 - create link and read'):
|
||||
|
||||
output = u_boot_console.run_command_list([
|
||||
'setenv filesize',
|
||||
'ln host 0:0 nowhere /link ',
|
||||
])
|
||||
assert('' in ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sload host 0:0 %x /link' %
|
||||
(fs_type, ADDR))
|
||||
with u_boot_console.disable_check('error_notification'):
|
||||
output = u_boot_console.run_command('printenv filesize')
|
||||
assert('"filesize" not defined' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
118
test/py/tests/test_fs/test_unlink.py
Normal file
118
test/py/tests/test_fs/test_unlink.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Linaro Limited
|
||||
# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
|
||||
#
|
||||
# U-Boot File System:unlink Test
|
||||
|
||||
"""
|
||||
This test verifies unlink operation (deleting a file or a directory)
|
||||
on file system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from fstest_helpers import assert_fs_integrity
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.slow
|
||||
class TestUnlink(object):
|
||||
def test_unlink1(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 1 - delete a file
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 1 - unlink (file)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir1/file1' % fs_type,
|
||||
'%sls host 0:0 dir1/file1' % fs_type])
|
||||
assert('' == ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir1/' % fs_type)
|
||||
assert(not 'file1' in output)
|
||||
assert('file2' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink2(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 2 - delete many files
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 2 - unlink (many)'):
|
||||
output = u_boot_console.run_command('host bind 0 %s' % fs_img)
|
||||
|
||||
for i in range(0, 20):
|
||||
output = u_boot_console.run_command_list([
|
||||
'%srm host 0:0 dir2/0123456789abcdef%02x' % (fs_type, i),
|
||||
'%sls host 0:0 dir2/0123456789abcdef%02x' % (fs_type, i)])
|
||||
assert('' == ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 dir2' % fs_type)
|
||||
assert('0 file(s), 2 dir(s)' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink3(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 3 - trying to delete a non-existing file should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 3 - unlink (non-existing)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir1/nofile' % fs_type])
|
||||
assert('nofile: doesn\'t exist' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink4(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 4 - delete an empty directory
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 4 - unlink (directory)'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir4' % fs_type])
|
||||
assert('' == ''.join(output))
|
||||
|
||||
output = u_boot_console.run_command(
|
||||
'%sls host 0:0 /' % fs_type)
|
||||
assert(not 'dir4' in output)
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink5(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 5 - trying to deleting a non-empty directory ".."
|
||||
should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 5 - unlink ("non-empty directory")'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir5' % fs_type])
|
||||
assert('directory is not empty' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink6(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 6 - trying to deleting a "." should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 6 - unlink (".")'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir5/.' % fs_type])
|
||||
assert('directory is not empty' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
|
||||
def test_unlink7(self, u_boot_console, fs_obj_unlink):
|
||||
"""
|
||||
Test Case 7 - trying to deleting a ".." should fail
|
||||
"""
|
||||
fs_type,fs_img = fs_obj_unlink
|
||||
with u_boot_console.log.section('Test Case 7 - unlink ("..")'):
|
||||
output = u_boot_console.run_command_list([
|
||||
'host bind 0 %s' % fs_img,
|
||||
'%srm host 0:0 dir5/..' % fs_type])
|
||||
assert('directory is not empty' in ''.join(output))
|
||||
assert_fs_integrity(fs_type, fs_img)
|
||||
178
test/py/tests/test_gpt.py
Normal file
178
test/py/tests/test_gpt.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2017 Alison Chaiken
|
||||
# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test GPT manipulation commands.
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
These tests rely on a 4 MB disk image, which is automatically created by
|
||||
the test.
|
||||
"""
|
||||
|
||||
class GptTestDiskImage(object):
|
||||
"""Disk Image used by the GPT tests."""
|
||||
|
||||
def __init__(self, u_boot_console):
|
||||
"""Initialize a new GptTestDiskImage object.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
filename = 'test_gpt_disk_image.bin'
|
||||
|
||||
persistent = u_boot_console.config.persistent_data_dir + '/' + filename
|
||||
self.path = u_boot_console.config.result_dir + '/' + filename
|
||||
|
||||
with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
|
||||
if os.path.exists(persistent):
|
||||
u_boot_console.log.action('Disk image file ' + persistent +
|
||||
' already exists')
|
||||
else:
|
||||
u_boot_console.log.action('Generating ' + persistent)
|
||||
fd = os.open(persistent, os.O_RDWR | os.O_CREAT)
|
||||
os.ftruncate(fd, 4194304)
|
||||
os.close(fd)
|
||||
cmd = ('sgdisk',
|
||||
'--disk-guid=375a56f7-d6c9-4e81-b5f0-09d41ca89efe',
|
||||
persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
# part1 offset 1MB size 1MB
|
||||
cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1',
|
||||
persistent)
|
||||
# part2 offset 2MB size 1.5MB
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
cmd = ('sgdisk', '--new=2:4096:7167', '--change-name=2:part2',
|
||||
persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
cmd = ('sgdisk', '--load-backup=' + persistent)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
cmd = ('cp', persistent, self.path)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
gtdi = None
|
||||
@pytest.fixture(scope='function')
|
||||
def state_disk_image(u_boot_console):
|
||||
"""pytest fixture to provide a GptTestDiskImage object to tests.
|
||||
This is function-scoped because it uses u_boot_console, which is also
|
||||
function-scoped. However, we don't need to actually do any function-scope
|
||||
work, so this simply returns the same object over and over each time."""
|
||||
|
||||
global gtdi
|
||||
if not gtdi:
|
||||
gtdi = GptTestDiskImage(u_boot_console)
|
||||
return gtdi
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.buildconfigspec('cmd_part')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_read(state_disk_image, u_boot_console):
|
||||
"""Test the gpt read command."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('gpt read host 0')
|
||||
assert 'Start 1MiB, size 1MiB' in output
|
||||
assert 'Block size 512, name part1' in output
|
||||
assert 'Start 2MiB, size 1MiB' in output
|
||||
assert 'Block size 512, name part2' in output
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000800 0x00000fff "part1"' in output
|
||||
assert '0x00001000 0x00001bff "part2"' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_verify(state_disk_image, u_boot_console):
|
||||
"""Test the gpt verify command."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('gpt verify host 0')
|
||||
assert 'Verify GPT: success!' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_guid(state_disk_image, u_boot_console):
|
||||
"""Test the gpt guid command."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('gpt guid host 0')
|
||||
assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_save_guid(state_disk_image, u_boot_console):
|
||||
"""Test the gpt guid command to save GUID into a string."""
|
||||
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_gpt', 'n') != 'y':
|
||||
pytest.skip('gpt command not supported')
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('gpt guid host 0 newguid')
|
||||
output = u_boot_console.run_command('printenv newguid')
|
||||
assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt_rename')
|
||||
@pytest.mark.buildconfigspec('cmd_part')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_rename_partition(state_disk_image, u_boot_console):
|
||||
"""Test the gpt rename command to write partition names."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
u_boot_console.run_command('gpt rename host 0 1 first')
|
||||
output = u_boot_console.run_command('gpt read host 0')
|
||||
assert 'name first' in output
|
||||
u_boot_console.run_command('gpt rename host 0 2 second')
|
||||
output = u_boot_console.run_command('gpt read host 0')
|
||||
assert 'name second' in output
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000800 0x00000fff "first"' in output
|
||||
assert '0x00001000 0x00001bff "second"' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt_rename')
|
||||
@pytest.mark.buildconfigspec('cmd_part')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_swap_partitions(state_disk_image, u_boot_console):
|
||||
"""Test the gpt swap command to exchange two partition names."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000800 0x00000fff "first"' in output
|
||||
assert '0x00001000 0x00001bff "second"' in output
|
||||
u_boot_console.run_command('gpt swap host 0 first second')
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000800 0x00000fff "second"' in output
|
||||
assert '0x00001000 0x00001bff "first"' in output
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_gpt')
|
||||
@pytest.mark.buildconfigspec('cmd_part')
|
||||
@pytest.mark.requiredtool('sgdisk')
|
||||
def test_gpt_write(state_disk_image, u_boot_console):
|
||||
"""Test the gpt write command."""
|
||||
|
||||
u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
|
||||
output = u_boot_console.run_command('gpt write host 0 "name=all,size=0"')
|
||||
assert 'Writing GPT: success!' in output
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000022 0x00001fde "all"' in output
|
||||
output = u_boot_console.run_command('gpt write host 0 "uuid_disk=375a56f7-d6c9-4e81-b5f0-09d41ca89efe;name=first,start=1M,size=1M;name=second,start=0x200000,size=0x180000;"')
|
||||
assert 'Writing GPT: success!' in output
|
||||
output = u_boot_console.run_command('part list host 0')
|
||||
assert '0x00000800 0x00000fff "first"' in output
|
||||
assert '0x00001000 0x00001bff "second"' in output
|
||||
output = u_boot_console.run_command('gpt guid host 0')
|
||||
assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
|
||||
15
test/py/tests/test_handoff.py
Normal file
15
test/py/tests/test_handoff.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2016 Google, Inc
|
||||
|
||||
import pytest
|
||||
|
||||
# Magic number to check that SPL handoff is working
|
||||
TEST_HANDOFF_MAGIC = 0x14f93c7b
|
||||
|
||||
@pytest.mark.boardspec('sandbox_spl')
|
||||
@pytest.mark.buildconfigspec('spl')
|
||||
def test_handoff(u_boot_console):
|
||||
"""Test that of-platdata can be generated and used in sandbox"""
|
||||
cons = u_boot_console
|
||||
response = cons.run_command('sb handoff')
|
||||
assert ('SPL handoff magic %x' % TEST_HANDOFF_MAGIC) in response
|
||||
8
test/py/tests/test_help.py
Normal file
8
test/py/tests/test_help.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
def test_help(u_boot_console):
|
||||
"""Test that the "help" command can be executed."""
|
||||
|
||||
u_boot_console.run_command('help')
|
||||
161
test/py/tests/test_hush_if_test.py
Normal file
161
test/py/tests/test_hush_if_test.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test operation of the "if" shell command.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.buildconfigspec('hush_parser')
|
||||
|
||||
# The list of "if test" conditions to test.
|
||||
subtests = (
|
||||
# Base if functionality.
|
||||
|
||||
('true', True),
|
||||
('false', False),
|
||||
|
||||
# Basic operators.
|
||||
|
||||
('test aaa = aaa', True),
|
||||
('test aaa = bbb', False),
|
||||
|
||||
('test aaa != bbb', True),
|
||||
('test aaa != aaa', False),
|
||||
|
||||
('test aaa < bbb', True),
|
||||
('test bbb < aaa', False),
|
||||
|
||||
('test bbb > aaa', True),
|
||||
('test aaa > bbb', False),
|
||||
|
||||
('test 123 -eq 123', True),
|
||||
('test 123 -eq 456', False),
|
||||
|
||||
('test 123 -ne 456', True),
|
||||
('test 123 -ne 123', False),
|
||||
|
||||
('test 123 -lt 456', True),
|
||||
('test 123 -lt 123', False),
|
||||
('test 456 -lt 123', False),
|
||||
|
||||
('test 123 -le 456', True),
|
||||
('test 123 -le 123', True),
|
||||
('test 456 -le 123', False),
|
||||
|
||||
('test 456 -gt 123', True),
|
||||
('test 123 -gt 123', False),
|
||||
('test 123 -gt 456', False),
|
||||
|
||||
('test 456 -ge 123', True),
|
||||
('test 123 -ge 123', True),
|
||||
('test 123 -ge 456', False),
|
||||
|
||||
('test -z ""', True),
|
||||
('test -z "aaa"', False),
|
||||
|
||||
('test -n "aaa"', True),
|
||||
('test -n ""', False),
|
||||
|
||||
# Inversion of simple tests.
|
||||
|
||||
('test ! aaa = aaa', False),
|
||||
('test ! aaa = bbb', True),
|
||||
('test ! ! aaa = aaa', True),
|
||||
('test ! ! aaa = bbb', False),
|
||||
|
||||
# Binary operators.
|
||||
|
||||
('test aaa != aaa -o bbb != bbb', False),
|
||||
('test aaa != aaa -o bbb = bbb', True),
|
||||
('test aaa = aaa -o bbb != bbb', True),
|
||||
('test aaa = aaa -o bbb = bbb', True),
|
||||
|
||||
('test aaa != aaa -a bbb != bbb', False),
|
||||
('test aaa != aaa -a bbb = bbb', False),
|
||||
('test aaa = aaa -a bbb != bbb', False),
|
||||
('test aaa = aaa -a bbb = bbb', True),
|
||||
|
||||
# Inversion within binary operators.
|
||||
|
||||
('test ! aaa != aaa -o ! bbb != bbb', True),
|
||||
('test ! aaa != aaa -o ! bbb = bbb', True),
|
||||
('test ! aaa = aaa -o ! bbb != bbb', True),
|
||||
('test ! aaa = aaa -o ! bbb = bbb', False),
|
||||
|
||||
('test ! ! aaa != aaa -o ! ! bbb != bbb', False),
|
||||
('test ! ! aaa != aaa -o ! ! bbb = bbb', True),
|
||||
('test ! ! aaa = aaa -o ! ! bbb != bbb', True),
|
||||
('test ! ! aaa = aaa -o ! ! bbb = bbb', True),
|
||||
|
||||
# -z operator.
|
||||
|
||||
('test -z "$ut_var_nonexistent"', True),
|
||||
('test -z "$ut_var_exists"', False),
|
||||
)
|
||||
|
||||
def exec_hush_if(u_boot_console, expr, result):
|
||||
"""Execute a shell "if" command, and validate its result."""
|
||||
|
||||
config = u_boot_console.config.buildconfig
|
||||
maxargs = int(config.get('config_sys_maxargs', '0'))
|
||||
args = len(expr.split(' ')) - 1
|
||||
if args > maxargs:
|
||||
u_boot_console.log.warning('CONFIG_SYS_MAXARGS too low; need ' +
|
||||
str(args))
|
||||
pytest.skip()
|
||||
|
||||
cmd = 'if ' + expr + '; then echo true; else echo false; fi'
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert response.strip() == str(result).lower()
|
||||
|
||||
def test_hush_if_test_setup(u_boot_console):
|
||||
"""Set up environment variables used during the "if" tests."""
|
||||
|
||||
u_boot_console.run_command('setenv ut_var_nonexistent')
|
||||
u_boot_console.run_command('setenv ut_var_exists 1')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.parametrize('expr,result', subtests)
|
||||
def test_hush_if_test(u_boot_console, expr, result):
|
||||
"""Test a single "if test" condition."""
|
||||
|
||||
exec_hush_if(u_boot_console, expr, result)
|
||||
|
||||
def test_hush_if_test_teardown(u_boot_console):
|
||||
"""Clean up environment variables used during the "if" tests."""
|
||||
|
||||
u_boot_console.run_command('setenv ut_var_exists')
|
||||
|
||||
# We might test this on real filesystems via UMS, DFU, 'save', etc.
|
||||
# Of those, only UMS currently allows file removal though.
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_hush_if_test_host_file_exists(u_boot_console):
|
||||
"""Test the "if test -e" shell command."""
|
||||
|
||||
test_file = u_boot_console.config.result_dir + \
|
||||
'/creating_this_file_breaks_u_boot_tests'
|
||||
|
||||
try:
|
||||
os.unlink(test_file)
|
||||
except:
|
||||
pass
|
||||
assert not os.path.exists(test_file)
|
||||
|
||||
expr = 'test -e hostfs - ' + test_file
|
||||
exec_hush_if(u_boot_console, expr, False)
|
||||
|
||||
try:
|
||||
with open(test_file, 'wb'):
|
||||
pass
|
||||
assert os.path.exists(test_file)
|
||||
|
||||
expr = 'test -e hostfs - ' + test_file
|
||||
exec_hush_if(u_boot_console, expr, True)
|
||||
finally:
|
||||
os.unlink(test_file)
|
||||
|
||||
expr = 'test -e hostfs - ' + test_file
|
||||
exec_hush_if(u_boot_console, expr, False)
|
||||
132
test/py/tests/test_log.py
Normal file
132
test/py/tests/test_log.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2016, Google Inc.
|
||||
#
|
||||
# U-Boot Verified Boot Test
|
||||
|
||||
"""
|
||||
This tests U-Boot logging. It uses the 'log test' command with various options
|
||||
and checks that the output is correct.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
LOGL_FIRST, LOGL_WARNING, LOGL_INFO = (0, 4, 6)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_log')
|
||||
def test_log(u_boot_console):
|
||||
"""Test that U-Boot logging works correctly."""
|
||||
def check_log_entries(lines, mask, max_level=LOGL_INFO):
|
||||
"""Check that the expected log records appear in the output
|
||||
|
||||
Args:
|
||||
lines: iterator containing lines to check
|
||||
mask: bit mask to select which lines to check for:
|
||||
bit 0: standard log line
|
||||
bit 1: _log line
|
||||
max_level: maximum log level to expect in the output
|
||||
"""
|
||||
for i in range(max_level):
|
||||
if mask & 1:
|
||||
assert 'log_run() log %d' % i == next(lines)
|
||||
if mask & 3:
|
||||
assert 'func() _log %d' % i == next(lines)
|
||||
|
||||
def run_test(testnum):
|
||||
"""Run a particular test number (the 'log test' command)
|
||||
|
||||
Args:
|
||||
testnum: Test number to run
|
||||
Returns:
|
||||
iterator containing the lines output from the command
|
||||
"""
|
||||
with cons.log.section('basic'):
|
||||
output = u_boot_console.run_command('log test %d' % testnum)
|
||||
split = output.replace('\r', '').splitlines()
|
||||
lines = iter(split)
|
||||
assert 'test %d' % testnum == next(lines)
|
||||
return lines
|
||||
|
||||
def test0():
|
||||
lines = run_test(0)
|
||||
check_log_entries(lines, 3)
|
||||
|
||||
def test1():
|
||||
lines = run_test(1)
|
||||
check_log_entries(lines, 3)
|
||||
|
||||
def test2():
|
||||
lines = run_test(2)
|
||||
|
||||
def test3():
|
||||
lines = run_test(3)
|
||||
check_log_entries(lines, 2)
|
||||
|
||||
def test4():
|
||||
lines = run_test(4)
|
||||
assert next(lines, None) == None
|
||||
|
||||
def test5():
|
||||
lines = run_test(5)
|
||||
check_log_entries(lines, 2)
|
||||
|
||||
def test6():
|
||||
lines = run_test(6)
|
||||
check_log_entries(lines, 3)
|
||||
|
||||
def test7():
|
||||
lines = run_test(7)
|
||||
check_log_entries(lines, 3, LOGL_WARNING)
|
||||
|
||||
def test8():
|
||||
lines = run_test(8)
|
||||
check_log_entries(lines, 3)
|
||||
|
||||
def test9():
|
||||
lines = run_test(9)
|
||||
check_log_entries(lines, 3)
|
||||
|
||||
def test10():
|
||||
lines = run_test(10)
|
||||
for i in range(7):
|
||||
assert 'log_test() level %d' % i == next(lines)
|
||||
|
||||
# TODO(sjg@chromium.org): Consider structuring this as separate tests
|
||||
cons = u_boot_console
|
||||
test0()
|
||||
test1()
|
||||
test2()
|
||||
test3()
|
||||
test4()
|
||||
test5()
|
||||
test6()
|
||||
test7()
|
||||
test8()
|
||||
test9()
|
||||
test10()
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_log')
|
||||
def test_log_format(u_boot_console):
|
||||
"""Test the 'log format' and 'log rec' commands"""
|
||||
def run_with_format(fmt, expected_output):
|
||||
"""Set up the log format and then write a log record
|
||||
|
||||
Args:
|
||||
fmt: Format to use for 'log format'
|
||||
expected_output: Expected output from the 'log rec' command
|
||||
"""
|
||||
output = cons.run_command('log format %s' % fmt)
|
||||
assert output == ''
|
||||
output = cons.run_command('log rec arch notice file.c 123 func msg')
|
||||
assert output == expected_output
|
||||
|
||||
cons = u_boot_console
|
||||
with cons.log.section('format'):
|
||||
run_with_format('all', 'NOTICE.arch,file.c:123-func() msg')
|
||||
output = cons.run_command('log format')
|
||||
assert output == 'Log format: clFLfm'
|
||||
|
||||
run_with_format('fm', 'func() msg')
|
||||
run_with_format('clfm', 'NOTICE.arch,func() msg')
|
||||
run_with_format('FLfm', 'file.c:123-func() msg')
|
||||
run_with_format('lm', 'NOTICE. msg')
|
||||
run_with_format('m', 'msg')
|
||||
36
test/py/tests/test_md.py
Normal file
36
test/py/tests/test_md.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_md(u_boot_console):
|
||||
"""Test that md reads memory as expected, and that memory can be modified
|
||||
using the mw command."""
|
||||
|
||||
ram_base = u_boot_utils.find_ram_base(u_boot_console)
|
||||
addr = '%08x' % ram_base
|
||||
val = 'a5f09876'
|
||||
expected_response = addr + ': ' + val
|
||||
u_boot_console.run_command('mw ' + addr + ' 0 10')
|
||||
response = u_boot_console.run_command('md ' + addr + ' 10')
|
||||
assert(not (expected_response in response))
|
||||
u_boot_console.run_command('mw ' + addr + ' ' + val)
|
||||
response = u_boot_console.run_command('md ' + addr + ' 10')
|
||||
assert(expected_response in response)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_md_repeat(u_boot_console):
|
||||
"""Test command repeat (via executing an empty command) operates correctly
|
||||
for "md"; the command must repeat and dump an incrementing address."""
|
||||
|
||||
ram_base = u_boot_utils.find_ram_base(u_boot_console)
|
||||
addr_base = '%08x' % ram_base
|
||||
words = 0x10
|
||||
addr_repeat = '%08x' % (ram_base + (words * 4))
|
||||
u_boot_console.run_command('md %s %x' % (addr_base, words))
|
||||
response = u_boot_console.run_command('')
|
||||
expected_response = addr_repeat + ': '
|
||||
assert(expected_response in response)
|
||||
286
test/py/tests/test_mmc_rd.py
Normal file
286
test/py/tests/test_mmc_rd.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test U-Boot's "mmc read" command. The test reads data from the eMMC or SD
|
||||
# card, and validates the no errors occurred, and that the expected data was
|
||||
# read if the test configuration contains a CRC of the expected data.
|
||||
|
||||
import pytest
|
||||
import time
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
This test relies on boardenv_* to containing configuration values to define
|
||||
which MMC devices should be tested. For example:
|
||||
|
||||
# Configuration data for test_mmc_dev, test_mmc_rescan, test_mmc_info; defines
|
||||
# whole MMC devices that mmc dev/rescan/info commands may operate upon.
|
||||
env__mmc_dev_configs = (
|
||||
{
|
||||
'fixture_id': 'emmc-boot0',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 1,
|
||||
'info_device': ???,
|
||||
'info_speed': ???,
|
||||
'info_mode': ???,
|
||||
'info_buswidth': ???.
|
||||
},
|
||||
{
|
||||
'fixture_id': 'emmc-boot1',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 2,
|
||||
'info_device': ???,
|
||||
'info_speed': ???,
|
||||
'info_mode': ???,
|
||||
'info_buswidth': ???.
|
||||
},
|
||||
{
|
||||
'fixture_id': 'emmc-data',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 0,
|
||||
'info_device': ???,
|
||||
'info_speed': ???,
|
||||
'info_mode': ???,
|
||||
'info_buswidth': ???.
|
||||
},
|
||||
{
|
||||
'fixture_id': 'sd',
|
||||
'is_emmc': False,
|
||||
'devid': 1,
|
||||
'partid': None,
|
||||
'info_device': ???,
|
||||
'info_speed': ???,
|
||||
'info_mode': ???,
|
||||
'info_buswidth': ???.
|
||||
},
|
||||
}
|
||||
|
||||
# Configuration data for test_mmc_rd; defines regions of the MMC (entire
|
||||
# devices, or ranges of sectors) which can be read:
|
||||
env__mmc_rd_configs = (
|
||||
{
|
||||
'fixture_id': 'emmc-boot0',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 1,
|
||||
'sector': 0x10,
|
||||
'count': 1,
|
||||
},
|
||||
{
|
||||
'fixture_id': 'emmc-boot1',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 2,
|
||||
'sector': 0x10,
|
||||
'count': 1,
|
||||
},
|
||||
{
|
||||
'fixture_id': 'emmc-data',
|
||||
'is_emmc': True,
|
||||
'devid': 0,
|
||||
'partid': 0,
|
||||
'sector': 0x10,
|
||||
'count': 0x1000,
|
||||
},
|
||||
{
|
||||
'fixture_id': 'sd-mbr',
|
||||
'is_emmc': False,
|
||||
'devid': 1,
|
||||
'partid': None,
|
||||
'sector': 0,
|
||||
'count': 1,
|
||||
'crc32': '8f6ecf0d',
|
||||
},
|
||||
{
|
||||
'fixture_id': 'sd-large',
|
||||
'is_emmc': False,
|
||||
'devid': 1,
|
||||
'partid': None,
|
||||
'sector': 0x10,
|
||||
'count': 0x1000,
|
||||
},
|
||||
)
|
||||
"""
|
||||
|
||||
def mmc_dev(u_boot_console, is_emmc, devid, partid):
|
||||
"""Run the "mmc dev" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
is_emmc: Whether the device is eMMC
|
||||
devid: Device ID
|
||||
partid: Partition ID
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
# Select MMC device
|
||||
cmd = 'mmc dev %d' % devid
|
||||
if is_emmc:
|
||||
cmd += ' %d' % partid
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert 'no card present' not in response
|
||||
if is_emmc:
|
||||
partid_response = '(part %d)' % partid
|
||||
else:
|
||||
partid_response = ''
|
||||
good_response = 'mmc%d%s is current device' % (devid, partid_response)
|
||||
assert good_response in response
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_mmc_dev(u_boot_console, env__mmc_dev_config):
|
||||
"""Test the "mmc dev" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__mmc_dev_config: The single MMC configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
is_emmc = env__mmc_dev_config['is_emmc']
|
||||
devid = env__mmc_dev_config['devid']
|
||||
partid = env__mmc_dev_config.get('partid', 0)
|
||||
|
||||
# Select MMC device
|
||||
mmc_dev(u_boot_console, is_emmc, devid, partid)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_mmc_rescan(u_boot_console, env__mmc_dev_config):
|
||||
"""Test the "mmc rescan" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__mmc_dev_config: The single MMC configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
is_emmc = env__mmc_dev_config['is_emmc']
|
||||
devid = env__mmc_dev_config['devid']
|
||||
partid = env__mmc_dev_config.get('partid', 0)
|
||||
|
||||
# Select MMC device
|
||||
mmc_dev(u_boot_console, is_emmc, devid, partid)
|
||||
|
||||
# Rescan MMC device
|
||||
cmd = 'mmc rescan'
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert 'no card present' not in response
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_mmc_info(u_boot_console, env__mmc_dev_config):
|
||||
"""Test the "mmc info" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__mmc_dev_config: The single MMC configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
is_emmc = env__mmc_dev_config['is_emmc']
|
||||
devid = env__mmc_dev_config['devid']
|
||||
partid = env__mmc_dev_config.get('partid', 0)
|
||||
info_device = env__mmc_dev_config['info_device']
|
||||
info_speed = env__mmc_dev_config['info_speed']
|
||||
info_mode = env__mmc_dev_config['info_mode']
|
||||
info_buswidth = env__mmc_dev_config['info_buswidth']
|
||||
|
||||
# Select MMC device
|
||||
mmc_dev(u_boot_console, is_emmc, devid, partid)
|
||||
|
||||
# Read MMC device information
|
||||
cmd = 'mmc info'
|
||||
response = u_boot_console.run_command(cmd)
|
||||
good_response = "Device: %s" % info_device
|
||||
assert good_response in response
|
||||
good_response = "Bus Speed: %s" % info_speed
|
||||
assert good_response in response
|
||||
good_response = "Mode : %s" % info_mode
|
||||
assert good_response in response
|
||||
good_response = "Bus Width: %s" % info_buswidth
|
||||
assert good_response in response
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
def test_mmc_rd(u_boot_console, env__mmc_rd_config):
|
||||
"""Test the "mmc read" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__mmc_rd_config: The single MMC configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
is_emmc = env__mmc_rd_config['is_emmc']
|
||||
devid = env__mmc_rd_config['devid']
|
||||
partid = env__mmc_rd_config.get('partid', 0)
|
||||
sector = env__mmc_rd_config.get('sector', 0)
|
||||
count_sectors = env__mmc_rd_config.get('count', 1)
|
||||
expected_crc32 = env__mmc_rd_config.get('crc32', None)
|
||||
read_duration_max = env__mmc_rd_config.get('read_duration_max', 0)
|
||||
|
||||
count_bytes = count_sectors * 512
|
||||
bcfg = u_boot_console.config.buildconfig
|
||||
has_cmd_memory = bcfg.get('config_cmd_memory', 'n') == 'y'
|
||||
has_cmd_crc32 = bcfg.get('config_cmd_crc32', 'n') == 'y'
|
||||
ram_base = u_boot_utils.find_ram_base(u_boot_console)
|
||||
addr = '0x%08x' % ram_base
|
||||
|
||||
# Select MMC device
|
||||
mmc_dev(u_boot_console, is_emmc, devid, partid)
|
||||
|
||||
# Clear target RAM
|
||||
if expected_crc32:
|
||||
if has_cmd_memory and has_cmd_crc32:
|
||||
cmd = 'mw.b %s 0 0x%x' % (addr, count_bytes)
|
||||
u_boot_console.run_command(cmd)
|
||||
|
||||
cmd = 'crc32 %s 0x%x' % (addr, count_bytes)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert expected_crc32 not in response
|
||||
else:
|
||||
u_boot_console.log.warning(
|
||||
'CONFIG_CMD_MEMORY or CONFIG_CMD_CRC32 != y: Skipping RAM clear')
|
||||
|
||||
# Read data
|
||||
cmd = 'mmc read %s %x %x' % (addr, sector, count_sectors)
|
||||
tstart = time.time()
|
||||
response = u_boot_console.run_command(cmd)
|
||||
tend = time.time()
|
||||
good_response = 'MMC read: dev # %d, block # %d, count %d ... %d blocks read: OK' % (
|
||||
devid, sector, count_sectors, count_sectors)
|
||||
assert good_response in response
|
||||
|
||||
# Check target RAM
|
||||
if expected_crc32:
|
||||
if has_cmd_crc32:
|
||||
cmd = 'crc32 %s 0x%x' % (addr, count_bytes)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert expected_crc32 in response
|
||||
else:
|
||||
u_boot_console.log.warning('CONFIG_CMD_CRC32 != y: Skipping check')
|
||||
|
||||
# Check if the command did not take too long
|
||||
if read_duration_max:
|
||||
elapsed = tend - tstart
|
||||
u_boot_console.log.info('Reading %d bytes took %f seconds' %
|
||||
(count_bytes, elapsed))
|
||||
assert elapsed <= (read_duration_max - 0.01)
|
||||
105
test/py/tests/test_mmc_wr.py
Normal file
105
test/py/tests/test_mmc_wr.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2019, Texas Instrument
|
||||
# Author: Jean-Jacques Hiblot <jjhiblot@ti.com>
|
||||
|
||||
# Test U-Boot's "mmc write" command. The test generates random data, writes it
|
||||
# to the eMMC or SD card, then reads it back and performs a comparison.
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
This test relies on boardenv_* to containing configuration values to define
|
||||
which MMC devices should be tested. For example:
|
||||
|
||||
env__mmc_wr_configs = (
|
||||
{
|
||||
"fixture_id": "emmc-boot0",
|
||||
"is_emmc": True,
|
||||
"devid": 1,
|
||||
"partid": 1,
|
||||
"sector": 0x10,
|
||||
"count": 100,
|
||||
"test_iterations": 50,
|
||||
},
|
||||
{
|
||||
"fixture_id": "emmc-boot1",
|
||||
"is_emmc": True,
|
||||
"devid": 1,
|
||||
"partid": 2,
|
||||
"sector": 0x10,
|
||||
"count": 100,
|
||||
"test_iterations": 50,
|
||||
},
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_mmc')
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
@pytest.mark.buildconfigspec('cmd_random')
|
||||
def test_mmc_wr(u_boot_console, env__mmc_wr_config):
|
||||
"""Test the "mmc write" command.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__mmc_wr_config: The single MMC configuration on which
|
||||
to run the test. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
is_emmc = env__mmc_wr_config['is_emmc']
|
||||
devid = env__mmc_wr_config['devid']
|
||||
partid = env__mmc_wr_config.get('partid', 0)
|
||||
sector = env__mmc_wr_config.get('sector', 0)
|
||||
count_sectors = env__mmc_wr_config.get('count', 1)
|
||||
test_iterations = env__mmc_wr_config.get('test_iterations', 1)
|
||||
|
||||
|
||||
count_bytes = count_sectors * 512
|
||||
bcfg = u_boot_console.config.buildconfig
|
||||
ram_base = u_boot_utils.find_ram_base(u_boot_console)
|
||||
src_addr = '0x%08x' % ram_base
|
||||
dst_addr = '0x%08x' % (ram_base + count_bytes)
|
||||
|
||||
|
||||
for i in range(test_iterations):
|
||||
# Generate random data
|
||||
cmd = 'random %s %x' % (src_addr, count_bytes)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
good_response = '%d bytes filled with random data' % (count_bytes)
|
||||
assert good_response in response
|
||||
|
||||
# Select MMC device
|
||||
cmd = 'mmc dev %d' % devid
|
||||
if is_emmc:
|
||||
cmd += ' %d' % partid
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert 'no card present' not in response
|
||||
if is_emmc:
|
||||
partid_response = "(part %d)" % partid
|
||||
else:
|
||||
partid_response = ""
|
||||
good_response = 'mmc%d%s is current device' % (devid, partid_response)
|
||||
assert good_response in response
|
||||
|
||||
# Write data
|
||||
cmd = 'mmc write %s %x %x' % (src_addr, sector, count_sectors)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
good_response = 'MMC write: dev # %d, block # %d, count %d ... %d blocks written: OK' % (devid, sector, count_sectors, count_sectors)
|
||||
assert good_response in response
|
||||
|
||||
# Read data
|
||||
cmd = 'mmc read %s %x %x' % (dst_addr, sector, count_sectors)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
good_response = 'MMC read: dev # %d, block # %d, count %d ... %d blocks read: OK' % (devid, sector, count_sectors, count_sectors)
|
||||
assert good_response in response
|
||||
|
||||
# Compare src and dst data
|
||||
cmd = 'cmp.b %s %s %x' % (src_addr, dst_addr, count_bytes)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
good_response = 'Total of %d byte(s) were the same' % (count_bytes)
|
||||
assert good_response in response
|
||||
208
test/py/tests/test_net.py
Normal file
208
test/py/tests/test_net.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test various network-related functionality, such as the dhcp, ping, and
|
||||
# tftpboot commands.
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on boardenv_* containing configuration values to define
|
||||
which the network environment available for testing. Without this, this test
|
||||
will be automatically skipped.
|
||||
|
||||
For example:
|
||||
|
||||
# Boolean indicating whether the Ethernet device is attached to USB, and hence
|
||||
# USB enumeration needs to be performed prior to network tests.
|
||||
# This variable may be omitted if its value is False.
|
||||
env__net_uses_usb = False
|
||||
|
||||
# Boolean indicating whether the Ethernet device is attached to PCI, and hence
|
||||
# PCI enumeration needs to be performed prior to network tests.
|
||||
# This variable may be omitted if its value is False.
|
||||
env__net_uses_pci = True
|
||||
|
||||
# True if a DHCP server is attached to the network, and should be tested.
|
||||
# If DHCP testing is not possible or desired, this variable may be omitted or
|
||||
# set to False.
|
||||
env__net_dhcp_server = True
|
||||
|
||||
# A list of environment variables that should be set in order to configure a
|
||||
# static IP. If solely relying on DHCP, this variable may be omitted or set to
|
||||
# an empty list.
|
||||
env__net_static_env_vars = [
|
||||
('ipaddr', '10.0.0.100'),
|
||||
('netmask', '255.255.255.0'),
|
||||
('serverip', '10.0.0.1'),
|
||||
]
|
||||
|
||||
# Details regarding a file that may be read from a TFTP server. This variable
|
||||
# may be omitted or set to None if TFTP testing is not possible or desired.
|
||||
env__net_tftp_readable_file = {
|
||||
'fn': 'ubtest-readable.bin',
|
||||
'addr': 0x10000000,
|
||||
'size': 5058624,
|
||||
'crc32': 'c2244b26',
|
||||
}
|
||||
|
||||
# Details regarding a file that may be read from a NFS server. This variable
|
||||
# may be omitted or set to None if NFS testing is not possible or desired.
|
||||
env__net_nfs_readable_file = {
|
||||
'fn': 'ubtest-readable.bin',
|
||||
'addr': 0x10000000,
|
||||
'size': 5058624,
|
||||
'crc32': 'c2244b26',
|
||||
}
|
||||
"""
|
||||
|
||||
net_set_up = False
|
||||
|
||||
def test_net_pre_commands(u_boot_console):
|
||||
"""Execute any commands required to enable network hardware.
|
||||
|
||||
These commands are provided by the boardenv_* file; see the comment at the
|
||||
beginning of this file.
|
||||
"""
|
||||
|
||||
init_usb = u_boot_console.config.env.get('env__net_uses_usb', False)
|
||||
if init_usb:
|
||||
u_boot_console.run_command('usb start')
|
||||
|
||||
init_pci = u_boot_console.config.env.get('env__net_uses_pci', False)
|
||||
if init_pci:
|
||||
u_boot_console.run_command('pci enum')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_dhcp')
|
||||
def test_net_dhcp(u_boot_console):
|
||||
"""Test the dhcp command.
|
||||
|
||||
The boardenv_* file may be used to enable/disable this test; see the
|
||||
comment at the beginning of this file.
|
||||
"""
|
||||
|
||||
test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False)
|
||||
if not test_dhcp:
|
||||
pytest.skip('No DHCP server available')
|
||||
|
||||
u_boot_console.run_command('setenv autoload no')
|
||||
output = u_boot_console.run_command('dhcp')
|
||||
assert 'DHCP client bound to address ' in output
|
||||
|
||||
global net_set_up
|
||||
net_set_up = True
|
||||
|
||||
@pytest.mark.buildconfigspec('net')
|
||||
def test_net_setup_static(u_boot_console):
|
||||
"""Set up a static IP configuration.
|
||||
|
||||
The configuration is provided by the boardenv_* file; see the comment at
|
||||
the beginning of this file.
|
||||
"""
|
||||
|
||||
env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None)
|
||||
if not env_vars:
|
||||
pytest.skip('No static network configuration is defined')
|
||||
|
||||
for (var, val) in env_vars:
|
||||
u_boot_console.run_command('setenv %s %s' % (var, val))
|
||||
|
||||
global net_set_up
|
||||
net_set_up = True
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_ping')
|
||||
def test_net_ping(u_boot_console):
|
||||
"""Test the ping command.
|
||||
|
||||
The $serverip (as set up by either test_net_dhcp or test_net_setup_static)
|
||||
is pinged. The test validates that the host is alive, as reported by the
|
||||
ping command's output.
|
||||
"""
|
||||
|
||||
if not net_set_up:
|
||||
pytest.skip('Network not initialized')
|
||||
|
||||
output = u_boot_console.run_command('ping $serverip')
|
||||
assert 'is alive' in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_net')
|
||||
def test_net_tftpboot(u_boot_console):
|
||||
"""Test the tftpboot command.
|
||||
|
||||
A file is downloaded from the TFTP server, its size and optionally its
|
||||
CRC32 are validated.
|
||||
|
||||
The details of the file to download are provided by the boardenv_* file;
|
||||
see the comment at the beginning of this file.
|
||||
"""
|
||||
|
||||
if not net_set_up:
|
||||
pytest.skip('Network not initialized')
|
||||
|
||||
f = u_boot_console.config.env.get('env__net_tftp_readable_file', None)
|
||||
if not f:
|
||||
pytest.skip('No TFTP readable file to read')
|
||||
|
||||
addr = f.get('addr', None)
|
||||
|
||||
fn = f['fn']
|
||||
if not addr:
|
||||
output = u_boot_console.run_command('tftpboot %s' % (fn))
|
||||
else:
|
||||
output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
|
||||
expected_text = 'Bytes transferred = '
|
||||
sz = f.get('size', None)
|
||||
if sz:
|
||||
expected_text += '%d' % sz
|
||||
assert expected_text in output
|
||||
|
||||
expected_crc = f.get('crc32', None)
|
||||
if not expected_crc:
|
||||
return
|
||||
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
|
||||
return
|
||||
|
||||
output = u_boot_console.run_command('crc32 $fileaddr $filesize')
|
||||
assert expected_crc in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_nfs')
|
||||
def test_net_nfs(u_boot_console):
|
||||
"""Test the nfs command.
|
||||
|
||||
A file is downloaded from the NFS server, its size and optionally its
|
||||
CRC32 are validated.
|
||||
|
||||
The details of the file to download are provided by the boardenv_* file;
|
||||
see the comment at the beginning of this file.
|
||||
"""
|
||||
|
||||
if not net_set_up:
|
||||
pytest.skip('Network not initialized')
|
||||
|
||||
f = u_boot_console.config.env.get('env__net_nfs_readable_file', None)
|
||||
if not f:
|
||||
pytest.skip('No NFS readable file to read')
|
||||
|
||||
addr = f.get('addr', None)
|
||||
if not addr:
|
||||
addr = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
fn = f['fn']
|
||||
output = u_boot_console.run_command('nfs %x %s' % (addr, fn))
|
||||
expected_text = 'Bytes transferred = '
|
||||
sz = f.get('size', None)
|
||||
if sz:
|
||||
expected_text += '%d' % sz
|
||||
assert expected_text in output
|
||||
|
||||
expected_crc = f.get('crc32', None)
|
||||
if not expected_crc:
|
||||
return
|
||||
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
|
||||
return
|
||||
|
||||
output = u_boot_console.run_command('crc32 %x $filesize' % addr)
|
||||
assert expected_crc in output
|
||||
70
test/py/tests/test_ofplatdata.py
Normal file
70
test/py/tests/test_ofplatdata.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2016 Google, Inc
|
||||
|
||||
import pytest
|
||||
import u_boot_utils as util
|
||||
|
||||
OF_PLATDATA_OUTPUT = '''
|
||||
of-platdata probe:
|
||||
bool 1
|
||||
byte 05
|
||||
bytearray 06 00 00
|
||||
int 1
|
||||
intarray 2 3 4 0
|
||||
longbytearray 09 0a 0b 0c 0d 0e 0f 10 11
|
||||
string message
|
||||
stringarray "multi-word" "message" ""
|
||||
of-platdata probe:
|
||||
bool 0
|
||||
byte 08
|
||||
bytearray 01 23 34
|
||||
int 3
|
||||
intarray 5 0 0 0
|
||||
longbytearray 09 00 00 00 00 00 00 00 00
|
||||
string message2
|
||||
stringarray "another" "multi-word" "message"
|
||||
of-platdata probe:
|
||||
bool 0
|
||||
byte 00
|
||||
bytearray 00 00 00
|
||||
int 0
|
||||
intarray 0 0 0 0
|
||||
longbytearray 00 00 00 00 00 00 00 00 00
|
||||
string <NULL>
|
||||
stringarray "one" "" ""
|
||||
of-platdata probe:
|
||||
bool 0
|
||||
byte 00
|
||||
bytearray 00 00 00
|
||||
int 0
|
||||
intarray 0 0 0 0
|
||||
longbytearray 00 00 00 00 00 00 00 00 00
|
||||
string <NULL>
|
||||
stringarray "spl" "" ""
|
||||
'''
|
||||
|
||||
@pytest.mark.buildconfigspec('spl_of_platdata')
|
||||
def test_ofplatdata(u_boot_console):
|
||||
"""Test that of-platdata can be generated and used in sandbox"""
|
||||
cons = u_boot_console
|
||||
cons.restart_uboot_with_flags(['--show_of_platdata'])
|
||||
output = cons.get_spawn_output().replace('\r', '')
|
||||
assert OF_PLATDATA_OUTPUT in output
|
||||
|
||||
@pytest.mark.buildconfigspec('spl_of_platdata')
|
||||
def test_spl_devicetree(u_boot_console):
|
||||
"""Test content of spl device-tree"""
|
||||
cons = u_boot_console
|
||||
dtb = cons.config.build_dir + '/spl/u-boot-spl.dtb'
|
||||
fdtgrep = cons.config.build_dir + '/tools/fdtgrep'
|
||||
output = util.run_and_log(cons, [fdtgrep, '-l', dtb])
|
||||
|
||||
assert "u-boot,dm-pre-reloc" not in output
|
||||
assert "u-boot,dm-pre-proper" not in output
|
||||
assert "u-boot,dm-spl" not in output
|
||||
assert "u-boot,dm-tpl" not in output
|
||||
|
||||
assert "spl-test4" in output
|
||||
assert "spl-test5" not in output
|
||||
assert "spl-test6" not in output
|
||||
assert "spl-test7" in output
|
||||
66
test/py/tests/test_pinmux.py
Normal file
66
test/py/tests/test_pinmux.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
def test_pinmux_usage_1(u_boot_console):
|
||||
"""Test that 'pinmux' command without parameters displays
|
||||
pinmux usage."""
|
||||
output = u_boot_console.run_command('pinmux')
|
||||
assert 'Usage:' in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
def test_pinmux_usage_2(u_boot_console):
|
||||
"""Test that 'pinmux status' executed without previous "pinmux dev"
|
||||
command displays pinmux usage."""
|
||||
output = u_boot_console.run_command('pinmux status')
|
||||
assert 'Usage:' in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_pinmux_status_all(u_boot_console):
|
||||
"""Test that 'pinmux status -a' displays pin's muxing."""
|
||||
output = u_boot_console.run_command('pinmux status -a')
|
||||
assert ('SCL : I2C SCL' in output)
|
||||
assert ('SDA : I2C SDA' in output)
|
||||
assert ('TX : Uart TX' in output)
|
||||
assert ('RX : Uart RX' in output)
|
||||
assert ('W1 : 1-wire gpio' in output)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_pinmux_list(u_boot_console):
|
||||
"""Test that 'pinmux list' returns the pin-controller list."""
|
||||
output = u_boot_console.run_command('pinmux list')
|
||||
assert 'sandbox_pinctrl' in output
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
def test_pinmux_dev_bad(u_boot_console):
|
||||
"""Test that 'pinmux dev' returns an error when trying to select a
|
||||
wrong pin controller."""
|
||||
pincontroller = 'bad_pin_controller_name'
|
||||
output = u_boot_console.run_command('pinmux dev ' + pincontroller)
|
||||
expected_output = 'Can\'t get the pin-controller: ' + pincontroller + '!'
|
||||
assert (expected_output in output)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_pinmux_dev(u_boot_console):
|
||||
"""Test that 'pinmux dev' select the wanted pin controller."""
|
||||
pincontroller = 'pinctrl'
|
||||
output = u_boot_console.run_command('pinmux dev ' + pincontroller)
|
||||
expected_output = 'dev: ' + pincontroller
|
||||
assert (expected_output in output)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_pinmux')
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_pinmux_status(u_boot_console):
|
||||
"""Test that 'pinmux status' displays selected pincontroller's pin
|
||||
muxing descriptions."""
|
||||
output = u_boot_console.run_command('pinmux status')
|
||||
assert ('SCL : I2C SCL' in output)
|
||||
assert ('SDA : I2C SDA' in output)
|
||||
assert ('TX : Uart TX' in output)
|
||||
assert ('RX : Uart RX' in output)
|
||||
assert ('W1 : 1-wire gpio' in output)
|
||||
21
test/py/tests/test_sandbox_exit.py
Normal file
21
test/py/tests/test_sandbox_exit.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
import pytest
|
||||
import signal
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('sysreset')
|
||||
def test_reset(u_boot_console):
|
||||
"""Test that the "reset" command exits sandbox process."""
|
||||
|
||||
u_boot_console.run_command('reset', wait_for_prompt=False)
|
||||
assert(u_boot_console.validate_exited())
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_ctrl_c(u_boot_console):
|
||||
"""Test that sending SIGINT to sandbox causes it to exit."""
|
||||
|
||||
u_boot_console.kill(signal.SIGINT)
|
||||
assert(u_boot_console.validate_exited())
|
||||
217
test/py/tests/test_sf.py
Normal file
217
test/py/tests/test_sf.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, Xilinx Inc. Michal Simek
|
||||
# Copyright (c) 2017, Xiphos Systems Corp. All rights reserved.
|
||||
|
||||
import re
|
||||
import pytest
|
||||
import random
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on boardenv_* containing configuration values to define
|
||||
which SPI Flash areas are available for testing. Without this, this test will
|
||||
be automatically skipped.
|
||||
For example:
|
||||
|
||||
# A list of sections of Flash memory to be tested.
|
||||
env__sf_configs = (
|
||||
{
|
||||
# Where in SPI Flash should the test operate.
|
||||
'offset': 0x00000000,
|
||||
# This value is optional.
|
||||
# If present, specifies the [[bus:]cs] argument used in `sf probe`
|
||||
# If missing, defaults to 0.
|
||||
'id': '0:1',
|
||||
# This value is optional.
|
||||
# If set as a number, specifies the speed of the SPI Flash.
|
||||
# If set as an array of 2, specifies a range for a random speed.
|
||||
# If missing, defaults to 0.
|
||||
'speed': 1000000,
|
||||
# This value is optional.
|
||||
# If present, specifies the size to use for read/write operations.
|
||||
# If missing, the SPI Flash page size is used as a default (based on
|
||||
# the `sf probe` output).
|
||||
'len': 0x10000,
|
||||
# This value is optional.
|
||||
# If present, specifies if the test can write to Flash offset
|
||||
# If missing, defaults to False.
|
||||
'writeable': False,
|
||||
# This value is optional.
|
||||
# If present, specifies the expected CRC32 value of the flash area.
|
||||
# If missing, extra check is ignored.
|
||||
'crc32': 0xCAFECAFE,
|
||||
},
|
||||
)
|
||||
"""
|
||||
|
||||
def sf_prepare(u_boot_console, env__sf_config):
|
||||
"""Check global state of the SPI Flash before running any test.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__sf_config: The single SPI Flash device configuration on which to
|
||||
run the tests.
|
||||
|
||||
Returns:
|
||||
sf_params: a dictionary of SPI Flash parameters.
|
||||
"""
|
||||
|
||||
sf_params = {}
|
||||
sf_params['ram_base'] = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
probe_id = env__sf_config.get('id', 0)
|
||||
speed = env__sf_config.get('speed', 0)
|
||||
if isinstance(speed, int):
|
||||
sf_params['speed'] = speed
|
||||
else:
|
||||
assert len(speed) == 2, "If speed is a list, it must have 2 entries"
|
||||
sf_params['speed'] = random.randint(speed[0], speed[1])
|
||||
|
||||
cmd = 'sf probe %d %d' % (probe_id, sf_params['speed'])
|
||||
|
||||
output = u_boot_console.run_command(cmd)
|
||||
assert 'SF: Detected' in output, 'No Flash device available'
|
||||
|
||||
m = re.search('page size (.+?) Bytes', output)
|
||||
assert m, 'SPI Flash page size not recognized'
|
||||
sf_params['page_size'] = int(m.group(1))
|
||||
|
||||
m = re.search('erase size (.+?) KiB', output)
|
||||
assert m, 'SPI Flash erase size not recognized'
|
||||
sf_params['erase_size'] = int(m.group(1))
|
||||
sf_params['erase_size'] *= 1024
|
||||
|
||||
m = re.search('total (.+?) MiB', output)
|
||||
assert m, 'SPI Flash total size not recognized'
|
||||
sf_params['total_size'] = int(m.group(1))
|
||||
sf_params['total_size'] *= 1024 * 1024
|
||||
|
||||
assert 'offset' in env__sf_config, \
|
||||
'\'offset\' is required for this test.'
|
||||
sf_params['len'] = env__sf_config.get('len', sf_params['erase_size'])
|
||||
|
||||
assert not env__sf_config['offset'] % sf_params['erase_size'], \
|
||||
'offset not multiple of erase size.'
|
||||
assert not sf_params['len'] % sf_params['erase_size'], \
|
||||
'erase length not multiple of erase size.'
|
||||
|
||||
assert not (env__sf_config.get('writeable', False) and
|
||||
'crc32' in env__sf_config), \
|
||||
'Cannot check crc32 on writeable sections'
|
||||
|
||||
return sf_params
|
||||
|
||||
def sf_read(u_boot_console, env__sf_config, sf_params):
|
||||
"""Helper function used to read and compute the CRC32 value of a section of
|
||||
SPI Flash memory.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__sf_config: The single SPI Flash device configuration on which to
|
||||
run the tests.
|
||||
sf_params: SPI Flash parameters.
|
||||
|
||||
Returns:
|
||||
CRC32 value of SPI Flash section
|
||||
"""
|
||||
|
||||
addr = sf_params['ram_base']
|
||||
offset = env__sf_config['offset']
|
||||
count = sf_params['len']
|
||||
pattern = random.randint(0, 0xFF)
|
||||
crc_expected = env__sf_config.get('crc32', None)
|
||||
|
||||
cmd = 'mw.b %08x %02x %x' % (addr, pattern, count)
|
||||
u_boot_console.run_command(cmd)
|
||||
crc_pattern = u_boot_utils.crc32(u_boot_console, addr, count)
|
||||
if crc_expected:
|
||||
assert crc_pattern != crc_expected
|
||||
|
||||
cmd = 'sf read %08x %08x %x' % (addr, offset, count)
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert 'Read: OK' in response, 'Read operation failed'
|
||||
crc_readback = u_boot_utils.crc32(u_boot_console, addr, count)
|
||||
assert crc_pattern != crc_readback, 'sf read did not update RAM content.'
|
||||
if crc_expected:
|
||||
assert crc_readback == crc_expected
|
||||
|
||||
return crc_readback
|
||||
|
||||
def sf_update(u_boot_console, env__sf_config, sf_params):
|
||||
"""Helper function used to update a section of SPI Flash memory.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__sf_config: The single SPI Flash device configuration on which to
|
||||
run the tests.
|
||||
|
||||
Returns:
|
||||
CRC32 value of SPI Flash section
|
||||
"""
|
||||
|
||||
addr = sf_params['ram_base']
|
||||
offset = env__sf_config['offset']
|
||||
count = sf_params['len']
|
||||
pattern = int(random.random() * 0xFF)
|
||||
|
||||
cmd = 'mw.b %08x %02x %x' % (addr, pattern, count)
|
||||
u_boot_console.run_command(cmd)
|
||||
crc_pattern = u_boot_utils.crc32(u_boot_console, addr, count)
|
||||
|
||||
cmd = 'sf update %08x %08x %x' % (addr, offset, count)
|
||||
u_boot_console.run_command(cmd)
|
||||
crc_readback = sf_read(u_boot_console, env__sf_config, sf_params)
|
||||
|
||||
assert crc_readback == crc_pattern
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_sf')
|
||||
@pytest.mark.buildconfigspec('cmd_crc32')
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_sf_read(u_boot_console, env__sf_config):
|
||||
sf_params = sf_prepare(u_boot_console, env__sf_config)
|
||||
sf_read(u_boot_console, env__sf_config, sf_params)
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_sf')
|
||||
@pytest.mark.buildconfigspec('cmd_crc32')
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_sf_read_twice(u_boot_console, env__sf_config):
|
||||
sf_params = sf_prepare(u_boot_console, env__sf_config)
|
||||
|
||||
crc1 = sf_read(u_boot_console, env__sf_config, sf_params)
|
||||
sf_params['ram_base'] += 0x100
|
||||
crc2 = sf_read(u_boot_console, env__sf_config, sf_params)
|
||||
|
||||
assert crc1 == crc2, 'CRC32 of two successive read operation do not match'
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_sf')
|
||||
@pytest.mark.buildconfigspec('cmd_crc32')
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_sf_erase(u_boot_console, env__sf_config):
|
||||
if not env__sf_config.get('writeable', False):
|
||||
pytest.skip('Flash config is tagged as not writeable')
|
||||
|
||||
sf_params = sf_prepare(u_boot_console, env__sf_config)
|
||||
addr = sf_params['ram_base']
|
||||
offset = env__sf_config['offset']
|
||||
count = sf_params['len']
|
||||
|
||||
cmd = 'sf erase %08x %x' % (offset, count)
|
||||
output = u_boot_console.run_command(cmd)
|
||||
assert 'Erased: OK' in output, 'Erase operation failed'
|
||||
|
||||
cmd = 'mw.b %08x ff %x' % (addr, count)
|
||||
u_boot_console.run_command(cmd)
|
||||
crc_ffs = u_boot_utils.crc32(u_boot_console, addr, count)
|
||||
|
||||
crc_read = sf_read(u_boot_console, env__sf_config, sf_params)
|
||||
assert crc_ffs == crc_read, 'Unexpected CRC32 after erase operation.'
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_sf')
|
||||
@pytest.mark.buildconfigspec('cmd_crc32')
|
||||
@pytest.mark.buildconfigspec('cmd_memory')
|
||||
def test_sf_update(u_boot_console, env__sf_config):
|
||||
if not env__sf_config.get('writeable', False):
|
||||
pytest.skip('Flash config is tagged as not writeable')
|
||||
|
||||
sf_params = sf_prepare(u_boot_console, env__sf_config)
|
||||
sf_update(u_boot_console, env__sf_config, sf_params)
|
||||
45
test/py/tests/test_shell_basics.py
Normal file
45
test/py/tests/test_shell_basics.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test basic shell functionality, such as commands separate by semi-colons.
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.buildconfigspec('cmd_echo')
|
||||
|
||||
def test_shell_execute(u_boot_console):
|
||||
"""Test any shell command."""
|
||||
|
||||
response = u_boot_console.run_command('echo hello')
|
||||
assert response.strip() == 'hello'
|
||||
|
||||
def test_shell_semicolon_two(u_boot_console):
|
||||
"""Test two shell commands separate by a semi-colon."""
|
||||
|
||||
cmd = 'echo hello; echo world'
|
||||
response = u_boot_console.run_command(cmd)
|
||||
# This validation method ignores the exact whitespace between the strings
|
||||
assert response.index('hello') < response.index('world')
|
||||
|
||||
def test_shell_semicolon_three(u_boot_console):
|
||||
"""Test three shell commands separate by a semi-colon, with variable
|
||||
expansion dependencies between them."""
|
||||
|
||||
cmd = 'setenv list 1; setenv list ${list}2; setenv list ${list}3; ' + \
|
||||
'echo ${list}'
|
||||
response = u_boot_console.run_command(cmd)
|
||||
assert response.strip() == '123'
|
||||
u_boot_console.run_command('setenv list')
|
||||
|
||||
def test_shell_run(u_boot_console):
|
||||
"""Test the "run" shell command."""
|
||||
|
||||
u_boot_console.run_command('setenv foo "setenv monty 1; setenv python 2"')
|
||||
u_boot_console.run_command('run foo')
|
||||
response = u_boot_console.run_command('echo $monty')
|
||||
assert response.strip() == '1'
|
||||
response = u_boot_console.run_command('echo $python')
|
||||
assert response.strip() == '2'
|
||||
u_boot_console.run_command('setenv foo')
|
||||
u_boot_console.run_command('setenv monty')
|
||||
u_boot_console.run_command('setenv python')
|
||||
35
test/py/tests/test_sleep.py
Normal file
35
test/py/tests/test_sleep.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
import pytest
|
||||
import time
|
||||
|
||||
"""
|
||||
Note: This test doesn't rely on boardenv_* configuration values but they can
|
||||
change test behavior.
|
||||
|
||||
# Setup env__sleep_accurate to False if time is not accurate on your platform
|
||||
env__sleep_accurate = False
|
||||
|
||||
"""
|
||||
|
||||
def test_sleep(u_boot_console):
|
||||
"""Test the sleep command, and validate that it sleeps for approximately
|
||||
the correct amount of time."""
|
||||
|
||||
sleep_skip = u_boot_console.config.env.get('env__sleep_accurate', True)
|
||||
if not sleep_skip:
|
||||
pytest.skip('sleep is not accurate')
|
||||
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_misc', 'n') != 'y':
|
||||
pytest.skip('sleep command not supported')
|
||||
# 3s isn't too long, but is enough to cross a few second boundaries.
|
||||
sleep_time = 3
|
||||
tstart = time.time()
|
||||
u_boot_console.run_command('sleep %d' % sleep_time)
|
||||
tend = time.time()
|
||||
elapsed = tend - tstart
|
||||
assert elapsed >= (sleep_time - 0.01)
|
||||
if not u_boot_console.config.gdbserver:
|
||||
# 0.25s margin is hopefully enough to account for any system overhead.
|
||||
assert elapsed < (sleep_time + 0.25)
|
||||
233
test/py/tests/test_tpm2.py
Normal file
233
test/py/tests/test_tpm2.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2018, Bootlin
|
||||
# Author: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
|
||||
import os.path
|
||||
import pytest
|
||||
import u_boot_utils
|
||||
import re
|
||||
import time
|
||||
|
||||
"""
|
||||
Test the TPMv2.x related commands. You must have a working hardware setup in
|
||||
order to do these tests.
|
||||
|
||||
Notes:
|
||||
* These tests will prove the password mechanism. The TPM chip must be cleared of
|
||||
any password.
|
||||
* Commands like pcr_setauthpolicy and pcr_resetauthpolicy are not implemented
|
||||
here because they would fail the tests in most cases (TPMs do not implement them
|
||||
and return an error).
|
||||
"""
|
||||
|
||||
updates = 0
|
||||
|
||||
def force_init(u_boot_console, force=False):
|
||||
"""When a test fails, U-Boot is reset. Because TPM stack must be initialized
|
||||
after each reboot, we must ensure these lines are always executed before
|
||||
trying any command or they will fail with no reason. Executing 'tpm init'
|
||||
twice will spawn an error used to detect that the TPM was not reset and no
|
||||
initialization code should be run.
|
||||
"""
|
||||
output = u_boot_console.run_command('tpm2 init')
|
||||
if force or not 'Error' in output:
|
||||
u_boot_console.run_command('echo --- start of init ---')
|
||||
u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR')
|
||||
u_boot_console.run_command('tpm2 self_test full')
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
if not output.endswith('0'):
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
|
||||
u_boot_console.run_command('echo --- end of init ---')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_init(u_boot_console):
|
||||
"""Init the software stack to use TPMv2 commands."""
|
||||
|
||||
u_boot_console.run_command('tpm2 init')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_startup(u_boot_console):
|
||||
"""Execute a TPM2_Startup command.
|
||||
|
||||
Initiate the TPM internal state machine.
|
||||
"""
|
||||
|
||||
u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_self_test_full(u_boot_console):
|
||||
"""Execute a TPM2_SelfTest (full) command.
|
||||
|
||||
Ask the TPM to perform all self tests to also enable full capabilities.
|
||||
"""
|
||||
|
||||
u_boot_console.run_command('tpm2 self_test full')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_continue_self_test(u_boot_console):
|
||||
"""Execute a TPM2_SelfTest (continued) command.
|
||||
|
||||
Ask the TPM to finish its self tests (alternative to the full test) in order
|
||||
to enter a fully operational state.
|
||||
"""
|
||||
|
||||
u_boot_console.run_command('tpm2 self_test continue')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_clear(u_boot_console):
|
||||
"""Execute a TPM2_Clear command.
|
||||
|
||||
Ask the TPM to reset entirely its internal state (including internal
|
||||
configuration, passwords, counters and DAM parameters). This is half of the
|
||||
TAKE_OWNERSHIP command from TPMv1.
|
||||
|
||||
Use the LOCKOUT hierarchy for this. The LOCKOUT/PLATFORM hierarchies must
|
||||
not have a password set, otherwise this test will fail. ENDORSEMENT and
|
||||
PLATFORM hierarchies are also available.
|
||||
"""
|
||||
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_change_auth(u_boot_console):
|
||||
"""Execute a TPM2_HierarchyChangeAuth command.
|
||||
|
||||
Ask the TPM to change the owner, ie. set a new password: 'unicorn'
|
||||
|
||||
Use the LOCKOUT hierarchy for this. ENDORSEMENT and PLATFORM hierarchies are
|
||||
also available.
|
||||
"""
|
||||
|
||||
force_init(u_boot_console)
|
||||
|
||||
u_boot_console.run_command('tpm2 change_auth TPM2_RH_LOCKOUT unicorn')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT unicorn')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
|
||||
assert output.endswith('0')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_get_capability(u_boot_console):
|
||||
"""Execute a TPM_GetCapability command.
|
||||
|
||||
Display one capability. In our test case, let's display the default DAM
|
||||
lockout counter that should be 0 since the CLEAR:
|
||||
- TPM_CAP_TPM_PROPERTIES = 0x6
|
||||
- TPM_PT_LOCKOUT_COUNTER (1st parameter) = PTR_VAR + 14
|
||||
|
||||
There is no expected default values because it would depend on the chip
|
||||
used. We can still save them in order to check they have changed later.
|
||||
"""
|
||||
|
||||
force_init(u_boot_console)
|
||||
ram = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20e 0x200 1') #0x%x 1' % ram)
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
assert 'Property 0x0000020e: 0x00000000' in read_cap
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_dam_parameters(u_boot_console):
|
||||
"""Execute a TPM2_DictionaryAttackParameters command.
|
||||
|
||||
Change Dictionary Attack Mitigation (DAM) parameters. Ask the TPM to change:
|
||||
- Max number of failed authentication before lockout: 3
|
||||
- Time before the failure counter is automatically decremented: 10 sec
|
||||
- Time after a lockout failure before it can be attempted again: 0 sec
|
||||
|
||||
For an unknown reason, the DAM parameters must be changed before changing
|
||||
the authentication, otherwise the lockout will be engaged after the first
|
||||
failed authentication attempt.
|
||||
"""
|
||||
|
||||
force_init(u_boot_console)
|
||||
ram = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
# Set the DAM parameters to known values
|
||||
u_boot_console.run_command('tpm2 dam_parameters 3 10 0')
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
# Check the values have been saved
|
||||
read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20f 0x%x 3' % ram)
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
assert 'Property 0x0000020f: 0x00000003' in read_cap
|
||||
assert 'Property 0x00000210: 0x0000000a' in read_cap
|
||||
assert 'Property 0x00000211: 0x00000000' in read_cap
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_pcr_read(u_boot_console):
|
||||
"""Execute a TPM2_PCR_Read command.
|
||||
|
||||
Perform a PCR read of the 0th PCR. Must be zero.
|
||||
"""
|
||||
|
||||
force_init(u_boot_console)
|
||||
ram = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram)
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
# Save the number of PCR updates
|
||||
str = re.findall(r'\d+ known updates', read_pcr)[0]
|
||||
global updates
|
||||
updates = int(re.findall(r'\d+', str)[0])
|
||||
|
||||
# Check the output value
|
||||
assert 'PCR #0 content' in read_pcr
|
||||
assert '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' in read_pcr
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_pcr_extend(u_boot_console):
|
||||
"""Execute a TPM2_PCR_Extend command.
|
||||
|
||||
Perform a PCR extension with a known hash in memory (zeroed since the board
|
||||
must have been rebooted).
|
||||
|
||||
No authentication mechanism is used here, not protecting against packet
|
||||
replay, yet.
|
||||
"""
|
||||
|
||||
force_init(u_boot_console)
|
||||
ram = u_boot_utils.find_ram_base(u_boot_console)
|
||||
|
||||
u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram)
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
|
||||
read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram)
|
||||
output = u_boot_console.run_command('echo $?')
|
||||
assert output.endswith('0')
|
||||
assert 'f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b' in read_pcr
|
||||
assert '43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 4b' in read_pcr
|
||||
|
||||
str = re.findall(r'\d+ known updates', read_pcr)[0]
|
||||
new_updates = int(re.findall(r'\d+', str)[0])
|
||||
assert (updates + 1) == new_updates
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_tpm_v2')
|
||||
def test_tpm2_cleanup(u_boot_console):
|
||||
"""Ensure the TPM is cleared from password or test related configuration."""
|
||||
|
||||
force_init(u_boot_console, True)
|
||||
236
test/py/tests/test_ums.py
Normal file
236
test/py/tests/test_ums.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB
|
||||
# device enumeration on the host, reads a small block of data from the UMS
|
||||
# block device, optionally mounts a partition and performs filesystem-based
|
||||
# read/write tests, and finally aborts the "ums" command in U-Boot.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
import re
|
||||
import time
|
||||
import u_boot_utils
|
||||
|
||||
"""
|
||||
Note: This test relies on:
|
||||
|
||||
a) boardenv_* to contain configuration values to define which USB ports are
|
||||
available for testing. Without this, this test will be automatically skipped.
|
||||
For example:
|
||||
|
||||
# Leave this list empty if you have no block_devs below with writable
|
||||
# partitions defined.
|
||||
env__mount_points = (
|
||||
'/mnt/ubtest-mnt-p2371-2180-na',
|
||||
)
|
||||
|
||||
env__usb_dev_ports = (
|
||||
{
|
||||
'fixture_id': 'micro_b',
|
||||
'tgt_usb_ctlr': '0',
|
||||
'host_ums_dev_node': '/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0',
|
||||
},
|
||||
)
|
||||
|
||||
env__block_devs = (
|
||||
# eMMC; always present
|
||||
{
|
||||
'fixture_id': 'emmc',
|
||||
'type': 'mmc',
|
||||
'id': '0',
|
||||
# The following two properties are optional.
|
||||
# If present, the partition will be mounted and a file written-to and
|
||||
# read-from it. If missing, only a simple block read test will be
|
||||
# performed.
|
||||
'writable_fs_partition': 1,
|
||||
'writable_fs_subdir': 'tmp/',
|
||||
},
|
||||
# SD card; present since I plugged one in
|
||||
{
|
||||
'fixture_id': 'sd',
|
||||
'type': 'mmc',
|
||||
'id': '1'
|
||||
},
|
||||
)
|
||||
|
||||
b) udev rules to set permissions on devices nodes, so that sudo is not
|
||||
required. For example:
|
||||
|
||||
ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
|
||||
|
||||
(You may wish to change the group ID instead of setting the permissions wide
|
||||
open. All that matters is that the user ID running the test can access the
|
||||
device.)
|
||||
|
||||
c) /etc/fstab entries to allow the block device to be mounted without requiring
|
||||
root permissions. For example:
|
||||
|
||||
/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0-part1 /mnt/ubtest-mnt-p2371-2180-na ext4 noauto,user,nosuid,nodev
|
||||
|
||||
This entry is only needed if any block_devs above contain a
|
||||
writable_fs_partition value.
|
||||
"""
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_usb_mass_storage')
|
||||
def test_ums(u_boot_console, env__usb_dev_port, env__block_devs):
|
||||
"""Test the "ums" command; the host system must be able to enumerate a UMS
|
||||
device when "ums" is running, block and optionally file I/O are tested,
|
||||
and this device must disappear when "ums" is aborted.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
env__usb_dev_port: The single USB device-mode port specification on
|
||||
which to run the test. See the file-level comment above for
|
||||
details of the format.
|
||||
env__block_devs: The list of block devices that the target U-Boot
|
||||
device has attached. See the file-level comment above for details
|
||||
of the format.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0]
|
||||
if not have_writable_fs_partition:
|
||||
# If 'writable_fs_subdir' is missing, we'll skip all parts of the
|
||||
# testing which mount filesystems.
|
||||
u_boot_console.log.warning(
|
||||
'boardenv missing "writable_fs_partition"; ' +
|
||||
'UMS testing will be limited.')
|
||||
|
||||
tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr']
|
||||
host_ums_dev_node = env__usb_dev_port['host_ums_dev_node']
|
||||
|
||||
# We're interested in testing USB device mode on each port, not the cross-
|
||||
# product of that with each device. So, just pick the first entry in the
|
||||
# device list here. We'll test each block device somewhere else.
|
||||
tgt_dev_type = env__block_devs[0]['type']
|
||||
tgt_dev_id = env__block_devs[0]['id']
|
||||
if have_writable_fs_partition:
|
||||
mount_point = u_boot_console.config.env['env__mount_points'][0]
|
||||
mount_subdir = env__block_devs[0]['writable_fs_subdir']
|
||||
part_num = env__block_devs[0]['writable_fs_partition']
|
||||
host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num)
|
||||
else:
|
||||
host_ums_part_node = host_ums_dev_node
|
||||
|
||||
test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin',
|
||||
1024 * 1024);
|
||||
if have_writable_fs_partition:
|
||||
mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn
|
||||
|
||||
def start_ums():
|
||||
"""Start U-Boot's ums shell command.
|
||||
|
||||
This also waits for the host-side USB enumeration process to complete.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
u_boot_console.log.action(
|
||||
'Starting long-running U-Boot ums shell command')
|
||||
cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id)
|
||||
u_boot_console.run_command(cmd, wait_for_prompt=False)
|
||||
u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]'))
|
||||
fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node)
|
||||
u_boot_console.log.action('Reading raw data from UMS device')
|
||||
fh.read(4096)
|
||||
fh.close()
|
||||
|
||||
def mount():
|
||||
"""Mount the block device that U-Boot exports.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
u_boot_console.log.action('Mounting exported UMS device')
|
||||
cmd = ('/bin/mount', host_ums_part_node)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
|
||||
def umount(ignore_errors):
|
||||
"""Unmount the block device that U-Boot exports.
|
||||
|
||||
Args:
|
||||
ignore_errors: Ignore any errors. This is useful if an error has
|
||||
already been detected, and the code is performing best-effort
|
||||
cleanup. In this case, we do not want to mask the original
|
||||
error by "honoring" any new errors.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
u_boot_console.log.action('Unmounting UMS device')
|
||||
cmd = ('/bin/umount', host_ums_part_node)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors)
|
||||
|
||||
def stop_ums(ignore_errors):
|
||||
"""Stop U-Boot's ums shell command from executing.
|
||||
|
||||
This also waits for the host-side USB de-enumeration process to
|
||||
complete.
|
||||
|
||||
Args:
|
||||
ignore_errors: Ignore any errors. This is useful if an error has
|
||||
already been detected, and the code is performing best-effort
|
||||
cleanup. In this case, we do not want to mask the original
|
||||
error by "honoring" any new errors.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
u_boot_console.log.action(
|
||||
'Stopping long-running U-Boot ums shell command')
|
||||
u_boot_console.ctrlc()
|
||||
u_boot_utils.wait_until_file_open_fails(host_ums_part_node,
|
||||
ignore_errors)
|
||||
|
||||
ignore_cleanup_errors = True
|
||||
try:
|
||||
start_ums()
|
||||
if not have_writable_fs_partition:
|
||||
# Skip filesystem-based testing if not configured
|
||||
return
|
||||
try:
|
||||
mount()
|
||||
u_boot_console.log.action('Writing test file via UMS')
|
||||
cmd = ('rm', '-f', mounted_test_fn)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
if os.path.exists(mounted_test_fn):
|
||||
raise Exception('Could not rm target UMS test file')
|
||||
cmd = ('cp', test_f.abs_fn, mounted_test_fn)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
ignore_cleanup_errors = False
|
||||
finally:
|
||||
umount(ignore_errors=ignore_cleanup_errors)
|
||||
finally:
|
||||
stop_ums(ignore_errors=ignore_cleanup_errors)
|
||||
|
||||
ignore_cleanup_errors = True
|
||||
try:
|
||||
start_ums()
|
||||
try:
|
||||
mount()
|
||||
u_boot_console.log.action('Reading test file back via UMS')
|
||||
read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn)
|
||||
cmd = ('rm', '-f', mounted_test_fn)
|
||||
u_boot_utils.run_and_log(u_boot_console, cmd)
|
||||
ignore_cleanup_errors = False
|
||||
finally:
|
||||
umount(ignore_errors=ignore_cleanup_errors)
|
||||
finally:
|
||||
stop_ums(ignore_errors=ignore_cleanup_errors)
|
||||
|
||||
written_hash = test_f.content_hash
|
||||
assert(written_hash == read_back_hash)
|
||||
13
test/py/tests/test_unknown_cmd.py
Normal file
13
test/py/tests/test_unknown_cmd.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
def test_unknown_command(u_boot_console):
|
||||
"""Test that executing an unknown command causes U-Boot to print an
|
||||
error."""
|
||||
|
||||
# The "unknown command" error is actively expected here,
|
||||
# so error detection for it is disabled.
|
||||
with u_boot_console.disable_check('unknown_command'):
|
||||
response = u_boot_console.run_command('non_existent_cmd')
|
||||
assert('Unknown command \'non_existent_cmd\' - try \'help\'' in response)
|
||||
28
test/py/tests/test_ut.py
Normal file
28
test/py/tests/test_ut.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
import os.path
|
||||
import pytest
|
||||
|
||||
@pytest.mark.buildconfigspec('ut_dm')
|
||||
def test_ut_dm_init(u_boot_console):
|
||||
"""Initialize data for ut dm tests."""
|
||||
|
||||
fn = u_boot_console.config.source_dir + '/testflash.bin'
|
||||
if not os.path.exists(fn):
|
||||
data = b'this is a test'
|
||||
data += b'\x00' * ((4 * 1024 * 1024) - len(data))
|
||||
with open(fn, 'wb') as fh:
|
||||
fh.write(data)
|
||||
|
||||
fn = u_boot_console.config.source_dir + '/spi.bin'
|
||||
if not os.path.exists(fn):
|
||||
data = b'\x00' * (2 * 1024 * 1024)
|
||||
with open(fn, 'wb') as fh:
|
||||
fh.write(data)
|
||||
|
||||
def test_ut(u_boot_console, ut_subtest):
|
||||
"""Execute a "ut" subtest."""
|
||||
|
||||
output = u_boot_console.run_command('ut ' + ut_subtest)
|
||||
assert output.endswith('Failures: 0')
|
||||
293
test/py/tests/test_vboot.py
Normal file
293
test/py/tests/test_vboot.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2016, Google Inc.
|
||||
#
|
||||
# U-Boot Verified Boot Test
|
||||
|
||||
"""
|
||||
This tests verified boot in the following ways:
|
||||
|
||||
For image verification:
|
||||
- Create FIT (unsigned) with mkimage
|
||||
- Check that verification shows that no keys are verified
|
||||
- Sign image
|
||||
- Check that verification shows that a key is now verified
|
||||
|
||||
For configuration verification:
|
||||
- Corrupt signature and check for failure
|
||||
- Create FIT (with unsigned configuration) with mkimage
|
||||
- Check that image verification works
|
||||
- Sign the FIT and mark the key as 'required' for verification
|
||||
- Check that image verification works
|
||||
- Corrupt the signature
|
||||
- Check that image verification no-longer works
|
||||
|
||||
Tests run with both SHA1 and SHA256 hashing.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import struct
|
||||
import u_boot_utils as util
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('fit_signature')
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('fdtget')
|
||||
@pytest.mark.requiredtool('fdtput')
|
||||
@pytest.mark.requiredtool('openssl')
|
||||
def test_vboot(u_boot_console):
|
||||
"""Test verified boot signing with mkimage and verification with 'bootm'.
|
||||
|
||||
This works using sandbox only as it needs to update the device tree used
|
||||
by U-Boot to hold public keys from the signing process.
|
||||
|
||||
The SHA1 and SHA256 tests are combined into a single test since the
|
||||
key-generation process is quite slow and we want to avoid doing it twice.
|
||||
"""
|
||||
def dtc(dts):
|
||||
"""Run the device tree compiler to compile a .dts file
|
||||
|
||||
The output file will be the same as the input file but with a .dtb
|
||||
extension.
|
||||
|
||||
Args:
|
||||
dts: Device tree file to compile.
|
||||
"""
|
||||
dtb = dts.replace('.dts', '.dtb')
|
||||
util.run_and_log(cons, 'dtc %s %s%s -O dtb '
|
||||
'-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
|
||||
|
||||
def run_bootm(sha_algo, test_type, expect_string, boots):
|
||||
"""Run a 'bootm' command U-Boot.
|
||||
|
||||
This always starts a fresh U-Boot instance since the device tree may
|
||||
contain a new public key.
|
||||
|
||||
Args:
|
||||
test_type: A string identifying the test type.
|
||||
expect_string: A string which is expected in the output.
|
||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
||||
use.
|
||||
boots: A boolean that is True if Linux should boot and False if
|
||||
we are expected to not boot
|
||||
"""
|
||||
cons.restart_uboot()
|
||||
with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
|
||||
output = cons.run_command_list(
|
||||
['host load hostfs - 100 %stest.fit' % tmpdir,
|
||||
'fdt addr 100',
|
||||
'bootm 100'])
|
||||
assert(expect_string in ''.join(output))
|
||||
if boots:
|
||||
assert('sandbox: continuing, as we cannot run' in ''.join(output))
|
||||
else:
|
||||
assert('sandbox: continuing, as we cannot run' not in ''.join(output))
|
||||
|
||||
def make_fit(its):
|
||||
"""Make a new FIT from the .its source file.
|
||||
|
||||
This runs 'mkimage -f' to create a new FIT.
|
||||
|
||||
Args:
|
||||
its: Filename containing .its source.
|
||||
"""
|
||||
util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
|
||||
'%s%s' % (datadir, its), fit])
|
||||
|
||||
def sign_fit(sha_algo):
|
||||
"""Sign the FIT
|
||||
|
||||
Signs the FIT and writes the signature into it. It also writes the
|
||||
public key into the dtb.
|
||||
|
||||
Args:
|
||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
||||
use.
|
||||
"""
|
||||
cons.log.action('%s: Sign images' % sha_algo)
|
||||
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
|
||||
'-r', fit])
|
||||
|
||||
def sign_fit_norequire(sha_algo):
|
||||
"""Sign the FIT
|
||||
|
||||
Signs the FIT and writes the signature into it. It also writes the
|
||||
public key into the dtb.
|
||||
|
||||
Args:
|
||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
||||
use.
|
||||
"""
|
||||
cons.log.action('%s: Sign images' % sha_algo)
|
||||
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
|
||||
fit])
|
||||
|
||||
def replace_fit_totalsize(size):
|
||||
"""Replace FIT header's totalsize with something greater.
|
||||
|
||||
The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
|
||||
If the size is greater, the signature verification should return false.
|
||||
|
||||
Args:
|
||||
size: The new totalsize of the header
|
||||
|
||||
Returns:
|
||||
prev_size: The previous totalsize read from the header
|
||||
"""
|
||||
total_size = 0
|
||||
with open(fit, 'r+b') as handle:
|
||||
handle.seek(4)
|
||||
total_size = handle.read(4)
|
||||
handle.seek(4)
|
||||
handle.write(struct.pack(">I", size))
|
||||
return struct.unpack(">I", total_size)[0]
|
||||
|
||||
def test_with_algo(sha_algo, padding):
|
||||
"""Test verified boot with the given hash algorithm.
|
||||
|
||||
This is the main part of the test code. The same procedure is followed
|
||||
for both hashing algorithms.
|
||||
|
||||
Args:
|
||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
||||
use.
|
||||
"""
|
||||
# Compile our device tree files for kernel and U-Boot. These are
|
||||
# regenerated here since mkimage will modify them (by adding a
|
||||
# public key) below.
|
||||
dtc('sandbox-kernel.dts')
|
||||
dtc('sandbox-u-boot.dts')
|
||||
|
||||
# Build the FIT, but don't sign anything yet
|
||||
cons.log.action('%s: Test FIT with signed images' % sha_algo)
|
||||
make_fit('sign-images-%s%s.its' % (sha_algo , padding))
|
||||
run_bootm(sha_algo, 'unsigned images', 'dev-', True)
|
||||
|
||||
# Sign images with our dev keys
|
||||
sign_fit(sha_algo)
|
||||
run_bootm(sha_algo, 'signed images', 'dev+', True)
|
||||
|
||||
# Create a fresh .dtb without the public keys
|
||||
dtc('sandbox-u-boot.dts')
|
||||
|
||||
cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
|
||||
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
|
||||
run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
|
||||
|
||||
# Sign images with our dev keys
|
||||
sign_fit(sha_algo)
|
||||
run_bootm(sha_algo, 'signed config', 'dev+', True)
|
||||
|
||||
cons.log.action('%s: Check signed config on the host' % sha_algo)
|
||||
|
||||
util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir,
|
||||
'-k', dtb])
|
||||
|
||||
# Replace header bytes
|
||||
bcfg = u_boot_console.config.buildconfig
|
||||
max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
|
||||
existing_size = replace_fit_totalsize(max_size + 1)
|
||||
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
|
||||
cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
|
||||
|
||||
# Replace with existing header bytes
|
||||
replace_fit_totalsize(existing_size)
|
||||
run_bootm(sha_algo, 'signed config', 'dev+', True)
|
||||
cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
|
||||
|
||||
# Increment the first byte of the signature, which should cause failure
|
||||
sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
|
||||
(fit, sig_node))
|
||||
byte_list = sig.split()
|
||||
byte = int(byte_list[0], 16)
|
||||
byte_list[0] = '%x' % (byte + 1)
|
||||
sig = ' '.join(byte_list)
|
||||
util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
|
||||
(fit, sig_node, sig))
|
||||
|
||||
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
|
||||
|
||||
cons.log.action('%s: Check bad config on the host' % sha_algo)
|
||||
util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
|
||||
'-k', dtb], 1, 'Failed to verify required signature')
|
||||
|
||||
def test_required_key(sha_algo, padding):
|
||||
"""Test verified boot with the given hash algorithm.
|
||||
|
||||
This function test if u-boot reject an image when a required
|
||||
key isn't used to sign a FIT.
|
||||
|
||||
Args:
|
||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
||||
use.
|
||||
"""
|
||||
# Compile our device tree files for kernel and U-Boot. These are
|
||||
# regenerated here since mkimage will modify them (by adding a
|
||||
# public key) below.
|
||||
dtc('sandbox-kernel.dts')
|
||||
dtc('sandbox-u-boot.dts')
|
||||
|
||||
# Build the FIT with prod key (keys required)
|
||||
# Build the FIT with dev key (keys NOT required)
|
||||
# The dtb contain the key prod and dev and the key prod are set as required.
|
||||
# Then try to boot the FIT with dev key
|
||||
# This FIT should not be accepted by u-boot because the key prod is required
|
||||
cons.log.action('%s: Test FIT with configs images' % sha_algo)
|
||||
make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
|
||||
sign_fit(sha_algo)
|
||||
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
|
||||
sign_fit(sha_algo)
|
||||
|
||||
run_bootm(sha_algo, 'signed configs', '', False)
|
||||
|
||||
cons = u_boot_console
|
||||
tmpdir = cons.config.result_dir + '/'
|
||||
tmp = tmpdir + 'vboot.tmp'
|
||||
datadir = cons.config.source_dir + '/test/py/tests/vboot/'
|
||||
fit = '%stest.fit' % tmpdir
|
||||
mkimage = cons.config.build_dir + '/tools/mkimage'
|
||||
fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
|
||||
dtc_args = '-I dts -O dtb -i %s' % tmpdir
|
||||
dtb = '%ssandbox-u-boot.dtb' % tmpdir
|
||||
sig_node = '/configurations/conf-1/signature'
|
||||
|
||||
# Create an RSA key pair
|
||||
public_exponent = 65537
|
||||
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
|
||||
'-pkeyopt rsa_keygen_bits:2048 '
|
||||
'-pkeyopt rsa_keygen_pubexp:%d' %
|
||||
(tmpdir, public_exponent))
|
||||
|
||||
# Create a certificate containing the public key
|
||||
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
|
||||
'%sdev.crt' % (tmpdir, tmpdir))
|
||||
|
||||
# Create an RSA key pair (prod)
|
||||
public_exponent = 65537
|
||||
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sprod.key '
|
||||
'-pkeyopt rsa_keygen_bits:2048 '
|
||||
'-pkeyopt rsa_keygen_pubexp:%d' %
|
||||
(tmpdir, public_exponent))
|
||||
|
||||
# Create a certificate containing the public key (prod)
|
||||
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sprod.key -out '
|
||||
'%sprod.crt' % (tmpdir, tmpdir))
|
||||
|
||||
# Create a number kernel image with zeroes
|
||||
with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
|
||||
fd.write(5000 * chr(0))
|
||||
|
||||
try:
|
||||
# We need to use our own device tree file. Remember to restore it
|
||||
# afterwards.
|
||||
old_dtb = cons.config.dtb
|
||||
cons.config.dtb = dtb
|
||||
test_with_algo('sha1','')
|
||||
test_with_algo('sha1','-pss')
|
||||
test_with_algo('sha256','')
|
||||
test_with_algo('sha256','-pss')
|
||||
test_required_key('sha256','-pss')
|
||||
finally:
|
||||
# Go back to the original U-Boot with the correct dtb.
|
||||
cons.config.dtb = old_dtb
|
||||
cons.restart_uboot()
|
||||
7
test/py/tests/vboot/sandbox-kernel.dts
Normal file
7
test/py/tests/vboot/sandbox-kernel.dts
Normal file
@@ -0,0 +1,7 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
model = "Sandbox Verified Boot Test";
|
||||
compatible = "sandbox";
|
||||
|
||||
};
|
||||
10
test/py/tests/vboot/sandbox-u-boot.dts
Normal file
10
test/py/tests/vboot/sandbox-u-boot.dts
Normal file
@@ -0,0 +1,10 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
model = "Sandbox Verified Boot Test";
|
||||
compatible = "sandbox";
|
||||
|
||||
reset@0 {
|
||||
compatible = "sandbox,reset";
|
||||
};
|
||||
};
|
||||
46
test/py/tests/vboot/sign-configs-sha1-pss.its
Normal file
46
test/py/tests/vboot/sign-configs-sha1-pss.its
Normal file
@@ -0,0 +1,46 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha1";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha1";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
sign-images = "fdt", "kernel";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
45
test/py/tests/vboot/sign-configs-sha1.its
Normal file
45
test/py/tests/vboot/sign-configs-sha1.its
Normal file
@@ -0,0 +1,45 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha1";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha1";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
sign-images = "fdt", "kernel";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
46
test/py/tests/vboot/sign-configs-sha256-pss-prod.its
Normal file
46
test/py/tests/vboot/sign-configs-sha256-pss-prod.its
Normal file
@@ -0,0 +1,46 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "prod";
|
||||
sign-images = "fdt", "kernel";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
46
test/py/tests/vboot/sign-configs-sha256-pss.its
Normal file
46
test/py/tests/vboot/sign-configs-sha256-pss.its
Normal file
@@ -0,0 +1,46 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
sign-images = "fdt", "kernel";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
45
test/py/tests/vboot/sign-configs-sha256.its
Normal file
45
test/py/tests/vboot/sign-configs-sha256.its
Normal file
@@ -0,0 +1,45 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
sign-images = "fdt", "kernel";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
44
test/py/tests/vboot/sign-images-sha1-pss.its
Normal file
44
test/py/tests/vboot/sign-images-sha1-pss.its
Normal file
@@ -0,0 +1,44 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
};
|
||||
};
|
||||
};
|
||||
42
test/py/tests/vboot/sign-images-sha1.its
Normal file
42
test/py/tests/vboot/sign-images-sha1.its
Normal file
@@ -0,0 +1,42 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
signature {
|
||||
algo = "sha1,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
};
|
||||
};
|
||||
};
|
||||
44
test/py/tests/vboot/sign-images-sha256-pss.its
Normal file
44
test/py/tests/vboot/sign-images-sha256-pss.its
Normal file
@@ -0,0 +1,44 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
padding = "pss";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
};
|
||||
};
|
||||
};
|
||||
42
test/py/tests/vboot/sign-images-sha256.its
Normal file
42
test/py/tests/vboot/sign-images-sha256.its
Normal file
@@ -0,0 +1,42 @@
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Chrome OS kernel image with one or more FDT blobs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
data = /incbin/("test-kernel.bin");
|
||||
type = "kernel_noload";
|
||||
arch = "sandbox";
|
||||
os = "linux";
|
||||
compression = "none";
|
||||
load = <0x4>;
|
||||
entry = <0x8>;
|
||||
kernel-version = <1>;
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
fdt-1 {
|
||||
description = "snow";
|
||||
data = /incbin/("sandbox-kernel.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
fdt-version = <1>;
|
||||
signature {
|
||||
algo = "sha256,rsa2048";
|
||||
key-name-hint = "dev";
|
||||
};
|
||||
};
|
||||
};
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
kernel = "kernel";
|
||||
fdt = "fdt-1";
|
||||
};
|
||||
};
|
||||
};
|
||||
468
test/py/u_boot_console_base.py
Normal file
468
test/py/u_boot_console_base.py
Normal file
@@ -0,0 +1,468 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# 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
|
||||
import re
|
||||
import sys
|
||||
import u_boot_spawn
|
||||
|
||||
# Regexes for text we expect U-Boot to send to the console.
|
||||
pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
|
||||
pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
|
||||
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 ###')
|
||||
|
||||
PAT_ID = 0
|
||||
PAT_RE = 1
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
class ConsoleDisableCheck(object):
|
||||
"""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
|
||||
order to test that the error condition is actually raised. This class is
|
||||
used internally by ConsoleBase::disable_check(); it is not intended for
|
||||
direct usage."""
|
||||
|
||||
def __init__(self, console, check_type):
|
||||
self.console = console
|
||||
self.check_type = check_type
|
||||
|
||||
def __enter__(self):
|
||||
self.console.disable_check_count[self.check_type] += 1
|
||||
self.console.eval_bad_patterns()
|
||||
|
||||
def __exit__(self, extype, value, traceback):
|
||||
self.console.disable_check_count[self.check_type] -= 1
|
||||
self.console.eval_bad_patterns()
|
||||
|
||||
class ConsoleSetupTimeout(object):
|
||||
"""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
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, extype, value, traceback):
|
||||
self.p.timeout = self.orig_timeout
|
||||
|
||||
class ConsoleBase(object):
|
||||
"""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
|
||||
are also provided too."""
|
||||
|
||||
def __init__(self, log, config, max_fifo_fill):
|
||||
"""Initialize a U-Boot console connection.
|
||||
|
||||
Can only usefully be called by sub-classes.
|
||||
|
||||
Args:
|
||||
log: A mulptiplex_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
|
||||
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.
|
||||
"""
|
||||
|
||||
self.log = log
|
||||
self.config = config
|
||||
self.max_fifo_fill = max_fifo_fill
|
||||
|
||||
self.logstream = self.log.get_stream('console', sys.stdout)
|
||||
|
||||
# Array slice removes leading/trailing quotes
|
||||
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.at_prompt = False
|
||||
self.at_prompt_logevt = None
|
||||
|
||||
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 close(self):
|
||||
"""Terminate the connection to the U-Boot console.
|
||||
|
||||
This function is only useful once all interaction with U-Boot is
|
||||
complete. Once this function is called, data cannot be sent to or
|
||||
received from U-Boot.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if self.p:
|
||||
self.p.close()
|
||||
self.logstream.close()
|
||||
|
||||
def run_command(self, cmd, wait_for_echo=True, send_nl=True,
|
||||
wait_for_prompt=True):
|
||||
"""Execute a command via the U-Boot console.
|
||||
|
||||
The command is always sent to U-Boot.
|
||||
|
||||
U-Boot echoes any command back to its output, and this function
|
||||
typically waits for that to occur. The wait can be disabled by setting
|
||||
wait_for_echo=False, which is useful e.g. when sending CTRL-C to
|
||||
interrupt a long-running command such as "ums".
|
||||
|
||||
Command execution is typically triggered by sending a newline
|
||||
character. This can be disabled by setting send_nl=False, which is
|
||||
also useful when sending CTRL-C.
|
||||
|
||||
This function typically waits for the command to finish executing, and
|
||||
returns the console output that it generated. This can be disabled by
|
||||
setting wait_for_prompt=False, which is useful when invoking a long-
|
||||
running command such as "ums".
|
||||
|
||||
Args:
|
||||
cmd: The command to send.
|
||||
wait_for_echo: Boolean indicating 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
|
||||
after the command string.
|
||||
wait_for_prompt: Boolean indicating whether to wait for the
|
||||
command prompt to be sent by U-Boot. This typically occurs
|
||||
immediately after the command has been executed.
|
||||
|
||||
Returns:
|
||||
If wait_for_prompt == False:
|
||||
Nothing.
|
||||
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)
|
||||
|
||||
try:
|
||||
self.at_prompt = False
|
||||
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 Exception('Bad pattern found on console: ' +
|
||||
self.bad_pattern_ids[m - 1])
|
||||
if not wait_for_prompt:
|
||||
return
|
||||
m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
|
||||
if m != 0:
|
||||
self.at_prompt = False
|
||||
raise Exception('Bad pattern found on console: ' +
|
||||
self.bad_pattern_ids[m - 1])
|
||||
self.at_prompt = True
|
||||
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')
|
||||
except Exception as ex:
|
||||
self.log.error(str(ex))
|
||||
self.cleanup_spawn()
|
||||
raise
|
||||
finally:
|
||||
self.log.timestamp()
|
||||
|
||||
def run_command_list(self, cmds):
|
||||
"""Run a list of commands.
|
||||
|
||||
This is a helper function to call run_command() with default arguments
|
||||
for each command in a list.
|
||||
|
||||
Args:
|
||||
cmd: List of commands (each a string).
|
||||
Returns:
|
||||
A list of output strings from each command, one element for each
|
||||
command.
|
||||
"""
|
||||
output = []
|
||||
for cmd in cmds:
|
||||
output.append(self.run_command(cmd))
|
||||
return output
|
||||
|
||||
def ctrlc(self):
|
||||
"""Send a CTRL-C character to U-Boot.
|
||||
|
||||
This is useful in order to stop execution of long-running synchronous
|
||||
commands such as "ums".
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.log.action('Sending Ctrl-C')
|
||||
self.run_command(chr(3), wait_for_echo=False, send_nl=False)
|
||||
|
||||
def wait_for(self, text):
|
||||
"""Wait for a pattern to be emitted by U-Boot.
|
||||
|
||||
This is useful when a long-running command such as "dfu" is executing,
|
||||
and it periodically emits some text that should show up at a specific
|
||||
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.
|
||||
"""
|
||||
|
||||
if type(text) == type(''):
|
||||
text = re.escape(text)
|
||||
m = self.p.expect([text] + self.bad_patterns)
|
||||
if m != 0:
|
||||
raise Exception('Bad pattern found on console: ' +
|
||||
self.bad_pattern_ids[m - 1])
|
||||
|
||||
def drain_console(self):
|
||||
"""Read from and log the U-Boot console for a short time.
|
||||
|
||||
U-Boot's console output is only logged when the test code actively
|
||||
waits for U-Boot to emit specific data. There are cases where tests
|
||||
can fail without doing this. For example, if a test asks U-Boot to
|
||||
enable USB device mode, then polls until a host-side device node
|
||||
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
|
||||
# the system is shutting down.
|
||||
if not self.p:
|
||||
return
|
||||
|
||||
orig_timeout = self.p.timeout
|
||||
try:
|
||||
# Drain the log for a relatively short time.
|
||||
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'])
|
||||
except:
|
||||
# We expect a timeout, since U-Boot won't print what we waited
|
||||
# for. Squash it when it happens.
|
||||
#
|
||||
# Squash any other exception too. This function is only used to
|
||||
# drain (and log) the U-Boot console output after a failed test.
|
||||
# The U-Boot process will be restarted, or target board reset, once
|
||||
# this function returns. So, we don't care about detecting any
|
||||
# additional errors, so they're squashed so that the rest of the
|
||||
# post-test-failure cleanup code can continue operation, and
|
||||
# correctly terminate any log sections, etc.
|
||||
pass
|
||||
finally:
|
||||
self.p.timeout = orig_timeout
|
||||
|
||||
def ensure_spawned(self):
|
||||
"""Ensure a connection to a correctly running U-Boot instance.
|
||||
|
||||
This may require spawning a new Sandbox process or resetting target
|
||||
hardware, as defined by the implementation sub-class.
|
||||
|
||||
This is an internal function and should not be called directly.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
if self.p:
|
||||
return
|
||||
try:
|
||||
self.log.start_section('Starting U-Boot')
|
||||
self.at_prompt = False
|
||||
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 = 30000
|
||||
self.p.logfile_read = self.logstream
|
||||
bcfg = self.config.buildconfig
|
||||
config_spl = bcfg.get('config_spl', 'n') == 'y'
|
||||
config_spl_serial_support = bcfg.get('config_spl_serial_support',
|
||||
'n') == 'y'
|
||||
env_spl_skipped = self.config.env.get('env__spl_skipped',
|
||||
False)
|
||||
if config_spl and config_spl_serial_support and not env_spl_skipped:
|
||||
m = self.p.expect([pattern_u_boot_spl_signon] +
|
||||
self.bad_patterns)
|
||||
if m != 0:
|
||||
raise Exception('Bad pattern found on SPL console: ' +
|
||||
self.bad_pattern_ids[m - 1])
|
||||
m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
|
||||
if m != 0:
|
||||
raise Exception('Bad pattern found on console: ' +
|
||||
self.bad_pattern_ids[m - 1])
|
||||
self.u_boot_version_string = self.p.after
|
||||
while True:
|
||||
m = self.p.expect([self.prompt_compiled,
|
||||
pattern_stop_autoboot_prompt] + self.bad_patterns)
|
||||
if m == 0:
|
||||
break
|
||||
if m == 1:
|
||||
self.p.send(' ')
|
||||
continue
|
||||
raise Exception('Bad pattern found on console: ' +
|
||||
self.bad_pattern_ids[m - 2])
|
||||
self.at_prompt = True
|
||||
self.at_prompt_logevt = self.logstream.logfile.cur_evt
|
||||
except Exception as ex:
|
||||
self.log.error(str(ex))
|
||||
self.cleanup_spawn()
|
||||
raise
|
||||
finally:
|
||||
self.log.timestamp()
|
||||
self.log.end_section('Starting U-Boot')
|
||||
|
||||
def cleanup_spawn(self):
|
||||
"""Shut down all interaction with the U-Boot instance.
|
||||
|
||||
This is used when an error is detected prior to re-establishing a
|
||||
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()
|
||||
except:
|
||||
pass
|
||||
self.p = None
|
||||
|
||||
def restart_uboot(self):
|
||||
"""Shut down and restart U-Boot."""
|
||||
self.cleanup_spawn()
|
||||
self.ensure_spawned()
|
||||
|
||||
def get_spawn_output(self):
|
||||
"""Return the start-up output from U-Boot
|
||||
|
||||
Returns:
|
||||
The output produced by ensure_spawed(), as a string.
|
||||
"""
|
||||
if self.p:
|
||||
return self.p.get_expect_output()
|
||||
return None
|
||||
|
||||
def validate_version_string_in_text(self, text):
|
||||
"""Assert that a command's output includes the U-Boot signon message.
|
||||
|
||||
This is primarily useful for validating the "version" command without
|
||||
duplicating the signon text regex in a test function.
|
||||
|
||||
Args:
|
||||
text: The command output text to check.
|
||||
|
||||
Returns:
|
||||
Nothing. An exception is raised if the validation fails.
|
||||
"""
|
||||
|
||||
assert(self.u_boot_version_string in text)
|
||||
|
||||
def disable_check(self, check_type):
|
||||
"""Temporarily disable an error check of U-Boot's output.
|
||||
|
||||
Create a new context manager (for use with the "with" statement) which
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A context manager object.
|
||||
"""
|
||||
|
||||
return ConsoleDisableCheck(self, check_type)
|
||||
|
||||
def temporary_timeout(self, timeout):
|
||||
"""Temporarily set up different timeout for commands.
|
||||
|
||||
Create a new context manager (for use with the "with" statement) which
|
||||
temporarily change timeout.
|
||||
|
||||
Args:
|
||||
timeout: Time in milliseconds.
|
||||
|
||||
Returns:
|
||||
A context manager object.
|
||||
"""
|
||||
|
||||
return ConsoleSetupTimeout(self, timeout)
|
||||
70
test/py/u_boot_console_exec_attach.py
Normal file
70
test/py/u_boot_console_exec_attach.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Logic to interact with U-Boot running on real hardware, typically via a
|
||||
# physical serial port.
|
||||
|
||||
import sys
|
||||
from u_boot_spawn import Spawn
|
||||
from u_boot_console_base import ConsoleBase
|
||||
|
||||
class ConsoleExecAttach(ConsoleBase):
|
||||
"""Represents a physical connection to a U-Boot console, typically via a
|
||||
serial port. This implementation executes a sub-process to attach to the
|
||||
console, expecting that the stdin/out of the sub-process will be forwarded
|
||||
to/from the physical hardware. This approach isolates the test infra-
|
||||
structure from the user-/installation-specific details of how to
|
||||
communicate with, and the identity of, serial ports etc."""
|
||||
|
||||
def __init__(self, log, config):
|
||||
"""Initialize a U-Boot console connection.
|
||||
|
||||
Args:
|
||||
log: A multiplexed_log.Logfile instance.
|
||||
config: A "configuration" object as defined in conftest.py.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
# The max_fifo_fill value might need tweaking per-board/-SoC?
|
||||
# 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)
|
||||
|
||||
with self.log.section('flash'):
|
||||
self.log.action('Flashing U-Boot')
|
||||
cmd = ['u-boot-test-flash', config.board_type, config.board_identity]
|
||||
runner = self.log.get_runner(cmd[0], sys.stdout)
|
||||
runner.run(cmd)
|
||||
runner.close()
|
||||
self.log.status_pass('OK')
|
||||
|
||||
def get_spawn(self):
|
||||
"""Connect to a fresh U-Boot instance.
|
||||
|
||||
The target board is reset, so that U-Boot begins running from scratch.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
A u_boot_spawn.Spawn object that is attached to U-Boot.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
return s
|
||||
108
test/py/u_boot_console_sandbox.py
Normal file
108
test/py/u_boot_console_sandbox.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015 Stephen Warren
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Logic to interact with the sandbox port of U-Boot, running as a sub-process.
|
||||
|
||||
import time
|
||||
from u_boot_spawn import Spawn
|
||||
from u_boot_console_base import ConsoleBase
|
||||
|
||||
class ConsoleSandbox(ConsoleBase):
|
||||
"""Represents a connection to a sandbox U-Boot console, executed as a sub-
|
||||
process."""
|
||||
|
||||
def __init__(self, log, config):
|
||||
"""Initialize a U-Boot console connection.
|
||||
|
||||
Args:
|
||||
log: A multiplexed_log.Logfile instance.
|
||||
config: A "configuration" object as defined in conftest.py.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
super(ConsoleSandbox, self).__init__(log, config, max_fifo_fill=1024)
|
||||
self.sandbox_flags = []
|
||||
|
||||
def get_spawn(self):
|
||||
"""Connect to a fresh U-Boot instance.
|
||||
|
||||
A new sandbox process is created, so that U-Boot begins running from
|
||||
scratch.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
A u_boot_spawn.Spawn object that is attached to U-Boot.
|
||||
"""
|
||||
|
||||
bcfg = self.config.buildconfig
|
||||
config_spl = bcfg.get('config_spl', 'n') == 'y'
|
||||
fname = '/spl/u-boot-spl' if config_spl else '/u-boot'
|
||||
print(fname)
|
||||
cmd = []
|
||||
if self.config.gdbserver:
|
||||
cmd += ['gdbserver', self.config.gdbserver]
|
||||
cmd += [
|
||||
self.config.build_dir + fname,
|
||||
'-v',
|
||||
'-d',
|
||||
self.config.dtb
|
||||
]
|
||||
cmd += self.sandbox_flags
|
||||
return Spawn(cmd, cwd=self.config.source_dir)
|
||||
|
||||
def restart_uboot_with_flags(self, flags):
|
||||
"""Run U-Boot with the given command-line flags
|
||||
|
||||
Args:
|
||||
flags: List of flags to pass, each a string
|
||||
|
||||
Returns:
|
||||
A u_boot_spawn.Spawn object that is attached to U-Boot.
|
||||
"""
|
||||
|
||||
try:
|
||||
self.sandbox_flags = flags
|
||||
return self.restart_uboot()
|
||||
finally:
|
||||
self.sandbox_flags = []
|
||||
|
||||
def kill(self, sig):
|
||||
"""Send a specific Unix signal to the sandbox process.
|
||||
|
||||
Args:
|
||||
sig: The Unix signal to send to the process.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.log.action('kill %d' % sig)
|
||||
self.p.kill(sig)
|
||||
|
||||
def validate_exited(self):
|
||||
"""Determine whether the sandbox process has exited.
|
||||
|
||||
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):
|
||||
ret = not p.isalive()
|
||||
if ret:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
p.close()
|
||||
return ret
|
||||
209
test/py/u_boot_spawn.py
Normal file
209
test/py/u_boot_spawn.py
Normal file
@@ -0,0 +1,209 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Logic to spawn a sub-process and interact with its stdio.
|
||||
|
||||
import os
|
||||
import re
|
||||
import pty
|
||||
import signal
|
||||
import select
|
||||
import time
|
||||
|
||||
class Timeout(Exception):
|
||||
"""An exception sub-class that indicates that a timeout occurred."""
|
||||
pass
|
||||
|
||||
class Spawn(object):
|
||||
"""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):
|
||||
"""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.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.waited = False
|
||||
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:
|
||||
try:
|
||||
# For some reason, SIGHUP is set to SIG_IGN at this point when
|
||||
# run under "go" (www.go.cd). Perhaps this happens under any
|
||||
# background (non-interactive) system?
|
||||
signal.signal(signal.SIGHUP, signal.SIG_DFL)
|
||||
if cwd:
|
||||
os.chdir(cwd)
|
||||
os.execvp(args[0], args)
|
||||
except:
|
||||
print('CHILD EXECEPTION:')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
os._exit(255)
|
||||
|
||||
try:
|
||||
self.poll = select.poll()
|
||||
self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def kill(self, sig):
|
||||
"""Send unix signal "sig" to the child process.
|
||||
|
||||
Args:
|
||||
sig: The signal number to send.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
def isalive(self):
|
||||
"""Determine whether the child process is still running.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Boolean indicating whether process is alive.
|
||||
"""
|
||||
|
||||
if self.waited:
|
||||
return False
|
||||
|
||||
w = os.waitpid(self.pid, os.WNOHANG)
|
||||
if w[0] == 0:
|
||||
return True
|
||||
|
||||
self.waited = True
|
||||
return False
|
||||
|
||||
def send(self, data):
|
||||
"""Send data to the sub-process's stdin.
|
||||
|
||||
Args:
|
||||
data: The data to send to the process.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
os.write(self.fd, data.encode(errors='replace'))
|
||||
|
||||
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 = os.read(self.fd, 1024).decode(errors='replace')
|
||||
if not c:
|
||||
raise EOFError()
|
||||
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.
|
||||
|
||||
This also waits a reasonable time for the sub-process to stop running.
|
||||
|
||||
Args:
|
||||
None.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
os.close(self.fd)
|
||||
for i in range(100):
|
||||
if not self.isalive():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
def get_expect_output(self):
|
||||
"""Return the output read by expect()
|
||||
|
||||
Returns:
|
||||
The output processed by expect(), as a string.
|
||||
"""
|
||||
return self.output
|
||||
340
test/py/u_boot_utils.py
Normal file
340
test/py/u_boot_utils.py
Normal file
@@ -0,0 +1,340 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
|
||||
# Utility code shared across multiple tests.
|
||||
|
||||
import hashlib
|
||||
import inspect
|
||||
import os
|
||||
import os.path
|
||||
import pytest
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
|
||||
def md5sum_data(data):
|
||||
"""Calculate the MD5 hash of some data.
|
||||
|
||||
Args:
|
||||
data: The data to hash.
|
||||
|
||||
Returns:
|
||||
The hash of the data, as a binary string.
|
||||
"""
|
||||
|
||||
h = hashlib.md5()
|
||||
h.update(data)
|
||||
return h.digest()
|
||||
|
||||
def md5sum_file(fn, max_length=None):
|
||||
"""Calculate the MD5 hash of the contents of a file.
|
||||
|
||||
Args:
|
||||
fn: The filename of the file to hash.
|
||||
max_length: The number of bytes to hash. If the file has more
|
||||
bytes than this, they will be ignored. If None or omitted, the
|
||||
entire file will be hashed.
|
||||
|
||||
Returns:
|
||||
The hash of the file content, as a binary string.
|
||||
"""
|
||||
|
||||
with open(fn, 'rb') as fh:
|
||||
if max_length:
|
||||
params = [max_length]
|
||||
else:
|
||||
params = []
|
||||
data = fh.read(*params)
|
||||
return md5sum_data(data)
|
||||
|
||||
class PersistentRandomFile(object):
|
||||
"""Generate and store information about a persistent file containing
|
||||
random data."""
|
||||
|
||||
def __init__(self, u_boot_console, fn, size):
|
||||
"""Create or process the persistent file.
|
||||
|
||||
If the file does not exist, it is generated.
|
||||
|
||||
If the file does exist, its content is hashed for later comparison.
|
||||
|
||||
These files are always located in the "persistent data directory" of
|
||||
the current test run.
|
||||
|
||||
Args:
|
||||
u_boot_console: A console connection to U-Boot.
|
||||
fn: The filename (without path) to create.
|
||||
size: The desired size of the file in bytes.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.fn = fn
|
||||
|
||||
self.abs_fn = u_boot_console.config.persistent_data_dir + '/' + fn
|
||||
|
||||
if os.path.exists(self.abs_fn):
|
||||
u_boot_console.log.action('Persistent data file ' + self.abs_fn +
|
||||
' already exists')
|
||||
self.content_hash = md5sum_file(self.abs_fn)
|
||||
else:
|
||||
u_boot_console.log.action('Generating ' + self.abs_fn +
|
||||
' (random, persistent, %d bytes)' % size)
|
||||
data = os.urandom(size)
|
||||
with open(self.abs_fn, 'wb') as fh:
|
||||
fh.write(data)
|
||||
self.content_hash = md5sum_data(data)
|
||||
|
||||
def attempt_to_open_file(fn):
|
||||
"""Attempt to open a file, without throwing exceptions.
|
||||
|
||||
Any errors (exceptions) that occur during the attempt to open the file
|
||||
are ignored. This is useful in order to test whether a file (in
|
||||
particular, a device node) exists and can be successfully opened, in order
|
||||
to poll for e.g. USB enumeration completion.
|
||||
|
||||
Args:
|
||||
fn: The filename to attempt to open.
|
||||
|
||||
Returns:
|
||||
An open file handle to the file, or None if the file could not be
|
||||
opened.
|
||||
"""
|
||||
|
||||
try:
|
||||
return open(fn, 'rb')
|
||||
except:
|
||||
return None
|
||||
|
||||
def wait_until_open_succeeds(fn):
|
||||
"""Poll until a file can be opened, or a timeout occurs.
|
||||
|
||||
Continually attempt to open a file, and return when this succeeds, or
|
||||
raise an exception after a timeout.
|
||||
|
||||
Args:
|
||||
fn: The filename to attempt to open.
|
||||
|
||||
Returns:
|
||||
An open file handle to the file.
|
||||
"""
|
||||
|
||||
for i in range(100):
|
||||
fh = attempt_to_open_file(fn)
|
||||
if fh:
|
||||
return fh
|
||||
time.sleep(0.1)
|
||||
raise Exception('File could not be opened')
|
||||
|
||||
def wait_until_file_open_fails(fn, ignore_errors):
|
||||
"""Poll until a file cannot be opened, or a timeout occurs.
|
||||
|
||||
Continually attempt to open a file, and return when this fails, or
|
||||
raise an exception after a timeout.
|
||||
|
||||
Args:
|
||||
fn: The filename to attempt to open.
|
||||
ignore_errors: Indicate whether to ignore timeout errors. If True, the
|
||||
function will simply return if a timeout occurs, otherwise an
|
||||
exception will be raised.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
for i in range(100):
|
||||
fh = attempt_to_open_file(fn)
|
||||
if not fh:
|
||||
return
|
||||
fh.close()
|
||||
time.sleep(0.1)
|
||||
if ignore_errors:
|
||||
return
|
||||
raise Exception('File can still be opened')
|
||||
|
||||
def run_and_log(u_boot_console, cmd, ignore_errors=False):
|
||||
"""Run a command and log its output.
|
||||
|
||||
Args:
|
||||
u_boot_console: A console connection to U-Boot.
|
||||
cmd: The command to run, as an array of argv[], or a string.
|
||||
If a string, note that it is split up so that quoted spaces
|
||||
will not be preserved. E.g. "fred and" becomes ['"fred', 'and"']
|
||||
ignore_errors: Indicate whether to ignore errors. If True, the function
|
||||
will simply return if the command cannot be executed or exits with
|
||||
an error code, otherwise an exception will be raised if such
|
||||
problems occur.
|
||||
|
||||
Returns:
|
||||
The output as a string.
|
||||
"""
|
||||
if isinstance(cmd, str):
|
||||
cmd = cmd.split()
|
||||
runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
|
||||
output = runner.run(cmd, ignore_errors=ignore_errors)
|
||||
runner.close()
|
||||
return output
|
||||
|
||||
def run_and_log_expect_exception(u_boot_console, cmd, retcode, msg):
|
||||
"""Run a command that is expected to fail.
|
||||
|
||||
This runs a command and checks that it fails with the expected return code
|
||||
and exception method. If not, an exception is raised.
|
||||
|
||||
Args:
|
||||
u_boot_console: A console connection to U-Boot.
|
||||
cmd: The command to run, as an array of argv[].
|
||||
retcode: Expected non-zero return code from the command.
|
||||
msg: String that should be contained within the command's output.
|
||||
"""
|
||||
try:
|
||||
runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
|
||||
runner.run(cmd)
|
||||
except Exception as e:
|
||||
assert(retcode == runner.exit_status)
|
||||
assert(msg in runner.output)
|
||||
else:
|
||||
raise Exception("Expected an exception with retcode %d message '%s',"
|
||||
"but it was not raised" % (retcode, msg))
|
||||
finally:
|
||||
runner.close()
|
||||
|
||||
ram_base = None
|
||||
def find_ram_base(u_boot_console):
|
||||
"""Find the running U-Boot's RAM location.
|
||||
|
||||
Probe the running U-Boot to determine the address of the first bank
|
||||
of RAM. This is useful for tests that test reading/writing RAM, or
|
||||
load/save files that aren't associated with some standard address
|
||||
typically represented in an environment variable such as
|
||||
${kernel_addr_r}. The value is cached so that it only needs to be
|
||||
actively read once.
|
||||
|
||||
Args:
|
||||
u_boot_console: A console connection to U-Boot.
|
||||
|
||||
Returns:
|
||||
The address of U-Boot's first RAM bank, as an integer.
|
||||
"""
|
||||
|
||||
global ram_base
|
||||
if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
|
||||
pytest.skip('bdinfo command not supported')
|
||||
if ram_base == -1:
|
||||
pytest.skip('Previously failed to find RAM bank start')
|
||||
if ram_base is not None:
|
||||
return ram_base
|
||||
|
||||
with u_boot_console.log.section('find_ram_base'):
|
||||
response = u_boot_console.run_command('bdinfo')
|
||||
for l in response.split('\n'):
|
||||
if '-> start' in l or 'memstart =' in l:
|
||||
ram_base = int(l.split('=')[1].strip(), 16)
|
||||
break
|
||||
if ram_base is None:
|
||||
ram_base = -1
|
||||
raise Exception('Failed to find RAM bank start in `bdinfo`')
|
||||
|
||||
# We don't want ram_base to be zero as some functions test if the given
|
||||
# address is NULL (0). Let's add 2MiB then (size of an ARM LPAE/v8 section).
|
||||
|
||||
if ram_base == 0:
|
||||
ram_base += 1024 * 1024 * 2
|
||||
|
||||
return ram_base
|
||||
|
||||
class PersistentFileHelperCtxMgr(object):
|
||||
"""A context manager for Python's "with" statement, which ensures that any
|
||||
generated file is deleted (and hence regenerated) if its mtime is older
|
||||
than the mtime of the Python module which generated it, and gets an mtime
|
||||
newer than the mtime of the Python module which generated after it is
|
||||
generated. Objects of this type should be created by factory function
|
||||
persistent_file_helper rather than directly."""
|
||||
|
||||
def __init__(self, log, filename):
|
||||
"""Initialize a new object.
|
||||
|
||||
Args:
|
||||
log: The Logfile object to log to.
|
||||
filename: The filename of the generated file.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
|
||||
self.log = log
|
||||
self.filename = filename
|
||||
|
||||
def __enter__(self):
|
||||
frame = inspect.stack()[1]
|
||||
module = inspect.getmodule(frame[0])
|
||||
self.module_filename = module.__file__
|
||||
self.module_timestamp = os.path.getmtime(self.module_filename)
|
||||
|
||||
if os.path.exists(self.filename):
|
||||
filename_timestamp = os.path.getmtime(self.filename)
|
||||
if filename_timestamp < self.module_timestamp:
|
||||
self.log.action('Removing stale generated file ' +
|
||||
self.filename)
|
||||
os.unlink(self.filename)
|
||||
|
||||
def __exit__(self, extype, value, traceback):
|
||||
if extype:
|
||||
try:
|
||||
os.path.unlink(self.filename)
|
||||
except:
|
||||
pass
|
||||
return
|
||||
logged = False
|
||||
for i in range(20):
|
||||
filename_timestamp = os.path.getmtime(self.filename)
|
||||
if filename_timestamp > self.module_timestamp:
|
||||
break
|
||||
if not logged:
|
||||
self.log.action(
|
||||
'Waiting for generated file timestamp to increase')
|
||||
logged = True
|
||||
os.utime(self.filename)
|
||||
time.sleep(0.1)
|
||||
|
||||
def persistent_file_helper(u_boot_log, filename):
|
||||
"""Manage the timestamps and regeneration of a persistent generated
|
||||
file. This function creates a context manager for Python's "with"
|
||||
statement
|
||||
|
||||
Usage:
|
||||
with persistent_file_helper(u_boot_console.log, filename):
|
||||
code to generate the file, if it's missing.
|
||||
|
||||
Args:
|
||||
u_boot_log: u_boot_console.log.
|
||||
filename: The filename of the generated file.
|
||||
|
||||
Returns:
|
||||
A context manager object.
|
||||
"""
|
||||
|
||||
return PersistentFileHelperCtxMgr(u_boot_log, filename)
|
||||
|
||||
def crc32(u_boot_console, address, count):
|
||||
"""Helper function used to compute the CRC32 value of a section of RAM.
|
||||
|
||||
Args:
|
||||
u_boot_console: A U-Boot console connection.
|
||||
address: Address where data starts.
|
||||
count: Amount of data to use for calculation.
|
||||
|
||||
Returns:
|
||||
CRC32 value
|
||||
"""
|
||||
|
||||
bcfg = u_boot_console.config.buildconfig
|
||||
has_cmd_crc32 = bcfg.get('config_cmd_crc32', 'n') == 'y'
|
||||
assert has_cmd_crc32, 'Cannot compute crc32 without CONFIG_CMD_CRC32.'
|
||||
output = u_boot_console.run_command('crc32 %08x %x' % (address, count))
|
||||
|
||||
m = re.search('==> ([0-9a-fA-F]{8})$', output)
|
||||
assert m, 'CRC32 operation failed.'
|
||||
|
||||
return m.group(1)
|
||||
Reference in New Issue
Block a user