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