diff options
Diffstat (limited to 'test/py/tests')
-rw-r--r-- | test/py/tests/test_000_version.py | 2 | ||||
-rw-r--r-- | test/py/tests/test_dfu.py | 279 | ||||
-rw-r--r-- | test/py/tests/test_env.py | 66 | ||||
-rw-r--r-- | test/py/tests/test_help.py | 2 | ||||
-rw-r--r-- | test/py/tests/test_hush_if_test.py | 10 | ||||
-rw-r--r-- | test/py/tests/test_md.py | 13 | ||||
-rw-r--r-- | test/py/tests/test_net.py | 155 | ||||
-rw-r--r-- | test/py/tests/test_sandbox_exit.py | 6 | ||||
-rw-r--r-- | test/py/tests/test_shell_basics.py | 12 | ||||
-rw-r--r-- | test/py/tests/test_sleep.py | 8 | ||||
-rw-r--r-- | test/py/tests/test_ums.py | 243 | ||||
-rw-r--r-- | test/py/tests/test_unknown_cmd.py | 4 |
12 files changed, 688 insertions, 112 deletions
diff --git a/test/py/tests/test_000_version.py b/test/py/tests/test_000_version.py index d262f05..43a02e7 100644 --- a/test/py/tests/test_000_version.py +++ b/test/py/tests/test_000_version.py @@ -9,7 +9,7 @@ # command prompt. def test_version(u_boot_console): - '''Test that the "version" command prints the U-Boot version.''' + """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 diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py new file mode 100644 index 0000000..093e8d0 --- /dev/null +++ b/test/py/tests/test_dfu.py @@ -0,0 +1,279 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# 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", + }, +) + +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), + }, +) + +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.) +""" + +# 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') +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. + """ + + 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') + + cmd = 'setenv dfu_alt_info "%s"' % 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', str(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(0, test_f.abs_fn) + + u_boot_console.log.action('Writing dummy data to DFU secondary ' + + 'altsetting to clear DFU buffers') + dfu_write(1, dummy_f.abs_fn) + + u_boot_console.log.action('Reading DFU primary altsetting for ' + + 'comparison') + dfu_read(0, 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) + + ignore_cleanup_errors = True + try: + start_dfu() + + u_boot_console.log.action( + 'Overwriting DFU primary altsetting with dummy data') + dfu_write(0, 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) diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py index a3e8dd3..c41aa5a 100644 --- a/test/py/tests/test_env.py +++ b/test/py/tests/test_env.py @@ -10,34 +10,34 @@ import pytest # 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. + """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. + """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. + """Read all current environment variables from U-Boot. Args: None. Returns: Nothing. - ''' + """ response = self.u_boot_console.run_command('printenv') self.env = {} @@ -48,27 +48,27 @@ class StateTestEnv(object): self.env[var] = value def get_existent_var(self): - '''Return the name of an environment variable that exists. + """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. + """Return the name of an environment variable that does not exist. Args: None. Returns: The name of an environment variable. - ''' + """ n = 0 while True: @@ -77,63 +77,67 @@ class StateTestEnv(object): return var n += 1 -@pytest.fixture(scope='module') +ste = None +@pytest.fixture(scope='function') def state_test_env(u_boot_console): - '''pytest fixture to provide a StateTestEnv object to tests.''' + """pytest fixture to provide a StateTestEnv object to tests.""" - return StateTestEnv(u_boot_console) + global ste + if not ste: + ste = StateTestEnv(u_boot_console) + return ste def unset_var(state_test_env, var): - '''Unset an environment variable. + """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 updata. + 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. + """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 updata. + state_test_env: The StateTestEnv object to update. var: The variable name to set. value: The value to set the variable to. Returns: Nothing. - ''' + """ state_test_env.u_boot_console.run_command('setenv %s "%s"' % (var, value)) 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. + """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. + """Validate that a variable is set, using U-Boot shell commands. Args: var: The variable name to test. @@ -141,7 +145,7 @@ def validate_set(state_test_env, var, value): Returns: Nothing. - ''' + """ # echo does not preserve leading, internal, or trailing whitespace in the # value. printenv does, and hence allows more complete testing. @@ -149,20 +153,20 @@ def validate_set(state_test_env, var, value): assert response == ('%s=%s' % (var, value)) def test_env_echo_exists(state_test_env): - '''Test echoing a variable that exists.''' + """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) def test_env_echo_non_existent(state_test_env): - '''Test echoing a variable that doesn't exist.''' + """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.''' + """Test printenv error message for non-existant variables.""" var = state_test_env.set_var c = state_test_env.u_boot_console @@ -171,14 +175,14 @@ def test_env_printenv_non_existent(state_test_env): assert(response == '## Error: "%s" not defined' % var) def test_env_unset_non_existent(state_test_env): - '''Test unsetting a nonexistent variable.''' + """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.''' + """Test set a non-existant variable.""" var = state_test_env.set_var value = 'foo' @@ -186,7 +190,7 @@ def test_env_set_non_existent(state_test_env): validate_set(state_test_env, var, value) def test_env_set_existing(state_test_env): - '''Test setting an existant variable.''' + """Test setting an existant variable.""" var = state_test_env.set_var value = 'bar' @@ -194,14 +198,14 @@ def test_env_set_existing(state_test_env): validate_set(state_test_env, var, value) def test_env_unset_existing(state_test_env): - '''Test unsetting a variable.''' + """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.''' + """Test expanding a variable that contains a space in its value.""" var_space = None var_test = None diff --git a/test/py/tests/test_help.py b/test/py/tests/test_help.py index 894f3b5..420090c 100644 --- a/test/py/tests/test_help.py +++ b/test/py/tests/test_help.py @@ -4,6 +4,6 @@ # SPDX-License-Identifier: GPL-2.0 def test_help(u_boot_console): - '''Test that the "help" command can be executed.''' + """Test that the "help" command can be executed.""" u_boot_console.run_command('help') diff --git a/test/py/tests/test_hush_if_test.py b/test/py/tests/test_hush_if_test.py index cf4c3ae..8b88425 100644 --- a/test/py/tests/test_hush_if_test.py +++ b/test/py/tests/test_hush_if_test.py @@ -95,7 +95,7 @@ subtests = ( ) def exec_hush_if(u_boot_console, expr, result): - '''Execute a shell "if" command, and validate its result.''' + """Execute a shell "if" command, and validate its result.""" cmd = 'if ' + expr + '; then echo true; else echo false; fi' response = u_boot_console.run_command(cmd) @@ -103,7 +103,7 @@ def exec_hush_if(u_boot_console, expr, result): @pytest.mark.buildconfigspec('sys_hush_parser') def test_hush_if_test_setup(u_boot_console): - '''Set up environment variables used during the "if" tests.''' + """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') @@ -111,13 +111,13 @@ def test_hush_if_test_setup(u_boot_console): @pytest.mark.buildconfigspec('sys_hush_parser') @pytest.mark.parametrize('expr,result', subtests) def test_hush_if_test(u_boot_console, expr, result): - '''Test a single "if test" condition.''' + """Test a single "if test" condition.""" exec_hush_if(u_boot_console, expr, result) @pytest.mark.buildconfigspec('sys_hush_parser') def test_hush_if_test_teardown(u_boot_console): - '''Clean up environment variables used during the "if" tests.''' + """Clean up environment variables used during the "if" tests.""" u_boot_console.run_command('setenv ut_var_exists') @@ -126,7 +126,7 @@ def test_hush_if_test_teardown(u_boot_console): # Of those, only UMS currently allows file removal though. @pytest.mark.boardspec('sandbox') def test_hush_if_test_host_file_exists(u_boot_console): - '''Test the "if test -e" shell command.''' + """Test the "if test -e" shell command.""" test_file = u_boot_console.config.result_dir + \ '/creating_this_file_breaks_u_boot_tests' diff --git a/test/py/tests/test_md.py b/test/py/tests/test_md.py index 94603c7..5fe2582 100644 --- a/test/py/tests/test_md.py +++ b/test/py/tests/test_md.py @@ -4,13 +4,14 @@ # SPDX-License-Identifier: GPL-2.0 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.''' + """Test that md reads memory as expected, and that memory can be modified + using the mw command.""" - ram_base = u_boot_console.find_ram_base() + ram_base = u_boot_utils.find_ram_base(u_boot_console) addr = '%08x' % ram_base val = 'a5f09876' expected_response = addr + ': ' + val @@ -23,10 +24,10 @@ def test_md(u_boot_console): @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.''' + """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_console.find_ram_base() + 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)) diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py new file mode 100644 index 0000000..07393eb --- /dev/null +++ b/test/py/tests/test_net.py @@ -0,0 +1,155 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# 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", + "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 = 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 + + 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 diff --git a/test/py/tests/test_sandbox_exit.py b/test/py/tests/test_sandbox_exit.py index 2aa8eb4..d1aa308 100644 --- a/test/py/tests/test_sandbox_exit.py +++ b/test/py/tests/test_sandbox_exit.py @@ -9,16 +9,14 @@ import signal @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('reset') def test_reset(u_boot_console): - '''Test that the "reset" command exits sandbox process.''' + """Test that the "reset" command exits sandbox process.""" u_boot_console.run_command('reset', wait_for_prompt=False) assert(u_boot_console.validate_exited()) - u_boot_console.ensure_spawned() @pytest.mark.boardspec('sandbox') def test_ctrl_c(u_boot_console): - '''Test that sending SIGINT to sandbox causes it to exit.''' + """Test that sending SIGINT to sandbox causes it to exit.""" u_boot_console.kill(signal.SIGINT) assert(u_boot_console.validate_exited()) - u_boot_console.ensure_spawned() diff --git a/test/py/tests/test_shell_basics.py b/test/py/tests/test_shell_basics.py index 719ce61..702e5e2 100644 --- a/test/py/tests/test_shell_basics.py +++ b/test/py/tests/test_shell_basics.py @@ -5,13 +5,13 @@ # Test basic shell functionality, such as commands separate by semi-colons. def test_shell_execute(u_boot_console): - '''Test any shell command.''' + """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.''' + """Test two shell commands separate by a semi-colon.""" cmd = 'echo hello; echo world' response = u_boot_console.run_command(cmd) @@ -19,8 +19,8 @@ def test_shell_semicolon_two(u_boot_console): 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.''' + """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}' @@ -29,9 +29,9 @@ def test_shell_semicolon_three(u_boot_console): u_boot_console.run_command('setenv list') def test_shell_run(u_boot_console): - '''Test the "run" shell command.''' + """Test the "run" shell command.""" - u_boot_console.run_command('setenv foo \"setenv monty 1; setenv python 2\"') + 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' diff --git a/test/py/tests/test_sleep.py b/test/py/tests/test_sleep.py index 64f1ddf..74add89 100644 --- a/test/py/tests/test_sleep.py +++ b/test/py/tests/test_sleep.py @@ -6,12 +6,8 @@ import pytest import time def test_sleep(u_boot_console): - '''Test the sleep command, and validate that it sleeps for approximately - the correct amount of time.''' - - # Do this before we time anything, to make sure U-Boot is already running. - # Otherwise, the system boot time is included in the time measurement. - u_boot_console.ensure_spawned() + """Test the sleep command, and validate that it sleeps for approximately + the correct amount of time.""" # 3s isn't too long, but is enough to cross a few second boundaries. sleep_time = 3 diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py index a137221..8c3ee2b 100644 --- a/test/py/tests/test_ums.py +++ b/test/py/tests/test_ums.py @@ -2,28 +2,58 @@ # # SPDX-License-Identifier: GPL-2.0 -# Test U-Boot's "ums" command. At present, this test only ensures that a UMS -# device can be enumerated by the host/test machine. In the future, this test -# should be enhanced to validate disk IO. +# 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 = ( - {'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'}, + { + "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 = ( - {'type': 'mmc', 'id': '0'}, # eMMC; always present - {'type': 'mmc', 'id': '1'}, # SD card; present since I plugged one in + # 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 @@ -34,47 +64,42 @@ ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="66 (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.) -''' -def open_ums_device(host_ums_dev_node): - '''Attempt to open a device node, returning either the opened file handle, - or None on any error.''' +c) /etc/fstab entries to allow the block device to be mounted without requiring +root permissions. For example: - try: - return open(host_ums_dev_node, 'rb') - except: - return None - -def wait_for_ums_device(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return the opened file handle, or raise an exception - after a timeout.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if fh: - return fh - time.sleep(0.1) - raise Exception('UMS device did not appear') - -def wait_for_ums_device_gone(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return once the device has disappeared, or raise an - exception if it does not before a timeout occurs.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if not fh: - return - fh.close() - time.sleep(0.1) - raise Exception('UMS device did not disappear') +/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, and this device must disappear when "ums" is - aborted.''' + """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'] @@ -84,11 +109,129 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): # 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. - cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) - u_boot_console.run_command('ums 0 mmc 0', wait_for_prompt=False) - fh = wait_for_ums_device(host_ums_dev_node) - fh.read(4096) - fh.close() - u_boot_console.ctrlc() - wait_for_ums_device_gone(host_ums_dev_node) + 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) diff --git a/test/py/tests/test_unknown_cmd.py b/test/py/tests/test_unknown_cmd.py index 2de93e0..c27ab49 100644 --- a/test/py/tests/test_unknown_cmd.py +++ b/test/py/tests/test_unknown_cmd.py @@ -4,8 +4,8 @@ # SPDX-License-Identifier: GPL-2.0 def test_unknown_command(u_boot_console): - '''Test that executing an unknown command causes U-Boot to print an - error.''' + """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. |