test/py: Add test support for three stage boot
[platform/kernel/u-boot.git] / test / py / u_boot_console_base.py
1 # SPDX-License-Identifier: GPL-2.0
2 # Copyright (c) 2015 Stephen Warren
3 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
4
5 # Common logic to interact with U-Boot via the console. This class provides
6 # the interface that tests use to execute U-Boot shell commands and wait for
7 # their results. Sub-classes exist to perform board-type-specific setup
8 # operations, such as spawning a sub-process for Sandbox, or attaching to the
9 # serial console of real hardware.
10
11 import multiplexed_log
12 import os
13 import pytest
14 import re
15 import sys
16 import u_boot_spawn
17
18 # Regexes for text we expect U-Boot to send to the console.
19 pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
20 pattern_u_boot_spl2_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
21 pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
22 pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
23 pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
24 pattern_error_notification = re.compile('## Error: ')
25 pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
26
27 PAT_ID = 0
28 PAT_RE = 1
29
30 bad_pattern_defs = (
31     ('spl_signon', pattern_u_boot_spl_signon),
32     ('spl2_signon', pattern_u_boot_spl2_signon),
33     ('main_signon', pattern_u_boot_main_signon),
34     ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
35     ('unknown_command', pattern_unknown_command),
36     ('error_notification', pattern_error_notification),
37     ('error_please_reset', pattern_error_please_reset),
38 )
39
40 class ConsoleDisableCheck(object):
41     """Context manager (for Python's with statement) that temporarily disables
42     the specified console output error check. This is useful when deliberately
43     executing a command that is known to trigger one of the error checks, in
44     order to test that the error condition is actually raised. This class is
45     used internally by ConsoleBase::disable_check(); it is not intended for
46     direct usage."""
47
48     def __init__(self, console, check_type):
49         self.console = console
50         self.check_type = check_type
51
52     def __enter__(self):
53         self.console.disable_check_count[self.check_type] += 1
54         self.console.eval_bad_patterns()
55
56     def __exit__(self, extype, value, traceback):
57         self.console.disable_check_count[self.check_type] -= 1
58         self.console.eval_bad_patterns()
59
60 class ConsoleSetupTimeout(object):
61     """Context manager (for Python's with statement) that temporarily sets up
62     timeout for specific command. This is useful when execution time is greater
63     then default 30s."""
64
65     def __init__(self, console, timeout):
66         self.p = console.p
67         self.orig_timeout = self.p.timeout
68         self.p.timeout = timeout
69
70     def __enter__(self):
71         return self
72
73     def __exit__(self, extype, value, traceback):
74         self.p.timeout = self.orig_timeout
75
76 class ConsoleBase(object):
77     """The interface through which test functions interact with the U-Boot
78     console. This primarily involves executing shell commands, capturing their
79     results, and checking for common error conditions. Some common utilities
80     are also provided too."""
81
82     def __init__(self, log, config, max_fifo_fill):
83         """Initialize a U-Boot console connection.
84
85         Can only usefully be called by sub-classes.
86
87         Args:
88             log: A mulptiplex_log.Logfile object, to which the U-Boot output
89                 will be logged.
90             config: A configuration data structure, as built by conftest.py.
91             max_fifo_fill: The maximum number of characters to send to U-Boot
92                 command-line before waiting for U-Boot to echo the characters
93                 back. For UART-based HW without HW flow control, this value
94                 should be set less than the UART RX FIFO size to avoid
95                 overflow, assuming that U-Boot can't keep up with full-rate
96                 traffic at the baud rate.
97
98         Returns:
99             Nothing.
100         """
101
102         self.log = log
103         self.config = config
104         self.max_fifo_fill = max_fifo_fill
105
106         self.logstream = self.log.get_stream('console', sys.stdout)
107
108         # Array slice removes leading/trailing quotes
109         self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
110         self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
111         self.p = None
112         self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
113         self.eval_bad_patterns()
114
115         self.at_prompt = False
116         self.at_prompt_logevt = None
117
118     def eval_bad_patterns(self):
119         self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
120             if self.disable_check_count[pat[PAT_ID]] == 0]
121         self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
122             if self.disable_check_count[pat[PAT_ID]] == 0]
123
124     def close(self):
125         """Terminate the connection to the U-Boot console.
126
127         This function is only useful once all interaction with U-Boot is
128         complete. Once this function is called, data cannot be sent to or
129         received from U-Boot.
130
131         Args:
132             None.
133
134         Returns:
135             Nothing.
136         """
137
138         if self.p:
139             self.p.close()
140         self.logstream.close()
141
142     def run_command(self, cmd, wait_for_echo=True, send_nl=True,
143             wait_for_prompt=True):
144         """Execute a command via the U-Boot console.
145
146         The command is always sent to U-Boot.
147
148         U-Boot echoes any command back to its output, and this function
149         typically waits for that to occur. The wait can be disabled by setting
150         wait_for_echo=False, which is useful e.g. when sending CTRL-C to
151         interrupt a long-running command such as "ums".
152
153         Command execution is typically triggered by sending a newline
154         character. This can be disabled by setting send_nl=False, which is
155         also useful when sending CTRL-C.
156
157         This function typically waits for the command to finish executing, and
158         returns the console output that it generated. This can be disabled by
159         setting wait_for_prompt=False, which is useful when invoking a long-
160         running command such as "ums".
161
162         Args:
163             cmd: The command to send.
164             wait_for_echo: Boolean indicating whether to wait for U-Boot to
165                 echo the command text back to its output.
166             send_nl: Boolean indicating whether to send a newline character
167                 after the command string.
168             wait_for_prompt: Boolean indicating whether to wait for the
169                 command prompt to be sent by U-Boot. This typically occurs
170                 immediately after the command has been executed.
171
172         Returns:
173             If wait_for_prompt == False:
174                 Nothing.
175             Else:
176                 The output from U-Boot during command execution. In other
177                 words, the text U-Boot emitted between the point it echod the
178                 command string and emitted the subsequent command prompts.
179         """
180
181         if self.at_prompt and \
182                 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
183             self.logstream.write(self.prompt, implicit=True)
184
185         try:
186             self.at_prompt = False
187             if send_nl:
188                 cmd += '\n'
189             while cmd:
190                 # Limit max outstanding data, so UART FIFOs don't overflow
191                 chunk = cmd[:self.max_fifo_fill]
192                 cmd = cmd[self.max_fifo_fill:]
193                 self.p.send(chunk)
194                 if not wait_for_echo:
195                     continue
196                 chunk = re.escape(chunk)
197                 chunk = chunk.replace('\\\n', '[\r\n]')
198                 m = self.p.expect([chunk] + self.bad_patterns)
199                 if m != 0:
200                     self.at_prompt = False
201                     raise Exception('Bad pattern found on console: ' +
202                                     self.bad_pattern_ids[m - 1])
203             if not wait_for_prompt:
204                 return
205             m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
206             if m != 0:
207                 self.at_prompt = False
208                 raise Exception('Bad pattern found on console: ' +
209                                 self.bad_pattern_ids[m - 1])
210             self.at_prompt = True
211             self.at_prompt_logevt = self.logstream.logfile.cur_evt
212             # Only strip \r\n; space/TAB might be significant if testing
213             # indentation.
214             return self.p.before.strip('\r\n')
215         except Exception as ex:
216             self.log.error(str(ex))
217             self.cleanup_spawn()
218             raise
219         finally:
220             self.log.timestamp()
221
222     def run_command_list(self, cmds):
223         """Run a list of commands.
224
225         This is a helper function to call run_command() with default arguments
226         for each command in a list.
227
228         Args:
229             cmd: List of commands (each a string).
230         Returns:
231             A list of output strings from each command, one element for each
232             command.
233         """
234         output = []
235         for cmd in cmds:
236             output.append(self.run_command(cmd))
237         return output
238
239     def ctrlc(self):
240         """Send a CTRL-C character to U-Boot.
241
242         This is useful in order to stop execution of long-running synchronous
243         commands such as "ums".
244
245         Args:
246             None.
247
248         Returns:
249             Nothing.
250         """
251
252         self.log.action('Sending Ctrl-C')
253         self.run_command(chr(3), wait_for_echo=False, send_nl=False)
254
255     def wait_for(self, text):
256         """Wait for a pattern to be emitted by U-Boot.
257
258         This is useful when a long-running command such as "dfu" is executing,
259         and it periodically emits some text that should show up at a specific
260         location in the log file.
261
262         Args:
263             text: The text to wait for; either a string (containing raw text,
264                 not a regular expression) or an re object.
265
266         Returns:
267             Nothing.
268         """
269
270         if type(text) == type(''):
271             text = re.escape(text)
272         m = self.p.expect([text] + self.bad_patterns)
273         if m != 0:
274             raise Exception('Bad pattern found on console: ' +
275                             self.bad_pattern_ids[m - 1])
276
277     def drain_console(self):
278         """Read from and log the U-Boot console for a short time.
279
280         U-Boot's console output is only logged when the test code actively
281         waits for U-Boot to emit specific data. There are cases where tests
282         can fail without doing this. For example, if a test asks U-Boot to
283         enable USB device mode, then polls until a host-side device node
284         exists. In such a case, it is useful to log U-Boot's console output
285         in case U-Boot printed clues as to why the host-side even did not
286         occur. This function will do that.
287
288         Args:
289             None.
290
291         Returns:
292             Nothing.
293         """
294
295         # If we are already not connected to U-Boot, there's nothing to drain.
296         # This should only happen when a previous call to run_command() or
297         # wait_for() failed (and hence the output has already been logged), or
298         # the system is shutting down.
299         if not self.p:
300             return
301
302         orig_timeout = self.p.timeout
303         try:
304             # Drain the log for a relatively short time.
305             self.p.timeout = 1000
306             # Wait for something U-Boot will likely never send. This will
307             # cause the console output to be read and logged.
308             self.p.expect(['This should never match U-Boot output'])
309         except:
310             # We expect a timeout, since U-Boot won't print what we waited
311             # for. Squash it when it happens.
312             #
313             # Squash any other exception too. This function is only used to
314             # drain (and log) the U-Boot console output after a failed test.
315             # The U-Boot process will be restarted, or target board reset, once
316             # this function returns. So, we don't care about detecting any
317             # additional errors, so they're squashed so that the rest of the
318             # post-test-failure cleanup code can continue operation, and
319             # correctly terminate any log sections, etc.
320             pass
321         finally:
322             self.p.timeout = orig_timeout
323
324     def ensure_spawned(self):
325         """Ensure a connection to a correctly running U-Boot instance.
326
327         This may require spawning a new Sandbox process or resetting target
328         hardware, as defined by the implementation sub-class.
329
330         This is an internal function and should not be called directly.
331
332         Args:
333             None.
334
335         Returns:
336             Nothing.
337         """
338
339         if self.p:
340             return
341         try:
342             self.log.start_section('Starting U-Boot')
343             self.at_prompt = False
344             self.p = self.get_spawn()
345             # Real targets can take a long time to scroll large amounts of
346             # text if LCD is enabled. This value may need tweaking in the
347             # future, possibly per-test to be optimal. This works for 'help'
348             # on board 'seaboard'.
349             if not self.config.gdbserver:
350                 self.p.timeout = 30000
351             self.p.logfile_read = self.logstream
352             bcfg = self.config.buildconfig
353             config_spl = bcfg.get('config_spl', 'n') == 'y'
354             config_spl_serial_support = bcfg.get('config_spl_serial_support',
355                                                  'n') == 'y'
356             env_spl_skipped = self.config.env.get('env__spl_skipped',
357                                                   False)
358             env_spl2_skipped = self.config.env.get('env__spl2_skipped',
359                                                   True)
360             if config_spl and config_spl_serial_support and not env_spl_skipped:
361                 m = self.p.expect([pattern_u_boot_spl_signon] +
362                                   self.bad_patterns)
363                 if m != 0:
364                     raise Exception('Bad pattern found on SPL console: ' +
365                                     self.bad_pattern_ids[m - 1])
366             if not env_spl2_skipped:
367                 m = self.p.expect([pattern_u_boot_spl2_signon] +
368                                   self.bad_patterns)
369                 if m != 0:
370                     raise Exception('Bad pattern found on SPL2 console: ' +
371                                     self.bad_pattern_ids[m - 1])
372             m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
373             if m != 0:
374                 raise Exception('Bad pattern found on console: ' +
375                                 self.bad_pattern_ids[m - 1])
376             self.u_boot_version_string = self.p.after
377             while True:
378                 m = self.p.expect([self.prompt_compiled,
379                     pattern_stop_autoboot_prompt] + self.bad_patterns)
380                 if m == 0:
381                     break
382                 if m == 1:
383                     self.p.send(' ')
384                     continue
385                 raise Exception('Bad pattern found on console: ' +
386                                 self.bad_pattern_ids[m - 2])
387             self.at_prompt = True
388             self.at_prompt_logevt = self.logstream.logfile.cur_evt
389         except Exception as ex:
390             self.log.error(str(ex))
391             self.cleanup_spawn()
392             raise
393         finally:
394             self.log.timestamp()
395             self.log.end_section('Starting U-Boot')
396
397     def cleanup_spawn(self):
398         """Shut down all interaction with the U-Boot instance.
399
400         This is used when an error is detected prior to re-establishing a
401         connection with a fresh U-Boot instance.
402
403         This is an internal function and should not be called directly.
404
405         Args:
406             None.
407
408         Returns:
409             Nothing.
410         """
411
412         try:
413             if self.p:
414                 self.p.close()
415         except:
416             pass
417         self.p = None
418
419     def restart_uboot(self):
420         """Shut down and restart U-Boot."""
421         self.cleanup_spawn()
422         self.ensure_spawned()
423
424     def get_spawn_output(self):
425         """Return the start-up output from U-Boot
426
427         Returns:
428             The output produced by ensure_spawed(), as a string.
429         """
430         if self.p:
431             return self.p.get_expect_output()
432         return None
433
434     def validate_version_string_in_text(self, text):
435         """Assert that a command's output includes the U-Boot signon message.
436
437         This is primarily useful for validating the "version" command without
438         duplicating the signon text regex in a test function.
439
440         Args:
441             text: The command output text to check.
442
443         Returns:
444             Nothing. An exception is raised if the validation fails.
445         """
446
447         assert(self.u_boot_version_string in text)
448
449     def disable_check(self, check_type):
450         """Temporarily disable an error check of U-Boot's output.
451
452         Create a new context manager (for use with the "with" statement) which
453         temporarily disables a particular console output error check.
454
455         Args:
456             check_type: The type of error-check to disable. Valid values may
457             be found in self.disable_check_count above.
458
459         Returns:
460             A context manager object.
461         """
462
463         return ConsoleDisableCheck(self, check_type)
464
465     def temporary_timeout(self, timeout):
466         """Temporarily set up different timeout for commands.
467
468         Create a new context manager (for use with the "with" statement) which
469         temporarily change timeout.
470
471         Args:
472             timeout: Time in milliseconds.
473
474         Returns:
475             A context manager object.
476         """
477
478         return ConsoleSetupTimeout(self, timeout)