1 # ===----------------------------------------------------------------------===##
3 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 # See https://llvm.org/LICENSE.txt for license information.
5 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 # ===----------------------------------------------------------------------===##
19 def _getTempPaths(test):
21 Return the values to use for the %T and %t substitutions, respectively.
23 The difference between this and Lit's default behavior is that we guarantee
24 that %T is a path unique to the test being run.
26 tmpDir, _ = lit.TestRunner.getTempPaths(test)
27 _, testName = os.path.split(test.getExecPath())
28 tmpDir = os.path.join(tmpDir, testName + ".dir")
29 tmpBase = os.path.join(tmpDir, "t")
30 return tmpDir, tmpBase
33 def _checkBaseSubstitutions(substitutions):
34 substitutions = [s for (s, _) in substitutions]
35 for s in ["%{cxx}", "%{compile_flags}", "%{link_flags}", "%{flags}", "%{exec}"]:
36 assert s in substitutions, "Required substitution {} was not provided".format(s)
38 def _parseLitOutput(fullOutput):
40 Parse output of a Lit ShTest to extract the actual output of the contained commands.
42 This takes output of the form
44 $ ":" "RUN: at line 11"
49 $ ":" "RUN: at line 12"
54 and returns a string containing
59 as-if the commands had been run directly. This is a workaround for the fact
60 that Lit doesn't let us execute ShTest and retrieve the raw output without
61 injecting additional Lit output around it.
64 for output in re.split('[$]\s*":"\s*"RUN: at line \d+"', fullOutput):
65 if output: # skip blank lines
66 commandOutput = re.search("# command output:\n(.+)\n$", output, flags=re.DOTALL)
68 parsed += commandOutput.group(1)
71 def _executeScriptInternal(test, litConfig, commands):
73 Returns (stdout, stderr, exitCode, timeoutInfo, parsedCommands)
75 TODO: This really should be easier to access from Lit itself
77 parsedCommands = parseScript(test, preamble=commands)
79 _, tmpBase = _getTempPaths(test)
80 execDir = os.path.dirname(test.getExecPath())
81 res = lit.TestRunner.executeScriptInternal(
82 test, litConfig, tmpBase, parsedCommands, execDir
84 if isinstance(res, lit.Test.Result): # Handle failure to parse the Lit test
85 res = ("", res.output, 127, None)
86 (out, err, exitCode, timeoutInfo) = res
88 # TODO: As a temporary workaround until https://reviews.llvm.org/D81892 lands, manually
89 # split any stderr output that is included in stdout. It shouldn't be there, but
90 # the Lit internal shell conflates stderr and stdout.
91 conflatedErrorOutput = re.search("(# command stderr:.+$)", out, flags=re.DOTALL)
92 if conflatedErrorOutput:
93 conflatedErrorOutput = conflatedErrorOutput.group(0)
94 out = out[: -len(conflatedErrorOutput)]
95 err += conflatedErrorOutput
97 return (out, err, exitCode, timeoutInfo, parsedCommands)
100 def parseScript(test, preamble):
102 Extract the script from a test, with substitutions applied.
104 Returns a list of commands ready to be executed.
107 The lit.Test to parse.
110 A list of commands to perform before any command in the test.
111 These commands can contain unexpanded substitutions, but they
112 must not be of the form 'RUN:' -- they must be proper commands
115 # Get the default substitutions
116 tmpDir, tmpBase = _getTempPaths(test)
117 substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir, tmpBase)
119 # Check base substitutions and add the %{build} and %{run} convenience substitutions
120 _checkBaseSubstitutions(substitutions)
121 substitutions.append(
122 ("%{build}", "%{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe")
124 substitutions.append(("%{run}", "%{exec} %t.exe"))
126 # Parse the test file, including custom directives
127 additionalCompileFlags = []
128 fileDependencies = []
130 lit.TestRunner.IntegratedTestKeywordParser(
131 "FILE_DEPENDENCIES:",
132 lit.TestRunner.ParserKind.LIST,
133 initial_value=fileDependencies,
135 lit.TestRunner.IntegratedTestKeywordParser(
136 "ADDITIONAL_COMPILE_FLAGS:",
137 lit.TestRunner.ParserKind.LIST,
138 initial_value=additionalCompileFlags,
142 # Add conditional parsers for ADDITIONAL_COMPILE_FLAGS. This should be replaced by first
143 # class support for conditional keywords in Lit, which would allow evaluating arbitrary
144 # Lit boolean expressions instead.
145 for feature in test.config.available_features:
146 parser = lit.TestRunner.IntegratedTestKeywordParser(
147 "ADDITIONAL_COMPILE_FLAGS({}):".format(feature),
148 lit.TestRunner.ParserKind.LIST,
149 initial_value=additionalCompileFlags,
151 parsers.append(parser)
153 scriptInTest = lit.TestRunner.parseIntegratedTestScript(
154 test, additional_parsers=parsers, require_script=not preamble
156 if isinstance(scriptInTest, lit.Test.Result):
161 # For each file dependency in FILE_DEPENDENCIES, inject a command to copy
162 # that file to the execution directory. Execute the copy from %S to allow
163 # relative paths from the test directory.
164 for dep in fileDependencies:
165 script += ["%dbg(SETUP) cd %S && cp {} %T".format(dep)]
167 script += scriptInTest
169 # Add compile flags specified with ADDITIONAL_COMPILE_FLAGS.
171 (s, x + " " + " ".join(additionalCompileFlags))
172 if s == "%{compile_flags}"
174 for (s, x) in substitutions
177 # Perform substitutions in the script itself.
178 script = lit.TestRunner.applySubstitutions(
179 script, substitutions, recursion_limit=test.config.recursiveExpansionLimit
185 class CxxStandardLibraryTest(lit.formats.FileBasedTest):
187 Lit test format for the C++ Standard Library conformance test suite.
189 This test format is based on top of the ShTest format -- it basically
190 creates a shell script performing the right operations (compile/link/run)
191 based on the extension of the test file it encounters. It supports files
192 with the following extensions:
194 FOO.pass.cpp - Compiles, links and runs successfully
195 FOO.pass.mm - Same as .pass.cpp, but for Objective-C++
197 FOO.compile.pass.cpp - Compiles successfully, link and run not attempted
198 FOO.compile.pass.mm - Same as .compile.pass.cpp, but for Objective-C++
199 FOO.compile.fail.cpp - Does not compile successfully
201 FOO.link.pass.cpp - Compiles and links successfully, run not attempted
202 FOO.link.pass.mm - Same as .link.pass.cpp, but for Objective-C++
203 FOO.link.fail.cpp - Compiles successfully, but fails to link
205 FOO.sh.<anything> - A builtin Lit Shell test
207 FOO.gen.<anything> - A .sh test that generates one or more Lit tests on the
208 fly. Executing this test must generate one or more files
209 as expected by LLVM split-file, and each generated file
210 leads to a separate Lit test that runs that file as
211 defined by the test format. This can be used to generate
212 multiple Lit tests from a single source file, which is
213 useful for testing repetitive properties in the library.
214 Be careful not to abuse this since this is not a replacement
215 for usual code reuse techniques.
217 FOO.verify.cpp - Compiles with clang-verify. This type of test is
218 automatically marked as UNSUPPORTED if the compiler
219 does not support Clang-verify.
222 Substitution requirements
223 ===============================
224 The test format operates by assuming that each test's configuration provides
225 the following substitutions, which it will reuse in the shell scripts it
227 %{cxx} - A command that can be used to invoke the compiler
228 %{compile_flags} - Flags to use when compiling a test case
229 %{link_flags} - Flags to use when linking a test case
230 %{flags} - Flags to use either when compiling or linking a test case
231 %{exec} - A command to prefix the execution of executables
233 Note that when building an executable (as opposed to only compiling a source
234 file), all three of %{flags}, %{compile_flags} and %{link_flags} will be used
235 in the same command line. In other words, the test format doesn't perform
236 separate compilation and linking steps in this case.
239 Additional supported directives
240 ===============================
241 In addition to everything that's supported in Lit ShTests, this test format
242 also understands the following directives inside test files:
244 // FILE_DEPENDENCIES: file, directory, /path/to/file
246 This directive expresses that the test requires the provided files
247 or directories in order to run. An example is a test that requires
248 some test input stored in a data file. When a test file contains
249 such a directive, this test format will collect them and copy them
250 to the directory represented by %T. The intent is that %T contains
251 all the inputs necessary to run the test, such that e.g. execution
252 on a remote host can be done by simply copying %T to the host.
254 // ADDITIONAL_COMPILE_FLAGS: flag1, flag2, flag3
256 This directive will cause the provided flags to be added to the
257 %{compile_flags} substitution for the test that contains it. This
258 allows adding special compilation flags without having to use a
259 .sh.cpp test, which would be more powerful but perhaps overkill.
262 Additional provided substitutions and features
263 ==============================================
264 The test format will define the following substitutions for use inside tests:
267 Expands to a command-line that builds the current source
268 file with the %{flags}, %{compile_flags} and %{link_flags}
269 substitutions, and that produces an executable named %t.exe.
272 Equivalent to `%{exec} %t.exe`. This is intended to be used
273 in conjunction with the %{build} substitution.
276 def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig):
277 SUPPORTED_SUFFIXES = [
280 "[.]compile[.]pass[.]cpp$",
281 "[.]compile[.]pass[.]mm$",
282 "[.]compile[.]fail[.]cpp$",
283 "[.]link[.]pass[.]cpp$",
284 "[.]link[.]pass[.]mm$",
285 "[.]link[.]fail[.]cpp$",
292 sourcePath = testSuite.getSourcePath(pathInSuite)
293 filename = os.path.basename(sourcePath)
295 # Ignore dot files, excluded tests and tests with an unsupported suffix
296 hasSupportedSuffix = lambda f: any([re.search(ext, f) for ext in SUPPORTED_SUFFIXES])
297 if filename.startswith(".") or filename in localConfig.excludes or not hasSupportedSuffix(filename):
300 # If this is a generated test, run the generation step and add
301 # as many Lit tests as necessary.
302 if re.search('[.]gen[.][^.]+$', filename):
303 for test in self._generateGenTest(testSuite, pathInSuite, litConfig, localConfig):
306 yield lit.Test.Test(testSuite, pathInSuite, localConfig)
308 def execute(self, test, litConfig):
310 "-Xclang -verify -Xclang -verify-ignore-unexpected=note -ferror-limit=0"
312 supportsVerify = "verify-support" in test.config.available_features
313 filename = test.path_in_suite[-1]
315 if re.search("[.]sh[.][^.]+$", filename):
316 steps = [] # The steps are already in the script
317 return self._executeShTest(test, litConfig, steps)
318 elif filename.endswith(".compile.pass.cpp") or filename.endswith(
322 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -fsyntax-only"
324 return self._executeShTest(test, litConfig, steps)
325 elif filename.endswith(".compile.fail.cpp"):
327 "%dbg(COMPILED WITH) ! %{cxx} %s %{flags} %{compile_flags} -fsyntax-only"
329 return self._executeShTest(test, litConfig, steps)
330 elif filename.endswith(".link.pass.cpp") or filename.endswith(".link.pass.mm"):
332 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe"
334 return self._executeShTest(test, litConfig, steps)
335 elif filename.endswith(".link.fail.cpp"):
337 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -c -o %t.o",
338 "%dbg(LINKED WITH) ! %{cxx} %t.o %{flags} %{link_flags} -o %t.exe",
340 return self._executeShTest(test, litConfig, steps)
341 elif filename.endswith(".verify.cpp"):
342 if not supportsVerify:
343 return lit.Test.Result(
344 lit.Test.UNSUPPORTED,
345 "Test {} requires support for Clang-verify, which isn't supported by the compiler".format(
350 # Note: Use -Wno-error to make sure all diagnostics are not treated as errors,
351 # which doesn't make sense for clang-verify tests.
352 "%dbg(COMPILED WITH) %{{cxx}} %s %{{flags}} %{{compile_flags}} -fsyntax-only -Wno-error {}".format(
356 return self._executeShTest(test, litConfig, steps)
357 # Make sure to check these ones last, since they will match other
358 # suffixes above too.
359 elif filename.endswith(".pass.cpp") or filename.endswith(".pass.mm"):
361 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe",
362 "%dbg(EXECUTED AS) %{exec} %t.exe",
364 return self._executeShTest(test, litConfig, steps)
366 return lit.Test.Result(
367 lit.Test.UNRESOLVED, "Unknown test suffix for '{}'".format(filename)
370 def _executeShTest(self, test, litConfig, steps):
371 if test.config.unsupported:
372 return lit.Test.Result(lit.Test.UNSUPPORTED, "Test is unsupported")
374 script = parseScript(test, steps)
375 if isinstance(script, lit.Test.Result):
378 if litConfig.noExecute:
379 return lit.Test.Result(
380 lit.Test.XFAIL if test.isExpectedToFail() else lit.Test.PASS
383 _, tmpBase = _getTempPaths(test)
384 useExternalSh = False
385 return lit.TestRunner._runShTest(
386 test, litConfig, useExternalSh, script, tmpBase
389 def _generateGenTest(self, testSuite, pathInSuite, litConfig, localConfig):
390 generator = lit.Test.Test(testSuite, pathInSuite, localConfig)
392 # Make sure we have a directory to execute the generator test in
393 generatorExecDir = os.path.dirname(testSuite.getExecPath(pathInSuite))
394 os.makedirs(generatorExecDir, exist_ok=True)
396 # Run the generator test
397 steps = [] # Steps must already be in the script
398 (out, err, exitCode, _, _) = _executeScriptInternal(generator, litConfig, steps)
400 raise RuntimeError(f"Error while trying to generate gen test\nstdout:\n{out}\n\nstderr:\n{err}")
402 # Split the generated output into multiple files and generate one test for each file
403 parsed = _parseLitOutput(out)
404 for (subfile, content) in self._splitFile(parsed):
405 generatedFile = testSuite.getExecPath(pathInSuite + (subfile, ))
406 os.makedirs(os.path.dirname(generatedFile), exist_ok=True)
407 with open(generatedFile, 'w') as f:
409 yield lit.Test.Test(testSuite, (generatedFile,), localConfig)
411 def _splitFile(self, input):
412 DELIM = r'^(//|#)---(.+)'
413 lines = input.splitlines()
417 match = re.match(DELIM, line)
419 if currentFile is not None:
420 yield (currentFile, '\n'.join(thisFileContent))
421 currentFile = match.group(2).strip()
423 assert currentFile is not None, f"Some input to split-file doesn't belong to any file, input was:\n{input}"
424 thisFileContent.append(line)
425 if currentFile is not None:
426 yield (currentFile, '\n'.join(thisFileContent))