xref: /rk3399_rockchip-uboot/test/py/u_boot_spawn.py (revision ebe621d5fb2f5c15aff50e0610372f2751fd152f)
1d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
2d201506cSStephen Warren#
3d201506cSStephen Warren# SPDX-License-Identifier: GPL-2.0
4d201506cSStephen Warren
5d201506cSStephen Warren# Logic to spawn a sub-process and interact with its stdio.
6d201506cSStephen Warren
7d201506cSStephen Warrenimport os
8d201506cSStephen Warrenimport re
9d201506cSStephen Warrenimport pty
10d201506cSStephen Warrenimport signal
11d201506cSStephen Warrenimport select
12d201506cSStephen Warrenimport time
13d201506cSStephen Warren
14d201506cSStephen Warrenclass Timeout(Exception):
15e8debf39SStephen Warren    """An exception sub-class that indicates that a timeout occurred."""
16d201506cSStephen Warren    pass
17d201506cSStephen Warren
18d201506cSStephen Warrenclass Spawn(object):
19e8debf39SStephen Warren    """Represents the stdio of a freshly created sub-process. Commands may be
20d201506cSStephen Warren    sent to the process, and responses waited for.
21*ebec58fbSSimon Glass
22*ebec58fbSSimon Glass    Members:
23*ebec58fbSSimon Glass        output: accumulated output from expect()
24e8debf39SStephen Warren    """
25d201506cSStephen Warren
26d27f2fc1SStephen Warren    def __init__(self, args, cwd=None):
27e8debf39SStephen Warren        """Spawn (fork/exec) the sub-process.
28d201506cSStephen Warren
29d201506cSStephen Warren        Args:
30d27f2fc1SStephen Warren            args: array of processs arguments. argv[0] is the command to
31d27f2fc1SStephen Warren              execute.
32d27f2fc1SStephen Warren            cwd: the directory to run the process in, or None for no change.
33d201506cSStephen Warren
34d201506cSStephen Warren        Returns:
35d201506cSStephen Warren            Nothing.
36e8debf39SStephen Warren        """
37d201506cSStephen Warren
38d201506cSStephen Warren        self.waited = False
39d201506cSStephen Warren        self.buf = ''
40*ebec58fbSSimon Glass        self.output = ''
41d201506cSStephen Warren        self.logfile_read = None
42d201506cSStephen Warren        self.before = ''
43d201506cSStephen Warren        self.after = ''
44d201506cSStephen Warren        self.timeout = None
45085e64ddSStephen Warren        # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
46085e64ddSStephen Warren        # Note that re.I doesn't seem to work with this regex (or perhaps the
47085e64ddSStephen Warren        # version of Python in Ubuntu 14.04), hence the inclusion of a-z inside
48085e64ddSStephen Warren        # [] instead.
49085e64ddSStephen Warren        self.re_vt100 = re.compile('(\x1b\[|\x9b)[^@-_a-z]*[@-_a-z]|\x1b[@-_a-z]')
50d201506cSStephen Warren
51d201506cSStephen Warren        (self.pid, self.fd) = pty.fork()
52d201506cSStephen Warren        if self.pid == 0:
53d201506cSStephen Warren            try:
54d201506cSStephen Warren                # For some reason, SIGHUP is set to SIG_IGN at this point when
55d201506cSStephen Warren                # run under "go" (www.go.cd). Perhaps this happens under any
56d201506cSStephen Warren                # background (non-interactive) system?
57d201506cSStephen Warren                signal.signal(signal.SIGHUP, signal.SIG_DFL)
58d27f2fc1SStephen Warren                if cwd:
59d27f2fc1SStephen Warren                    os.chdir(cwd)
60d201506cSStephen Warren                os.execvp(args[0], args)
61d201506cSStephen Warren            except:
62d201506cSStephen Warren                print 'CHILD EXECEPTION:'
63d201506cSStephen Warren                import traceback
64d201506cSStephen Warren                traceback.print_exc()
65d201506cSStephen Warren            finally:
66d201506cSStephen Warren                os._exit(255)
67d201506cSStephen Warren
6893134e18SStephen Warren        try:
69d201506cSStephen Warren            self.poll = select.poll()
70d201506cSStephen Warren            self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
7193134e18SStephen Warren        except:
7293134e18SStephen Warren            self.close()
7393134e18SStephen Warren            raise
74d201506cSStephen Warren
75d201506cSStephen Warren    def kill(self, sig):
76e8debf39SStephen Warren        """Send unix signal "sig" to the child process.
77d201506cSStephen Warren
78d201506cSStephen Warren        Args:
79d201506cSStephen Warren            sig: The signal number to send.
80d201506cSStephen Warren
81d201506cSStephen Warren        Returns:
82d201506cSStephen Warren            Nothing.
83e8debf39SStephen Warren        """
84d201506cSStephen Warren
85d201506cSStephen Warren        os.kill(self.pid, sig)
86d201506cSStephen Warren
87d201506cSStephen Warren    def isalive(self):
88e8debf39SStephen Warren        """Determine whether the child process is still running.
89d201506cSStephen Warren
90d201506cSStephen Warren        Args:
91d201506cSStephen Warren            None.
92d201506cSStephen Warren
93d201506cSStephen Warren        Returns:
94d201506cSStephen Warren            Boolean indicating whether process is alive.
95e8debf39SStephen Warren        """
96d201506cSStephen Warren
97d201506cSStephen Warren        if self.waited:
98d201506cSStephen Warren            return False
99d201506cSStephen Warren
100d201506cSStephen Warren        w = os.waitpid(self.pid, os.WNOHANG)
101d201506cSStephen Warren        if w[0] == 0:
102d201506cSStephen Warren            return True
103d201506cSStephen Warren
104d201506cSStephen Warren        self.waited = True
105d201506cSStephen Warren        return False
106d201506cSStephen Warren
107d201506cSStephen Warren    def send(self, data):
108e8debf39SStephen Warren        """Send data to the sub-process's stdin.
109d201506cSStephen Warren
110d201506cSStephen Warren        Args:
111d201506cSStephen Warren            data: The data to send to the process.
112d201506cSStephen Warren
113d201506cSStephen Warren        Returns:
114d201506cSStephen Warren            Nothing.
115e8debf39SStephen Warren        """
116d201506cSStephen Warren
117d201506cSStephen Warren        os.write(self.fd, data)
118d201506cSStephen Warren
119d201506cSStephen Warren    def expect(self, patterns):
120e8debf39SStephen Warren        """Wait for the sub-process to emit specific data.
121d201506cSStephen Warren
122d201506cSStephen Warren        This function waits for the process to emit one pattern from the
123d201506cSStephen Warren        supplied list of patterns, or for a timeout to occur.
124d201506cSStephen Warren
125d201506cSStephen Warren        Args:
126d201506cSStephen Warren            patterns: A list of strings or regex objects that we expect to
127d201506cSStephen Warren                see in the sub-process' stdout.
128d201506cSStephen Warren
129d201506cSStephen Warren        Returns:
130d201506cSStephen Warren            The index within the patterns array of the pattern the process
131d201506cSStephen Warren            emitted.
132d201506cSStephen Warren
133d201506cSStephen Warren        Notable exceptions:
134d201506cSStephen Warren            Timeout, if the process did not emit any of the patterns within
135d201506cSStephen Warren            the expected time.
136e8debf39SStephen Warren        """
137d201506cSStephen Warren
138d201506cSStephen Warren        for pi in xrange(len(patterns)):
139d201506cSStephen Warren            if type(patterns[pi]) == type(''):
140d201506cSStephen Warren                patterns[pi] = re.compile(patterns[pi])
141d201506cSStephen Warren
142d314e247SStephen Warren        tstart_s = time.time()
143d201506cSStephen Warren        try:
144d201506cSStephen Warren            while True:
145d201506cSStephen Warren                earliest_m = None
146d201506cSStephen Warren                earliest_pi = None
147d201506cSStephen Warren                for pi in xrange(len(patterns)):
148d201506cSStephen Warren                    pattern = patterns[pi]
149d201506cSStephen Warren                    m = pattern.search(self.buf)
150d201506cSStephen Warren                    if not m:
151d201506cSStephen Warren                        continue
15244ac762bSStephen Warren                    if earliest_m and m.start() >= earliest_m.start():
153d201506cSStephen Warren                        continue
154d201506cSStephen Warren                    earliest_m = m
155d201506cSStephen Warren                    earliest_pi = pi
156d201506cSStephen Warren                if earliest_m:
157d201506cSStephen Warren                    pos = earliest_m.start()
158d8926811SStephen Warren                    posafter = earliest_m.end()
159d201506cSStephen Warren                    self.before = self.buf[:pos]
160d201506cSStephen Warren                    self.after = self.buf[pos:posafter]
161*ebec58fbSSimon Glass                    self.output += self.buf[:posafter]
162d201506cSStephen Warren                    self.buf = self.buf[posafter:]
163d201506cSStephen Warren                    return earliest_pi
164d314e247SStephen Warren                tnow_s = time.time()
16589ab8410SStephen Warren                if self.timeout:
166d314e247SStephen Warren                    tdelta_ms = (tnow_s - tstart_s) * 1000
16789ab8410SStephen Warren                    poll_maxwait = self.timeout - tdelta_ms
168d314e247SStephen Warren                    if tdelta_ms > self.timeout:
169d314e247SStephen Warren                        raise Timeout()
17089ab8410SStephen Warren                else:
17189ab8410SStephen Warren                    poll_maxwait = None
17289ab8410SStephen Warren                events = self.poll.poll(poll_maxwait)
173d201506cSStephen Warren                if not events:
174d201506cSStephen Warren                    raise Timeout()
175d201506cSStephen Warren                c = os.read(self.fd, 1024)
176d201506cSStephen Warren                if not c:
177d201506cSStephen Warren                    raise EOFError()
178d201506cSStephen Warren                if self.logfile_read:
179d201506cSStephen Warren                    self.logfile_read.write(c)
180d201506cSStephen Warren                self.buf += c
181085e64ddSStephen Warren                # count=0 is supposed to be the default, which indicates
182085e64ddSStephen Warren                # unlimited substitutions, but in practice the version of
183085e64ddSStephen Warren                # Python in Ubuntu 14.04 appears to default to count=2!
184085e64ddSStephen Warren                self.buf = self.re_vt100.sub('', self.buf, count=1000000)
185d201506cSStephen Warren        finally:
186d201506cSStephen Warren            if self.logfile_read:
187d201506cSStephen Warren                self.logfile_read.flush()
188d201506cSStephen Warren
189d201506cSStephen Warren    def close(self):
190e8debf39SStephen Warren        """Close the stdio connection to the sub-process.
191d201506cSStephen Warren
192d201506cSStephen Warren        This also waits a reasonable time for the sub-process to stop running.
193d201506cSStephen Warren
194d201506cSStephen Warren        Args:
195d201506cSStephen Warren            None.
196d201506cSStephen Warren
197d201506cSStephen Warren        Returns:
198d201506cSStephen Warren            Nothing.
199e8debf39SStephen Warren        """
200d201506cSStephen Warren
201d201506cSStephen Warren        os.close(self.fd)
202d201506cSStephen Warren        for i in xrange(100):
203d201506cSStephen Warren            if not self.isalive():
204d201506cSStephen Warren                break
205d201506cSStephen Warren            time.sleep(0.1)
206*ebec58fbSSimon Glass
207*ebec58fbSSimon Glass    def get_expect_output(self):
208*ebec58fbSSimon Glass        """Return the output read by expect()
209*ebec58fbSSimon Glass
210*ebec58fbSSimon Glass        Returns:
211*ebec58fbSSimon Glass            The output processed by expect(), as a string.
212*ebec58fbSSimon Glass        """
213*ebec58fbSSimon Glass        return self.output
214