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