blob: 5c1e8ea5be1a915578481c06f1f767428f324006 [file] [log] [blame]
Daniel Dunbarbe7ada72009-09-08 05:31:18 +00001import os, signal, subprocess, sys
2import StringIO
3
4import ShUtil
5import Test
6import Util
7
8def executeCommand(command, cwd=None, env=None):
9 p = subprocess.Popen(command, cwd=cwd,
10 stdin=subprocess.PIPE,
11 stdout=subprocess.PIPE,
12 stderr=subprocess.PIPE,
13 env=env)
14 out,err = p.communicate()
15 exitCode = p.wait()
16
17 # Detect Ctrl-C in subprocess.
18 if exitCode == -signal.SIGINT:
19 raise KeyboardInterrupt
20
21 return out, err, exitCode
22
23def executeShCmd(cmd, cfg, cwd, results):
24 if isinstance(cmd, ShUtil.Seq):
25 if cmd.op == ';':
26 res = executeShCmd(cmd.lhs, cfg, cwd, results)
27 return executeShCmd(cmd.rhs, cfg, cwd, results)
28
29 if cmd.op == '&':
30 raise NotImplementedError,"unsupported test command: '&'"
31
32 if cmd.op == '||':
33 res = executeShCmd(cmd.lhs, cfg, cwd, results)
34 if res != 0:
35 res = executeShCmd(cmd.rhs, cfg, cwd, results)
36 return res
37 if cmd.op == '&&':
38 res = executeShCmd(cmd.lhs, cfg, cwd, results)
39 if res is None:
40 return res
41
42 if res == 0:
43 res = executeShCmd(cmd.rhs, cfg, cwd, results)
44 return res
45
46 raise ValueError,'Unknown shell command: %r' % cmd.op
47
48 assert isinstance(cmd, ShUtil.Pipeline)
49 procs = []
50 input = subprocess.PIPE
51 for j in cmd.commands:
52 redirects = [(0,), (1,), (2,)]
53 for r in j.redirects:
54 if r[0] == ('>',2):
55 redirects[2] = [r[1], 'w', None]
56 elif r[0] == ('>&',2) and r[1] in '012':
57 redirects[2] = redirects[int(r[1])]
58 elif r[0] == ('>&',) or r[0] == ('&>',):
59 redirects[1] = redirects[2] = [r[1], 'w', None]
60 elif r[0] == ('>',):
61 redirects[1] = [r[1], 'w', None]
62 elif r[0] == ('<',):
63 redirects[0] = [r[1], 'r', None]
64 else:
65 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
66
67 final_redirects = []
68 for index,r in enumerate(redirects):
69 if r == (0,):
70 result = input
71 elif r == (1,):
72 if index == 0:
73 raise NotImplementedError,"Unsupported redirect for stdin"
74 elif index == 1:
75 result = subprocess.PIPE
76 else:
77 result = subprocess.STDOUT
78 elif r == (2,):
79 if index != 2:
80 raise NotImplementedError,"Unsupported redirect on stdout"
81 result = subprocess.PIPE
82 else:
83 if r[2] is None:
84 r[2] = open(r[0], r[1])
85 result = r[2]
86 final_redirects.append(result)
87
88 stdin, stdout, stderr = final_redirects
89
90 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
91 # stderr on a pipe and treat it as stdout.
92 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
93 stderr = subprocess.PIPE
94 stderrIsStdout = True
95 else:
96 stderrIsStdout = False
97 procs.append(subprocess.Popen(j.args, cwd=cwd,
98 stdin = stdin,
99 stdout = stdout,
100 stderr = stderr,
101 env = cfg.environment,
102 close_fds = True))
103
104 # Immediately close stdin for any process taking stdin from us.
105 if stdin == subprocess.PIPE:
106 procs[-1].stdin.close()
107 procs[-1].stdin = None
108
109 # Update the current stdin source.
110 if stdout == subprocess.PIPE:
111 input = procs[-1].stdout
112 elif stderrIsStdout:
113 input = procs[-1].stderr
114 else:
115 input = subprocess.PIPE
116
117 # FIXME: There is a potential for deadlock here, when we have a pipe and
118 # some process other than the last one ends up blocked on stderr.
119 procData = [None] * len(procs)
120 procData[-1] = procs[-1].communicate()
121 for i in range(len(procs) - 1):
122 if procs[i].stdout is not None:
123 out = procs[i].stdout.read()
124 else:
125 out = ''
126 if procs[i].stderr is not None:
127 err = procs[i].stderr.read()
128 else:
129 err = ''
130 procData[i] = (out,err)
131
132 exitCode = None
133 for i,(out,err) in enumerate(procData):
134 res = procs[i].wait()
135 # Detect Ctrl-C in subprocess.
136 if res == -signal.SIGINT:
137 raise KeyboardInterrupt
138
139 results.append((cmd.commands[i], out, err, res))
140 if cmd.pipe_err:
141 # Python treats the exit code as a signed char.
142 if res < 0:
143 exitCode = min(exitCode, res)
144 else:
145 exitCode = max(exitCode, res)
146 else:
147 exitCode = res
148
149 if cmd.negate:
150 exitCode = not exitCode
151
152 return exitCode
153
154def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
155 ln = ' &&\n'.join(commands)
156 try:
157 cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
158 except:
159 return (Test.FAIL, "shell parser error on: %r" % ln)
160
161 results = []
162 exitCode = executeShCmd(cmd, test.config, cwd, results)
163
164 out = err = ''
165 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
166 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
167 out += 'Command %d Result: %r\n' % (i, res)
168 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
169 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
170
171 return out, err, exitCode
172
173def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
174 import TclUtil
175 cmds = []
176 for ln in commands:
177 # Given the unfortunate way LLVM's test are written, the line gets
178 # backslash substitution done twice.
179 ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
180
181 try:
182 tokens = list(TclUtil.TclLexer(ln).lex())
183 except:
184 return (Test.FAIL, "Tcl lexer error on: %r" % ln)
185
186 # Validate there are no control tokens.
187 for t in tokens:
188 if not isinstance(t, str):
189 return (Test.FAIL,
190 "Invalid test line: %r containing %r" % (ln, t))
191
192 try:
193 cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
194 except:
Daniel Dunbar2c0a49c2009-09-08 05:37:51 +0000195 return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000196
197 cmd = cmds[0]
198 for c in cmds[1:]:
199 cmd = ShUtil.Seq(cmd, '&&', c)
200
201 if litConfig.useTclAsSh:
202 script = tmpBase + '.script'
203
204 # Write script file
205 f = open(script,'w')
206 print >>f, 'set -o pipefail'
207 cmd.toShell(f, pipefail = True)
208 f.close()
209
210 if 0:
211 print >>sys.stdout, cmd
212 print >>sys.stdout, open(script).read()
213 print >>sys.stdout
214 return '', '', 0
215
216 command = ['/bin/sh', script]
217 out,err,exitCode = executeCommand(command, cwd=cwd,
218 env=test.config.environment)
219
220 # Tcl commands fail on standard error output.
221 if err:
222 exitCode = 1
223 out = 'Command has output on stderr!\n\n' + out
224
225 return out,err,exitCode
226 else:
227 results = []
228 exitCode = executeShCmd(cmd, test.config, cwd, results)
229
230 out = err = ''
231
232 # Tcl commands fail on standard error output.
233 if [True for _,_,err,res in results if err]:
234 exitCode = 1
235 out += 'Command has output on stderr!\n\n'
236
237 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
238 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
239 out += 'Command %d Result: %r\n' % (i, res)
240 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
241 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
242
243 return out, err, exitCode
244
245def executeScript(test, litConfig, tmpBase, commands, cwd):
246 script = tmpBase + '.script'
247 if litConfig.isWindows:
248 script += '.bat'
249
250 # Write script file
251 f = open(script,'w')
252 if litConfig.isWindows:
253 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
254 else:
255 f.write(' &&\n'.join(commands))
256 f.write('\n')
257 f.close()
258
259 if litConfig.isWindows:
260 command = ['cmd','/c', script]
261 else:
262 command = ['/bin/sh', script]
263 if litConfig.useValgrind:
264 # FIXME: Running valgrind on sh is overkill. We probably could just
265 # run on clang with no real loss.
266 valgrindArgs = ['valgrind', '-q',
267 '--tool=memcheck', '--trace-children=yes',
268 '--error-exitcode=123']
269 valgrindArgs.extend(litConfig.valgrindArgs)
270
271 command = valgrindArgs + command
272
273 return executeCommand(command, cwd=cwd, env=test.config.environment)
274
275def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
276 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
277 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
278 information. The RUN lines also will have variable substitution performed.
279 """
280
281 # Get the temporary location, this is always relative to the test suite
282 # root, not test source root.
283 #
284 # FIXME: This should not be here?
285 sourcepath = test.getSourcePath()
286 execpath = test.getExecPath()
287 execdir,execbase = os.path.split(execpath)
288 tmpBase = os.path.join(execdir, 'Output', execbase)
289
290 # We use #_MARKER_# to hide %% while we do the other substitutions.
291 substitutions = [('%%', '#_MARKER_#')]
292 substitutions.extend(test.config.substitutions)
293 substitutions.extend([('%s', sourcepath),
294 ('%S', os.path.dirname(sourcepath)),
295 ('%p', os.path.dirname(sourcepath)),
296 ('%t', tmpBase + '.tmp'),
297 ('#_MARKER_#', '%')])
298
299 # Collect the test lines from the script.
300 script = []
301 xfails = []
302 xtargets = []
303 for ln in open(sourcepath):
304 if 'RUN:' in ln:
305 # Isolate the command to run.
306 index = ln.index('RUN:')
307 ln = ln[index+4:]
308
309 # Trim trailing whitespace.
310 ln = ln.rstrip()
311
312 # Collapse lines with trailing '\\'.
313 if script and script[-1][-1] == '\\':
314 script[-1] = script[-1][:-1] + ln
315 else:
316 script.append(ln)
317 elif xfailHasColon and 'XFAIL:' in ln:
318 items = ln[ln.index('XFAIL:') + 6:].split(',')
319 xfails.extend([s.strip() for s in items])
320 elif not xfailHasColon and 'XFAIL' in ln:
321 items = ln[ln.index('XFAIL') + 5:].split(',')
322 xfails.extend([s.strip() for s in items])
323 elif 'XTARGET:' in ln:
324 items = ln[ln.index('XTARGET:') + 8:].split(',')
325 xtargets.extend([s.strip() for s in items])
326 elif 'END.' in ln:
327 # Check for END. lines.
328 if ln[ln.index('END.'):].strip() == 'END.':
329 break
330
331 # Apply substitutions to the script.
332 def processLine(ln):
333 # Apply substitutions
334 for a,b in substitutions:
335 ln = ln.replace(a,b)
336
337 # Strip the trailing newline and any extra whitespace.
338 return ln.strip()
339 script = map(processLine, script)
340
341 # Verify the script contains a run line.
342 if not script:
343 return (Test.UNRESOLVED, "Test has no run line!")
344
345 if script[-1][-1] == '\\':
346 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
347
348 # Validate interior lines for '&&', a lovely historical artifact.
349 if requireAndAnd:
350 for i in range(len(script) - 1):
351 ln = script[i]
352
353 if not ln.endswith('&&'):
354 return (Test.FAIL,
355 ("MISSING \'&&\': %s\n" +
356 "FOLLOWED BY : %s\n") % (ln, script[i + 1]))
357
358 # Strip off '&&'
359 script[i] = ln[:-2]
360
361 return script,xfails,xtargets,tmpBase,execdir
362
363def formatTestOutput(status, out, err, exitCode, script):
364 output = StringIO.StringIO()
365 print >>output, "Script:"
366 print >>output, "--"
367 print >>output, '\n'.join(script)
368 print >>output, "--"
369 print >>output, "Exit Code: %r" % exitCode
370 print >>output, "Command Output (stdout):"
371 print >>output, "--"
372 output.write(out)
373 print >>output, "--"
374 print >>output, "Command Output (stderr):"
375 print >>output, "--"
376 output.write(err)
377 print >>output, "--"
378 return (status, output.getvalue())
379
380def executeTclTest(test, litConfig):
381 if test.config.unsupported:
382 return (Test.UNSUPPORTED, 'Test is unsupported')
383
384 res = parseIntegratedTestScript(test, True, False)
385 if len(res) == 2:
386 return res
387
388 script, xfails, xtargets, tmpBase, execdir = res
389
390 if litConfig.noExecute:
391 return (Test.PASS, '')
392
393 # Create the output directory if it does not already exist.
394 Util.mkdir_p(os.path.dirname(tmpBase))
395
396 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
397 if len(res) == 2:
398 return res
399
400 isXFail = False
401 for item in xfails:
402 if item == '*' or item in test.suite.config.target_triple:
403 isXFail = True
404 break
405
406 # If this is XFAIL, see if it is expected to pass on this target.
407 if isXFail:
408 for item in xtargets:
409 if item == '*' or item in test.suite.config.target_triple:
410 isXFail = False
411 break
412
413 out,err,exitCode = res
414 if isXFail:
415 ok = exitCode != 0
416 status = (Test.XPASS, Test.XFAIL)[ok]
417 else:
418 ok = exitCode == 0
419 status = (Test.FAIL, Test.PASS)[ok]
420
421 if ok:
422 return (status,'')
423
424 return formatTestOutput(status, out, err, exitCode, script)
425
426def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
427 if test.config.unsupported:
428 return (Test.UNSUPPORTED, 'Test is unsupported')
429
430 res = parseIntegratedTestScript(test, False, requireAndAnd)
431 if len(res) == 2:
432 return res
433
434 script, xfails, xtargets, tmpBase, execdir = res
435
436 if litConfig.noExecute:
437 return (Test.PASS, '')
438
439 # Create the output directory if it does not already exist.
440 Util.mkdir_p(os.path.dirname(tmpBase))
441
442 if useExternalSh:
443 res = executeScript(test, litConfig, tmpBase, script, execdir)
444 else:
445 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
446 if len(res) == 2:
447 return res
448
449 out,err,exitCode = res
450 if xfails:
451 ok = exitCode != 0
452 status = (Test.XPASS, Test.XFAIL)[ok]
453 else:
454 ok = exitCode == 0
455 status = (Test.FAIL, Test.PASS)[ok]
456
457 if ok:
458 return (status,'')
459
460 return formatTestOutput(status, out, err, exitCode, script)