efb06cad0af06bc992bd21cf5bb0da98df3a29fc
[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         self.p.expect([text])
235
236     def drain_console(self):
237         """Read from and log the U-Boot console for a short time.
238
239         U-Boot's console output is only logged when the test code actively
240         waits for U-Boot to emit specific data. There are cases where tests
241         can fail without doing this. For example, if a test asks U-Boot to
242         enable USB device mode, then polls until a host-side device node
243         exists. In such a case, it is useful to log U-Boot's console output
244         in case U-Boot printed clues as to why the host-side even did not
245         occur. This function will do that.
246
247         Args:
248             None.
249
250         Returns:
251             Nothing.
252         """
253
254         # If we are already not connected to U-Boot, there's nothing to drain.
255         # This should only happen when a previous call to run_command() or
256         # wait_for() failed (and hence the output has already been logged), or
257         # the system is shutting down.
258         if not self.p:
259             return
260
261         orig_timeout = self.p.timeout
262         try:
263             # Drain the log for a relatively short time.
264             self.p.timeout = 1000
265             # Wait for something U-Boot will likely never send. This will
266             # cause the console output to be read and logged.
267             self.p.expect(['This should never match U-Boot output'])
268         except u_boot_spawn.Timeout:
269             pass
270         finally:
271             self.p.timeout = orig_timeout
272
273     def ensure_spawned(self):
274         """Ensure a connection to a correctly running U-Boot instance.
275
276         This may require spawning a new Sandbox process or resetting target
277         hardware, as defined by the implementation sub-class.
278
279         This is an internal function and should not be called directly.
280
281         Args:
282             None.
283
284         Returns:
285             Nothing.
286         """
287
288         if self.p:
289             return
290         try:
291             self.at_prompt = False
292             self.log.action('Starting U-Boot')
293             self.p = self.get_spawn()
294             # Real targets can take a long time to scroll large amounts of
295             # text if LCD is enabled. This value may need tweaking in the
296             # future, possibly per-test to be optimal. This works for 'help'
297             # on board 'seaboard'.
298             self.p.timeout = 30000
299             self.p.logfile_read = self.logstream
300             if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
301                 self.p.expect([pattern_u_boot_spl_signon])
302             self.p.expect([pattern_u_boot_main_signon])
303             signon = self.p.after
304             build_idx = signon.find(', Build:')
305             if build_idx == -1:
306                 self.u_boot_version_string = signon
307             else:
308                 self.u_boot_version_string = signon[:build_idx]
309             while True:
310                 match = self.p.expect([self.prompt_escaped,
311                                        pattern_stop_autoboot_prompt])
312                 if match == 1:
313                     self.p.send(chr(3)) # CTRL-C
314                     continue
315                 break
316             self.at_prompt = True
317             self.at_prompt_logevt = self.logstream.logfile.cur_evt
318         except Exception as ex:
319             self.log.error(str(ex))
320             self.cleanup_spawn()
321             raise
322
323     def cleanup_spawn(self):
324         """Shut down all interaction with the U-Boot instance.
325
326         This is used when an error is detected prior to re-establishing a
327         connection with a fresh U-Boot instance.
328
329         This is an internal function and should not be called directly.
330
331         Args:
332             None.
333
334         Returns:
335             Nothing.
336         """
337
338         try:
339             if self.p:
340                 self.p.close()
341         except:
342             pass
343         self.p = None
344
345     def validate_version_string_in_text(self, text):
346         """Assert that a command's output includes the U-Boot signon message.
347
348         This is primarily useful for validating the "version" command without
349         duplicating the signon text regex in a test function.
350
351         Args:
352             text: The command output text to check.
353
354         Returns:
355             Nothing. An exception is raised if the validation fails.
356         """
357
358         assert(self.u_boot_version_string in text)
359
360     def disable_check(self, check_type):
361         """Temporarily disable an error check of U-Boot's output.
362
363         Create a new context manager (for use with the "with" statement) which
364         temporarily disables a particular console output error check.
365
366         Args:
367             check_type: The type of error-check to disable. Valid values may
368             be found in self.disable_check_count above.
369
370         Returns:
371             A context manager object.
372         """
373
374         return ConsoleDisableCheck(self, check_type)