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