test/py: check for bad patterns everywhere we wait
[platform/kernel/u-boot.git] / test / py / u_boot_console_base.py
1 # Copyright (c) 2015 Stephen Warren
2 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3 #
4 # SPDX-License-Identifier: GPL-2.0
5
6 # Common logic to interact with U-Boot via the console. This class provides
7 # the interface that tests use to execute U-Boot shell commands and wait for
8 # their results. Sub-classes exist to perform board-type-specific setup
9 # operations, such as spawning a sub-process for Sandbox, or attaching to the
10 # serial console of real hardware.
11
12 import multiplexed_log
13 import os
14 import pytest
15 import re
16 import sys
17 import u_boot_spawn
18
19 # Regexes for text we expect U-Boot to send to the console.
20 pattern_u_boot_spl_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
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 )
36
37 class ConsoleDisableCheck(object):
38     """Context manager (for Python's with statement) that temporarily disables
39     the specified console output error check. This is useful when deliberately
40     executing a command that is known to trigger one of the error checks, in
41     order to test that the error condition is actually raised. This class is
42     used internally by ConsoleBase::disable_check(); it is not intended for
43     direct usage."""
44
45     def __init__(self, console, check_type):
46         self.console = console
47         self.check_type = check_type
48
49     def __enter__(self):
50         self.console.disable_check_count[self.check_type] += 1
51         self.console.eval_bad_patterns()
52
53     def __exit__(self, extype, value, traceback):
54         self.console.disable_check_count[self.check_type] -= 1
55         self.console.eval_bad_patterns()
56
57 class ConsoleBase(object):
58     """The interface through which test functions interact with the U-Boot
59     console. This primarily involves executing shell commands, capturing their
60     results, and checking for common error conditions. Some common utilities
61     are also provided too."""
62
63     def __init__(self, log, config, max_fifo_fill):
64         """Initialize a U-Boot console connection.
65
66         Can only usefully be called by sub-classes.
67
68         Args:
69             log: A mulptiplex_log.Logfile object, to which the U-Boot output
70                 will be logged.
71             config: A configuration data structure, as built by conftest.py.
72             max_fifo_fill: The maximum number of characters to send to U-Boot
73                 command-line before waiting for U-Boot to echo the characters
74                 back. For UART-based HW without HW flow control, this value
75                 should be set less than the UART RX FIFO size to avoid
76                 overflow, assuming that U-Boot can't keep up with full-rate
77                 traffic at the baud rate.
78
79         Returns:
80             Nothing.
81         """
82
83         self.log = log
84         self.config = config
85         self.max_fifo_fill = max_fifo_fill
86
87         self.logstream = self.log.get_stream('console', sys.stdout)
88
89         # Array slice removes leading/trailing quotes
90         self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
91         self.prompt_escaped = re.escape(self.prompt)
92         self.p = None
93         self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
94         self.eval_bad_patterns()
95
96         self.at_prompt = False
97         self.at_prompt_logevt = None
98
99     def eval_bad_patterns(self):
100         self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
101             if self.disable_check_count[pat[PAT_ID]] == 0]
102         self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
103             if self.disable_check_count[pat[PAT_ID]] == 0]
104
105     def close(self):
106         """Terminate the connection to the U-Boot console.
107
108         This function is only useful once all interaction with U-Boot is
109         complete. Once this function is called, data cannot be sent to or
110         received from U-Boot.
111
112         Args:
113             None.
114
115         Returns:
116             Nothing.
117         """
118
119         if self.p:
120             self.p.close()
121         self.logstream.close()
122
123     def run_command(self, cmd, wait_for_echo=True, send_nl=True,
124             wait_for_prompt=True):
125         """Execute a command via the U-Boot console.
126
127         The command is always sent to U-Boot.
128
129         U-Boot echoes any command back to its output, and this function
130         typically waits for that to occur. The wait can be disabled by setting
131         wait_for_echo=False, which is useful e.g. when sending CTRL-C to
132         interrupt a long-running command such as "ums".
133
134         Command execution is typically triggered by sending a newline
135         character. This can be disabled by setting send_nl=False, which is
136         also useful when sending CTRL-C.
137
138         This function typically waits for the command to finish executing, and
139         returns the console output that it generated. This can be disabled by
140         setting wait_for_prompt=False, which is useful when invoking a long-
141         running command such as "ums".
142
143         Args:
144             cmd: The command to send.
145             wait_for_each: Boolean indicating whether to wait for U-Boot to
146                 echo the command text back to its output.
147             send_nl: Boolean indicating whether to send a newline character
148                 after the command string.
149             wait_for_prompt: Boolean indicating whether to wait for the
150                 command prompt to be sent by U-Boot. This typically occurs
151                 immediately after the command has been executed.
152
153         Returns:
154             If wait_for_prompt == False:
155                 Nothing.
156             Else:
157                 The output from U-Boot during command execution. In other
158                 words, the text U-Boot emitted between the point it echod the
159                 command string and emitted the subsequent command prompts.
160         """
161
162         if self.at_prompt and \
163                 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
164             self.logstream.write(self.prompt, implicit=True)
165
166         try:
167             self.at_prompt = False
168             if send_nl:
169                 cmd += '\n'
170             while cmd:
171                 # Limit max outstanding data, so UART FIFOs don't overflow
172                 chunk = cmd[:self.max_fifo_fill]
173                 cmd = cmd[self.max_fifo_fill:]
174                 self.p.send(chunk)
175                 if not wait_for_echo:
176                     continue
177                 chunk = re.escape(chunk)
178                 chunk = chunk.replace('\\\n', '[\r\n]')
179                 m = self.p.expect([chunk] + self.bad_patterns)
180                 if m != 0:
181                     self.at_prompt = False
182                     raise Exception('Bad pattern found on console: ' +
183                                     self.bad_pattern_ids[m - 1])
184             if not wait_for_prompt:
185                 return
186             m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
187             if m != 0:
188                 self.at_prompt = False
189                 raise Exception('Bad pattern found on console: ' +
190                                 self.bad_pattern_ids[m - 1])
191             self.at_prompt = True
192             self.at_prompt_logevt = self.logstream.logfile.cur_evt
193             # Only strip \r\n; space/TAB might be significant if testing
194             # indentation.
195             return self.p.before.strip('\r\n')
196         except Exception as ex:
197             self.log.error(str(ex))
198             self.cleanup_spawn()
199             raise
200
201     def ctrlc(self):
202         """Send a CTRL-C character to U-Boot.
203
204         This is useful in order to stop execution of long-running synchronous
205         commands such as "ums".
206
207         Args:
208             None.
209
210         Returns:
211             Nothing.
212         """
213
214         self.log.action('Sending Ctrl-C')
215         self.run_command(chr(3), wait_for_echo=False, send_nl=False)
216
217     def wait_for(self, text):
218         """Wait for a pattern to be emitted by U-Boot.
219
220         This is useful when a long-running command such as "dfu" is executing,
221         and it periodically emits some text that should show up at a specific
222         location in the log file.
223
224         Args:
225             text: The text to wait for; either a string (containing raw text,
226                 not a regular expression) or an re object.
227
228         Returns:
229             Nothing.
230         """
231
232         if type(text) == type(''):
233             text = re.escape(text)
234         m = self.p.expect([text] + self.bad_patterns)
235         if m != 0:
236             raise Exception('Bad pattern found on console: ' +
237                             self.bad_pattern_ids[m - 1])
238
239     def drain_console(self):
240         """Read from and log the U-Boot console for a short time.
241
242         U-Boot's console output is only logged when the test code actively
243         waits for U-Boot to emit specific data. There are cases where tests
244         can fail without doing this. For example, if a test asks U-Boot to
245         enable USB device mode, then polls until a host-side device node
246         exists. In such a case, it is useful to log U-Boot's console output
247         in case U-Boot printed clues as to why the host-side even did not
248         occur. This function will do that.
249
250         Args:
251             None.
252
253         Returns:
254             Nothing.
255         """
256
257         # If we are already not connected to U-Boot, there's nothing to drain.
258         # This should only happen when a previous call to run_command() or
259         # wait_for() failed (and hence the output has already been logged), or
260         # the system is shutting down.
261         if not self.p:
262             return
263
264         orig_timeout = self.p.timeout
265         try:
266             # Drain the log for a relatively short time.
267             self.p.timeout = 1000
268             # Wait for something U-Boot will likely never send. This will
269             # cause the console output to be read and logged.
270             self.p.expect(['This should never match U-Boot output'])
271         except u_boot_spawn.Timeout:
272             pass
273         finally:
274             self.p.timeout = orig_timeout
275
276     def ensure_spawned(self):
277         """Ensure a connection to a correctly running U-Boot instance.
278
279         This may require spawning a new Sandbox process or resetting target
280         hardware, as defined by the implementation sub-class.
281
282         This is an internal function and should not be called directly.
283
284         Args:
285             None.
286
287         Returns:
288             Nothing.
289         """
290
291         if self.p:
292             return
293         try:
294             self.at_prompt = False
295             self.log.action('Starting U-Boot')
296             self.p = self.get_spawn()
297             # Real targets can take a long time to scroll large amounts of
298             # text if LCD is enabled. This value may need tweaking in the
299             # future, possibly per-test to be optimal. This works for 'help'
300             # on board 'seaboard'.
301             self.p.timeout = 30000
302             self.p.logfile_read = self.logstream
303             if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
304                 m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns)
305                 if m != 0:
306                     raise Exception('Bad pattern found on console: ' +
307                                     self.bad_pattern_ids[m - 1])
308             m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
309             if m != 0:
310                 raise Exception('Bad pattern found on console: ' +
311                                 self.bad_pattern_ids[m - 1])
312             signon = self.p.after
313             build_idx = signon.find(', Build:')
314             if build_idx == -1:
315                 self.u_boot_version_string = signon
316             else:
317                 self.u_boot_version_string = signon[:build_idx]
318             while True:
319                 m = self.p.expect([self.prompt_escaped,
320                     pattern_stop_autoboot_prompt] + self.bad_patterns)
321                 if m == 0:
322                     break
323                 if m == 1:
324                     self.p.send(chr(3)) # CTRL-C
325                     continue
326                 raise Exception('Bad pattern found on console: ' +
327                                 self.bad_pattern_ids[m - 2])
328             self.at_prompt = True
329             self.at_prompt_logevt = self.logstream.logfile.cur_evt
330         except Exception as ex:
331             self.log.error(str(ex))
332             self.cleanup_spawn()
333             raise
334
335     def cleanup_spawn(self):
336         """Shut down all interaction with the U-Boot instance.
337
338         This is used when an error is detected prior to re-establishing a
339         connection with a fresh U-Boot instance.
340
341         This is an internal function and should not be called directly.
342
343         Args:
344             None.
345
346         Returns:
347             Nothing.
348         """
349
350         try:
351             if self.p:
352                 self.p.close()
353         except:
354             pass
355         self.p = None
356
357     def validate_version_string_in_text(self, text):
358         """Assert that a command's output includes the U-Boot signon message.
359
360         This is primarily useful for validating the "version" command without
361         duplicating the signon text regex in a test function.
362
363         Args:
364             text: The command output text to check.
365
366         Returns:
367             Nothing. An exception is raised if the validation fails.
368         """
369
370         assert(self.u_boot_version_string in text)
371
372     def disable_check(self, check_type):
373         """Temporarily disable an error check of U-Boot's output.
374
375         Create a new context manager (for use with the "with" statement) which
376         temporarily disables a particular console output error check.
377
378         Args:
379             check_type: The type of error-check to disable. Valid values may
380             be found in self.disable_check_count above.
381
382         Returns:
383             A context manager object.
384         """
385
386         return ConsoleDisableCheck(self, check_type)