]> git.ipfire.org Git - people/ms/u-boot.git/blame - test/py/u_boot_console_base.py
test/py: move U-Boot respawn trigger to the test core
[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
17
18# Regexes for text we expect U-Boot to send to the console.
19pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)')
20pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
21pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
22pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
23pattern_error_notification = re.compile('## Error: ')
24
25class ConsoleDisableCheck(object):
26 '''Context manager (for Python's with statement) that temporarily disables
27 the specified console output error check. This is useful when deliberately
28 executing a command that is known to trigger one of the error checks, in
29 order to test that the error condition is actually raised. This class is
30 used internally by ConsoleBase::disable_check(); it is not intended for
31 direct usage.'''
32
33 def __init__(self, console, check_type):
34 self.console = console
35 self.check_type = check_type
36
37 def __enter__(self):
38 self.console.disable_check_count[self.check_type] += 1
39
40 def __exit__(self, extype, value, traceback):
41 self.console.disable_check_count[self.check_type] -= 1
42
43class ConsoleBase(object):
44 '''The interface through which test functions interact with the U-Boot
45 console. This primarily involves executing shell commands, capturing their
46 results, and checking for common error conditions. Some common utilities
47 are also provided too.'''
48
49 def __init__(self, log, config, max_fifo_fill):
50 '''Initialize a U-Boot console connection.
51
52 Can only usefully be called by sub-classes.
53
54 Args:
55 log: A mulptiplex_log.Logfile object, to which the U-Boot output
56 will be logged.
57 config: A configuration data structure, as built by conftest.py.
58 max_fifo_fill: The maximum number of characters to send to U-Boot
59 command-line before waiting for U-Boot to echo the characters
60 back. For UART-based HW without HW flow control, this value
61 should be set less than the UART RX FIFO size to avoid
62 overflow, assuming that U-Boot can't keep up with full-rate
63 traffic at the baud rate.
64
65 Returns:
66 Nothing.
67 '''
68
69 self.log = log
70 self.config = config
71 self.max_fifo_fill = max_fifo_fill
72
73 self.logstream = self.log.get_stream('console', sys.stdout)
74
75 # Array slice removes leading/trailing quotes
76 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
77 self.prompt_escaped = re.escape(self.prompt)
78 self.p = None
79 self.disable_check_count = {
80 'spl_signon': 0,
81 'main_signon': 0,
82 'unknown_command': 0,
83 'error_notification': 0,
84 }
85
86 self.at_prompt = False
87 self.at_prompt_logevt = None
88 self.ram_base = 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
d201506c
SW
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 and
154 self.u_boot_spl_signon):
155 bad_patterns.append(self.u_boot_spl_signon_escaped)
156 bad_pattern_ids.append('SPL signon')
157 if self.disable_check_count['main_signon'] == 0:
158 bad_patterns.append(self.u_boot_main_signon_escaped)
159 bad_pattern_ids.append('U-Boot main signon')
160 if self.disable_check_count['unknown_command'] == 0:
161 bad_patterns.append(pattern_unknown_command)
162 bad_pattern_ids.append('Unknown command')
163 if self.disable_check_count['error_notification'] == 0:
164 bad_patterns.append(pattern_error_notification)
165 bad_pattern_ids.append('Error notification')
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] + bad_patterns)
180 if m != 0:
181 self.at_prompt = False
182 raise Exception('Bad pattern found on console: ' +
183 bad_pattern_ids[m - 1])
184 if not wait_for_prompt:
185 return
186 m = self.p.expect([self.prompt_escaped] + bad_patterns)
187 if m != 0:
188 self.at_prompt = False
189 raise Exception('Bad pattern found on console: ' +
190 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.run_command(chr(3), wait_for_echo=False, send_nl=False)
215
216 def ensure_spawned(self):
217 '''Ensure a connection to a correctly running U-Boot instance.
218
219 This may require spawning a new Sandbox process or resetting target
220 hardware, as defined by the implementation sub-class.
221
222 This is an internal function and should not be called directly.
223
224 Args:
225 None.
226
227 Returns:
228 Nothing.
229 '''
230
231 if self.p:
232 return
233 try:
234 self.at_prompt = False
235 self.log.action('Starting U-Boot')
236 self.p = self.get_spawn()
237 # Real targets can take a long time to scroll large amounts of
238 # text if LCD is enabled. This value may need tweaking in the
239 # future, possibly per-test to be optimal. This works for 'help'
240 # on board 'seaboard'.
241 self.p.timeout = 30000
242 self.p.logfile_read = self.logstream
243 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
244 self.p.expect([pattern_u_boot_spl_signon])
245 self.u_boot_spl_signon = self.p.after
246 self.u_boot_spl_signon_escaped = re.escape(self.p.after)
247 else:
248 self.u_boot_spl_signon = None
249 self.p.expect([pattern_u_boot_main_signon])
250 self.u_boot_main_signon = self.p.after
251 self.u_boot_main_signon_escaped = re.escape(self.p.after)
252 build_idx = self.u_boot_main_signon.find(', Build:')
253 if build_idx == -1:
254 self.u_boot_version_string = self.u_boot_main_signon
255 else:
256 self.u_boot_version_string = self.u_boot_main_signon[:build_idx]
257 while True:
258 match = self.p.expect([self.prompt_escaped,
259 pattern_stop_autoboot_prompt])
260 if match == 1:
261 self.p.send(chr(3)) # CTRL-C
262 continue
263 break
264 self.at_prompt = True
265 self.at_prompt_logevt = self.logstream.logfile.cur_evt
266 except Exception as ex:
267 self.log.error(str(ex))
268 self.cleanup_spawn()
269 raise
270
271 def cleanup_spawn(self):
272 '''Shut down all interaction with the U-Boot instance.
273
274 This is used when an error is detected prior to re-establishing a
275 connection with a fresh U-Boot instance.
276
277 This is an internal function and should not be called directly.
278
279 Args:
280 None.
281
282 Returns:
283 Nothing.
284 '''
285
286 try:
287 if self.p:
288 self.p.close()
289 except:
290 pass
291 self.p = None
292
293 def validate_version_string_in_text(self, text):
294 '''Assert that a command's output includes the U-Boot signon message.
295
296 This is primarily useful for validating the "version" command without
297 duplicating the signon text regex in a test function.
298
299 Args:
300 text: The command output text to check.
301
302 Returns:
303 Nothing. An exception is raised if the validation fails.
304 '''
305
306 assert(self.u_boot_version_string in text)
307
308 def disable_check(self, check_type):
309 '''Temporarily disable an error check of U-Boot's output.
310
311 Create a new context manager (for use with the "with" statement) which
312 temporarily disables a particular console output error check.
313
314 Args:
315 check_type: The type of error-check to disable. Valid values may
316 be found in self.disable_check_count above.
317
318 Returns:
319 A context manager object.
320 '''
321
322 return ConsoleDisableCheck(self, check_type)
323
324 def find_ram_base(self):
325 '''Find the running U-Boot's RAM location.
326
327 Probe the running U-Boot to determine the address of the first bank
328 of RAM. This is useful for tests that test reading/writing RAM, or
329 load/save files that aren't associated with some standard address
330 typically represented in an environment variable such as
331 ${kernel_addr_r}. The value is cached so that it only needs to be
332 actively read once.
333
334 Args:
335 None.
336
337 Returns:
338 The address of U-Boot's first RAM bank, as an integer.
339 '''
340
341 if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
342 pytest.skip('bdinfo command not supported')
343 if self.ram_base == -1:
344 pytest.skip('Previously failed to find RAM bank start')
345 if self.ram_base is not None:
346 return self.ram_base
347
348 with self.log.section('find_ram_base'):
349 response = self.run_command('bdinfo')
350 for l in response.split('\n'):
351 if '-> start' in l:
352 self.ram_base = int(l.split('=')[1].strip(), 16)
353 break
354 if self.ram_base is None:
355 self.ram_base = -1
356 raise Exception('Failed to find RAM bank start in `bdinfo`')
357
358 return self.ram_base