blob: 24d369035e5db114b92e81462f1c0f000e58b6df [file] [log] [blame]
Stephen Warrend2015062016-01-15 11:15:24 -07001# SPDX-License-Identifier: GPL-2.0
Tom Rini83d290c2018-05-06 17:58:06 -04002# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warrend2015062016-01-15 11:15:24 -07003
Heinrich Schuchardt67e9b642021-11-23 00:01:46 +01004"""
5Logic to spawn a sub-process and interact with its stdio.
6"""
Stephen Warrend2015062016-01-15 11:15:24 -07007
8import os
9import re
10import pty
Simon Glass85d7dae2024-10-09 18:29:03 -060011import pytest
Stephen Warrend2015062016-01-15 11:15:24 -070012import signal
13import select
14import time
Heinrich Schuchardt67e9b642021-11-23 00:01:46 +010015import traceback
Stephen Warrend2015062016-01-15 11:15:24 -070016
17class Timeout(Exception):
Stephen Warrene8debf32016-01-26 13:41:30 -070018 """An exception sub-class that indicates that a timeout occurred."""
Stephen Warrend2015062016-01-15 11:15:24 -070019
Simon Glass681b8f82024-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 Glass85d7dae2024-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 Schuchardt67e9b642021-11-23 00:01:46 +010068class Spawn:
Stephen Warrene8debf32016-01-26 13:41:30 -070069 """Represents the stdio of a freshly created sub-process. Commands may be
Stephen Warrend2015062016-01-15 11:15:24 -070070 sent to the process, and responses waited for.
Simon Glassebec58f2016-07-04 11:58:39 -060071
72 Members:
73 output: accumulated output from expect()
Stephen Warrene8debf32016-01-26 13:41:30 -070074 """
Stephen Warrend2015062016-01-15 11:15:24 -070075
Simon Glass190933f2024-06-23 14:30:30 -060076 def __init__(self, args, cwd=None, decode_signal=False):
Stephen Warrene8debf32016-01-26 13:41:30 -070077 """Spawn (fork/exec) the sub-process.
Stephen Warrend2015062016-01-15 11:15:24 -070078
79 Args:
Stephen Warrend27f2fc2016-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 Glass190933f2024-06-23 14:30:30 -060083 decode_signal (bool): True to indicate the exception number when
84 something goes wrong
Stephen Warrend2015062016-01-15 11:15:24 -070085
86 Returns:
87 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070088 """
Simon Glass190933f2024-06-23 14:30:30 -060089 self.decode_signal = decode_signal
Stephen Warrend2015062016-01-15 11:15:24 -070090 self.waited = False
Simon Glass35839ed2021-10-08 09:15:23 -060091 self.exit_code = 0
92 self.exit_info = ''
Stephen Warrend2015062016-01-15 11:15:24 -070093 self.buf = ''
Simon Glassebec58f2016-07-04 11:58:39 -060094 self.output = ''
Stephen Warrend2015062016-01-15 11:15:24 -070095 self.logfile_read = None
96 self.before = ''
97 self.after = ''
98 self.timeout = None
Stephen Warren085e64d2016-07-06 10:34:30 -060099 # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
Tom Rini15579632019-10-24 11:59:28 -0400100 self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
Stephen Warrend2015062016-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 Warrend27f2fc2016-01-27 23:57:53 -0700109 if cwd:
110 os.chdir(cwd)
Stephen Warrend2015062016-01-15 11:15:24 -0700111 os.execvp(args[0], args)
112 except:
Paul Burtondffd56d2017-09-14 14:34:43 -0700113 print('CHILD EXECEPTION:')
Stephen Warrend2015062016-01-15 11:15:24 -0700114 traceback.print_exc()
115 finally:
116 os._exit(255)
117
Stephen Warren93134e12016-02-10 16:54:37 -0700118 try:
119 self.poll = select.poll()
Heinrich Schuchardt67e9b642021-11-23 00:01:46 +0100120 self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR |
121 select.POLLHUP | select.POLLNVAL)
Stephen Warren93134e12016-02-10 16:54:37 -0700122 except:
123 self.close()
124 raise
Stephen Warrend2015062016-01-15 11:15:24 -0700125
126 def kill(self, sig):
Stephen Warrene8debf32016-01-26 13:41:30 -0700127 """Send unix signal "sig" to the child process.
Stephen Warrend2015062016-01-15 11:15:24 -0700128
129 Args:
130 sig: The signal number to send.
131
132 Returns:
133 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700134 """
Stephen Warrend2015062016-01-15 11:15:24 -0700135
136 os.kill(self.pid, sig)
137
Simon Glass35839ed2021-10-08 09:15:23 -0600138 def checkalive(self):
139 """Determine whether the child process is still running.
140
141 Returns:
142 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')
146 """
147
148 if self.waited:
149 return False, self.exit_code, self.exit_info
150
151 w = os.waitpid(self.pid, os.WNOHANG)
152 if w[0] == 0:
153 return True, 0, 'running'
154 status = w[1]
155
156 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 Schuchardt67e9b642021-11-23 00:01:46 +0100162 self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum).name)
Simon Glass35839ed2021-10-08 09:15:23 -0600163 self.waited = True
164 return False, self.exit_code, self.exit_info
165
Stephen Warrend2015062016-01-15 11:15:24 -0700166 def isalive(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700167 """Determine whether the child process is still running.
Stephen Warrend2015062016-01-15 11:15:24 -0700168
169 Args:
170 None.
171
172 Returns:
173 Boolean indicating whether process is alive.
Stephen Warrene8debf32016-01-26 13:41:30 -0700174 """
Simon Glass35839ed2021-10-08 09:15:23 -0600175 return self.checkalive()[0]
Stephen Warrend2015062016-01-15 11:15:24 -0700176
177 def send(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700178 """Send data to the sub-process's stdin.
Stephen Warrend2015062016-01-15 11:15:24 -0700179
180 Args:
181 data: The data to send to the process.
182
183 Returns:
184 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700185 """
Stephen Warrend2015062016-01-15 11:15:24 -0700186
Tom Rinifd31fc12019-10-24 11:59:21 -0400187 os.write(self.fd, data.encode(errors='replace'))
Stephen Warrend2015062016-01-15 11:15:24 -0700188
Simon Glasse4ad9012024-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 Warrend2015062016-01-15 11:15:24 -0700215 def expect(self, patterns):
Stephen Warrene8debf32016-01-26 13:41:30 -0700216 """Wait for the sub-process to emit specific data.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700232 """
Stephen Warrend2015062016-01-15 11:15:24 -0700233
Paul Burtonb8c45552017-09-14 14:34:44 -0700234 for pi in range(len(patterns)):
Stephen Warrend2015062016-01-15 11:15:24 -0700235 if type(patterns[pi]) == type(''):
236 patterns[pi] = re.compile(patterns[pi])
237
Stephen Warrend314e242016-01-22 12:30:07 -0700238 tstart_s = time.time()
Stephen Warrend2015062016-01-15 11:15:24 -0700239 try:
240 while True:
241 earliest_m = None
242 earliest_pi = None
Paul Burtonb8c45552017-09-14 14:34:44 -0700243 for pi in range(len(patterns)):
Stephen Warrend2015062016-01-15 11:15:24 -0700244 pattern = patterns[pi]
245 m = pattern.search(self.buf)
246 if not m:
247 continue
Stephen Warren44ac7622016-01-27 23:57:47 -0700248 if earliest_m and m.start() >= earliest_m.start():
Stephen Warrend2015062016-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 Warrend8926812016-02-05 18:04:42 -0700254 posafter = earliest_m.end()
Stephen Warrend2015062016-01-15 11:15:24 -0700255 self.before = self.buf[:pos]
256 self.after = self.buf[pos:posafter]
Simon Glassebec58f2016-07-04 11:58:39 -0600257 self.output += self.buf[:posafter]
Stephen Warrend2015062016-01-15 11:15:24 -0700258 self.buf = self.buf[posafter:]
259 return earliest_pi
Stephen Warrend314e242016-01-22 12:30:07 -0700260 tnow_s = time.time()
Stephen Warren89ab8412016-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 Warrend2015062016-01-15 11:15:24 -0700269 if not events:
270 raise Timeout()
Simon Glasse4ad9012024-10-09 18:29:01 -0600271 c = self.receive(1024)
Stephen Warrend2015062016-01-15 11:15:24 -0700272 if self.logfile_read:
273 self.logfile_read.write(c)
274 self.buf += c
Stephen Warren085e64d2016-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 Warrend2015062016-01-15 11:15:24 -0700279 finally:
280 if self.logfile_read:
281 self.logfile_read.flush()
282
283 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700284 """Close the stdio connection to the sub-process.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700293 """
Stephen Warrend2015062016-01-15 11:15:24 -0700294
295 os.close(self.fd)
Heinrich Schuchardt67e9b642021-11-23 00:01:46 +0100296 for _ in range(100):
Stephen Warrend2015062016-01-15 11:15:24 -0700297 if not self.isalive():
298 break
299 time.sleep(0.1)
Simon Glassebec58f2016-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