blob: e605ef60138de3b537e73e97afdf62b56dfa2dad [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)
29 if res is None:
30 return res
31
32 return executeShCmd(cmd.rhs, cfg, cwd, results)
33
34 if cmd.op == '&':
35 Util.warning("unsupported test command: '&'")
36 return None
37
38 if cmd.op == '||':
39 res = executeShCmd(cmd.lhs, cfg, cwd, results)
40 if res is None:
41 return res
42
43 if res != 0:
44 res = executeShCmd(cmd.rhs, cfg, cwd, results)
45 return res
46 if cmd.op == '&&':
47 res = executeShCmd(cmd.lhs, cfg, cwd, results)
48 if res is None:
49 return res
50
51 if res == 0:
52 res = executeShCmd(cmd.rhs, cfg, cwd, results)
53 return res
54
55 raise ValueError,'Unknown shell command: %r' % cmd.op
56
57 assert isinstance(cmd, ShUtil.Pipeline)
58 procs = []
59 input = subprocess.PIPE
60 for j in cmd.commands:
61 # FIXME: This is broken, it doesn't account for the accumulative nature
62 # of redirects.
63 stdin = input
64 stdout = stderr = subprocess.PIPE
65 for r in j.redirects:
66 if r[0] == ('>',2):
67 stderr = open(r[1], 'w')
68 elif r[0] == ('>&',2) and r[1] == '1':
69 stderr = subprocess.STDOUT
70 elif r[0] == ('>',):
71 stdout = open(r[1], 'w')
72 elif r[0] == ('<',):
73 stdin = open(r[1], 'r')
74 else:
75 return None
76
77 procs.append(subprocess.Popen(j.args, cwd=cwd,
78 stdin = stdin,
79 stdout = stdout,
80 stderr = stderr,
81 env = cfg.environment))
82
83 # Immediately close stdin for any process taking stdin from us.
84 if stdin == subprocess.PIPE:
85 procs[-1].stdin.close()
86 procs[-1].stdin = None
87
88 if stdout == subprocess.PIPE:
89 input = procs[-1].stdout
90 else:
91 input = subprocess.PIPE
92
93 # FIXME: There is a potential for deadlock here, when we have a pipe and
94 # some process other than the last one ends up blocked on stderr.
95 procData = [None] * len(procs)
96 procData[-1] = procs[-1].communicate()
97 for i in range(len(procs) - 1):
98 if procs[i].stdout is not None:
99 out = procs[i].stdout.read()
100 else:
101 out = ''
102 if procs[i].stderr is not None:
103 err = procs[i].stderr.read()
104 else:
105 err = ''
106 procData[i] = (out,err)
107
108 # FIXME: Fix tests to work with pipefail, and make exitCode max across
109 # procs.
110 for i,(out,err) in enumerate(procData):
111 exitCode = res = procs[i].wait()
112 results.append((cmd.commands[i], out, err, res))
113
114 if cmd.negate:
115 exitCode = not exitCode
116
117 return exitCode
118
119def executeScriptInternal(cfg, commands, cwd):
120 cmd = ShUtil.ShParser(' &&\n'.join(commands)).parse()
121
122 results = []
123 exitCode = executeShCmd(cmd, cfg, cwd, results)
124 if exitCode is None:
125 return None
126
127 out = err = ''
128 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
129 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
130 out += 'Command %d Result: %r\n' % (i, res)
131 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
132 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
133
134 return out, err, exitCode
135
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000136def executeScript(cfg, script, commands, cwd):
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000137 # Write script file
138 f = open(script,'w')
139 if kSystemName == 'Windows':
140 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
141 else:
142 f.write(' &&\n'.join(commands))
143 f.write('\n')
144 f.close()
145
146 if kSystemName == 'Windows':
147 command = ['cmd','/c', script]
148 else:
149 command = ['/bin/sh', script]
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000150 if cfg.useValgrind:
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000151 # FIXME: Running valgrind on sh is overkill. We probably could just
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000152 # run on clang with no real loss.
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000153 command = ['valgrind', '-q',
154 '--tool=memcheck', '--leak-check=no', '--trace-children=yes',
155 '--error-exitcode=123'] + command
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000156
157 p = subprocess.Popen(command, cwd=cwd,
158 stdin=subprocess.PIPE,
159 stdout=subprocess.PIPE,
160 stderr=subprocess.PIPE,
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000161 env=cfg.environment)
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000162 out,err = p.communicate()
163 exitCode = p.wait()
164
Daniel Dunbar10aebbb2009-07-25 15:26:08 +0000165 return out, err, exitCode
166
Daniel Dunbara957d992009-07-25 14:46:05 +0000167import StringIO
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000168def runOneTest(cfg, testPath, tmpBase):
Daniel Dunbara957d992009-07-25 14:46:05 +0000169 # Make paths absolute.
170 tmpBase = os.path.abspath(tmpBase)
171 testPath = os.path.abspath(testPath)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000172
173 # Create the output directory if it does not already exist.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000174
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000175 Util.mkdir_p(os.path.dirname(tmpBase))
Daniel Dunbara957d992009-07-25 14:46:05 +0000176 script = tmpBase + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000177 if kSystemName == 'Windows':
Daniel Dunbara957d992009-07-25 14:46:05 +0000178 script += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000179
Daniel Dunbara957d992009-07-25 14:46:05 +0000180 substitutions = [('%s', testPath),
181 ('%S', os.path.dirname(testPath)),
182 ('%t', tmpBase + '.tmp'),
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000183 (' clang ', ' ' + cfg.clang + ' '),
184 (' clang-cc ', ' ' + cfg.clangcc + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000185
186 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000187 scriptLines = []
188 xfailLines = []
Daniel Dunbara957d992009-07-25 14:46:05 +0000189 for ln in open(testPath):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000190 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000191 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000192 index = ln.index('RUN:')
193 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000194
Daniel Dunbar322f7892009-07-25 12:23:35 +0000195 # Strip trailing newline.
196 scriptLines.append(ln)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000197 elif 'XFAIL' in ln:
198 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000199
200 # FIXME: Support something like END, in case we need to process large
201 # files.
Daniel Dunbara957d992009-07-25 14:46:05 +0000202
203 # Verify the script contains a run line.
204 if not scriptLines:
205 return (TestStatus.Fail, "Test has no run line!")
Daniel Dunbar322f7892009-07-25 12:23:35 +0000206
207 # Apply substitutions to the script.
208 def processLine(ln):
209 # Apply substitutions
210 for a,b in substitutions:
211 ln = ln.replace(a,b)
212
Daniel Dunbar322f7892009-07-25 12:23:35 +0000213 # Strip the trailing newline and any extra whitespace.
214 return ln.strip()
215 scriptLines = map(processLine, scriptLines)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000216
217 # Validate interior lines for '&&', a lovely historical artifact.
218 for i in range(len(scriptLines) - 1):
219 ln = scriptLines[i]
220
221 if not ln.endswith('&&'):
Daniel Dunbara957d992009-07-25 14:46:05 +0000222 return (TestStatus.Fail,
Daniel Dunbar67796472009-07-27 19:01:13 +0000223 ("MISSING \'&&\': %s\n" +
224 "FOLLOWED BY : %s\n") % (ln, scriptLines[i + 1]))
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000225
226 # Strip off '&&'
227 scriptLines[i] = ln[:-2]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000228
Daniel Dunbar0dec8382009-08-01 10:18:01 +0000229 if not cfg.useExternalShell:
230 res = executeScriptInternal(cfg, scriptLines, os.path.dirname(testPath))
231
232 if res is not None:
233 out, err, exitCode = res
234 elif True:
235 return (TestStatus.Fail,
236 "Unable to execute internally:\n%s\n"
237 % '\n'.join(scriptLines))
238 else:
239 out, err, exitCode = executeScript(cfg, script, scriptLines,
240 os.path.dirname(testPath))
241 else:
242 out, err, exitCode = executeScript(cfg, script, scriptLines,
243 os.path.dirname(testPath))
244
245 # Detect Ctrl-C in subprocess.
246 if exitCode == -signal.SIGINT:
247 raise KeyboardInterrupt
248
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000249 if xfailLines:
Daniel Dunbara957d992009-07-25 14:46:05 +0000250 ok = exitCode != 0
251 status = (TestStatus.XPass, TestStatus.XFail)[ok]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000252 else:
Daniel Dunbara957d992009-07-25 14:46:05 +0000253 ok = exitCode == 0
254 status = (TestStatus.Fail, TestStatus.Pass)[ok]
255
256 if ok:
257 return (status,'')
258
259 output = StringIO.StringIO()
260 print >>output, "Script:"
261 print >>output, "--"
262 print >>output, '\n'.join(scriptLines)
263 print >>output, "--"
264 print >>output, "Exit Code: %r" % exitCode
265 print >>output, "Command Output (stdout):"
266 print >>output, "--"
267 output.write(out)
268 print >>output, "--"
269 print >>output, "Command Output (stderr):"
270 print >>output, "--"
271 output.write(err)
272 print >>output, "--"
273 return (status, output.getvalue())
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000274
275def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000276 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000277 out,_ = p.communicate()
278 return out
279
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000280def inferClang(cfg):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000281 # Determine which clang to use.
282 clang = os.getenv('CLANG')
283
284 # If the user set clang in the environment, definitely use that and don't
285 # try to validate.
286 if clang:
287 return clang
288
289 # Otherwise look in the path.
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000290 clang = Util.which('clang', cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000291
292 if not clang:
293 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
294 sys.exit(1)
295
296 return clang
297
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000298def inferClangCC(cfg, clang):
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000299 clangcc = os.getenv('CLANGCC')
300
301 # If the user set clang in the environment, definitely use that and don't
302 # try to validate.
303 if clangcc:
304 return clangcc
305
306 # Otherwise try adding -cc since we expect to be looking in a build
307 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000308 if clang.endswith('.exe'):
309 clangccName = clang[:-4] + '-cc.exe'
310 else:
311 clangccName = clang + '-cc'
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000312 clangcc = Util.which(clangccName, cfg.environment['PATH'])
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000313 if not clangcc:
314 # Otherwise ask clang.
315 res = capture([clang, '-print-prog-name=clang-cc'])
316 res = res.strip()
317 if res and os.path.exists(res):
318 clangcc = res
319
320 if not clangcc:
321 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
322 sys.exit(1)
323
324 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000325
Daniel Dunbardf084892009-07-25 09:53:43 +0000326def getTestOutputBase(dir, testpath):
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000327 """getTestOutputBase(dir, testpath) - Get the full path for temporary files
Daniel Dunbardf084892009-07-25 09:53:43 +0000328 corresponding to the given test path."""
329
330 # Form the output base out of the test parent directory name and the test
331 # name. FIXME: Find a better way to organize test results.
332 return os.path.join(dir,
333 os.path.basename(os.path.dirname(testpath)),
334 os.path.basename(testpath))