blob: 42799093d195929cf24ef7ae854c7a1701e8e9a5 [file] [log] [blame]
Daniel Dunbar69e07a72009-06-17 21:33:37 +00001import os
Daniel Dunbar8fe83f12009-07-25 09:42:24 +00002import platform
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00003import re
Daniel Dunbar69e07a72009-06-17 21:33:37 +00004import signal
5import subprocess
6import sys
7
Daniel Dunbar0dec8382009-08-01 10:18:01 +00008import ShUtil
Daniel Dunbar1db467f2009-07-31 05:54:17 +00009import Util
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000010
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000011kSystemName = platform.system()
12
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000013class TestStatus:
14 Pass = 0
15 XFail = 1
16 Fail = 2
17 XPass = 3
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000018 Invalid = 4
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000019
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000020 kNames = ['Pass','XFail','Fail','XPass','Invalid']
Daniel Dunbarfbbb1e72009-07-02 23:58:07 +000021 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000022 def getName(code):
23 return TestStatus.kNames[code]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000024
Daniel Dunbar0dec8382009-08-01 10:18:01 +000025def executeShCmd(cmd, cfg, cwd, results):
26 if isinstance(cmd, ShUtil.Seq):
27 if cmd.op == ';':
28 res = executeShCmd(cmd.lhs, cfg, cwd, results)
Daniel Dunbar0dec8382009-08-01 10:18:01 +000029 return executeShCmd(cmd.rhs, cfg, cwd, results)
30
31 if cmd.op == '&':
Daniel Dunbar5242e822009-08-01 23:18:27 +000032 raise NotImplementedError,"unsupported test command: '&'"
Daniel Dunbar0dec8382009-08-01 10:18:01 +000033
34 if cmd.op == '||':
35 res = executeShCmd(cmd.lhs, cfg, cwd, results)
Daniel Dunbar0dec8382009-08-01 10:18:01 +000036 if res != 0:
37 res = executeShCmd(cmd.rhs, cfg, cwd, results)
38 return res
39 if cmd.op == '&&':
40 res = executeShCmd(cmd.lhs, cfg, cwd, results)
41 if res is None:
42 return res
43
44 if res == 0:
45 res = executeShCmd(cmd.rhs, cfg, cwd, results)
46 return res
47
48 raise ValueError,'Unknown shell command: %r' % cmd.op
49
50 assert isinstance(cmd, ShUtil.Pipeline)
51 procs = []
52 input = subprocess.PIPE
53 for j in cmd.commands:
54 # FIXME: This is broken, it doesn't account for the accumulative nature
55 # of redirects.
56 stdin = input
57 stdout = stderr = subprocess.PIPE
58 for r in j.redirects:
59 if r[0] == ('>',2):
60 stderr = open(r[1], 'w')
61 elif r[0] == ('>&',2) and r[1] == '1':
62 stderr = subprocess.STDOUT
63 elif r[0] == ('>',):
64 stdout = open(r[1], 'w')
65 elif r[0] == ('<',):
66 stdin = open(r[1], 'r')
67 else:
Daniel Dunbar5242e822009-08-01 23:18:27 +000068 raise NotImplementedError,"Unsupported redirect: %r" % r
Daniel Dunbar0dec8382009-08-01 10:18:01 +000069
70 procs.append(subprocess.Popen(j.args, cwd=cwd,
71 stdin = stdin,
72 stdout = stdout,
73 stderr = stderr,
74 env = cfg.environment))
75
76 # Immediately close stdin for any process taking stdin from us.
77 if stdin == subprocess.PIPE:
78 procs[-1].stdin.close()
79 procs[-1].stdin = None
80
81 if stdout == subprocess.PIPE:
82 input = procs[-1].stdout
83 else:
84 input = subprocess.PIPE
85
86 # FIXME: There is a potential for deadlock here, when we have a pipe and
87 # some process other than the last one ends up blocked on stderr.
88 procData = [None] * len(procs)
89 procData[-1] = procs[-1].communicate()
90 for i in range(len(procs) - 1):
91 if procs[i].stdout is not None:
92 out = procs[i].stdout.read()
93 else:
94 out = ''
95 if procs[i].stderr is not None:
96 err = procs[i].stderr.read()
97 else:
98 err = ''
99 procData[i] = (out,err)
100
101 # FIXME: Fix tests to work with pipefail, and make exitCode max across
102 # procs.
103 for i,(out,err) in enumerate(procData):
104 exitCode = res = procs[i].wait()
105 results.append((cmd.commands[i], out, err, res))
106
107 if cmd.negate:
108 exitCode = not exitCode
109
110 return exitCode
111
112def executeScriptInternal(cfg, commands, cwd):
113 cmd = ShUtil.ShParser(' &&\n'.join(commands)).parse()
114
115 results = []
Daniel Dunbar5242e822009-08-01 23:18:27 +0000116 try:
117 exitCode = executeShCmd(cmd, cfg, cwd, results)
118 except:
119 import traceback
120
121 out = ''
122 err = 'Exception during script execution:\n%s\n' % traceback.format_exc()
123 return out, err, 127
Daniel Dunbar0dec8382009-08-01 10:18:01 +0000124
125 out = err = ''
126 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
127 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
128 out += 'Command %d Result: %r\n' % (i, res)
129 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
130 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
131
132 return out, err, exitCode
133
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000134def executeScript(cfg, script, commands, cwd):
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000135 # Write script file
136 f = open(script,'w')
137 if kSystemName == 'Windows':
138 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
139 else:
140 f.write(' &&\n'.join(commands))
141 f.write('\n')
142 f.close()
143
144 if kSystemName == 'Windows':
145 command = ['cmd','/c', script]
146 else:
147 command = ['/bin/sh', script]
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000148 if cfg.useValgrind:
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000149 # FIXME: Running valgrind on sh is overkill. We probably could just
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000150 # run on clang with no real loss.
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000151 command = ['valgrind', '-q',
152 '--tool=memcheck', '--leak-check=no', '--trace-children=yes',
153 '--error-exitcode=123'] + command
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000154
155 p = subprocess.Popen(command, cwd=cwd,
156 stdin=subprocess.PIPE,
157 stdout=subprocess.PIPE,
158 stderr=subprocess.PIPE,
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000159 env=cfg.environment)
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000160 out,err = p.communicate()
161 exitCode = p.wait()
162
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000163 return out, err, exitCode
164
Daniel Dunbara957d992009-07-25 14:46:05 +0000165import StringIO
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000166def runOneTest(cfg, testPath, tmpBase):
Daniel Dunbara957d992009-07-25 14:46:05 +0000167 # Make paths absolute.
168 tmpBase = os.path.abspath(tmpBase)
169 testPath = os.path.abspath(testPath)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000170
171 # Create the output directory if it does not already exist.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000172
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000173 Util.mkdir_p(os.path.dirname(tmpBase))
Daniel Dunbara957d992009-07-25 14:46:05 +0000174 script = tmpBase + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000175 if kSystemName == 'Windows':
Daniel Dunbara957d992009-07-25 14:46:05 +0000176 script += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000177
Daniel Dunbara957d992009-07-25 14:46:05 +0000178 substitutions = [('%s', testPath),
179 ('%S', os.path.dirname(testPath)),
180 ('%t', tmpBase + '.tmp'),
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000181 (' clang ', ' ' + cfg.clang + ' '),
182 (' clang-cc ', ' ' + cfg.clangcc + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000183
184 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000185 scriptLines = []
186 xfailLines = []
Daniel Dunbara957d992009-07-25 14:46:05 +0000187 for ln in open(testPath):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000188 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000189 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000190 index = ln.index('RUN:')
191 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000192
Daniel Dunbar322f7892009-07-25 12:23:35 +0000193 # Strip trailing newline.
194 scriptLines.append(ln)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000195 elif 'XFAIL' in ln:
196 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000197
198 # FIXME: Support something like END, in case we need to process large
199 # files.
Daniel Dunbara957d992009-07-25 14:46:05 +0000200
201 # Verify the script contains a run line.
202 if not scriptLines:
203 return (TestStatus.Fail, "Test has no run line!")
Daniel Dunbar322f7892009-07-25 12:23:35 +0000204
205 # Apply substitutions to the script.
206 def processLine(ln):
207 # Apply substitutions
208 for a,b in substitutions:
209 ln = ln.replace(a,b)
210
Daniel Dunbar322f7892009-07-25 12:23:35 +0000211 # Strip the trailing newline and any extra whitespace.
212 return ln.strip()
213 scriptLines = map(processLine, scriptLines)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000214
215 # Validate interior lines for '&&', a lovely historical artifact.
216 for i in range(len(scriptLines) - 1):
217 ln = scriptLines[i]
218
219 if not ln.endswith('&&'):
Daniel Dunbara957d992009-07-25 14:46:05 +0000220 return (TestStatus.Fail,
Daniel Dunbar67796472009-07-27 19:01:13 +0000221 ("MISSING \'&&\': %s\n" +
222 "FOLLOWED BY : %s\n") % (ln, scriptLines[i + 1]))
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000223
224 # Strip off '&&'
225 scriptLines[i] = ln[:-2]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000226
Daniel Dunbar0dec8382009-08-01 10:18:01 +0000227 if not cfg.useExternalShell:
228 res = executeScriptInternal(cfg, scriptLines, os.path.dirname(testPath))
229
230 if res is not None:
231 out, err, exitCode = res
232 elif True:
233 return (TestStatus.Fail,
234 "Unable to execute internally:\n%s\n"
235 % '\n'.join(scriptLines))
236 else:
237 out, err, exitCode = executeScript(cfg, script, scriptLines,
238 os.path.dirname(testPath))
239 else:
240 out, err, exitCode = executeScript(cfg, script, scriptLines,
241 os.path.dirname(testPath))
242
243 # Detect Ctrl-C in subprocess.
244 if exitCode == -signal.SIGINT:
245 raise KeyboardInterrupt
246
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000247 if xfailLines:
Daniel Dunbara957d992009-07-25 14:46:05 +0000248 ok = exitCode != 0
249 status = (TestStatus.XPass, TestStatus.XFail)[ok]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000250 else:
Daniel Dunbara957d992009-07-25 14:46:05 +0000251 ok = exitCode == 0
252 status = (TestStatus.Fail, TestStatus.Pass)[ok]
253
254 if ok:
255 return (status,'')
256
257 output = StringIO.StringIO()
258 print >>output, "Script:"
259 print >>output, "--"
260 print >>output, '\n'.join(scriptLines)
261 print >>output, "--"
262 print >>output, "Exit Code: %r" % exitCode
263 print >>output, "Command Output (stdout):"
264 print >>output, "--"
265 output.write(out)
266 print >>output, "--"
267 print >>output, "Command Output (stderr):"
268 print >>output, "--"
269 output.write(err)
270 print >>output, "--"
271 return (status, output.getvalue())
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000272
273def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000274 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000275 out,_ = p.communicate()
276 return out
277
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000278def inferClang(cfg):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000279 # Determine which clang to use.
280 clang = os.getenv('CLANG')
281
282 # If the user set clang in the environment, definitely use that and don't
283 # try to validate.
284 if clang:
285 return clang
286
287 # Otherwise look in the path.
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000288 clang = Util.which('clang', cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000289
290 if not clang:
291 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
292 sys.exit(1)
293
294 return clang
295
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000296def inferClangCC(cfg, clang):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000297 clangcc = os.getenv('CLANGCC')
298
299 # If the user set clang in the environment, definitely use that and don't
300 # try to validate.
301 if clangcc:
302 return clangcc
303
304 # Otherwise try adding -cc since we expect to be looking in a build
305 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000306 if clang.endswith('.exe'):
307 clangccName = clang[:-4] + '-cc.exe'
308 else:
309 clangccName = clang + '-cc'
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000310 clangcc = Util.which(clangccName, cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000311 if not clangcc:
312 # Otherwise ask clang.
313 res = capture([clang, '-print-prog-name=clang-cc'])
314 res = res.strip()
315 if res and os.path.exists(res):
316 clangcc = res
317
318 if not clangcc:
319 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
320 sys.exit(1)
321
322 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000323
Daniel Dunbardf084892009-07-25 09:53:43 +0000324def getTestOutputBase(dir, testpath):
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000325 """getTestOutputBase(dir, testpath) - Get the full path for temporary files
Daniel Dunbardf084892009-07-25 09:53:43 +0000326 corresponding to the given test path."""
327
328 # Form the output base out of the test parent directory name and the test
329 # name. FIXME: Find a better way to organize test results.
330 return os.path.join(dir,
331 os.path.basename(os.path.dirname(testpath)),
332 os.path.basename(testpath))