runTest.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2#------------------------------------------------------------------------------
3
4import os
5import sys
6import subprocess
7import re
8import argparse
9import tempfile
10import datetime
11import difflib
12import platform
13#------------------------------------------------------------------------------
14
15mypath = '.'
16
17def runCmd(cmd, data=None):
18 if input is None:
19 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
20 stdout, stderr = p.communicate()
21 else:
22 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
23 stdout, stderr = p.communicate(input=data)
24
25 return stdout.decode('utf-8'), stderr.decode('utf-8'), p.returncode
26#------------------------------------------------------------------------------
27
28def cleanStderr(stderr, fileName=None):
29 if fileName is not None:
30 stderr = stderr.replace(fileName, '.tmp.cpp')
31 else:
32 stderr = re.sub('(.*).cpp:', '.tmp:', stderr)
33# stderr = re.sub('[\n ](.*).cpp:', '\\1%s:' %(fileName), stderr)
34
35 # Replace paths, as for example, the STL path differs from a local build to Travis-CI at least for macOS
36 stderr = re.sub('/(.*)/(.*?:[0-9]+):', '... \\2:', stderr)
37 stderr = re.sub('RecoveryExpr 0x[a-f0-9]+ ', 'RecoveryExpr ', stderr)
38
39 return stderr
40#------------------------------------------------------------------------------
41
42def testCompare(tmpFileName, stdout, expectFile, f, args, time):
43 expect = open(expectFile, 'r', encoding='utf-8').read()
44
45 # align line-endings under Windows to Unix-style
46 if os.name == 'nt':
47 stdout = stdout.replace('\r\n', '\n')
48
49 if stdout != expect:
50 print('[FAILED] %s - %s' %(f, time))
51
52 for line in difflib.unified_diff(expect.splitlines(keepends=True), stdout.splitlines(keepends=True), fromfile=expectFile, tofile='stdout', n=3):
53 print('%s' %((line[1:] if line.startswith(' ') else line) ), end='')
54 else:
55 print('[PASSED] %-50s - %s' %(f, time))
56 return True
57
58 return False
59#------------------------------------------------------------------------------
60
61def testCompile(tmpFileName, f, args, fileName, cppStd):
62 if os.name == 'nt':
63 cppStd = cppStd.replace('-std=', '/std:')
64 cppStd = cppStd.replace('2a', 'latest')
65
66 cmd = [args['cxx'], cppStd, '-D__cxa_guard_acquire(x)=true', '-D__cxa_guard_release(x)', '-D__cxa_guard_abort(x)', '-I', os.getcwd()]
67
68 if os.name != 'nt':
69 arch = platform.architecture()[0]
70 if (arch != '64bit') or ((arch == '64bit') and (sys.platform == 'darwin')):
71 cmd.append('-m64')
72 else:
73 cmd.extend(['/nologo', '/EHsc', '/IGNORE:C4335']) # C4335: mac file format detected. EHsc assume only C++ functions throw exceptions.
74
75 # GCC seems to dislike empty ''
76 if '-std=c++98' == cppStd:
77 cmd += ['-Dalignas(x)=']
78
79 cmd += ['-c', tmpFileName]
80
81 stdout, stderr, returncode = runCmd(cmd)
82
83 compileErrorFile = os.path.join(mypath, fileName + '.cerr')
84 if 0 != returncode:
85 if os.path.isfile(compileErrorFile):
86 ce = open(compileErrorFile, 'r', encoding='utf-8').read()
87 stderr = cleanStderr(stderr, tmpFileName)
88
89 if ce == stderr:
90 print(f'[PASSED] Compile: {f}')
91 return True, None
92
93 compileErrorFile = os.path.join(mypath, fileName + '.ccerr')
94 if os.path.isfile(compileErrorFile):
95 ce = open(compileErrorFile, 'r', encoding='utf-8').read()
96 stderr = stderr.replace(tmpFileName, '.tmp.cpp')
97
98 if ce == stderr:
99 print('f[PASSED] Compile: {f}')
100 return True, None
101
102 print(f'[ERROR] Compile failed: {f}')
103 print(stderr)
104 else:
105 if os.path.isfile(compileErrorFile):
106 print('unused file: %s' %(compileErrorFile))
107
108 ext = 'obj' if os.name == 'nt' else 'o'
109
110 objFileName = '%s.%s' %(os.path.splitext(os.path.basename(tmpFileName))[0], ext)
111 os.remove(objFileName)
112
113 print(f'[PASSED] Compile: {f}')
114 return True, None
115
116 return False, stderr
117#------------------------------------------------------------------------------
118
119
120def main():
121 parser = argparse.ArgumentParser(description='Description of your program')
122 parser.add_argument('--insights', help='C++ Insights binary', required=True)
123 parser.add_argument('--cxx', help='C++ compiler to used', default='/usr/local/clang-current/bin/clang++')
124 parser.add_argument('--failure-is-ok', help='Failing tests are ok', default=False, action='store_true')
125 parser.add_argument('--update-tests', help='Update failing tests', default=False, action='store_true')
126 parser.add_argument('--std', help='C++ Standard to used', default='c++17')
127 parser.add_argument('--use-libcpp', help='Use libst++', default=False, action='store_true')
128 parser.add_argument('--llvm-prof-dir', help='LLVM profiles data dir', default='')
129 parser.add_argument('args', nargs=argparse.REMAINDER)
130 args = vars(parser.parse_args())
131
132 insightsPath = args['insights']
133 remainingArgs = args['args']
134 bFailureIsOk = args['failure_is_ok']
135 bUpdateTests = args['update_tests']
136 defaultCppStd = f"-std={args['std']}"
137
138 if args['llvm_prof_dir'] != '':
139 os.environ['LLVM_PROFILE_FILE'] = os.path.join(args['llvm_prof_dir'], 'prof%p.profraw')
140
141 if 0 == len(remainingArgs):
142 cppFiles = [f for f in os.listdir(mypath) if (os.path.isfile(os.path.join(mypath, f)) and f.endswith('.cpp'))]
143 else:
144 cppFiles = remainingArgs
145
146 filesPassed = 0
147 missingExpected = 0
148 crashes = 0
149 ret = 0
150
151 regEx = re.compile('.*cmdline:(.*)')
152 regExInsights = re.compile('.*cmdlineinsights:(.*)')
153
154 for f in sorted(cppFiles):
155 fileName = os.path.splitext(f)[0]
156 expectFile = os.path.join(mypath, fileName + '.expect')
157 ignoreFile = os.path.join(mypath, fileName + '.ignore')
158 cppStd = defaultCppStd
159 insightsOpts = ''
160
161 fh = open(f, 'r', encoding='utf-8')
162 fileHeader = fh.readline()
163 fileHeader += fh.readline()
164 m = regEx.search(fileHeader)
165 if m is not None:
166 cppStd = m.group(1)
167
168 m = regExInsights.search(fileHeader)
169 if m is not None:
170 insightsOpts = m.group(1).split(' ')
171
172 if not os.path.isfile(expectFile) and not os.path.isfile(ignoreFile):
173 print(f'Missing expect/ignore for: {f}')
174 missingExpected += 1
175 continue
176
177 if os.path.isfile(ignoreFile):
178 print(f'Ignoring: {f}')
179 filesPassed += 1
180 continue
181
182 cmd = [insightsPath, f]
183
184 if args['use_libcpp']:
185 cmd.append('-use-libc++')
186
187
188 if len(insightsOpts):
189 cmd.extend(insightsOpts)
190
191 cmd.extend(['--', cppStd, '-m64'])
192
193 begin = datetime.datetime.now()
194 stdout, stderr, returncode = runCmd(cmd)
195 end = datetime.datetime.now()
196
197 if 0 != returncode:
198 compileErrorFile = os.path.join(mypath, fileName + '.cerr')
199 if os.path.isfile(compileErrorFile):
200 ce = open(compileErrorFile, 'r', encoding='utf-8').read()
201
202 # Linker errors name the tmp file and not the .tmp.cpp, replace the name here to be able to suppress
203 # these errors.
204 ce = re.sub('(.*).cpp:', '.tmp:', ce)
205 ce = re.sub('(.*).cpp.', '.tmp:', ce)
206 stderr = re.sub('(Error while processing.*.cpp.)', '', stderr)
207 stderr = cleanStderr(stderr)
208
209 # The cerr output matches and the return code says that we hit a compile error, accept it as passed
210 if ((ce == stderr) and (1 == returncode)) or os.path.exists(os.path.join(mypath, fileName + '.failure')):
211 print(f'[PASSED] Transform: {f}')
212 filesPassed += 1
213 continue
214 else:
215 print(f'[ERROR] Transform: {f}')
216 ret = 1
217
218 crashes += 1
219 print(f'Insight crashed for: {f} with: {returncode}')
220 print(stderr)
221
222 if not bUpdateTests:
223 continue
224
225 fd, tmpFileName = tempfile.mkstemp('.cpp')
226 try:
227 with os.fdopen(fd, 'w', encoding='utf-8') as tmp:
228 # write the data to the temp file
229 tmp.write(stdout)
230
231 equal = testCompare(tmpFileName, stdout, expectFile, f, args, end-begin)
232 bCompiles, stderr = testCompile(tmpFileName, f, args, fileName, cppStd)
233 compileErrorFile = os.path.join(mypath, fileName + '.cerr')
234
235
236 if (bCompiles and equal) or bFailureIsOk:
237 filesPassed += 1
238 elif bUpdateTests:
239 if bCompiles and not equal:
240 open(expectFile, 'w', encoding='utf-8').write(stdout)
241 print('Updating test')
242 elif not bCompiles and os.path.exists(compileErrorFile):
243 open(expectFile, 'w', encoding='utf-8').write(stdout)
244 open(compileErrorFile, 'w', encoding='utf-8').write(stderr)
245 print('Updating test cerr')
246
247
248 finally:
249 os.remove(tmpFileName)
250
251
252
253 expectedToPass = len(cppFiles)-missingExpected
254 print('-----------------------------------------------------------------')
255 print(f'Tests passed: {filesPassed}/{expectedToPass}')
256
257 print(f'Insights crashed: {crashes}')
258 print(f'Missing expected files: {missingExpected}')
259
260 passed = (0 == missingExpected) and (expectedToPass == filesPassed)
261
262 return (passed is False) # note bash expects 0 for ok
263#------------------------------------------------------------------------------
264
265
266sys.exit(main())
267#------------------------------------------------------------------------------
268
runCmd(cmd, data=None)
Definition runTest.py:17
testCompare(tmpFileName, stdout, expectFile, f, args, time)
Definition runTest.py:42
testCompile(tmpFileName, f, args, fileName, cppStd)
Definition runTest.py:61
cleanStderr(stderr, fileName=None)
Definition runTest.py:28