diff options
Diffstat (limited to 'test/py')
-rw-r--r-- | test/py/tests/test_net_boot.py | 403 | ||||
-rw-r--r-- | test/py/u_boot_console_base.py | 59 |
2 files changed, 451 insertions, 11 deletions
diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py new file mode 100644 index 00000000000..4729ccf07fe --- /dev/null +++ b/test/py/tests/test_net_boot.py @@ -0,0 +1,403 @@ +# SPDX-License-Identifier: GPL-2.0 +# (C) Copyright 2023, Advanced Micro Devices, Inc. + +import pytest +import u_boot_utils +import test_net +import re + +""" +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: + +# Details regarding a boot image file that may be read from a TFTP server. This +# variable may be omitted or set to None if TFTP boot testing is not possible +# or desired. +env__net_tftp_bootable_file = { + 'fn': 'image.ub', + 'addr': 0x10000000, + 'size': 5058624, + 'crc32': 'c2244b26', + 'pattern': 'Linux', + 'config': 'config@2', + 'timeout': 50000, + 'check_type': 'boot_error', + 'check_pattern': 'ERROR', +} + +# False or omitted if a TFTP boot test should be tested. +# If TFTP boot testing is not possible or desired, set this variable to True. +# For example: If FIT image is not proper to boot +env__tftp_boot_test_skip = False + +# Here is the example of FIT image configurations: +configurations { + default = "config@1"; + config@1 { + description = "Boot Linux kernel with config@1"; + kernel = "kernel@0"; + fdt = "fdt@0"; + ramdisk = "ramdisk@0"; + hash@1 { + algo = "sha1"; + }; + }; + config@2 { + description = "Boot Linux kernel with config@2"; + kernel = "kernel@1"; + fdt = "fdt@1"; + ramdisk = "ramdisk@1"; + hash@1 { + algo = "sha1"; + }; + }; +}; + +# Details regarding a file that may be read from a TFTP server. This variable +# may be omitted or set to None if PXE testing is not possible or desired. +env__net_pxe_bootable_file = { + 'fn': 'default', + 'addr': 0x10000000, + 'size': 74, + 'timeout': 50000, + 'pattern': 'Linux', + 'valid_label': '1', + 'invalid_label': '2', + 'exp_str_invalid': 'Skipping install for failure retrieving', + 'local_label': '3', + 'exp_str_local': 'missing environment variable: localcmd', + 'empty_label': '4', + 'exp_str_empty': 'No kernel given, skipping boot', + 'check_type': 'boot_error', + 'check_pattern': 'ERROR', +} + +# False or omitted if a PXE boot test should be tested. +# If PXE boot testing is not possible or desired, set this variable to True. +# For example: If pxe configuration file is not proper to boot +env__pxe_boot_test_skip = False + +# Here is the example of pxe configuration file ordered based on the execution +# flow: +1) /tftpboot/pxelinux.cfg/default-arm-zynqmp + + menu include pxelinux.cfg/default-arm + timeout 50 + + default Linux + +2) /tftpboot/pxelinux.cfg/default-arm + + menu title Linux boot selections + menu include pxelinux.cfg/default + + label install + menu label Invalid boot + kernel kernels/install.bin + append console=ttyAMA0,38400 debug earlyprintk + initrd initrds/uzInitrdDebInstall + + label local + menu label Local boot + append root=/dev/sdb1 + localboot 1 + + label boot + menu label Empty boot + +3) /tftpboot/pxelinux.cfg/default + + label Linux + menu label Boot kernel + kernel Image + fdt system.dtb + initrd rootfs.cpio.gz.u-boot +""" + +def setup_networking(u_boot_console): + test_net.test_net_dhcp(u_boot_console) + if not test_net.net_set_up: + test_net.test_net_setup_static(u_boot_console) + +def setup_tftpboot_boot(u_boot_console): + f = u_boot_console.config.env.get('env__net_tftp_bootable_file', None) + if not f: + pytest.skip('No TFTP bootable file to read') + + setup_networking(u_boot_console) + addr = f.get('addr', None) + if not addr: + addr = u_boot_utils.find_ram_base(u_boot_console) + + fn = f['fn'] + timeout = f.get('timeout', 50000) + + with u_boot_console.temporary_timeout(timeout): + 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) + output = u_boot_console.run_command('crc32 %x $filesize' % addr) + if expected_crc: + assert expected_crc in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + config = f.get('config', None) + + return addr, timeout, pattern, chk_type, chk_pattern, config + +@pytest.mark.buildconfigspec('cmd_net') +def test_net_tftpboot_boot(u_boot_console): + """Boot the loaded image + + A boot file (fit image) is downloaded from the TFTP server and booted using + bootm command with the default fit configuration, its boot log pattern 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 u_boot_console.config.env.get('env__tftp_boot_test_skip', True): + pytest.skip('TFTP boot test is not enabled!') + + addr, timeout, pattern, chk_type, chk_pattern, imcfg = setup_tftpboot_boot( + u_boot_console + ) + + if imcfg: + bootcmd = 'bootm %x#%s' % (addr, imcfg) + else: + bootcmd = 'bootm %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + # wait_for_prompt=False makes the core code not wait for the U-Boot + # prompt code to be seen, since it won't be on a successful kernel + # boot + u_boot_console.run_command(bootcmd, wait_for_prompt=False) + + # Wait for boot log pattern + u_boot_console.wait_for(pattern) + finally: + # This forces the console object to be shutdown, so any subsequent + # test will reset the board back into U-Boot. We want to force this + # no matter whether the kernel boot passed or failed. + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +def setup_pxe_boot(u_boot_console): + f = u_boot_console.config.env.get('env__net_pxe_bootable_file', None) + if not f: + pytest.skip('No PXE bootable file to read') + + setup_networking(u_boot_console) + bootfile = u_boot_console.run_command('echo $bootfile') + if not bootfile: + bootfile = '<NULL>' + + return f, bootfile + +@pytest.mark.buildconfigspec('cmd_net') +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot(u_boot_console): + """Test the pxe boot command. + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +@pytest.mark.buildconfigspec('cmd_net') +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot_config(u_boot_console): + """Test the pxe boot command by selecting different combination of labels + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + local_label = f['local_label'] + empty_label = f['empty_label'] + exp_str_local = f['exp_str_local'] + exp_str_empty = f['exp_str_empty'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + + # pxe config is loaded where multiple labels are there and need to + # select particular label to boot and check for expected string + # In this case, local label is selected and it should look for + # localcmd env variable and if that variable is not defined it + # should not boot it and come out to u-boot prompt + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(local_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_local]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_local} did not match!' + + # In this case, empty label is selected and it should look for + # kernel image path and if it is not set it should fail it and load + # default label to boot + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(empty_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_empty]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_empty} did not match!' + + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +@pytest.mark.buildconfigspec('cmd_net') +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot_config_invalid(u_boot_console): + """Test the pxe boot command by selecting invalid label + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + invalid_label = f['invalid_label'] + exp_str_invalid = f['exp_str_invalid'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + + # pxe config is loaded where multiple labels are there and need to + # select particular label to boot and check for expected string + # In this case invalid label is selected, it should load invalid + # label and if it fails it should load the default label to boot + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(invalid_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_invalid]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_invalid} did not match!' + + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 26b6de07f88..76a550d45a1 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -17,7 +17,6 @@ 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_spl2_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\'') @@ -29,7 +28,6 @@ PAT_RE = 1 bad_pattern_defs = ( ('spl_signon', pattern_u_boot_spl_signon), - ('spl2_signon', pattern_u_boot_spl2_signon), ('main_signon', pattern_u_boot_main_signon), ('stop_autoboot_prompt', pattern_stop_autoboot_prompt), ('unknown_command', pattern_unknown_command), @@ -57,6 +55,32 @@ class ConsoleDisableCheck(object): self.console.disable_check_count[self.check_type] -= 1 self.console.eval_bad_patterns() +class ConsoleEnableCheck(object): + """Context manager (for Python's with statement) that temporarily enables + the specified console output error check. This is useful when executing a + command that might raise an extra bad pattern, beyond the default bad + patterns, in order to validate that the extra bad pattern is actually + detected. This class is used internally by ConsoleBase::enable_check(); it + is not intended for direct usage.""" + + def __init__(self, console, check_type, check_pattern): + self.console = console + self.check_type = check_type + self.check_pattern = check_pattern + + def __enter__(self): + global bad_pattern_defs + self.default_bad_patterns = bad_pattern_defs + bad_pattern_defs += ((self.check_type, self.check_pattern),) + self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} + self.console.eval_bad_patterns() + + def __exit__(self, extype, value, traceback): + global bad_pattern_defs + bad_pattern_defs = self.default_bad_patterns + self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} + self.console.eval_bad_patterns() + 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 @@ -152,25 +176,20 @@ class ConsoleBase(object): """ try: bcfg = self.config.buildconfig - config_spl = bcfg.get('config_spl', 'n') == 'y' config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y' env_spl_skipped = self.config.env.get('env__spl_skipped', False) - env_spl2_skipped = self.config.env.get('env__spl2_skipped', True) + env_spl_banner_times = self.config.env.get('env__spl_banner_times', 1) while loop_num > 0: loop_num -= 1 - if config_spl and config_spl_serial and not env_spl_skipped: + while config_spl_serial and not env_spl_skipped and env_spl_banner_times > 0: m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns) if m != 0: raise Exception('Bad pattern found on SPL console: ' + self.bad_pattern_ids[m - 1]) - if not env_spl2_skipped: - m = self.p.expect([pattern_u_boot_spl2_signon] + - self.bad_patterns) - if m != 0: - raise Exception('Bad pattern found on SPL2 console: ' + - self.bad_pattern_ids[m - 1]) + env_spl_banner_times -= 1 + m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns) if m != 0: raise Exception('Bad pattern found on console: ' + @@ -499,6 +518,24 @@ class ConsoleBase(object): return ConsoleDisableCheck(self, check_type) + def enable_check(self, check_type, check_pattern): + """Temporarily enable an error check of U-Boot's output. + + Create a new context manager (for use with the "with" statement) which + temporarily enables a particular console output error check. The + arguments form a new element of bad_pattern_defs defined above. + + Args: + check_type: The type of error-check or bad pattern to enable. + check_pattern: The regexes for text error pattern or bad pattern + to be checked. + + Returns: + A context manager object. + """ + + return ConsoleEnableCheck(self, check_type, check_pattern) + def temporary_timeout(self, timeout): """Temporarily set up different timeout for commands. |