/
newformat.py
307 lines (256 loc) · 15.4 KB
/
newformat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===----------------------------------------------------------------------===##
import lit
import os
import pipes
import re
import subprocess
class CxxStandardLibraryTest(lit.formats.TestFormat):
"""
Lit test format for the C++ Standard Library conformance test suite.
This test format is based on top of the ShTest format -- it basically
creates a shell script performing the right operations (compile/link/run)
based on the extension of the test file it encounters. It supports files
with the following extensions:
FOO.pass.cpp - Compiles, links and runs successfully
FOO.pass.mm - Same as .pass.cpp, but for Objective-C++
FOO.run.fail.cpp - Compiles and links successfully, but fails at runtime
FOO.compile.pass.cpp - Compiles successfully, link and run not attempted
FOO.compile.fail.cpp - Does not compile successfully
FOO.link.pass.cpp - Compiles and links successfully, run not attempted
FOO.link.fail.cpp - Compiles successfully, but fails to link
FOO.sh.<anything> - A builtin Lit Shell test
FOO.verify.cpp - Compiles with clang-verify
FOO.fail.cpp - Compiled with clang-verify if clang-verify is
supported, and equivalent to a .compile.fail.cpp
test otherwise. This is supported only for backwards
compatibility with the test suite.
Substitution requirements
===============================
The test format operates by assuming that each test's configuration provides
the following substitutions, which it will reuse in the shell scripts it
constructs:
%{cxx} - A command that can be used to invoke the compiler
%{compile_flags} - Flags to use when compiling a test case
%{link_flags} - Flags to use when linking a test case
%{flags} - Flags to use either when compiling or linking a test case
%{exec} - A command to prefix the execution of executables
Note that when building an executable (as opposed to only compiling a source
file), all three of %{flags}, %{compile_flags} and %{link_flags} will be used
in the same command line. In other words, the test format doesn't perform
separate compilation and linking steps in this case.
Additional supported directives
===============================
In addition to everything that's supported in Lit ShTests, this test format
also understands the following directives inside test files:
// FILE_DEPENDENCIES: file, directory, /path/to/file
This directive expresses that the test requires the provided files
or directories in order to run. An example is a test that requires
some test input stored in a data file. When a test file contains
such a directive, this test format will collect them and make them
available in a special %{file_dependencies} substitution. The intent
is that if one needs to e.g. execute tests on a remote host, the
%{exec} substitution could use %{file_dependencies} to know which
files and directories to copy to the remote host.
// ADDITIONAL_COMPILE_FLAGS: flag1, flag2, flag3
This directive will cause the provided flags to be added to the
%{compile_flags} substitution for the test that contains it. This
allows adding special compilation flags without having to use a
.sh.cpp test, which would be more powerful but perhaps overkill.
Additional provided substitutions and features
==============================================
The test format will define the following substitutions for use inside
tests:
%{verify}
This expands to the set of flags that must be passed to the
compiler in order to use Clang-verify, if that is supported.
verify-support
This Lit feature will be made available when the compiler supports
Clang-verify. This can be used to disable tests that require that
feature, such as `.verify.cpp` tests.
%{file_dependencies}
Expands to the list of files that this test depends on.
See FILE_DEPENDENCIES above.
%{build}
Expands to a command-line that builds the current source
file with the %{flags}, %{compile_flags} and %{link_flags}
substitutions, and that produces an executable named %t.exe.
%{run}
Equivalent to `%{exec} %t.exe`. This is intended to be used
in conjunction with the %{build} substitution.
Design notes
============
This test format never implicitly disables a type of test. For example,
we could be tempted to automatically mark `.verify.cpp` tests as
UNSUPPORTED when clang-verify isn't supported by the compiler. However,
this sort of logic has been known to cause tests to be ignored in the
past, so we favour having tests mark themselves as unsupported explicitly.
This test format still needs work in the following areas:
- It is unknown how well it works on Windows yet.
"""
def getTestsInDirectory(self, testSuite, pathInSuite, litConfig, localConfig):
SUPPORTED_SUFFIXES = ['[.]pass[.]cpp$', '[.]pass[.]mm$', '[.]run[.]fail[.]cpp$',
'[.]compile[.]pass[.]cpp$', '[.]compile[.]fail[.]cpp$',
'[.]link[.]pass[.]cpp$', '[.]link[.]fail[.]cpp$',
'[.]sh[.][^.]+$',
'[.]verify[.]cpp$',
'[.]fail[.]cpp$']
sourcePath = testSuite.getSourcePath(pathInSuite)
for filename in os.listdir(sourcePath):
# Ignore dot files and excluded tests.
if filename.startswith('.') or filename in localConfig.excludes:
continue
filepath = os.path.join(sourcePath, filename)
if not os.path.isdir(filepath):
if any([re.search(ext, filename) for ext in SUPPORTED_SUFFIXES]):
yield lit.Test.Test(testSuite, pathInSuite + (filename,), localConfig)
def _checkBaseSubstitutions(self, substitutions):
substitutions = [s for (s, _) in substitutions]
for s in ['%{cxx}', '%{compile_flags}', '%{link_flags}', '%{flags}', '%{exec}']:
assert s in substitutions, "Required substitution {} was not provided".format(s)
# Determine whether clang-verify is supported.
def _supportsVerify(self, test):
command = "%{{cxx}} -xc++ {} -Werror -fsyntax-only -Xclang -verify-ignore-unexpected".format(os.devnull)
command = lit.TestRunner.applySubstitutions([command], test.config.substitutions,
recursion_limit=test.config.recursiveExpansionLimit)[0]
devNull = open(os.devnull, 'w')
result = subprocess.call(command, shell=True, stdout=devNull, stderr=devNull)
return result == 0
def _disableWithModules(self, test):
with open(test.getSourcePath(), 'rb') as f:
contents = f.read()
return b'#define _LIBCPP_ASSERT' in contents
def execute(self, test, litConfig):
self._checkBaseSubstitutions(test.config.substitutions)
filename = test.path_in_suite[-1]
# TODO(ldionne): We currently disable tests that re-define _LIBCPP_ASSERT
# when we run with modules enabled. Instead, we should
# split the part that does a death test outside of the
# test, and only disable that part when modules are
# enabled.
if '-fmodules' in test.config.available_features and self._disableWithModules(test):
return lit.Test.Result(lit.Test.UNSUPPORTED, 'Test {} is unsupported when modules are enabled')
# TODO(ldionne): Enable -Werror with all supported compilers.
clangOrAppleClang = {'clang', 'apple-clang'}.intersection(test.config.available_features) != set()
werror = '-Werror' if clangOrAppleClang else ''
if re.search('[.]sh[.][^.]+$', filename):
steps = [ ] # The steps are already in the script
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.compile.pass.cpp'):
steps = [
"%dbg(COMPILED WITH) %{{cxx}} %s {} %{{flags}} %{{compile_flags}} -fsyntax-only".format(werror)
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.compile.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) ! %{{cxx}} %s {} %{{flags}} %{{compile_flags}} -fsyntax-only".format(werror)
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.link.pass.cpp'):
steps = [
"%dbg(COMPILED WITH) %{{cxx}} %s {} %{{flags}} %{{compile_flags}} %{{link_flags}} -o %t.exe".format(werror)
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.link.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) %{{cxx}} %s {} %{{flags}} %{{compile_flags}} -c -o %t.o".format(werror),
"%dbg(LINKED WITH) ! %{cxx} %t.o %{flags} %{link_flags} -o %t.exe"
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.run.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) %{{cxx}} %s {} %{{flags}} %{{compile_flags}} %{{link_flags}} -o %t.exe".format(werror),
"%dbg(EXECUTED AS) %{exec} ! %t.exe"
]
return self._executeShTest(test, litConfig, steps, fileDependencies=['%t.exe'])
elif filename.endswith('.verify.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -fsyntax-only %{verify}"
]
return self._executeShTest(test, litConfig, steps)
# Make sure to check these ones last, since they will match other
# suffixes above too.
elif filename.endswith('.pass.cpp') or filename.endswith('.pass.mm'):
steps = [
"%dbg(COMPILED WITH) %{{cxx}} %s {} %{{flags}} %{{compile_flags}} %{{link_flags}} -o %t.exe".format(werror),
"%dbg(EXECUTED AS) %{exec} %t.exe"
]
return self._executeShTest(test, litConfig, steps, fileDependencies=['%t.exe'])
# This is like a .verify.cpp test when clang-verify is supported,
# otherwise it's like a .compile.fail.cpp test. This is only provided
# for backwards compatibility with the test suite.
elif filename.endswith('.fail.cpp'):
if self._supportsVerify(test):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -fsyntax-only %{verify}"
]
else:
steps = [
"%dbg(COMPILED WITH) ! %{{cxx}} {} %s %{{flags}} %{{compile_flags}} -fsyntax-only".format(werror)
]
return self._executeShTest(test, litConfig, steps)
else:
return lit.Test.Result(lit.Test.UNRESOLVED, "Unknown test suffix for '{}'".format(filename))
# Utility function to add compile flags in lit.local.cfg files.
def addCompileFlags(self, config, *flags):
string = ' '.join(flags)
config.substitutions = [(s, x + ' ' + string) if s == '%{compile_flags}' else (s, x) for (s, x) in config.substitutions]
# Modified version of lit.TestRunner.executeShTest to handle custom parsers correctly.
def _executeShTest(self, test, litConfig, steps, fileDependencies=None):
if test.config.unsupported:
return lit.Test.Result(lit.Test.UNSUPPORTED, 'Test is unsupported')
# Get the default substitutions
tmpDir, tmpBase = lit.TestRunner.getTempPaths(test)
useExternalSh = True
substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir, tmpBase,
normalize_slashes=useExternalSh)
# Add the %{build} and %{run} convenience substitutions
substitutions.append(('%{build}', '%{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe'))
substitutions.append(('%{run}', '%{exec} %t.exe'))
# Add the %{verify} substitution and the verify-support feature if Clang-verify is supported
if self._supportsVerify(test):
test.config.available_features.add('verify-support')
substitutions.append(('%{verify}', '-Xclang -verify -Xclang -verify-ignore-unexpected=note -ferror-limit=0'))
# Parse the test file, including custom directives
additionalCompileFlags = []
fileDependencies = fileDependencies or []
parsers = [
lit.TestRunner.IntegratedTestKeywordParser('FILE_DEPENDENCIES:',
lit.TestRunner.ParserKind.LIST,
initial_value=fileDependencies),
lit.TestRunner.IntegratedTestKeywordParser('ADDITIONAL_COMPILE_FLAGS:',
lit.TestRunner.ParserKind.LIST,
initial_value=additionalCompileFlags)
]
script = list(steps)
parsed = lit.TestRunner.parseIntegratedTestScript(test, additional_parsers=parsers,
require_script=not script)
if isinstance(parsed, lit.Test.Result):
return parsed
script += parsed
# Add compile flags specified with ADDITIONAL_COMPILE_FLAGS.
substitutions = [(s, x + ' ' + ' '.join(additionalCompileFlags)) if s == '%{compile_flags}'
else (s, x) for (s, x) in substitutions]
# Perform substitutions inside FILE_DEPENDENCIES lines (or injected dependencies).
# This allows using variables like %t in file dependencies. Also note that we really
# need to resolve %{file_dependencies} now, because otherwise we won't be able to
# make all paths absolute below.
fileDependencies = lit.TestRunner.applySubstitutions(fileDependencies, substitutions,
recursion_limit=test.config.recursiveExpansionLimit)
# Add the %{file_dependencies} substitution before we perform substitutions
# inside the script.
testDir = os.path.dirname(test.getSourcePath())
fileDependencies = [f if os.path.isabs(f) else os.path.join(testDir, f) for f in fileDependencies]
substitutions.append(('%{file_dependencies}', ' '.join(map(pipes.quote, fileDependencies))))
# Perform substitution in the script itself.
script = lit.TestRunner.applySubstitutions(script, substitutions,
recursion_limit=test.config.recursiveExpansionLimit)
if litConfig.noExecute:
return lit.Test.Result(lit.Test.PASS)
else:
return lit.TestRunner._runShTest(test, litConfig, useExternalSh, script, tmpBase)