]> git.ipfire.org Git - people/ms/u-boot.git/blame - test/py/u_boot_console_base.py
test/py: add timestamps to log
[people/ms/u-boot.git] / test / py / u_boot_console_base.py
CommitLineData
d201506c
SW
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
12import multiplexed_log
13import os
14import pytest
15import re
16import sys
c10eb9d3 17import u_boot_spawn
d201506c
SW
18
19# Regexes for text we expect U-Boot to send to the console.
c82ce04a
SW
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]*\\))')
d201506c
SW
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: ')
9129d9f5 25pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
d201506c 26
e4119ebb
SW
27PAT_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),
9129d9f5 36 ('error_please_reset', pattern_error_please_reset),
e4119ebb
SW
37)
38
d201506c 39class ConsoleDisableCheck(object):
e8debf39 40 """Context manager (for Python's with statement) that temporarily disables
d201506c
SW
41 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
e8debf39 45 direct usage."""
d201506c
SW
46
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
e4119ebb 53 self.console.eval_bad_patterns()
d201506c
SW
54
55 def __exit__(self, extype, value, traceback):
56 self.console.disable_check_count[self.check_type] -= 1
e4119ebb 57 self.console.eval_bad_patterns()
d201506c 58
87861c19
MS
59class 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
d201506c 75class ConsoleBase(object):
e8debf39 76 """The interface through which test functions interact with the U-Boot
d201506c
SW
77 console. This primarily involves executing shell commands, capturing their
78 results, and checking for common error conditions. Some common utilities
e8debf39 79 are also provided too."""
d201506c
SW
80
81 def __init__(self, log, config, max_fifo_fill):
e8debf39 82 """Initialize a U-Boot console connection.
d201506c
SW
83
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.
e8debf39 99 """
d201506c
SW
100
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]
4ba58bda 109 self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
d201506c 110 self.p = None
e4119ebb
SW
111 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
112 self.eval_bad_patterns()
d201506c
SW
113
114 self.at_prompt = False
115 self.at_prompt_logevt = None
d201506c 116
e4119ebb
SW
117 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
d201506c 123 def close(self):
e8debf39 124 """Terminate the connection to the U-Boot console.
d201506c
SW
125
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.
e8debf39 135 """
d201506c
SW
136
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):
e8debf39 143 """Execute a command via the U-Boot console.
d201506c
SW
144
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.
cd3e8a72 163 wait_for_echo: Boolean indicating whether to wait for U-Boot to
d201506c
SW
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.
e8debf39 178 """
d201506c 179
d201506c
SW
180 if self.at_prompt and \
181 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
182 self.logstream.write(self.prompt, implicit=True)
183
d201506c
SW
184 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]')
e4119ebb 197 m = self.p.expect([chunk] + self.bad_patterns)
d201506c
SW
198 if m != 0:
199 self.at_prompt = False
200 raise Exception('Bad pattern found on console: ' +
e4119ebb 201 self.bad_pattern_ids[m - 1])
d201506c
SW
202 if not wait_for_prompt:
203 return
4ba58bda 204 m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
d201506c
SW
205 if m != 0:
206 self.at_prompt = False
207 raise Exception('Bad pattern found on console: ' +
e4119ebb 208 self.bad_pattern_ids[m - 1])
d201506c
SW
209 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
9679d339
SW
218 finally:
219 self.log.timestamp()
d201506c 220
73a9054d
SG
221 def run_command_list(self, cmds):
222 """Run a list of commands.
223
224 This is a helper function to call run_command() with default arguments
225 for each command in a list.
226
227 Args:
72f52268 228 cmd: List of commands (each a string).
73a9054d 229 Returns:
f6d34651
SG
230 A list of output strings from each command, one element for each
231 command.
73a9054d 232 """
f6d34651 233 output = []
73a9054d 234 for cmd in cmds:
f6d34651 235 output.append(self.run_command(cmd))
73a9054d
SG
236 return output
237
d201506c 238 def ctrlc(self):
e8debf39 239 """Send a CTRL-C character to U-Boot.
d201506c
SW
240
241 This is useful in order to stop execution of long-running synchronous
242 commands such as "ums".
243
244 Args:
245 None.
246
247 Returns:
248 Nothing.
e8debf39 249 """
d201506c 250
783cbcd3 251 self.log.action('Sending Ctrl-C')
d201506c
SW
252 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
253
76b46939 254 def wait_for(self, text):
e8debf39 255 """Wait for a pattern to be emitted by U-Boot.
76b46939
SW
256
257 This is useful when a long-running command such as "dfu" is executing,
258 and it periodically emits some text that should show up at a specific
259 location in the log file.
260
261 Args:
262 text: The text to wait for; either a string (containing raw text,
263 not a regular expression) or an re object.
264
265 Returns:
266 Nothing.
e8debf39 267 """
76b46939
SW
268
269 if type(text) == type(''):
270 text = re.escape(text)
0c6189b5
SW
271 m = self.p.expect([text] + self.bad_patterns)
272 if m != 0:
273 raise Exception('Bad pattern found on console: ' +
274 self.bad_pattern_ids[m - 1])
76b46939 275
c10eb9d3 276 def drain_console(self):
e8debf39 277 """Read from and log the U-Boot console for a short time.
c10eb9d3
SW
278
279 U-Boot's console output is only logged when the test code actively
280 waits for U-Boot to emit specific data. There are cases where tests
281 can fail without doing this. For example, if a test asks U-Boot to
282 enable USB device mode, then polls until a host-side device node
283 exists. In such a case, it is useful to log U-Boot's console output
284 in case U-Boot printed clues as to why the host-side even did not
285 occur. This function will do that.
286
287 Args:
288 None.
289
290 Returns:
291 Nothing.
e8debf39 292 """
c10eb9d3
SW
293
294 # If we are already not connected to U-Boot, there's nothing to drain.
295 # This should only happen when a previous call to run_command() or
296 # wait_for() failed (and hence the output has already been logged), or
297 # the system is shutting down.
298 if not self.p:
299 return
300
301 orig_timeout = self.p.timeout
302 try:
303 # Drain the log for a relatively short time.
304 self.p.timeout = 1000
305 # Wait for something U-Boot will likely never send. This will
306 # cause the console output to be read and logged.
307 self.p.expect(['This should never match U-Boot output'])
308 except u_boot_spawn.Timeout:
309 pass
310 finally:
311 self.p.timeout = orig_timeout
312
d201506c 313 def ensure_spawned(self):
e8debf39 314 """Ensure a connection to a correctly running U-Boot instance.
d201506c
SW
315
316 This may require spawning a new Sandbox process or resetting target
317 hardware, as defined by the implementation sub-class.
318
319 This is an internal function and should not be called directly.
320
321 Args:
322 None.
323
324 Returns:
325 Nothing.
e8debf39 326 """
d201506c
SW
327
328 if self.p:
329 return
330 try:
97255438 331 self.log.start_section('Starting U-Boot')
d201506c 332 self.at_prompt = False
d201506c
SW
333 self.p = self.get_spawn()
334 # Real targets can take a long time to scroll large amounts of
335 # text if LCD is enabled. This value may need tweaking in the
336 # future, possibly per-test to be optimal. This works for 'help'
337 # on board 'seaboard'.
89ab8410
SW
338 if not self.config.gdbserver:
339 self.p.timeout = 30000
d201506c 340 self.p.logfile_read = self.logstream
b1309a23
HS
341 bcfg = self.config.buildconfig
342 config_spl = bcfg.get('config_spl', 'n') == 'y'
343 config_spl_serial_support = bcfg.get('config_spl_serial_support',
344 'n') == 'y'
299e5bb7
MS
345 env_spl_skipped = self.config.env.get('env__spl_skipped',
346 False)
347 if config_spl and config_spl_serial_support and not env_spl_skipped:
b1309a23
HS
348 m = self.p.expect([pattern_u_boot_spl_signon] +
349 self.bad_patterns)
0c6189b5 350 if m != 0:
c7f636f5 351 raise Exception('Bad pattern found on SPL console: ' +
0c6189b5
SW
352 self.bad_pattern_ids[m - 1])
353 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
354 if m != 0:
355 raise Exception('Bad pattern found on console: ' +
356 self.bad_pattern_ids[m - 1])
c82ce04a 357 self.u_boot_version_string = self.p.after
d201506c 358 while True:
4ba58bda 359 m = self.p.expect([self.prompt_compiled,
0c6189b5
SW
360 pattern_stop_autoboot_prompt] + self.bad_patterns)
361 if m == 0:
362 break
363 if m == 1:
38831ca3 364 self.p.send(' ')
d201506c 365 continue
0c6189b5
SW
366 raise Exception('Bad pattern found on console: ' +
367 self.bad_pattern_ids[m - 2])
d201506c
SW
368 self.at_prompt = True
369 self.at_prompt_logevt = self.logstream.logfile.cur_evt
370 except Exception as ex:
371 self.log.error(str(ex))
372 self.cleanup_spawn()
373 raise
97255438 374 finally:
9679d339 375 self.log.timestamp()
97255438 376 self.log.end_section('Starting U-Boot')
d201506c
SW
377
378 def cleanup_spawn(self):
e8debf39 379 """Shut down all interaction with the U-Boot instance.
d201506c
SW
380
381 This is used when an error is detected prior to re-establishing a
382 connection with a fresh U-Boot instance.
383
384 This is an internal function and should not be called directly.
385
386 Args:
387 None.
388
389 Returns:
390 Nothing.
e8debf39 391 """
d201506c
SW
392
393 try:
394 if self.p:
395 self.p.close()
396 except:
397 pass
398 self.p = None
399
27c087d5
SG
400 def restart_uboot(self):
401 """Shut down and restart U-Boot."""
402 self.cleanup_spawn()
403 self.ensure_spawned()
404
ebec58fb
SG
405 def get_spawn_output(self):
406 """Return the start-up output from U-Boot
407
408 Returns:
409 The output produced by ensure_spawed(), as a string.
410 """
411 if self.p:
412 return self.p.get_expect_output()
413 return None
414
d201506c 415 def validate_version_string_in_text(self, text):
e8debf39 416 """Assert that a command's output includes the U-Boot signon message.
d201506c
SW
417
418 This is primarily useful for validating the "version" command without
419 duplicating the signon text regex in a test function.
420
421 Args:
422 text: The command output text to check.
423
424 Returns:
425 Nothing. An exception is raised if the validation fails.
e8debf39 426 """
d201506c
SW
427
428 assert(self.u_boot_version_string in text)
429
430 def disable_check(self, check_type):
e8debf39 431 """Temporarily disable an error check of U-Boot's output.
d201506c
SW
432
433 Create a new context manager (for use with the "with" statement) which
434 temporarily disables a particular console output error check.
435
436 Args:
437 check_type: The type of error-check to disable. Valid values may
438 be found in self.disable_check_count above.
439
440 Returns:
441 A context manager object.
e8debf39 442 """
d201506c
SW
443
444 return ConsoleDisableCheck(self, check_type)
87861c19
MS
445
446 def temporary_timeout(self, timeout):
447 """Temporarily set up different timeout for commands.
448
449 Create a new context manager (for use with the "with" statement) which
450 temporarily change timeout.
451
452 Args:
453 timeout: Time in milliseconds.
454
455 Returns:
456 A context manager object.
457 """
458
459 return ConsoleSetupTimeout(self, timeout)