blob: 9fa05057848a14af7500f25065ba008ba2ca63c5 [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
Simon Glass833fd662024-11-12 07:13:21 -07008import io
Stephen Warren10e50632016-01-15 11:15:24 -07009import os
10import re
11import pty
Simon Glassd834d9a2024-10-09 18:29:03 -060012import pytest
Stephen Warren10e50632016-01-15 11:15:24 -070013import signal
14import select
Simon Glass833fd662024-11-12 07:13:21 -070015import sys
16import termios
Stephen Warren10e50632016-01-15 11:15:24 -070017import time
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010018import traceback
Stephen Warren10e50632016-01-15 11:15:24 -070019
20class Timeout(Exception):
Stephen Warren75e731e2016-01-26 13:41:30 -070021 """An exception sub-class that indicates that a timeout occurred."""
Stephen Warren10e50632016-01-15 11:15:24 -070022
Simon Glass573171e2024-10-09 18:29:02 -060023class BootFail(Exception):
24 """An exception sub-class that indicates that a boot failure occurred.
25
26 This is used when a bad pattern is seen when waiting for the boot prompt.
27 It is regarded as fatal, to avoid trying to boot the again and again to no
28 avail.
29 """
30
31class Unexpected(Exception):
32 """An exception sub-class that indicates that unexpected test was seen."""
33
Simon Glassd834d9a2024-10-09 18:29:03 -060034
35def handle_exception(ubconfig, console, log, err, name, fatal, output=''):
36 """Handle an exception from the console
37
38 Exceptions can occur when there is unexpected output or due to the board
39 crashing or hanging. Some exceptions are likely fatal, where retrying will
40 just chew up time to no available. In those cases it is best to cause
41 further tests be skipped.
42
43 Args:
44 ubconfig (ArbitraryAttributeContainer): ubconfig object
45 log (Logfile): Place to log errors
46 console (ConsoleBase): Console to clean up, if fatal
47 err (Exception): Exception which was thrown
48 name (str): Name of problem, to log
49 fatal (bool): True to abort all tests
50 output (str): Extra output to report on boot failure. This can show the
51 target's console output as it tried to boot
52 """
53 msg = f'{name}: '
54 if fatal:
55 msg += 'Marking connection bad - no other tests will run'
56 else:
57 msg += 'Assuming that lab is healthy'
58 print(msg)
59 log.error(msg)
60 log.error(f'Error: {err}')
61
62 if output:
63 msg += f'; output {output}'
64
65 if fatal:
66 ubconfig.connection_ok = False
67 console.cleanup_spawn()
68 pytest.exit(msg)
69
70
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010071class Spawn:
Stephen Warren75e731e2016-01-26 13:41:30 -070072 """Represents the stdio of a freshly created sub-process. Commands may be
Stephen Warren10e50632016-01-15 11:15:24 -070073 sent to the process, and responses waited for.
Simon Glass9bc20832016-07-04 11:58:39 -060074
75 Members:
76 output: accumulated output from expect()
Stephen Warren75e731e2016-01-26 13:41:30 -070077 """
Stephen Warren10e50632016-01-15 11:15:24 -070078
Simon Glass3f0bd0c2024-06-23 14:30:30 -060079 def __init__(self, args, cwd=None, decode_signal=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070080 """Spawn (fork/exec) the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -070081
82 Args:
Stephen Warrena85fce92016-01-27 23:57:53 -070083 args: array of processs arguments. argv[0] is the command to
84 execute.
85 cwd: the directory to run the process in, or None for no change.
Simon Glass3f0bd0c2024-06-23 14:30:30 -060086 decode_signal (bool): True to indicate the exception number when
87 something goes wrong
Stephen Warren10e50632016-01-15 11:15:24 -070088
89 Returns:
90 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070091 """
Simon Glass3f0bd0c2024-06-23 14:30:30 -060092 self.decode_signal = decode_signal
Stephen Warren10e50632016-01-15 11:15:24 -070093 self.waited = False
Simon Glassafd00042021-10-08 09:15:23 -060094 self.exit_code = 0
95 self.exit_info = ''
Stephen Warren10e50632016-01-15 11:15:24 -070096 self.buf = ''
Simon Glass9bc20832016-07-04 11:58:39 -060097 self.output = ''
Stephen Warren10e50632016-01-15 11:15:24 -070098 self.logfile_read = None
99 self.before = ''
100 self.after = ''
101 self.timeout = None
Stephen Warren8e7c3ec2016-07-06 10:34:30 -0600102 # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
Tom Rini649bdaf2019-10-24 11:59:28 -0400103 self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
Stephen Warren10e50632016-01-15 11:15:24 -0700104
105 (self.pid, self.fd) = pty.fork()
106 if self.pid == 0:
107 try:
108 # For some reason, SIGHUP is set to SIG_IGN at this point when
109 # run under "go" (www.go.cd). Perhaps this happens under any
110 # background (non-interactive) system?
111 signal.signal(signal.SIGHUP, signal.SIG_DFL)
Stephen Warrena85fce92016-01-27 23:57:53 -0700112 if cwd:
113 os.chdir(cwd)
Stephen Warren10e50632016-01-15 11:15:24 -0700114 os.execvp(args[0], args)
115 except:
Paul Burton00f2d202017-09-14 14:34:43 -0700116 print('CHILD EXECEPTION:')
Stephen Warren10e50632016-01-15 11:15:24 -0700117 traceback.print_exc()
118 finally:
119 os._exit(255)
120
Simon Glass833fd662024-11-12 07:13:21 -0700121 old = None
Stephen Warren65503db2016-02-10 16:54:37 -0700122 try:
Simon Glass833fd662024-11-12 07:13:21 -0700123 isatty = False
124 try:
125 isatty = os.isatty(sys.stdout.fileno())
126
127 # with --capture=tee-sys we cannot call fileno()
128 except io.UnsupportedOperation as exc:
129 pass
130 if isatty:
131 new = termios.tcgetattr(self.fd)
132 old = new
133 new[3] = new[3] & ~(termios.ICANON | termios.ISIG)
134 new[3] = new[3] & ~termios.ECHO
135 new[6][termios.VMIN] = 0
136 new[6][termios.VTIME] = 0
137 termios.tcsetattr(self.fd, termios.TCSANOW, new)
138
Stephen Warren65503db2016-02-10 16:54:37 -0700139 self.poll = select.poll()
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100140 self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR |
141 select.POLLHUP | select.POLLNVAL)
Stephen Warren65503db2016-02-10 16:54:37 -0700142 except:
Simon Glass833fd662024-11-12 07:13:21 -0700143 if old:
144 termios.tcsetattr(self.fd, termios.TCSANOW, old)
Stephen Warren65503db2016-02-10 16:54:37 -0700145 self.close()
146 raise
Stephen Warren10e50632016-01-15 11:15:24 -0700147
148 def kill(self, sig):
Stephen Warren75e731e2016-01-26 13:41:30 -0700149 """Send unix signal "sig" to the child process.
Stephen Warren10e50632016-01-15 11:15:24 -0700150
151 Args:
152 sig: The signal number to send.
153
154 Returns:
155 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700156 """
Stephen Warren10e50632016-01-15 11:15:24 -0700157
158 os.kill(self.pid, sig)
159
Simon Glassafd00042021-10-08 09:15:23 -0600160 def checkalive(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700161 """Determine whether the child process is still running.
Stephen Warren10e50632016-01-15 11:15:24 -0700162
Stephen Warren10e50632016-01-15 11:15:24 -0700163 Returns:
Simon Glassafd00042021-10-08 09:15:23 -0600164 tuple:
165 True if process is alive, else False
166 0 if process is alive, else exit code of process
167 string describing what happened ('' or 'status/signal n')
Stephen Warren75e731e2016-01-26 13:41:30 -0700168 """
Stephen Warren10e50632016-01-15 11:15:24 -0700169
170 if self.waited:
Simon Glassafd00042021-10-08 09:15:23 -0600171 return False, self.exit_code, self.exit_info
Stephen Warren10e50632016-01-15 11:15:24 -0700172
173 w = os.waitpid(self.pid, os.WNOHANG)
174 if w[0] == 0:
Simon Glassafd00042021-10-08 09:15:23 -0600175 return True, 0, 'running'
176 status = w[1]
Stephen Warren10e50632016-01-15 11:15:24 -0700177
Simon Glassafd00042021-10-08 09:15:23 -0600178 if os.WIFEXITED(status):
179 self.exit_code = os.WEXITSTATUS(status)
180 self.exit_info = 'status %d' % self.exit_code
181 elif os.WIFSIGNALED(status):
182 signum = os.WTERMSIG(status)
183 self.exit_code = -signum
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100184 self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum).name)
Stephen Warren10e50632016-01-15 11:15:24 -0700185 self.waited = True
Simon Glassafd00042021-10-08 09:15:23 -0600186 return False, self.exit_code, self.exit_info
187
188 def isalive(self):
189 """Determine whether the child process is still running.
190
191 Args:
192 None.
193
194 Returns:
195 Boolean indicating whether process is alive.
196 """
197 return self.checkalive()[0]
Stephen Warren10e50632016-01-15 11:15:24 -0700198
199 def send(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700200 """Send data to the sub-process's stdin.
Stephen Warren10e50632016-01-15 11:15:24 -0700201
202 Args:
203 data: The data to send to the process.
204
205 Returns:
206 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700207 """
Stephen Warren10e50632016-01-15 11:15:24 -0700208
Tom Rini6a990412019-10-24 11:59:21 -0400209 os.write(self.fd, data.encode(errors='replace'))
Stephen Warren10e50632016-01-15 11:15:24 -0700210
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600211 def receive(self, num_bytes):
212 """Receive data from the sub-process's stdin.
213
214 Args:
215 num_bytes (int): Maximum number of bytes to read
216
217 Returns:
218 str: The data received
219
220 Raises:
221 ValueError if U-Boot died
222 """
223 try:
224 c = os.read(self.fd, num_bytes).decode(errors='replace')
225 except OSError as err:
226 # With sandbox, try to detect when U-Boot exits when it
227 # shouldn't and explain why. This is much more friendly than
228 # just dying with an I/O error
229 if self.decode_signal and err.errno == 5: # I/O error
230 alive, _, info = self.checkalive()
231 if alive:
232 raise err
233 raise ValueError('U-Boot exited with %s' % info)
234 raise
235 return c
236
Stephen Warren10e50632016-01-15 11:15:24 -0700237 def expect(self, patterns):
Stephen Warren75e731e2016-01-26 13:41:30 -0700238 """Wait for the sub-process to emit specific data.
Stephen Warren10e50632016-01-15 11:15:24 -0700239
240 This function waits for the process to emit one pattern from the
241 supplied list of patterns, or for a timeout to occur.
242
243 Args:
244 patterns: A list of strings or regex objects that we expect to
245 see in the sub-process' stdout.
246
247 Returns:
248 The index within the patterns array of the pattern the process
249 emitted.
250
251 Notable exceptions:
252 Timeout, if the process did not emit any of the patterns within
253 the expected time.
Stephen Warren75e731e2016-01-26 13:41:30 -0700254 """
Stephen Warren10e50632016-01-15 11:15:24 -0700255
Paul Burtond2849ed2017-09-14 14:34:44 -0700256 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700257 if type(patterns[pi]) == type(''):
258 patterns[pi] = re.compile(patterns[pi])
259
Stephen Warren22eba122016-01-22 12:30:07 -0700260 tstart_s = time.time()
Stephen Warren10e50632016-01-15 11:15:24 -0700261 try:
262 while True:
263 earliest_m = None
264 earliest_pi = None
Paul Burtond2849ed2017-09-14 14:34:44 -0700265 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700266 pattern = patterns[pi]
267 m = pattern.search(self.buf)
268 if not m:
269 continue
Stephen Warren3880dec2016-01-27 23:57:47 -0700270 if earliest_m and m.start() >= earliest_m.start():
Stephen Warren10e50632016-01-15 11:15:24 -0700271 continue
272 earliest_m = m
273 earliest_pi = pi
274 if earliest_m:
275 pos = earliest_m.start()
Stephen Warren4223a2f2016-02-05 18:04:42 -0700276 posafter = earliest_m.end()
Stephen Warren10e50632016-01-15 11:15:24 -0700277 self.before = self.buf[:pos]
278 self.after = self.buf[pos:posafter]
Simon Glass9bc20832016-07-04 11:58:39 -0600279 self.output += self.buf[:posafter]
Stephen Warren10e50632016-01-15 11:15:24 -0700280 self.buf = self.buf[posafter:]
281 return earliest_pi
Stephen Warren22eba122016-01-22 12:30:07 -0700282 tnow_s = time.time()
Stephen Warren33db1ee2016-02-04 16:11:50 -0700283 if self.timeout:
284 tdelta_ms = (tnow_s - tstart_s) * 1000
285 poll_maxwait = self.timeout - tdelta_ms
286 if tdelta_ms > self.timeout:
287 raise Timeout()
288 else:
289 poll_maxwait = None
290 events = self.poll.poll(poll_maxwait)
Stephen Warren10e50632016-01-15 11:15:24 -0700291 if not events:
292 raise Timeout()
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600293 c = self.receive(1024)
Stephen Warren10e50632016-01-15 11:15:24 -0700294 if self.logfile_read:
295 self.logfile_read.write(c)
296 self.buf += c
Stephen Warren8e7c3ec2016-07-06 10:34:30 -0600297 # count=0 is supposed to be the default, which indicates
298 # unlimited substitutions, but in practice the version of
299 # Python in Ubuntu 14.04 appears to default to count=2!
300 self.buf = self.re_vt100.sub('', self.buf, count=1000000)
Stephen Warren10e50632016-01-15 11:15:24 -0700301 finally:
302 if self.logfile_read:
303 self.logfile_read.flush()
304
305 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700306 """Close the stdio connection to the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -0700307
308 This also waits a reasonable time for the sub-process to stop running.
309
310 Args:
311 None.
312
313 Returns:
314 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700315 """
Stephen Warren10e50632016-01-15 11:15:24 -0700316
317 os.close(self.fd)
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100318 for _ in range(100):
Stephen Warren10e50632016-01-15 11:15:24 -0700319 if not self.isalive():
320 break
321 time.sleep(0.1)
Simon Glass9bc20832016-07-04 11:58:39 -0600322
323 def get_expect_output(self):
324 """Return the output read by expect()
325
326 Returns:
327 The output processed by expect(), as a string.
328 """
329 return self.output