6
6
#
7
7
# ===----------------------------------------------------------------------===##
8
8
9
+ import contextlib
10
+ import io
9
11
import lit
10
12
import lit .formats
11
13
import os
@@ -33,6 +35,38 @@ def _checkBaseSubstitutions(substitutions):
33
35
for s in ["%{cxx}" , "%{compile_flags}" , "%{link_flags}" , "%{flags}" , "%{exec}" ]:
34
36
assert s in substitutions , "Required substitution {} was not provided" .format (s )
35
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
36
70
37
71
def _executeScriptInternal (test , litConfig , commands ):
38
72
"""
@@ -170,6 +204,16 @@ class CxxStandardLibraryTest(lit.formats.TestFormat):
170
204
171
205
FOO.sh.<anything> - A builtin Lit Shell test
172
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
+
173
217
FOO.verify.cpp - Compiles with clang-verify. This type of test is
174
218
automatically marked as UNSUPPORTED if the compiler
175
219
does not support Clang-verify.
@@ -245,6 +289,7 @@ def getTestsInDirectory(self, testSuite, pathInSuite, litConfig, localConfig):
245
289
"[.]link[.]pass[.]mm$" ,
246
290
"[.]link[.]fail[.]cpp$" ,
247
291
"[.]sh[.][^.]+$" ,
292
+ "[.]gen[.][^.]+$" ,
248
293
"[.]verify[.]cpp$" ,
249
294
"[.]fail[.]cpp$" ,
250
295
]
@@ -257,9 +302,13 @@ def getTestsInDirectory(self, testSuite, pathInSuite, litConfig, localConfig):
257
302
filepath = os .path .join (sourcePath , filename )
258
303
if not os .path .isdir (filepath ):
259
304
if any ([re .search (ext , filename ) for ext in SUPPORTED_SUFFIXES ]):
260
- yield lit .Test .Test (
261
- testSuite , pathInSuite + (filename ,), localConfig
262
- )
305
+ # If this is a generated test, run the generation step and add
306
+ # as many Lit tests as necessary.
307
+ if re .search ('[.]gen[.][^.]+$' , filename ):
308
+ for test in self ._generateGenTest (testSuite , pathInSuite + (filename ,), litConfig , localConfig ):
309
+ yield test
310
+ else :
311
+ yield lit .Test .Test (testSuite , pathInSuite + (filename ,), localConfig )
263
312
264
313
def execute (self , test , litConfig ):
265
314
VERIFY_FLAGS = (
@@ -356,3 +405,42 @@ def _executeShTest(self, test, litConfig, steps):
356
405
return lit .TestRunner ._runShTest (
357
406
test , litConfig , useExternalSh , script , tmpBase
358
407
)
408
+
409
+ def _generateGenTest (self , testSuite , pathInSuite , litConfig , localConfig ):
410
+ generator = lit .Test .Test (testSuite , pathInSuite , localConfig )
411
+
412
+ # Make sure we have a directory to execute the generator test in
413
+ generatorExecDir = os .path .dirname (testSuite .getExecPath (pathInSuite ))
414
+ os .makedirs (generatorExecDir , exist_ok = True )
415
+
416
+ # Run the generator test
417
+ steps = [] # Steps must already be in the script
418
+ (out , err , exitCode , _ , _ ) = _executeScriptInternal (generator , litConfig , steps )
419
+ if exitCode != 0 :
420
+ raise RuntimeError (f"Error while trying to generate gen test\n stdout:\n { out } \n \n stderr:\n { err } " )
421
+
422
+ # Split the generated output into multiple files and generate one test for each file
423
+ parsed = _parseLitOutput (out )
424
+ for (subfile , content ) in self ._splitFile (parsed ):
425
+ generatedFile = testSuite .getExecPath (pathInSuite + (subfile , ))
426
+ os .makedirs (os .path .dirname (generatedFile ), exist_ok = True )
427
+ with open (generatedFile , 'w' ) as f :
428
+ f .write (content )
429
+ yield lit .Test .Test (testSuite , (generatedFile ,), localConfig )
430
+
431
+ def _splitFile (self , input ):
432
+ DELIM = r'^(//|#)---(.+)'
433
+ lines = input .splitlines ()
434
+ currentFile = None
435
+ thisFileContent = []
436
+ for line in lines :
437
+ match = re .match (DELIM , line )
438
+ if match :
439
+ if currentFile is not None :
440
+ yield (currentFile , '\n ' .join (thisFileContent ))
441
+ currentFile = match .group (2 ).strip ()
442
+ thisFileContent = []
443
+ assert currentFile is not None , f"Some input to split-file doesn't belong to any file, input was:\n { input } "
444
+ thisFileContent .append (line )
445
+ if currentFile is not None :
446
+ yield (currentFile , '\n ' .join (thisFileContent ))
0 commit comments