blob: ee9b928756fab9fd43d39c30c91de49a1083934e [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.
Stephen Warrenc82ce042016-02-05 18:04:43 -070020pattern_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]*\\))')
Stephen Warrend2015062016-01-15 11:15:24 -070022pattern_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: ')
Stephen Warren9129d9f2016-01-27 23:57:50 -070025pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
Stephen Warrend2015062016-01-15 11:15:24 -070026
Stephen Warrene4119eb2016-01-27 23:57:48 -070027PAT_ID = 0
28PAT_RE = 1
29
30bad_pattern_defs = (
31 ('spl_signon', pattern_u_boot_spl_signon),
32 ('main_signon', pattern_u_boot_main_signon),
33 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
34 ('unknown_command', pattern_unknown_command),
35 ('error_notification', pattern_error_notification),
Stephen Warren9129d9f2016-01-27 23:57:50 -070036 ('error_please_reset', pattern_error_please_reset),
Stephen Warrene4119eb2016-01-27 23:57:48 -070037)
38
Stephen Warrend2015062016-01-15 11:15:24 -070039class ConsoleDisableCheck(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070040 """Context manager (for Python's with statement) that temporarily disables
Stephen Warrend2015062016-01-15 11:15:24 -070041 the specified console output error check. This is useful when deliberately
42 executing a command that is known to trigger one of the error checks, in
43 order to test that the error condition is actually raised. This class is
44 used internally by ConsoleBase::disable_check(); it is not intended for
Stephen Warrene8debf32016-01-26 13:41:30 -070045 direct usage."""
Stephen Warrend2015062016-01-15 11:15:24 -070046
47 def __init__(self, console, check_type):
48 self.console = console
49 self.check_type = check_type
50
51 def __enter__(self):
52 self.console.disable_check_count[self.check_type] += 1
Stephen Warrene4119eb2016-01-27 23:57:48 -070053 self.console.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -070054
55 def __exit__(self, extype, value, traceback):
56 self.console.disable_check_count[self.check_type] -= 1
Stephen Warrene4119eb2016-01-27 23:57:48 -070057 self.console.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -070058
Michal Simek87861c12016-05-19 07:57:41 +020059class ConsoleSetupTimeout(object):
60 """Context manager (for Python's with statement) that temporarily sets up
61 timeout for specific command. This is useful when execution time is greater
62 then default 30s."""
63
64 def __init__(self, console, timeout):
65 self.p = console.p
66 self.orig_timeout = self.p.timeout
67 self.p.timeout = timeout
68
69 def __enter__(self):
70 return self
71
72 def __exit__(self, extype, value, traceback):
73 self.p.timeout = self.orig_timeout
74
Stephen Warrend2015062016-01-15 11:15:24 -070075class ConsoleBase(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070076 """The interface through which test functions interact with the U-Boot
Stephen Warrend2015062016-01-15 11:15:24 -070077 console. This primarily involves executing shell commands, capturing their
78 results, and checking for common error conditions. Some common utilities
Stephen Warrene8debf32016-01-26 13:41:30 -070079 are also provided too."""
Stephen Warrend2015062016-01-15 11:15:24 -070080
81 def __init__(self, log, config, max_fifo_fill):
Stephen Warrene8debf32016-01-26 13:41:30 -070082 """Initialize a U-Boot console connection.
Stephen Warrend2015062016-01-15 11:15:24 -070083
84 Can only usefully be called by sub-classes.
85
86 Args:
87 log: A mulptiplex_log.Logfile object, to which the U-Boot output
88 will be logged.
89 config: A configuration data structure, as built by conftest.py.
90 max_fifo_fill: The maximum number of characters to send to U-Boot
91 command-line before waiting for U-Boot to echo the characters
92 back. For UART-based HW without HW flow control, this value
93 should be set less than the UART RX FIFO size to avoid
94 overflow, assuming that U-Boot can't keep up with full-rate
95 traffic at the baud rate.
96
97 Returns:
98 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070099 """
Stephen Warrend2015062016-01-15 11:15:24 -0700100
101 self.log = log
102 self.config = config
103 self.max_fifo_fill = max_fifo_fill
104
105 self.logstream = self.log.get_stream('console', sys.stdout)
106
107 # Array slice removes leading/trailing quotes
108 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
109 self.prompt_escaped = re.escape(self.prompt)
110 self.p = None
Stephen Warrene4119eb2016-01-27 23:57:48 -0700111 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
112 self.eval_bad_patterns()
Stephen Warrend2015062016-01-15 11:15:24 -0700113
114 self.at_prompt = False
115 self.at_prompt_logevt = None
Stephen Warrend2015062016-01-15 11:15:24 -0700116
Stephen Warrene4119eb2016-01-27 23:57:48 -0700117 def eval_bad_patterns(self):
118 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
119 if self.disable_check_count[pat[PAT_ID]] == 0]
120 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
121 if self.disable_check_count[pat[PAT_ID]] == 0]
122
Stephen Warrend2015062016-01-15 11:15:24 -0700123 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700124 """Terminate the connection to the U-Boot console.
Stephen Warrend2015062016-01-15 11:15:24 -0700125
126 This function is only useful once all interaction with U-Boot is
127 complete. Once this function is called, data cannot be sent to or
128 received from U-Boot.
129
130 Args:
131 None.
132
133 Returns:
134 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700135 """
Stephen Warrend2015062016-01-15 11:15:24 -0700136
137 if self.p:
138 self.p.close()
139 self.logstream.close()
140
141 def run_command(self, cmd, wait_for_echo=True, send_nl=True,
142 wait_for_prompt=True):
Stephen Warrene8debf32016-01-26 13:41:30 -0700143 """Execute a command via the U-Boot console.
Stephen Warrend2015062016-01-15 11:15:24 -0700144
145 The command is always sent to U-Boot.
146
147 U-Boot echoes any command back to its output, and this function
148 typically waits for that to occur. The wait can be disabled by setting
149 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
150 interrupt a long-running command such as "ums".
151
152 Command execution is typically triggered by sending a newline
153 character. This can be disabled by setting send_nl=False, which is
154 also useful when sending CTRL-C.
155
156 This function typically waits for the command to finish executing, and
157 returns the console output that it generated. This can be disabled by
158 setting wait_for_prompt=False, which is useful when invoking a long-
159 running command such as "ums".
160
161 Args:
162 cmd: The command to send.
163 wait_for_each: Boolean indicating whether to wait for U-Boot to
164 echo the command text back to its output.
165 send_nl: Boolean indicating whether to send a newline character
166 after the command string.
167 wait_for_prompt: Boolean indicating whether to wait for the
168 command prompt to be sent by U-Boot. This typically occurs
169 immediately after the command has been executed.
170
171 Returns:
172 If wait_for_prompt == False:
173 Nothing.
174 Else:
175 The output from U-Boot during command execution. In other
176 words, the text U-Boot emitted between the point it echod the
177 command string and emitted the subsequent command prompts.
Stephen Warrene8debf32016-01-26 13:41:30 -0700178 """
Stephen Warrend2015062016-01-15 11:15:24 -0700179
Stephen Warrend2015062016-01-15 11:15:24 -0700180 if self.at_prompt and \
181 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
182 self.logstream.write(self.prompt, implicit=True)
183
Stephen Warrend2015062016-01-15 11:15:24 -0700184 try:
185 self.at_prompt = False
186 if send_nl:
187 cmd += '\n'
188 while cmd:
189 # Limit max outstanding data, so UART FIFOs don't overflow
190 chunk = cmd[:self.max_fifo_fill]
191 cmd = cmd[self.max_fifo_fill:]
192 self.p.send(chunk)
193 if not wait_for_echo:
194 continue
195 chunk = re.escape(chunk)
196 chunk = chunk.replace('\\\n', '[\r\n]')
Stephen Warrene4119eb2016-01-27 23:57:48 -0700197 m = self.p.expect([chunk] + self.bad_patterns)
Stephen Warrend2015062016-01-15 11:15:24 -0700198 if m != 0:
199 self.at_prompt = False
200 raise Exception('Bad pattern found on console: ' +
Stephen Warrene4119eb2016-01-27 23:57:48 -0700201 self.bad_pattern_ids[m - 1])
Stephen Warrend2015062016-01-15 11:15:24 -0700202 if not wait_for_prompt:
203 return
Stephen Warrene4119eb2016-01-27 23:57:48 -0700204 m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
Stephen Warrend2015062016-01-15 11:15:24 -0700205 if m != 0:
206 self.at_prompt = False
207 raise Exception('Bad pattern found on console: ' +
Stephen Warrene4119eb2016-01-27 23:57:48 -0700208 self.bad_pattern_ids[m - 1])
Stephen Warrend2015062016-01-15 11:15:24 -0700209 self.at_prompt = True
210 self.at_prompt_logevt = self.logstream.logfile.cur_evt
211 # Only strip \r\n; space/TAB might be significant if testing
212 # indentation.
213 return self.p.before.strip('\r\n')
214 except Exception as ex:
215 self.log.error(str(ex))
216 self.cleanup_spawn()
217 raise
218
Simon Glass73a90542016-07-03 09:40:42 -0600219 def run_command_list(self, cmds):
220 """Run a list of commands.
221
222 This is a helper function to call run_command() with default arguments
223 for each command in a list.
224
225 Args:
Simon Glass72f52262016-07-31 17:35:04 -0600226 cmd: List of commands (each a string).
Simon Glass73a90542016-07-03 09:40:42 -0600227 Returns:
Simon Glassf6d34652016-07-31 17:35:09 -0600228 A list of output strings from each command, one element for each
229 command.
Simon Glass73a90542016-07-03 09:40:42 -0600230 """
Simon Glassf6d34652016-07-31 17:35:09 -0600231 output = []
Simon Glass73a90542016-07-03 09:40:42 -0600232 for cmd in cmds:
Simon Glassf6d34652016-07-31 17:35:09 -0600233 output.append(self.run_command(cmd))
Simon Glass73a90542016-07-03 09:40:42 -0600234 return output
235
Stephen Warrend2015062016-01-15 11:15:24 -0700236 def ctrlc(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700237 """Send a CTRL-C character to U-Boot.
Stephen Warrend2015062016-01-15 11:15:24 -0700238
239 This is useful in order to stop execution of long-running synchronous
240 commands such as "ums".
241
242 Args:
243 None.
244
245 Returns:
246 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700247 """
Stephen Warrend2015062016-01-15 11:15:24 -0700248
Stephen Warren783cbcd2016-01-22 12:30:10 -0700249 self.log.action('Sending Ctrl-C')
Stephen Warrend2015062016-01-15 11:15:24 -0700250 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
251
Stephen Warren76b46932016-01-22 12:30:12 -0700252 def wait_for(self, text):
Stephen Warrene8debf32016-01-26 13:41:30 -0700253 """Wait for a pattern to be emitted by U-Boot.
Stephen Warren76b46932016-01-22 12:30:12 -0700254
255 This is useful when a long-running command such as "dfu" is executing,
256 and it periodically emits some text that should show up at a specific
257 location in the log file.
258
259 Args:
260 text: The text to wait for; either a string (containing raw text,
261 not a regular expression) or an re object.
262
263 Returns:
264 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700265 """
Stephen Warren76b46932016-01-22 12:30:12 -0700266
267 if type(text) == type(''):
268 text = re.escape(text)
Stephen Warren0c6189b2016-01-27 23:57:49 -0700269 m = self.p.expect([text] + self.bad_patterns)
270 if m != 0:
271 raise Exception('Bad pattern found on console: ' +
272 self.bad_pattern_ids[m - 1])
Stephen Warren76b46932016-01-22 12:30:12 -0700273
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700274 def drain_console(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700275 """Read from and log the U-Boot console for a short time.
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700276
277 U-Boot's console output is only logged when the test code actively
278 waits for U-Boot to emit specific data. There are cases where tests
279 can fail without doing this. For example, if a test asks U-Boot to
280 enable USB device mode, then polls until a host-side device node
281 exists. In such a case, it is useful to log U-Boot's console output
282 in case U-Boot printed clues as to why the host-side even did not
283 occur. This function will do that.
284
285 Args:
286 None.
287
288 Returns:
289 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700290 """
Stephen Warrenc10eb9d2016-01-22 12:30:09 -0700291
292 # If we are already not connected to U-Boot, there's nothing to drain.
293 # This should only happen when a previous call to run_command() or
294 # wait_for() failed (and hence the output has already been logged), or
295 # the system is shutting down.
296 if not self.p:
297 return
298
299 orig_timeout = self.p.timeout
300 try:
301 # Drain the log for a relatively short time.
302 self.p.timeout = 1000
303 # Wait for something U-Boot will likely never send. This will
304 # cause the console output to be read and logged.
305 self.p.expect(['This should never match U-Boot output'])
306 except u_boot_spawn.Timeout:
307 pass
308 finally:
309 self.p.timeout = orig_timeout
310
Stephen Warrend2015062016-01-15 11:15:24 -0700311 def ensure_spawned(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700312 """Ensure a connection to a correctly running U-Boot instance.
Stephen Warrend2015062016-01-15 11:15:24 -0700313
314 This may require spawning a new Sandbox process or resetting target
315 hardware, as defined by the implementation sub-class.
316
317 This is an internal function and should not be called directly.
318
319 Args:
320 None.
321
322 Returns:
323 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700324 """
Stephen Warrend2015062016-01-15 11:15:24 -0700325
326 if self.p:
327 return
328 try:
Stephen Warren97255432016-02-11 11:46:12 -0700329 self.log.start_section('Starting U-Boot')
Stephen Warrend2015062016-01-15 11:15:24 -0700330 self.at_prompt = False
Stephen Warrend2015062016-01-15 11:15:24 -0700331 self.p = self.get_spawn()
332 # Real targets can take a long time to scroll large amounts of
333 # text if LCD is enabled. This value may need tweaking in the
334 # future, possibly per-test to be optimal. This works for 'help'
335 # on board 'seaboard'.
Stephen Warren89ab8412016-02-04 16:11:50 -0700336 if not self.config.gdbserver:
337 self.p.timeout = 30000
Stephen Warrend2015062016-01-15 11:15:24 -0700338 self.p.logfile_read = self.logstream
Heiko Schocherb1309a22016-02-17 18:32:51 +0100339 bcfg = self.config.buildconfig
340 config_spl = bcfg.get('config_spl', 'n') == 'y'
341 config_spl_serial_support = bcfg.get('config_spl_serial_support',
342 'n') == 'y'
Michal Simek299e5bb2016-02-25 14:58:24 +0100343 env_spl_skipped = self.config.env.get('env__spl_skipped',
344 False)
345 if config_spl and config_spl_serial_support and not env_spl_skipped:
Heiko Schocherb1309a22016-02-17 18:32:51 +0100346 m = self.p.expect([pattern_u_boot_spl_signon] +
347 self.bad_patterns)
Stephen Warren0c6189b2016-01-27 23:57:49 -0700348 if m != 0:
Simon Glassc7f636f2016-07-04 11:58:38 -0600349 raise Exception('Bad pattern found on SPL console: ' +
Stephen Warren0c6189b2016-01-27 23:57:49 -0700350 self.bad_pattern_ids[m - 1])
351 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
352 if m != 0:
353 raise Exception('Bad pattern found on console: ' +
354 self.bad_pattern_ids[m - 1])
Stephen Warrenc82ce042016-02-05 18:04:43 -0700355 self.u_boot_version_string = self.p.after
Stephen Warrend2015062016-01-15 11:15:24 -0700356 while True:
Stephen Warren0c6189b2016-01-27 23:57:49 -0700357 m = self.p.expect([self.prompt_escaped,
358 pattern_stop_autoboot_prompt] + self.bad_patterns)
359 if m == 0:
360 break
361 if m == 1:
Stephen Warren38831ca2016-02-15 17:39:38 -0700362 self.p.send(' ')
Stephen Warrend2015062016-01-15 11:15:24 -0700363 continue
Stephen Warren0c6189b2016-01-27 23:57:49 -0700364 raise Exception('Bad pattern found on console: ' +
365 self.bad_pattern_ids[m - 2])
Stephen Warrend2015062016-01-15 11:15:24 -0700366 self.at_prompt = True
367 self.at_prompt_logevt = self.logstream.logfile.cur_evt
368 except Exception as ex:
369 self.log.error(str(ex))
370 self.cleanup_spawn()
371 raise
Stephen Warren97255432016-02-11 11:46:12 -0700372 finally:
373 self.log.end_section('Starting U-Boot')
Stephen Warrend2015062016-01-15 11:15:24 -0700374
375 def cleanup_spawn(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700376 """Shut down all interaction with the U-Boot instance.
Stephen Warrend2015062016-01-15 11:15:24 -0700377
378 This is used when an error is detected prior to re-establishing a
379 connection with a fresh U-Boot instance.
380
381 This is an internal function and should not be called directly.
382
383 Args:
384 None.
385
386 Returns:
387 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700388 """
Stephen Warrend2015062016-01-15 11:15:24 -0700389
390 try:
391 if self.p:
392 self.p.close()
393 except:
394 pass
395 self.p = None
396
Simon Glass27c087d2016-07-31 17:35:08 -0600397 def restart_uboot(self):
398 """Shut down and restart U-Boot."""
399 self.cleanup_spawn()
400 self.ensure_spawned()
401
Simon Glassebec58f2016-07-04 11:58:39 -0600402 def get_spawn_output(self):
403 """Return the start-up output from U-Boot
404
405 Returns:
406 The output produced by ensure_spawed(), as a string.
407 """
408 if self.p:
409 return self.p.get_expect_output()
410 return None
411
Stephen Warrend2015062016-01-15 11:15:24 -0700412 def validate_version_string_in_text(self, text):
Stephen Warrene8debf32016-01-26 13:41:30 -0700413 """Assert that a command's output includes the U-Boot signon message.
Stephen Warrend2015062016-01-15 11:15:24 -0700414
415 This is primarily useful for validating the "version" command without
416 duplicating the signon text regex in a test function.
417
418 Args:
419 text: The command output text to check.
420
421 Returns:
422 Nothing. An exception is raised if the validation fails.
Stephen Warrene8debf32016-01-26 13:41:30 -0700423 """
Stephen Warrend2015062016-01-15 11:15:24 -0700424
425 assert(self.u_boot_version_string in text)
426
427 def disable_check(self, check_type):
Stephen Warrene8debf32016-01-26 13:41:30 -0700428 """Temporarily disable an error check of U-Boot's output.
Stephen Warrend2015062016-01-15 11:15:24 -0700429
430 Create a new context manager (for use with the "with" statement) which
431 temporarily disables a particular console output error check.
432
433 Args:
434 check_type: The type of error-check to disable. Valid values may
435 be found in self.disable_check_count above.
436
437 Returns:
438 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700439 """
Stephen Warrend2015062016-01-15 11:15:24 -0700440
441 return ConsoleDisableCheck(self, check_type)
Michal Simek87861c12016-05-19 07:57:41 +0200442
443 def temporary_timeout(self, timeout):
444 """Temporarily set up different timeout for commands.
445
446 Create a new context manager (for use with the "with" statement) which
447 temporarily change timeout.
448
449 Args:
450 timeout: Time in milliseconds.
451
452 Returns:
453 A context manager object.
454 """
455
456 return ConsoleSetupTimeout(self, timeout)