OILS / test / process_table.py View on Github | oilshell.org

224 lines, 162 significant
1#!/usr/bin/env python2
2"""Utility for checking the output of test/process-table.sh."""
3from __future__ import print_function
4
5import re
6import sys
7
8
9class Process(object):
10
11 def __init__(self, pid, ppid, pgid, comm):
12 self.pid = pid
13 self.ppid = ppid
14 self.pgid = pgid
15 self.comm = comm
16
17 def __str__(self):
18 return '\t'.join((self.pid, self.ppid, self.pgid, self.comm))
19
20 def assert_pgid(self, pgid):
21 if self.pgid != pgid:
22 print('[%s] has pgid %s. expected %s.' % (self, self.pgid, pgid),
23 file=sys.stderr)
24 sys.exit(1)
25
26
27class ProcessTree(object):
28
29 def __init__(self, proc):
30 self.proc = proc
31 self.children = []
32
33 def __str__(self):
34 lines = [str(self.proc)]
35 for child in self.children:
36 lines.append(str(child))
37
38 return '\n'.join(lines)
39
40 def assert_child_count(self, n):
41 if len(self.children) != n:
42 print('[%s] has %d children. expected %d.' %
43 (self.proc, len(self.children), n),
44 file=sys.stderr)
45 sys.exit(1)
46
47
48def parse_process_tree(f, runner_pid):
49 procs = {}
50
51 for line in f:
52 m = re.match(r'^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(-?\d+)\s+(\w+)$',
53 line)
54 if not m:
55 continue
56 # TODO: use SID and TPGID
57 pid, ppid, pgid, _, _, comm = m.groups()
58 proc = Process(pid, ppid, pgid, comm)
59 ptree = ProcessTree(proc)
60 procs[proc.pid] = ptree
61 if proc.ppid in procs:
62 procs[proc.ppid].children.append(ptree)
63
64 if runner_pid not in procs:
65 print('malformed ps output', file=sys.stderr)
66 sys.exit(1)
67
68 # first process is the test harness
69 root = procs[runner_pid]
70 root.assert_child_count(1)
71 return root.children[0]
72
73
74def check_proc(ptree, shell, interactive):
75 assert len(ptree.children) == 1
76 ps = ptree.children[0]
77 if interactive:
78 ps.proc.assert_pgid(ps.proc.pid)
79 else:
80 ps.proc.assert_pgid(ptree.proc.pgid)
81
82
83def check_pipe(ptree, shell, snippet, interactive):
84 if snippet == 'fgpipe-lastpipe' and ('zsh' in shell or 'osh' in shell):
85 expected_children = 2
86 else:
87 expected_children = 3
88
89 ptree.assert_child_count(expected_children)
90
91 first = None
92 for child in ptree.children:
93 if child.proc.pid == child.proc.pgid:
94 first = child
95 break
96
97 if not first and interactive:
98 print('interactive pipeline has no leader', file=sys.stderr)
99 sys.exit(1)
100
101 pgid = first.proc.pgid if first else ptree.proc.pgid
102
103 for child in ptree.children:
104 child.proc.assert_pgid(pgid)
105
106
107def check_subshell(ptree, shell, interactive):
108 ptree.assert_child_count(1)
109 subshell = ptree.children[0]
110 subshell.assert_child_count(1)
111 ps = subshell.children[0]
112
113 if interactive:
114 subshell.proc.assert_pgid(subshell.proc.pid)
115 ps.proc.assert_pgid(subshell.proc.pid)
116 else:
117 subshell.proc.assert_pgid(ptree.proc.pgid)
118 ps.proc.assert_pgid(ptree.proc.pgid)
119
120
121def check_csub(ptree, shell, interactive):
122 ptree.assert_child_count(1)
123 ps = ptree.children[0]
124 ps.proc.assert_pgid(ptree.proc.pgid)
125
126
127def check_psub(ptree, shell, interactive):
128 ps, cat, subshell = None, None, None
129 if shell == 'bash':
130 ptree.assert_child_count(2)
131 for child in ptree.children:
132 if len(child.children) == 1:
133 subshell = child
134 ps = child.children[0]
135 elif len(child.children) == 0:
136 cat = child
137 else:
138 print('[%s] has unexpected child [%s]' % (ptree.proc, child),
139 file=sys.stderr)
140 sys.exit(1)
141
142 if not subshell:
143 print('missing expected subshell', file=sys.stderr)
144 sys.exit(1)
145 else:
146 ptree.assert_child_count(2)
147 # NOTE: Ideally we would check the comm field of the children, but `ps` may
148 # have run before some of them called exec(). Luckily we're only checkign
149 # that both children are in their own group in this case, so we just
150 # guess...
151 ps = ptree.children[0]
152 cat = ptree.children[1]
153
154 if not ps:
155 print('missing ps', file=sys.stderr)
156 sys.exit(1)
157
158 if not cat:
159 print('missing cat', file=sys.stderr)
160 sys.exit(1)
161
162 if not interactive:
163 ps.proc.assert_pgid(ptree.proc.pgid)
164 cat.proc.assert_pgid(ptree.proc.pgid)
165 if subshell:
166 subshell.proc.assert_pgid(ptree.proc.pgid)
167 else:
168 if shell == 'bash':
169 # bash is interesting
170 subshell.proc.assert_pgid(ptree.proc.pid)
171 ps.proc.assert_pgid(ptree.proc.pid)
172 cat.proc.assert_pgid(cat.proc.pid)
173 else:
174 # osh and zsh put all children in their own group
175 ps.proc.assert_pgid(ps.proc.pid)
176 cat.proc.assert_pgid(cat.proc.pid)
177
178
179def main(argv):
180 runner_pid = argv[1]
181 shell = argv[2]
182 snippet = argv[3]
183 interactive = (argv[4] == 'yes')
184
185 ptree = parse_process_tree(sys.stdin, runner_pid)
186 if snippet == 'fgproc':
187 check_proc(ptree, shell, interactive)
188
189 elif snippet == 'bgproc':
190 check_proc(ptree, shell, interactive)
191
192 elif snippet == 'fgpipe':
193 check_pipe(ptree, shell, snippet, interactive)
194
195 elif snippet == 'fgpipe-lastpipe':
196 check_pipe(ptree, shell, snippet, interactive)
197
198 elif snippet == 'bgpipe':
199 check_pipe(ptree, shell, snippet, interactive)
200
201 elif snippet == 'bgpipe-lastpipe':
202 check_pipe(ptree, shell, snippet, interactive)
203
204 elif snippet == 'subshell':
205 check_subshell(ptree, shell, interactive)
206
207 elif snippet == 'csub':
208 check_csub(ptree, shell, interactive)
209
210 elif snippet == 'psub':
211 check_psub(ptree, shell, interactive)
212
213 else:
214 raise RuntimeError('Invalid snippet %r' % snippet)
215
216 return 0
217
218
219if __name__ == '__main__':
220 try:
221 sys.exit(main(sys.argv))
222 except RuntimeError as e:
223 print('FATAL: %s' % e, file=sys.stderr)
224 sys.exit(1)