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