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

255 lines, 158 significant
1#!/usr/bin/env python2
2"""test/syscall.py
3
4Print a results table.
5
6Input looks like
7
801-dash
901-dash
1001-osh
1101-osh
1201-bash-4.4
13...
14"""
15from __future__ import print_function
16
17import collections
18import optparse
19import os
20import re
21import sys
22
23
24def log(msg, *args):
25 if args:
26 msg = msg % args
27 print(msg, file=sys.stderr)
28
29
30def Cell(i):
31 """Visually show number of processes.
32
33 ^ ^^ ^^^ etc.
34 """
35 s = '^' * i
36 return '%6s' % s
37
38
39# lines look like this:
40#
41# 554 01-osh.1234
42# 553 01-osh.1235
43
44WC_LINE = re.compile(
45 r'''
46\s*
47(\d+) # number of lines
48\s+
49([a-z0-9.-]+) # shell name, could be bash-4.4
50__
51(\d{2}) # case ID
52''', re.VERBOSE)
53
54assert WC_LINE.match(' 68 osh-cpp__01.19610')
55# This is unfortunate
56assert WC_LINE.match(' 68 bash-4.4__01.19610')
57
58
59def WriteHeader(f, shells, col=''):
60 f.write("ID\t")
61 for sh in shells:
62 # abbreviate
63 if sh.startswith('bash-4'):
64 sh = 'bash-4'
65 elif sh.startswith('bash-5'):
66 sh = 'bash-5'
67 f.write("%6s\t" % sh)
68 f.write('%s\t' % col)
69 f.write('Description')
70 f.write("\n")
71
72
73def WriteProcessReport(f, cases, code_strs, proc_sh, num_procs,
74 procs_by_shell):
75 f.write('Number of Processes Started, by shell and test case\n\n')
76
77 WriteHeader(f, proc_sh, col='osh>min')
78
79 not_minimum = 0
80 more_than_bash = 0
81 fewer_than_bash = 0
82
83 for case_id in sorted(cases):
84 f.write(case_id + "\t")
85 min_procs = 20
86 for sh in proc_sh:
87 n = num_procs[case_id, sh]
88 f.write(Cell(n) + "\t")
89 min_procs = min(n, min_procs)
90
91 osh_count = num_procs[case_id, 'osh']
92 if osh_count != min_procs:
93 f.write('%d>%d\t' % (osh_count, min_procs))
94 not_minimum += 1
95 else:
96 f.write('\t')
97
98 bash_count = num_procs[case_id, 'bash-4.4']
99 if osh_count > bash_count:
100 more_than_bash += 1
101 if osh_count < bash_count:
102 fewer_than_bash += 1
103
104 f.write(code_strs[case_id])
105 f.write("\n")
106
107 f.write("TOTAL\t")
108 for sh in proc_sh:
109 f.write('%6d\t' % procs_by_shell[sh])
110 f.write('\n\n')
111 f.write("Cases where ...\n")
112 f.write(" OSH isn't the minimum: %d\n" % not_minimum)
113 f.write(" OSH starts more than bash: %d\n" % more_than_bash)
114 f.write(" OSH starts fewer than bash: %d\n\n" % fewer_than_bash)
115
116 return not_minimum, more_than_bash, fewer_than_bash
117
118
119def WriteSyscallReport(f, cases, code_strs, syscall_sh, num_syscalls,
120 syscalls_by_shell):
121 f.write('Number of Syscalls\n\n')
122
123 WriteHeader(f, syscall_sh)
124
125 for case_id in sorted(cases):
126 f.write(case_id + "\t")
127 #min_procs = 20
128 for sh in syscall_sh:
129 n = num_syscalls[case_id, sh]
130 f.write('%6d\t' % n)
131 #min_procs = min(n, min_procs)
132
133 f.write('\t')
134
135 f.write(code_strs[case_id])
136 f.write("\n")
137
138 f.write("TOTAL\t")
139 for sh in syscall_sh:
140 f.write('%6d\t' % syscalls_by_shell[sh])
141 f.write('\n\n')
142
143
144def Options():
145 """Returns an option parser instance."""
146 p = optparse.OptionParser()
147 p.add_option('--suite',
148 dest='suite',
149 default='SUITE',
150 help='Test suite name')
151 p.add_option(
152 '--not-minimum',
153 dest='not_minimum',
154 type=int,
155 default=0,
156 help=
157 "Expected number of cases where OSH doesn't start the minimum number of"
158 "processes")
159 p.add_option(
160 '--more-than-bash',
161 dest='more_than_bash',
162 type=int,
163 default=0,
164 help=
165 'Expected number of cases where OSH starts more processes than bash')
166 return p
167
168
169def main(argv):
170 o = Options()
171 opts, argv = o.parse_args(argv[1:])
172
173 cases_path = argv[0]
174 out_dir = argv[1]
175
176 code_strs = {}
177 with open(cases_path) as f:
178 for line in f:
179 case_id, code_str = line.split(None, 1) # whitespace
180 code_strs[case_id] = code_str
181
182 cases = set()
183 shells = set()
184
185 num_procs = collections.defaultdict(int)
186 procs_by_shell = collections.defaultdict(int)
187
188 num_syscalls = collections.defaultdict(int)
189 syscalls_by_shell = collections.defaultdict(int)
190
191 #
192 # Summarize Data
193 #
194
195 for line in sys.stdin:
196 m = WC_LINE.match(line)
197 if not m:
198 raise RuntimeError('Invalid line %r' % line)
199 num_sys, sh, case = m.groups()
200 num_sys = int(num_sys)
201
202 cases.add(case)
203 shells.add(sh)
204
205 num_procs[case, sh] += 1
206 num_syscalls[case, sh] += num_sys
207
208 procs_by_shell[sh] += 1
209 syscalls_by_shell[sh] += num_sys
210
211 # Orders columns by how good the results are, then shell name.
212 proc_sh = sorted(procs_by_shell, key=lambda sh: (procs_by_shell[sh], sh))
213 syscall_sh = sorted(syscalls_by_shell,
214 key=lambda sh: (syscalls_by_shell[sh], sh))
215
216 #
217 # Print Tables
218 #
219
220 out_path = os.path.join(out_dir, 'processes.%s.txt' % opts.suite)
221 with open(out_path, 'w') as f:
222 not_minimum, more_than_bash, fewer_than_bash = WriteProcessReport(
223 f, cases, code_strs, proc_sh, num_procs, procs_by_shell)
224 log('Wrote %s', out_path)
225
226 #
227 # Print Table of Syscall Counts
228 #
229
230 out_path = os.path.join(out_dir, 'syscalls.%s.txt' % opts.suite)
231 with open(out_path, 'w') as f:
232 WriteSyscallReport(f, cases, code_strs, syscall_sh, num_syscalls,
233 syscalls_by_shell)
234 log('Wrote %s', out_path)
235
236 ok = True
237 if more_than_bash != opts.more_than_bash:
238 log('Expected %d more than bash, got %d', opts.more_than_bash,
239 more_than_bash)
240 ok = False
241
242 if not_minimum != opts.not_minimum:
243 log('Expected %d that are not minimal, got %d', opts.not_minimum,
244 not_minimum)
245 ok = False
246
247 return 0 if ok else 1
248
249
250if __name__ == '__main__':
251 try:
252 sys.exit(main(sys.argv))
253 except RuntimeError as e:
254 print('FATAL: %s' % e, file=sys.stderr)
255 sys.exit(1)