blob: fb3d172fb6609776dac86378bf5aea44669f773a [file] [log] [blame]
Eli Friedman77a1fe92009-07-10 20:15:12 +00001#!/usr/bin/env python
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00002#
3# TestRunner.py - This script is used to run arbitrary unit tests. Unit
4# tests must contain the command used to run them in the input file, starting
5# immediately after a "RUN:" string.
6#
7# This runner recognizes and replaces the following strings in the command:
8#
9# %s - Replaced with the input name of the program, or the program to
10# execute, as appropriate.
Daniel Dunbar4d8076a2009-04-10 19:49:21 +000011# %S - Replaced with the directory where the input resides.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000012# %llvmgcc - llvm-gcc command
13# %llvmgxx - llvm-g++ command
14# %prcontext - prcontext.tcl script
15# %t - temporary file name (derived from testcase name)
16#
17
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000018import errno
Daniel Dunbar69e07a72009-06-17 21:33:37 +000019import os
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000020import platform
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000021import re
Daniel Dunbar69e07a72009-06-17 21:33:37 +000022import signal
23import subprocess
24import sys
25
Daniel Dunbar1db467f2009-07-31 05:54:17 +000026import Util
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000027
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000028kSystemName = platform.system()
29
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000030class TestStatus:
31 Pass = 0
32 XFail = 1
33 Fail = 2
34 XPass = 3
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000035 Invalid = 4
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000036
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000037 kNames = ['Pass','XFail','Fail','XPass','Invalid']
Daniel Dunbarfbbb1e72009-07-02 23:58:07 +000038 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000039 def getName(code):
40 return TestStatus.kNames[code]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000041
Daniel Dunbar1db467f2009-07-31 05:54:17 +000042def executeScript(cfg, script, commands, cwd, useValgrind):
Daniel Dunbar10aebbb2009-07-25 15:26:08 +000043 # Write script file
44 f = open(script,'w')
45 if kSystemName == 'Windows':
46 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
47 else:
48 f.write(' &&\n'.join(commands))
49 f.write('\n')
50 f.close()
51
52 if kSystemName == 'Windows':
53 command = ['cmd','/c', script]
54 else:
55 command = ['/bin/sh', script]
Daniel Dunbar9a676b72009-07-29 02:57:25 +000056 if useValgrind:
57 # FIXME: Running valgrind on sh is overkill. We probably could just
58 # ron on clang with no real loss.
59 command = ['valgrind', '-q',
60 '--tool=memcheck', '--leak-check=no', '--trace-children=yes',
61 '--error-exitcode=123'] + command
Daniel Dunbar10aebbb2009-07-25 15:26:08 +000062
63 p = subprocess.Popen(command, cwd=cwd,
64 stdin=subprocess.PIPE,
65 stdout=subprocess.PIPE,
66 stderr=subprocess.PIPE,
Daniel Dunbar1db467f2009-07-31 05:54:17 +000067 env=cfg.environment)
Daniel Dunbar10aebbb2009-07-25 15:26:08 +000068 out,err = p.communicate()
69 exitCode = p.wait()
70
71 # Detect Ctrl-C in subprocess.
72 if exitCode == -signal.SIGINT:
73 raise KeyboardInterrupt
74
75 return out, err, exitCode
76
Daniel Dunbara957d992009-07-25 14:46:05 +000077import StringIO
Daniel Dunbar1db467f2009-07-31 05:54:17 +000078def runOneTest(cfg, testPath, tmpBase, clang, clangcc, useValgrind):
Daniel Dunbara957d992009-07-25 14:46:05 +000079 # Make paths absolute.
80 tmpBase = os.path.abspath(tmpBase)
81 testPath = os.path.abspath(testPath)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000082
83 # Create the output directory if it does not already exist.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000084
Daniel Dunbar1db467f2009-07-31 05:54:17 +000085 Util.mkdir_p(os.path.dirname(tmpBase))
Daniel Dunbara957d992009-07-25 14:46:05 +000086 script = tmpBase + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000087 if kSystemName == 'Windows':
Daniel Dunbara957d992009-07-25 14:46:05 +000088 script += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000089
Daniel Dunbara957d992009-07-25 14:46:05 +000090 substitutions = [('%s', testPath),
91 ('%S', os.path.dirname(testPath)),
92 ('%t', tmpBase + '.tmp'),
93 (' clang ', ' ' + clang + ' '),
94 (' clang-cc ', ' ' + clangcc + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +000095
96 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000097 scriptLines = []
98 xfailLines = []
Daniel Dunbara957d992009-07-25 14:46:05 +000099 for ln in open(testPath):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000100 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000101 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000102 index = ln.index('RUN:')
103 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000104
Daniel Dunbar322f7892009-07-25 12:23:35 +0000105 # Strip trailing newline.
106 scriptLines.append(ln)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000107 elif 'XFAIL' in ln:
108 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000109
110 # FIXME: Support something like END, in case we need to process large
111 # files.
Daniel Dunbara957d992009-07-25 14:46:05 +0000112
113 # Verify the script contains a run line.
114 if not scriptLines:
115 return (TestStatus.Fail, "Test has no run line!")
Daniel Dunbar322f7892009-07-25 12:23:35 +0000116
117 # Apply substitutions to the script.
118 def processLine(ln):
119 # Apply substitutions
120 for a,b in substitutions:
121 ln = ln.replace(a,b)
122
Daniel Dunbar322f7892009-07-25 12:23:35 +0000123 # Strip the trailing newline and any extra whitespace.
124 return ln.strip()
125 scriptLines = map(processLine, scriptLines)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000126
127 # Validate interior lines for '&&', a lovely historical artifact.
128 for i in range(len(scriptLines) - 1):
129 ln = scriptLines[i]
130
131 if not ln.endswith('&&'):
Daniel Dunbara957d992009-07-25 14:46:05 +0000132 return (TestStatus.Fail,
Daniel Dunbar67796472009-07-27 19:01:13 +0000133 ("MISSING \'&&\': %s\n" +
134 "FOLLOWED BY : %s\n") % (ln, scriptLines[i + 1]))
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000135
136 # Strip off '&&'
137 scriptLines[i] = ln[:-2]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000138
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000139 out, err, exitCode = executeScript(cfg, script, scriptLines,
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000140 os.path.dirname(testPath),
141 useValgrind)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000142 if xfailLines:
Daniel Dunbara957d992009-07-25 14:46:05 +0000143 ok = exitCode != 0
144 status = (TestStatus.XPass, TestStatus.XFail)[ok]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000145 else:
Daniel Dunbara957d992009-07-25 14:46:05 +0000146 ok = exitCode == 0
147 status = (TestStatus.Fail, TestStatus.Pass)[ok]
148
149 if ok:
150 return (status,'')
151
152 output = StringIO.StringIO()
153 print >>output, "Script:"
154 print >>output, "--"
155 print >>output, '\n'.join(scriptLines)
156 print >>output, "--"
157 print >>output, "Exit Code: %r" % exitCode
158 print >>output, "Command Output (stdout):"
159 print >>output, "--"
160 output.write(out)
161 print >>output, "--"
162 print >>output, "Command Output (stderr):"
163 print >>output, "--"
164 output.write(err)
165 print >>output, "--"
166 return (status, output.getvalue())
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000167
168def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000169 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000170 out,_ = p.communicate()
171 return out
172
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000173def inferClang(cfg):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000174 # Determine which clang to use.
175 clang = os.getenv('CLANG')
176
177 # If the user set clang in the environment, definitely use that and don't
178 # try to validate.
179 if clang:
180 return clang
181
182 # Otherwise look in the path.
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000183 clang = Util.which('clang', cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000184
185 if not clang:
186 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
187 sys.exit(1)
188
189 return clang
190
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000191def inferClangCC(cfg, clang):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000192 clangcc = os.getenv('CLANGCC')
193
194 # If the user set clang in the environment, definitely use that and don't
195 # try to validate.
196 if clangcc:
197 return clangcc
198
199 # Otherwise try adding -cc since we expect to be looking in a build
200 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000201 if clang.endswith('.exe'):
202 clangccName = clang[:-4] + '-cc.exe'
203 else:
204 clangccName = clang + '-cc'
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000205 clangcc = Util.which(clangccName, cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000206 if not clangcc:
207 # Otherwise ask clang.
208 res = capture([clang, '-print-prog-name=clang-cc'])
209 res = res.strip()
210 if res and os.path.exists(res):
211 clangcc = res
212
213 if not clangcc:
214 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
215 sys.exit(1)
216
217 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000218
Daniel Dunbardf084892009-07-25 09:53:43 +0000219def getTestOutputBase(dir, testpath):
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000220 """getTestOutputBase(dir, testpath) - Get the full path for temporary files
Daniel Dunbardf084892009-07-25 09:53:43 +0000221 corresponding to the given test path."""
222
223 # Form the output base out of the test parent directory name and the test
224 # name. FIXME: Find a better way to organize test results.
225 return os.path.join(dir,
226 os.path.basename(os.path.dirname(testpath)),
227 os.path.basename(testpath))