blob: 24d369035e5db114b92e81462f1c0f000e58b6df [file] [log] [blame]
Stephen Warren10e50632016-01-15 11:15:24 -07001# SPDX-License-Identifier: GPL-2.0
Tom Rini10e47792018-05-06 17:58:06 -04002# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warren10e50632016-01-15 11:15:24 -07003
Heinrich Schuchardt725bd312021-11-23 00:01:46 +01004"""
5Logic to spawn a sub-process and interact with its stdio.
6"""
Stephen Warren10e50632016-01-15 11:15:24 -07007
8import os
9import re
10import pty
Simon Glassd834d9a2024-10-09 18:29:03 -060011import pytest
Stephen Warren10e50632016-01-15 11:15:24 -070012import signal
13import select
14import time
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010015import traceback
Stephen Warren10e50632016-01-15 11:15:24 -070016
17class Timeout(Exception):
Stephen Warren75e731e2016-01-26 13:41:30 -070018 """An exception sub-class that indicates that a timeout occurred."""
Stephen Warren10e50632016-01-15 11:15:24 -070019
Simon Glass573171e2024-10-09 18:29:02 -060020class BootFail(Exception):
21 """An exception sub-class that indicates that a boot failure occurred.
22
23 This is used when a bad pattern is seen when waiting for the boot prompt.
24 It is regarded as fatal, to avoid trying to boot the again and again to no
25 avail.
26 """
27
28class Unexpected(Exception):
29 """An exception sub-class that indicates that unexpected test was seen."""
30
Simon Glassd834d9a2024-10-09 18:29:03 -060031
32def handle_exception(ubconfig, console, log, err, name, fatal, output=''):
33 """Handle an exception from the console
34
35 Exceptions can occur when there is unexpected output or due to the board
36 crashing or hanging. Some exceptions are likely fatal, where retrying will
37 just chew up time to no available. In those cases it is best to cause
38 further tests be skipped.
39
40 Args:
41 ubconfig (ArbitraryAttributeContainer): ubconfig object
42 log (Logfile): Place to log errors
43 console (ConsoleBase): Console to clean up, if fatal
44 err (Exception): Exception which was thrown
45 name (str): Name of problem, to log
46 fatal (bool): True to abort all tests
47 output (str): Extra output to report on boot failure. This can show the
48 target's console output as it tried to boot
49 """
50 msg = f'{name}: '
51 if fatal:
52 msg += 'Marking connection bad - no other tests will run'
53 else:
54 msg += 'Assuming that lab is healthy'
55 print(msg)
56 log.error(msg)
57 log.error(f'Error: {err}')
58
59 if output:
60 msg += f'; output {output}'
61
62 if fatal:
63 ubconfig.connection_ok = False
64 console.cleanup_spawn()
65 pytest.exit(msg)
66
67
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010068class Spawn:
Stephen Warren75e731e2016-01-26 13:41:30 -070069 """Represents the stdio of a freshly created sub-process. Commands may be
Stephen Warren10e50632016-01-15 11:15:24 -070070 sent to the process, and responses waited for.
Simon Glass9bc20832016-07-04 11:58:39 -060071
72 Members:
73 output: accumulated output from expect()
Stephen Warren75e731e2016-01-26 13:41:30 -070074 """
Stephen Warren10e50632016-01-15 11:15:24 -070075
Simon Glass3f0bd0c2024-06-23 14:30:30 -060076 def __init__(self, args, cwd=None, decode_signal=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070077 """Spawn (fork/exec) the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -070078
79 Args:
Stephen Warrena85fce92016-01-27 23:57:53 -070080 args: array of processs arguments. argv[0] is the command to
81 execute.
82 cwd: the directory to run the process in, or None for no change.
Simon Glass3f0bd0c2024-06-23 14:30:30 -060083 decode_signal (bool): True to indicate the exception number when
84 something goes wrong
Stephen Warren10e50632016-01-15 11:15:24 -070085
86 Returns:
87 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070088 """
Simon Glass3f0bd0c2024-06-23 14:30:30 -060089 self.decode_signal = decode_signal
Stephen Warren10e50632016-01-15 11:15:24 -070090 self.waited = False
Simon Glassafd00042021-10-08 09:15:23 -060091 self.exit_code = 0
92 self.exit_info = ''
Stephen Warren10e50632016-01-15 11:15:24 -070093 self.buf = ''
Simon Glass9bc20832016-07-04 11:58:39 -060094 self.output = ''
Stephen Warren10e50632016-01-15 11:15:24 -070095 self.logfile_read = None
96 self.before = ''
97 self.after = ''
98 self.timeout = None
Stephen Warren8e7c3ec2016-07-06 10:34:30 -060099 # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
Tom Rini649bdaf2019-10-24 11:59:28 -0400100 self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
Stephen Warren10e50632016-01-15 11:15:24 -0700101
102 (self.pid, self.fd) = pty.fork()
103 if self.pid == 0:
104 try:
105 # For some reason, SIGHUP is set to SIG_IGN at this point when
106 # run under "go" (www.go.cd). Perhaps this happens under any
107 # background (non-interactive) system?
108 signal.signal(signal.SIGHUP, signal.SIG_DFL)
Stephen Warrena85fce92016-01-27 23:57:53 -0700109 if cwd:
110 os.chdir(cwd)
Stephen Warren10e50632016-01-15 11:15:24 -0700111 os.execvp(args[0], args)
112 except:
Paul Burton00f2d202017-09-14 14:34:43 -0700113 print('CHILD EXECEPTION:')
Stephen Warren10e50632016-01-15 11:15:24 -0700114 traceback.print_exc()
115 finally:
116 os._exit(255)
117
Stephen Warren65503db2016-02-10 16:54:37 -0700118 try:
119 self.poll = select.poll()
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100120 self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR |
121 select.POLLHUP | select.POLLNVAL)
Stephen Warren65503db2016-02-10 16:54:37 -0700122 except:
123 self.close()
124 raise
Stephen Warren10e50632016-01-15 11:15:24 -0700125
126 def kill(self, sig):
Stephen Warren75e731e2016-01-26 13:41:30 -0700127 """Send unix signal "sig" to the child process.
Stephen Warren10e50632016-01-15 11:15:24 -0700128
129 Args:
130 sig: The signal number to send.
131
132 Returns:
133 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700134 """
Stephen Warren10e50632016-01-15 11:15:24 -0700135
136 os.kill(self.pid, sig)
137
Simon Glassafd00042021-10-08 09:15:23 -0600138 def checkalive(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700139 """Determine whether the child process is still running.
Stephen Warren10e50632016-01-15 11:15:24 -0700140
Stephen Warren10e50632016-01-15 11:15:24 -0700141 Returns:
Simon Glassafd00042021-10-08 09:15:23 -0600142 tuple:
143 True if process is alive, else False
144 0 if process is alive, else exit code of process
145 string describing what happened ('' or 'status/signal n')
Stephen Warren75e731e2016-01-26 13:41:30 -0700146 """
Stephen Warren10e50632016-01-15 11:15:24 -0700147
148 if self.waited:
Simon Glassafd00042021-10-08 09:15:23 -0600149 return False, self.exit_code, self.exit_info
Stephen Warren10e50632016-01-15 11:15:24 -0700150
151 w = os.waitpid(self.pid, os.WNOHANG)
152 if w[0] == 0:
Simon Glassafd00042021-10-08 09:15:23 -0600153 return True, 0, 'running'
154 status = w[1]
Stephen Warren10e50632016-01-15 11:15:24 -0700155
Simon Glassafd00042021-10-08 09:15:23 -0600156 if os.WIFEXITED(status):
157 self.exit_code = os.WEXITSTATUS(status)
158 self.exit_info = 'status %d' % self.exit_code
159 elif os.WIFSIGNALED(status):
160 signum = os.WTERMSIG(status)
161 self.exit_code = -signum
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100162 self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum).name)
Stephen Warren10e50632016-01-15 11:15:24 -0700163 self.waited = True
Simon Glassafd00042021-10-08 09:15:23 -0600164 return False, self.exit_code, self.exit_info
165
166 def isalive(self):
167 """Determine whether the child process is still running.
168
169 Args:
170 None.
171
172 Returns:
173 Boolean indicating whether process is alive.
174 """
175 return self.checkalive()[0]
Stephen Warren10e50632016-01-15 11:15:24 -0700176
177 def send(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700178 """Send data to the sub-process's stdin.
Stephen Warren10e50632016-01-15 11:15:24 -0700179
180 Args:
181 data: The data to send to the process.
182
183 Returns:
184 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700185 """
Stephen Warren10e50632016-01-15 11:15:24 -0700186
Tom Rini6a990412019-10-24 11:59:21 -0400187 os.write(self.fd, data.encode(errors='replace'))
Stephen Warren10e50632016-01-15 11:15:24 -0700188
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600189 def receive(self, num_bytes):
190 """Receive data from the sub-process's stdin.
191
192 Args:
193 num_bytes (int): Maximum number of bytes to read
194
195 Returns:
196 str: The data received
197
198 Raises:
199 ValueError if U-Boot died
200 """
201 try:
202 c = os.read(self.fd, num_bytes).decode(errors='replace')
203 except OSError as err:
204 # With sandbox, try to detect when U-Boot exits when it
205 # shouldn't and explain why. This is much more friendly than
206 # just dying with an I/O error
207 if self.decode_signal and err.errno == 5: # I/O error
208 alive, _, info = self.checkalive()
209 if alive:
210 raise err
211 raise ValueError('U-Boot exited with %s' % info)
212 raise
213 return c
214
Stephen Warren10e50632016-01-15 11:15:24 -0700215 def expect(self, patterns):
Stephen Warren75e731e2016-01-26 13:41:30 -0700216 """Wait for the sub-process to emit specific data.
Stephen Warren10e50632016-01-15 11:15:24 -0700217
218 This function waits for the process to emit one pattern from the
219 supplied list of patterns, or for a timeout to occur.
220
221 Args:
222 patterns: A list of strings or regex objects that we expect to
223 see in the sub-process' stdout.
224
225 Returns:
226 The index within the patterns array of the pattern the process
227 emitted.
228
229 Notable exceptions:
230 Timeout, if the process did not emit any of the patterns within
231 the expected time.
Stephen Warren75e731e2016-01-26 13:41:30 -0700232 """
Stephen Warren10e50632016-01-15 11:15:24 -0700233
Paul Burtond2849ed2017-09-14 14:34:44 -0700234 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700235 if type(patterns[pi]) == type(''):
236 patterns[pi] = re.compile(patterns[pi])
237
Stephen Warren22eba122016-01-22 12:30:07 -0700238 tstart_s = time.time()
Stephen Warren10e50632016-01-15 11:15:24 -0700239 try:
240 while True:
241 earliest_m = None
242 earliest_pi = None
Paul Burtond2849ed2017-09-14 14:34:44 -0700243 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700244 pattern = patterns[pi]
245 m = pattern.search(self.buf)
246 if not m:
247 continue
Stephen Warren3880dec2016-01-27 23:57:47 -0700248 if earliest_m and m.start() >= earliest_m.start():
Stephen Warren10e50632016-01-15 11:15:24 -0700249 continue
250 earliest_m = m
251 earliest_pi = pi
252 if earliest_m:
253 pos = earliest_m.start()
Stephen Warren4223a2f2016-02-05 18:04:42 -0700254 posafter = earliest_m.end()
Stephen Warren10e50632016-01-15 11:15:24 -0700255 self.before = self.buf[:pos]
256 self.after = self.buf[pos:posafter]
Simon Glass9bc20832016-07-04 11:58:39 -0600257 self.output += self.buf[:posafter]
Stephen Warren10e50632016-01-15 11:15:24 -0700258 self.buf = self.buf[posafter:]
259 return earliest_pi
Stephen Warren22eba122016-01-22 12:30:07 -0700260 tnow_s = time.time()
Stephen Warren33db1ee2016-02-04 16:11:50 -0700261 if self.timeout:
262 tdelta_ms = (tnow_s - tstart_s) * 1000
263 poll_maxwait = self.timeout - tdelta_ms
264 if tdelta_ms > self.timeout:
265 raise Timeout()
266 else:
267 poll_maxwait = None
268 events = self.poll.poll(poll_maxwait)
Stephen Warren10e50632016-01-15 11:15:24 -0700269 if not events:
270 raise Timeout()
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600271 c = self.receive(1024)
Stephen Warren10e50632016-01-15 11:15:24 -0700272 if self.logfile_read:
273 self.logfile_read.write(c)
274 self.buf += c
Stephen Warren8e7c3ec2016-07-06 10:34:30 -0600275 # count=0 is supposed to be the default, which indicates
276 # unlimited substitutions, but in practice the version of
277 # Python in Ubuntu 14.04 appears to default to count=2!
278 self.buf = self.re_vt100.sub('', self.buf, count=1000000)
Stephen Warren10e50632016-01-15 11:15:24 -0700279 finally:
280 if self.logfile_read:
281 self.logfile_read.flush()
282
283 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700284 """Close the stdio connection to the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -0700285
286 This also waits a reasonable time for the sub-process to stop running.
287
288 Args:
289 None.
290
291 Returns:
292 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700293 """
Stephen Warren10e50632016-01-15 11:15:24 -0700294
295 os.close(self.fd)
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100296 for _ in range(100):
Stephen Warren10e50632016-01-15 11:15:24 -0700297 if not self.isalive():
298 break
299 time.sleep(0.1)
Simon Glass9bc20832016-07-04 11:58:39 -0600300
301 def get_expect_output(self):
302 """Return the output read by expect()
303
304 Returns:
305 The output processed by expect(), as a string.
306 """
307 return self.output