diff options
Diffstat (limited to 'test/py/u_boot_console_base.py')
-rw-r--r-- | test/py/u_boot_console_base.py | 226 |
1 files changed, 127 insertions, 99 deletions
diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 520f9a9e9f..392f8cb885 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -14,6 +14,7 @@ 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]*)') @@ -21,14 +22,27 @@ 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 + """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.''' + direct usage.""" def __init__(self, console, check_type): self.console = console @@ -36,18 +50,20 @@ class ConsoleDisableCheck(object): 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 ConsoleBase(object): - '''The interface through which test functions interact with the U-Boot + """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.''' + are also provided too.""" def __init__(self, log, config, max_fifo_fill): - '''Initialize a U-Boot console connection. + """Initialize a U-Boot console connection. Can only usefully be called by sub-classes. @@ -64,7 +80,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ self.log = log self.config = config @@ -76,19 +92,20 @@ class ConsoleBase(object): self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] self.prompt_escaped = re.escape(self.prompt) self.p = None - self.disable_check_count = { - 'spl_signon': 0, - 'main_signon': 0, - 'unknown_command': 0, - 'error_notification': 0, - } + 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 - self.ram_base = 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. + """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 @@ -99,7 +116,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ if self.p: self.p.close() @@ -107,7 +124,7 @@ class ConsoleBase(object): def run_command(self, cmd, wait_for_echo=True, send_nl=True, wait_for_prompt=True): - '''Execute a command via the U-Boot console. + """Execute a command via the U-Boot console. The command is always sent to U-Boot. @@ -142,29 +159,12 @@ class ConsoleBase(object): 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. - ''' - - self.ensure_spawned() + """ if self.at_prompt and \ self.at_prompt_logevt != self.logstream.logfile.cur_evt: self.logstream.write(self.prompt, implicit=True) - bad_patterns = [] - bad_pattern_ids = [] - if (self.disable_check_count['spl_signon'] == 0 and - self.u_boot_spl_signon): - bad_patterns.append(self.u_boot_spl_signon_escaped) - bad_pattern_ids.append('SPL signon') - if self.disable_check_count['main_signon'] == 0: - bad_patterns.append(self.u_boot_main_signon_escaped) - bad_pattern_ids.append('U-Boot main signon') - if self.disable_check_count['unknown_command'] == 0: - bad_patterns.append(pattern_unknown_command) - bad_pattern_ids.append('Unknown command') - if self.disable_check_count['error_notification'] == 0: - bad_patterns.append(pattern_error_notification) - bad_pattern_ids.append('Error notification') try: self.at_prompt = False if send_nl: @@ -178,18 +178,18 @@ class ConsoleBase(object): continue chunk = re.escape(chunk) chunk = chunk.replace('\\\n', '[\r\n]') - m = self.p.expect([chunk] + bad_patterns) + m = self.p.expect([chunk] + self.bad_patterns) if m != 0: self.at_prompt = False raise Exception('Bad pattern found on console: ' + - bad_pattern_ids[m - 1]) + self.bad_pattern_ids[m - 1]) if not wait_for_prompt: return - m = self.p.expect([self.prompt_escaped] + bad_patterns) + m = self.p.expect([self.prompt_escaped] + self.bad_patterns) if m != 0: self.at_prompt = False raise Exception('Bad pattern found on console: ' + - bad_pattern_ids[m - 1]) + 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 @@ -201,7 +201,7 @@ class ConsoleBase(object): raise def ctrlc(self): - '''Send a CTRL-C character to U-Boot. + """Send a CTRL-C character to U-Boot. This is useful in order to stop execution of long-running synchronous commands such as "ums". @@ -211,12 +211,72 @@ class ConsoleBase(object): 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 u_boot_spawn.Timeout: + pass + finally: + self.p.timeout = orig_timeout + def ensure_spawned(self): - '''Ensure a connection to a correctly running U-Boot instance. + """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. @@ -228,7 +288,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ if self.p: return @@ -243,26 +303,30 @@ class ConsoleBase(object): self.p.timeout = 30000 self.p.logfile_read = self.logstream if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': - self.p.expect([pattern_u_boot_spl_signon]) - self.u_boot_spl_signon = self.p.after - self.u_boot_spl_signon_escaped = re.escape(self.p.after) - else: - self.u_boot_spl_signon = None - self.p.expect([pattern_u_boot_main_signon]) - self.u_boot_main_signon = self.p.after - self.u_boot_main_signon_escaped = re.escape(self.p.after) - build_idx = self.u_boot_main_signon.find(', Build:') + m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns) + if m != 0: + raise Exception('Bad pattern found on 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]) + signon = self.p.after + build_idx = signon.find(', Build:') if build_idx == -1: - self.u_boot_version_string = self.u_boot_main_signon + self.u_boot_version_string = signon else: - self.u_boot_version_string = self.u_boot_main_signon[:build_idx] + self.u_boot_version_string = signon[:build_idx] while True: - match = self.p.expect([self.prompt_escaped, - pattern_stop_autoboot_prompt]) - if match == 1: + m = self.p.expect([self.prompt_escaped, + pattern_stop_autoboot_prompt] + self.bad_patterns) + if m == 0: + break + if m == 1: self.p.send(chr(3)) # CTRL-C continue - break + 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: @@ -271,7 +335,7 @@ class ConsoleBase(object): raise def cleanup_spawn(self): - '''Shut down all interaction with the U-Boot instance. + """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. @@ -283,7 +347,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ try: if self.p: @@ -293,7 +357,7 @@ class ConsoleBase(object): self.p = None def validate_version_string_in_text(self, text): - '''Assert that a command's output includes the U-Boot signon message. + """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. @@ -303,12 +367,12 @@ class ConsoleBase(object): 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. + """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. @@ -319,42 +383,6 @@ class ConsoleBase(object): Returns: A context manager object. - ''' + """ return ConsoleDisableCheck(self, check_type) - - def find_ram_base(self): - '''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: - None. - - Returns: - The address of U-Boot's first RAM bank, as an integer. - ''' - - if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': - pytest.skip('bdinfo command not supported') - if self.ram_base == -1: - pytest.skip('Previously failed to find RAM bank start') - if self.ram_base is not None: - return self.ram_base - - with self.log.section('find_ram_base'): - response = self.run_command('bdinfo') - for l in response.split('\n'): - if '-> start' in l: - self.ram_base = int(l.split('=')[1].strip(), 16) - break - if self.ram_base is None: - self.ram_base = -1 - raise Exception('Failed to find RAM bank start in `bdinfo`') - - return self.ram_base |