ddd88f25646eaa8c6905a3bd732fbd8e39f3fc44
[platform/upstream/llvm.git] / libcxx / utils / libcxx / test / format.py
1 # ===----------------------------------------------------------------------===##
2 #
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
6 #
7 # ===----------------------------------------------------------------------===##
8
9 import contextlib
10 import io
11 import lit
12 import lit.formats
13 import os
14 import pipes
15 import re
16 import shutil
17
18
19 def _getTempPaths(test):
20     """
21     Return the values to use for the %T and %t substitutions, respectively.
22
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.
25     """
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
31
32
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)
37
38 def _parseLitOutput(fullOutput):
39     """
40     Parse output of a Lit ShTest to extract the actual output of the contained commands.
41
42     This takes output of the form
43
44         $ ":" "RUN: at line 11"
45         $ "echo" "OUTPUT1"
46         # command output:
47         OUTPUT1
48
49         $ ":" "RUN: at line 12"
50         $ "echo" "OUTPUT2"
51         # command output:
52         OUTPUT2
53
54     and returns a string containing
55
56         OUTPUT1
57         OUTPUT2
58
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.
62     """
63     parsed = ''
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)
67             if commandOutput:
68                 parsed += commandOutput.group(1)
69     return parsed
70
71 def _executeScriptInternal(test, litConfig, commands):
72     """
73     Returns (stdout, stderr, exitCode, timeoutInfo, parsedCommands)
74
75     TODO: This really should be easier to access from Lit itself
76     """
77     parsedCommands = parseScript(test, preamble=commands)
78
79     _, tmpBase = _getTempPaths(test)
80     execDir = os.path.dirname(test.getExecPath())
81     res = lit.TestRunner.executeScriptInternal(
82         test, litConfig, tmpBase, parsedCommands, execDir
83     )
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
87
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
96
97     return (out, err, exitCode, timeoutInfo, parsedCommands)
98
99
100 def parseScript(test, preamble):
101     """
102     Extract the script from a test, with substitutions applied.
103
104     Returns a list of commands ready to be executed.
105
106     - test
107         The lit.Test to parse.
108
109     - preamble
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
113         once substituted.
114     """
115     # Get the default substitutions
116     tmpDir, tmpBase = _getTempPaths(test)
117     substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir, tmpBase)
118
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")
123     )
124     substitutions.append(("%{run}", "%{exec} %t.exe"))
125
126     # Parse the test file, including custom directives
127     additionalCompileFlags = []
128     fileDependencies = []
129     parsers = [
130         lit.TestRunner.IntegratedTestKeywordParser(
131             "FILE_DEPENDENCIES:",
132             lit.TestRunner.ParserKind.LIST,
133             initial_value=fileDependencies,
134         ),
135         lit.TestRunner.IntegratedTestKeywordParser(
136             "ADDITIONAL_COMPILE_FLAGS:",
137             lit.TestRunner.ParserKind.LIST,
138             initial_value=additionalCompileFlags,
139         ),
140     ]
141
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,
150         )
151         parsers.append(parser)
152
153     scriptInTest = lit.TestRunner.parseIntegratedTestScript(
154         test, additional_parsers=parsers, require_script=not preamble
155     )
156     if isinstance(scriptInTest, lit.Test.Result):
157         return scriptInTest
158
159     script = []
160
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)]
166     script += preamble
167     script += scriptInTest
168
169     # Add compile flags specified with ADDITIONAL_COMPILE_FLAGS.
170     substitutions = [
171         (s, x + " " + " ".join(additionalCompileFlags))
172         if s == "%{compile_flags}"
173         else (s, x)
174         for (s, x) in substitutions
175     ]
176
177     # Perform substitutions in the script itself.
178     script = lit.TestRunner.applySubstitutions(
179         script, substitutions, recursion_limit=test.config.recursiveExpansionLimit
180     )
181
182     return script
183
184
185 class CxxStandardLibraryTest(lit.formats.FileBasedTest):
186     """
187     Lit test format for the C++ Standard Library conformance test suite.
188
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:
193
194     FOO.pass.cpp            - Compiles, links and runs successfully
195     FOO.pass.mm             - Same as .pass.cpp, but for Objective-C++
196
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
200
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
204
205     FOO.sh.<anything>       - A builtin Lit Shell test
206
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.
216
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.
220
221
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
226     constructs:
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
232
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.
237
238
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:
243
244         // FILE_DEPENDENCIES: file, directory, /path/to/file
245
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.
253
254         // ADDITIONAL_COMPILE_FLAGS: flag1, flag2, flag3
255
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.
260
261
262     Additional provided substitutions and features
263     ==============================================
264     The test format will define the following substitutions for use inside tests:
265
266         %{build}
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.
270
271         %{run}
272             Equivalent to `%{exec} %t.exe`. This is intended to be used
273             in conjunction with the %{build} substitution.
274     """
275
276     def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig):
277         SUPPORTED_SUFFIXES = [
278             "[.]pass[.]cpp$",
279             "[.]pass[.]mm$",
280             "[.]compile[.]pass[.]cpp$",
281             "[.]compile[.]pass[.]mm$",
282             "[.]compile[.]fail[.]cpp$",
283             "[.]link[.]pass[.]cpp$",
284             "[.]link[.]pass[.]mm$",
285             "[.]link[.]fail[.]cpp$",
286             "[.]sh[.][^.]+$",
287             "[.]gen[.][^.]+$",
288             "[.]verify[.]cpp$",
289             "[.]fail[.]cpp$",
290         ]
291
292         sourcePath = testSuite.getSourcePath(pathInSuite)
293         filename = os.path.basename(sourcePath)
294
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):
298             return
299
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):
304                 yield test
305         else:
306             yield lit.Test.Test(testSuite, pathInSuite, localConfig)
307
308     def execute(self, test, litConfig):
309         VERIFY_FLAGS = (
310             "-Xclang -verify -Xclang -verify-ignore-unexpected=note -ferror-limit=0"
311         )
312         supportsVerify = "verify-support" in test.config.available_features
313         filename = test.path_in_suite[-1]
314
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(
319             ".compile.pass.mm"
320         ):
321             steps = [
322                 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -fsyntax-only"
323             ]
324             return self._executeShTest(test, litConfig, steps)
325         elif filename.endswith(".compile.fail.cpp"):
326             steps = [
327                 "%dbg(COMPILED WITH) ! %{cxx} %s %{flags} %{compile_flags} -fsyntax-only"
328             ]
329             return self._executeShTest(test, litConfig, steps)
330         elif filename.endswith(".link.pass.cpp") or filename.endswith(".link.pass.mm"):
331             steps = [
332                 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe"
333             ]
334             return self._executeShTest(test, litConfig, steps)
335         elif filename.endswith(".link.fail.cpp"):
336             steps = [
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",
339             ]
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(
346                         test.getFullName()
347                     ),
348                 )
349             steps = [
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(
353                     VERIFY_FLAGS
354                 )
355             ]
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"):
360             steps = [
361                 "%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} %{link_flags} -o %t.exe",
362                 "%dbg(EXECUTED AS) %{exec} %t.exe",
363             ]
364             return self._executeShTest(test, litConfig, steps)
365         else:
366             return lit.Test.Result(
367                 lit.Test.UNRESOLVED, "Unknown test suffix for '{}'".format(filename)
368             )
369
370     def _executeShTest(self, test, litConfig, steps):
371         if test.config.unsupported:
372             return lit.Test.Result(lit.Test.UNSUPPORTED, "Test is unsupported")
373
374         script = parseScript(test, steps)
375         if isinstance(script, lit.Test.Result):
376             return script
377
378         if litConfig.noExecute:
379             return lit.Test.Result(
380                 lit.Test.XFAIL if test.isExpectedToFail() else lit.Test.PASS
381             )
382         else:
383             _, tmpBase = _getTempPaths(test)
384             useExternalSh = False
385             return lit.TestRunner._runShTest(
386                 test, litConfig, useExternalSh, script, tmpBase
387             )
388
389     def _generateGenTest(self, testSuite, pathInSuite, litConfig, localConfig):
390         generator = lit.Test.Test(testSuite, pathInSuite, localConfig)
391
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)
395
396         # Run the generator test
397         steps = [] # Steps must already be in the script
398         (out, err, exitCode, _, _) = _executeScriptInternal(generator, litConfig, steps)
399         if exitCode != 0:
400             raise RuntimeError(f"Error while trying to generate gen test\nstdout:\n{out}\n\nstderr:\n{err}")
401
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:
408                 f.write(content)
409             yield lit.Test.Test(testSuite, (generatedFile,), localConfig)
410
411     def _splitFile(self, input):
412         DELIM = r'^(//|#)---(.+)'
413         lines = input.splitlines()
414         currentFile = None
415         thisFileContent = []
416         for line in lines:
417             match = re.match(DELIM, line)
418             if match:
419                 if currentFile is not None:
420                     yield (currentFile, '\n'.join(thisFileContent))
421                 currentFile = match.group(2).strip()
422                 thisFileContent = []
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))