1 | """
|
2 | spec_lib.py
|
3 |
|
4 | Shared between sh_spec.py (Python 2) and spec/stateful/harness.py (Python 3)!
|
5 | """
|
6 | from __future__ import print_function
|
7 |
|
8 | import os
|
9 | import re
|
10 | import sys
|
11 |
|
12 |
|
13 | def log(msg, *args):
|
14 | # type: (str, *Any) -> None
|
15 | if args:
|
16 | msg = msg % args
|
17 | print(msg, file=sys.stderr)
|
18 |
|
19 |
|
20 | # Note that devtools/release.sh spec-all runs with bin/osh and $DIR/_bin/osh,
|
21 | # which should NOT match
|
22 |
|
23 | OSH_CPP_RE = re.compile(r'_bin/\w+-\w+(-sh)?/osh') # e.g. $PWD/_bin/cxx-dbg/osh
|
24 | YSH_CPP_RE = re.compile(r'_bin/\w+-\w+(-sh)?/ysh') # e.g. $PWD/_bin/cxx-dbg/ysh
|
25 | OIL_CPP_RE = re.compile(r'_bin/\w+-\w+(-sh)?/oil')
|
26 |
|
27 | # e.g. bash-4.4 bash 5.2.21
|
28 | BASH_RE = re.compile(r'(bash-\d)[\d.]+$')
|
29 |
|
30 | def MakeShellPairs(shells):
|
31 | shell_pairs = []
|
32 |
|
33 | saw_osh = False
|
34 | saw_ysh = False
|
35 | saw_oil = False
|
36 |
|
37 | for path in shells:
|
38 | m = BASH_RE.match(path)
|
39 | if m:
|
40 | label = m.group(1) # bash-4 or to fit
|
41 | else:
|
42 | first, _ = os.path.splitext(path)
|
43 | label = os.path.basename(first)
|
44 |
|
45 | if label == 'osh':
|
46 | # change the second 'osh' to 'osh_ALT' so it's distinct
|
47 | if saw_osh:
|
48 | if OSH_CPP_RE.search(path):
|
49 | label = 'osh-cpp'
|
50 | else:
|
51 | label = 'osh_ALT'
|
52 | saw_osh = True
|
53 |
|
54 | elif label == 'ysh':
|
55 | if saw_ysh:
|
56 | if YSH_CPP_RE.search(path):
|
57 | label = 'ysh-cpp'
|
58 | else:
|
59 | label = 'ysh_ALT'
|
60 |
|
61 | saw_ysh = True
|
62 |
|
63 | elif label == 'oil': # TODO: remove this
|
64 | if saw_oil:
|
65 | if OIL_CPP_RE.search(path):
|
66 | label = 'oil-cpp'
|
67 | else:
|
68 | label = 'oil_ALT'
|
69 |
|
70 | saw_oil = True
|
71 |
|
72 | shell_pairs.append((label, path))
|
73 | return shell_pairs
|
74 |
|
75 |
|
76 | RANGE_RE = re.compile('(\d+) \s* - \s* (\d+)', re.VERBOSE)
|
77 |
|
78 |
|
79 | def ParseRange(range_str):
|
80 | try:
|
81 | d = int(range_str)
|
82 | return d, d # singleton range
|
83 | except ValueError:
|
84 | m = RANGE_RE.match(range_str)
|
85 | if not m:
|
86 | raise RuntimeError('Invalid range %r' % range_str)
|
87 | b, e = m.groups()
|
88 | return int(b), int(e)
|
89 |
|
90 |
|
91 | class RangePredicate(object):
|
92 | """Zero-based indexing, inclusive ranges."""
|
93 |
|
94 | def __init__(self, begin, end):
|
95 | self.begin = begin
|
96 | self.end = end
|
97 |
|
98 | def __call__(self, i, case):
|
99 | return self.begin <= i <= self.end
|
100 |
|
101 |
|
102 | class RegexPredicate(object):
|
103 | """Filter by name."""
|
104 |
|
105 | def __init__(self, desc_re):
|
106 | self.desc_re = desc_re
|
107 |
|
108 | def __call__(self, i, case):
|
109 | return bool(self.desc_re.search(case['desc']))
|
110 |
|
111 |
|
112 |
|
113 | def DefineCommon(p):
|
114 | """Flags shared between sh_spec.py and stateful/harness.py."""
|
115 | p.add_option(
|
116 | '-v', '--verbose', dest='verbose', action='store_true', default=False,
|
117 | help='Show details about test failures')
|
118 | p.add_option(
|
119 | '-r', '--range', dest='range', default=None,
|
120 | help='Execute only a given test range, e.g. 5-10, 5-, -10, or 5')
|
121 | p.add_option(
|
122 | '--regex', dest='regex', default=None,
|
123 | help='Execute only tests whose description matches a given regex '
|
124 | '(case-insensitive)')
|
125 | p.add_option(
|
126 | '--list', dest='do_list', action='store_true', default=None,
|
127 | help='Just list tests')
|
128 | p.add_option(
|
129 | '--oils-failures-allowed', dest='oils_failures_allowed', type='int',
|
130 | default=0, help="Allow this number of Oils failures")
|
131 |
|
132 | # Select what shells to run
|
133 | p.add_option(
|
134 | '--oils-bin-dir', dest='oils_bin_dir', default=None,
|
135 | help="Directory that osh and ysh live in")
|
136 | p.add_option(
|
137 | '--oils-cpp-bin-dir', dest='oils_cpp_bin_dir', default=None,
|
138 | help="Directory that native C++ osh and ysh live in")
|
139 | p.add_option(
|
140 | '--ovm-bin-dir', dest='ovm_bin_dir', default=None,
|
141 | help="Directory of the legacy OVM/CPython build")
|
142 | p.add_option(
|
143 | '--compare-shells', dest='compare_shells', action='store_true',
|
144 | help="Compare against shells specified at the top of each file")
|
145 |
|
146 |
|
147 | def DefineStateful(p):
|
148 | p.add_option(
|
149 | '--num-retries', dest='num_retries',
|
150 | type='int', default=4,
|
151 | help='Number of retries (for spec/stateful only)')
|
152 | p.add_option(
|
153 | '--pexpect-timeout', dest='pexpect_timeout',
|
154 | type='float', default=1.0,
|
155 | help='In seconds')
|
156 | p.add_option(
|
157 | '--results-file', dest='results_file', default=None,
|
158 | help='Write table of results to this file. Default is stdout.')
|
159 |
|
160 |
|
161 | def DefineShSpec(p):
|
162 | p.add_option(
|
163 | '-d', '--details', dest='details', action='store_true', default=False,
|
164 | help='Show details even for successful cases (requires -v)')
|
165 | p.add_option(
|
166 | '-t', '--trace', dest='trace', action='store_true', default=False,
|
167 | help='trace execution of shells to diagnose hangs')
|
168 |
|
169 | # Execution modes
|
170 | p.add_option(
|
171 | '-p', '--print', dest='do_print', action='store_true', default=None,
|
172 | help="Print test code, but don't run it")
|
173 | p.add_option(
|
174 | '--print-spec-suite', dest='print_spec_suite', action='store_true', default=None,
|
175 | help="Print suite this file belongs to")
|
176 | p.add_option(
|
177 | '--print-table', dest='print_table', action='store_true', default=None,
|
178 | help="Print table of test files")
|
179 | p.add_option(
|
180 | '--print-tagged', dest='print_tagged',
|
181 | help="Print spec files tagged with a certain string")
|
182 |
|
183 | # Output control
|
184 | p.add_option(
|
185 | '--format', dest='format', choices=['ansi', 'html'],
|
186 | default='ansi', help="Output format (default 'ansi')")
|
187 | p.add_option(
|
188 | '--stats-file', dest='stats_file', default=None,
|
189 | help="File to write stats to")
|
190 | p.add_option(
|
191 | '--tsv-output', dest='tsv_output', default=None,
|
192 | help="Write a TSV log to this file. Subsumes --stats-file.")
|
193 | p.add_option(
|
194 | '--stats-template', dest='stats_template', default='',
|
195 | help="Python format string for stats")
|
196 |
|
197 | p.add_option(
|
198 | '--path-env', dest='path_env', default='',
|
199 | help="The full PATH, for finding binaries used in tests.")
|
200 | p.add_option(
|
201 | '--tmp-env', dest='tmp_env', default='',
|
202 | help="A temporary directory that the tests can use.")
|
203 |
|
204 | # Notes:
|
205 | # - utf-8 is the Ubuntu default
|
206 | # - this flag has limited usefulness. It may be better to simply export LANG=
|
207 | # in this test case itself.
|
208 | p.add_option(
|
209 | '--lang-env', dest='lang_env', default='en_US.UTF-8',
|
210 | help="The LANG= setting, which affects various libc functions.")
|
211 | p.add_option(
|
212 | '--env-pair', dest='env_pair', default=[], action='append',
|
213 | help='A key=value pair to add to the environment')
|
214 |
|
215 | p.add_option(
|
216 | '--timeout', dest='timeout', default='',
|
217 | help="Prefix shell invocation with 'timeout N'")
|
218 | p.add_option(
|
219 | '--timeout-bin', dest='timeout_bin', default=None,
|
220 | help="Use the smoosh timeout binary at this location.")
|
221 |
|
222 | p.add_option(
|
223 | '--posix', dest='posix', default=False, action='store_true',
|
224 | help='Pass -o posix to the shell (when applicable)')
|
225 |
|
226 | p.add_option(
|
227 | '--sh-env-var-name', dest='sh_env_var_name', default='SH',
|
228 | help="Set this environment variable to the path of the shell")
|
229 |
|
230 | p.add_option(
|
231 | '--pyann-out-dir', dest='pyann_out_dir', default=None,
|
232 | help='Run OSH with PYANN_OUT=$dir/$case_num.json')
|