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