blob: 71a00e863385f43d7390b58def5a0f958e188b36 [file] [log] [blame]
Stephen Warrend2015062016-01-15 11:15:24 -07001# 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
12import multiplexed_log
13import os
14import pytest
15import re
16import sys
Stephen Warrenc10eb9d2016-01-22 12:30:09 -070017import u_boot_spawn
Stephen Warrend2015062016-01-15 11:15:24 -070018
19# Regexes for text we expect U-Boot to send to the console.
20pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)')
21pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
22pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
23pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
24pattern_error_notification = re.compile('## Error: ')
25
Stephen Warrene4119eb2016-01-27 23:57:48 -070026PAT_ID = 0
27PAT_RE = 1
28
29bad_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
Stephen Warrend2015062016-01-15 11:15:24 -070037class ConsoleDisableCheck(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070038 """Context manager (for Python's with statement) that temporarily disables
Stephen Warrend2015062016-01-15 11:15:24 -070039 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
Stephen Warrene8debf32016-01-26 13:41:30 -070043 direct usage."""
Stephen Warrend2015062016-01-15 11:15:24 -070044
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
Stephen Warrene4119eb2016-01-27 23:57:48 -070051 self.console.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -070052
53 def __exit__(self, extype, value, traceback):
54 self.console.disable_check_count[self.check_type] -= 1
Stephen Warrene4119eb2016-01-27 23:57:48 -070055 self.console.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -070056
57class ConsoleBase(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070058 """The interface through which test functions interact with the U-Boot
Stephen Warrend2015062016-01-15 11:15:24 -070059 console. This primarily involves executing shell commands, capturing their
60 results, and checking for common error conditions. Some common utilities
Stephen Warrene8debf32016-01-26 13:41:30 -070061 are also provided too."""
Stephen Warrend2015062016-01-15 11:15:24 -070062
63 def __init__(self, log, config, max_fifo_fill):
Stephen Warrene8debf32016-01-26 13:41:30 -070064 """Initialize a U-Boot console connection.
Stephen Warrend2015062016-01-15 11:15:24 -070065
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.
Stephen Warrene8debf32016-01-26 13:41:30 -070081 """
Stephen Warrend2015062016-01-15 11:15:24 -070082
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
Stephen Warrene4119eb2016-01-27 23:57:48 -070093 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
94 self.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -070095
96 self.at_prompt = False
97 self.at_prompt_logevt = None
Stephen Warrend2015062016-01-15 11:15:24 -070098
Stephen Warrene4119eb2016-01-27 23:57:48 -070099 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
Stephen Warrend2015062016-01-15 11:15:24 -0700105 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700106 """Terminate the connection to the U-Boot console.
Stephen Warrend2015062016-01-15 11:15:24 -0700107
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700117 """
Stephen Warrend2015062016-01-15 11:15:24 -0700118
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):
Stephen Warrene8debf32016-01-26 13:41:30 -0700125 """Execute a command via the U-Boot console.
Stephen Warrend2015062016-01-15 11:15:24 -0700126
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700160 """
Stephen Warrend2015062016-01-15 11:15:24 -0700161
Stephen Warrend2015062016-01-15 11:15:24 -0700162 if self.at_prompt and \
163 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
164 self.logstream.write(self.prompt, implicit=True)
165
Stephen Warrend2015062016-01-15 11:15:24 -0700166 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]')
Stephen Warrene4119eb2016-01-27 23:57:48 -0700179 m = self.p.expect([chunk] + self.bad_patterns)
Stephen Warrend2015062016-01-15 11:15:24 -0700180 if m != 0:
181 self.at_prompt = False
182 raise Exception('Bad pattern found on console: ' +
Stephen Warrene4119eb2016-01-27 23:57:48 -0700183 self.bad_pattern_ids[m - 1])
Stephen Warrend2015062016-01-15 11:15:24 -0700184 if not wait_for_prompt:
185 return
Stephen Warrene4119eb2016-01-27 23:57:48 -0700186 m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
Stephen Warrend2015062016-01-15 11:15:24 -0700187 if m != 0:
188 self.at_prompt = False
189 raise Exception('Bad pattern found on console: ' +
Stephen Warrene4119eb2016-01-27 23:57:48 -0700190 self.bad_pattern_ids[m - 1])
Stephen Warrend2015062016-01-15 11:15:24 -0700191 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):
Stephen Warrene8debf32016-01-26 13:41:30 -0700202 """Send a CTRL-C character to U-Boot.
Stephen Warrend2015062016-01-15 11:15:24 -0700203
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700212 """
Stephen Warrend2015062016-01-15 11:15:24 -0700213
Stephen Warren783cbcd2016-01-22 12:30:10 -0700214 self.log.action('Sending Ctrl-C')
Stephen Warrend2015062016-01-15 11:15:24 -0700215 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
216
Stephen Warren76b46932016-01-22 12:30:12 -0700217 def wait_for(self, text):
Stephen Warrene8debf32016-01-26 13:41:30 -0700218 """Wait for a pattern to be emitted by U-Boot.
Stephen Warren76b46932016-01-22 12:30:12 -0700219
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700230 """
Stephen Warren76b46932016-01-22 12:30:12 -0700231
232 if type(text) == type(''):
233 text = re.escape(text)
Stephen Warren0c6189b2016-01-27 23:57:49 -0700234 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])
Stephen Warren76b46932016-01-22 12:30:12 -0700238
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700239 def drain_console(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700240 """Read from and log the U-Boot console for a short time.
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700241
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700255 """
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700256
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
Stephen Warrend2015062016-01-15 11:15:24 -0700276 def ensure_spawned(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700277 """Ensure a connection to a correctly running U-Boot instance.
Stephen Warrend2015062016-01-15 11:15:24 -0700278
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700289 """
Stephen Warrend2015062016-01-15 11:15:24 -0700290
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':
Stephen Warren0c6189b2016-01-27 23:57:49 -0700304 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])
Stephen Warrene787a582016-01-25 15:07:58 -0700312 signon = self.p.after
313 build_idx = signon.find(', Build:')
Stephen Warrend2015062016-01-15 11:15:24 -0700314 if build_idx == -1:
Stephen Warrene787a582016-01-25 15:07:58 -0700315 self.u_boot_version_string = signon
Stephen Warrend2015062016-01-15 11:15:24 -0700316 else:
Stephen Warrene787a582016-01-25 15:07:58 -0700317 self.u_boot_version_string = signon[:build_idx]
Stephen Warrend2015062016-01-15 11:15:24 -0700318 while True:
Stephen Warren0c6189b2016-01-27 23:57:49 -0700319 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:
Stephen Warrend2015062016-01-15 11:15:24 -0700324 self.p.send(chr(3)) # CTRL-C
325 continue
Stephen Warren0c6189b2016-01-27 23:57:49 -0700326 raise Exception('Bad pattern found on console: ' +
327 self.bad_pattern_ids[m - 2])
Stephen Warrend2015062016-01-15 11:15:24 -0700328 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):
Stephen Warrene8debf32016-01-26 13:41:30 -0700336 """Shut down all interaction with the U-Boot instance.
Stephen Warrend2015062016-01-15 11:15:24 -0700337
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700348 """
Stephen Warrend2015062016-01-15 11:15:24 -0700349
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):
Stephen Warrene8debf32016-01-26 13:41:30 -0700358 """Assert that a command's output includes the U-Boot signon message.
Stephen Warrend2015062016-01-15 11:15:24 -0700359
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700368 """
Stephen Warrend2015062016-01-15 11:15:24 -0700369
370 assert(self.u_boot_version_string in text)
371
372 def disable_check(self, check_type):
Stephen Warrene8debf32016-01-26 13:41:30 -0700373 """Temporarily disable an error check of U-Boot's output.
Stephen Warrend2015062016-01-15 11:15:24 -0700374
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.
Stephen Warrene8debf32016-01-26 13:41:30 -0700384 """
Stephen Warrend2015062016-01-15 11:15:24 -0700385
386 return ConsoleDisableCheck(self, check_type)