blob: bee1167f6af78a6e6358d17f29ab8e4d96b3adf8 [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
Daniel Dunbar42543b72009-11-03 07:26:38 +0000336def isExpectedFail(xfails, xtargets, target_triple):
337 # Check if any xfail matches this target.
338 for item in xfails:
339 if item == '*' or item in target_triple:
340 break
341 else:
342 return False
343
344 # If so, see if it is expected to pass on this target.
345 #
346 # FIXME: Rename XTARGET to something that makes sense, like XPASS.
347 for item in xtargets:
348 if item == '*' or item in target_triple:
349 return False
350
351 return True
352
353def parseIntegratedTestScript(test, requireAndAnd):
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000354 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
355 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
356 information. The RUN lines also will have variable substitution performed.
357 """
358
359 # Get the temporary location, this is always relative to the test suite
360 # root, not test source root.
361 #
362 # FIXME: This should not be here?
363 sourcepath = test.getSourcePath()
364 execpath = test.getExecPath()
365 execdir,execbase = os.path.split(execpath)
366 tmpBase = os.path.join(execdir, 'Output', execbase)
367
368 # We use #_MARKER_# to hide %% while we do the other substitutions.
369 substitutions = [('%%', '#_MARKER_#')]
370 substitutions.extend(test.config.substitutions)
371 substitutions.extend([('%s', sourcepath),
372 ('%S', os.path.dirname(sourcepath)),
373 ('%p', os.path.dirname(sourcepath)),
374 ('%t', tmpBase + '.tmp'),
Daniel Dunbar51106092009-09-13 01:39:50 +0000375 # FIXME: Remove this once we kill DejaGNU.
376 ('%abs_tmp', tmpBase + '.tmp'),
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000377 ('#_MARKER_#', '%')])
378
379 # Collect the test lines from the script.
380 script = []
381 xfails = []
382 xtargets = []
383 for ln in open(sourcepath):
384 if 'RUN:' in ln:
385 # Isolate the command to run.
386 index = ln.index('RUN:')
387 ln = ln[index+4:]
388
389 # Trim trailing whitespace.
390 ln = ln.rstrip()
391
392 # Collapse lines with trailing '\\'.
393 if script and script[-1][-1] == '\\':
394 script[-1] = script[-1][:-1] + ln
395 else:
396 script.append(ln)
Daniel Dunbar42543b72009-11-03 07:26:38 +0000397 elif 'XFAIL:' in ln:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000398 items = ln[ln.index('XFAIL:') + 6:].split(',')
399 xfails.extend([s.strip() for s in items])
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000400 elif 'XTARGET:' in ln:
401 items = ln[ln.index('XTARGET:') + 8:].split(',')
402 xtargets.extend([s.strip() for s in items])
403 elif 'END.' in ln:
404 # Check for END. lines.
405 if ln[ln.index('END.'):].strip() == 'END.':
406 break
407
408 # Apply substitutions to the script.
409 def processLine(ln):
410 # Apply substitutions
411 for a,b in substitutions:
412 ln = ln.replace(a,b)
413
414 # Strip the trailing newline and any extra whitespace.
415 return ln.strip()
416 script = map(processLine, script)
417
418 # Verify the script contains a run line.
419 if not script:
420 return (Test.UNRESOLVED, "Test has no run line!")
421
422 if script[-1][-1] == '\\':
423 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
424
425 # Validate interior lines for '&&', a lovely historical artifact.
426 if requireAndAnd:
427 for i in range(len(script) - 1):
428 ln = script[i]
429
430 if not ln.endswith('&&'):
431 return (Test.FAIL,
432 ("MISSING \'&&\': %s\n" +
433 "FOLLOWED BY : %s\n") % (ln, script[i + 1]))
434
435 # Strip off '&&'
436 script[i] = ln[:-2]
437
Daniel Dunbar42543b72009-11-03 07:26:38 +0000438 isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
439 return script,isXFail,tmpBase,execdir
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000440
441def formatTestOutput(status, out, err, exitCode, script):
442 output = StringIO.StringIO()
443 print >>output, "Script:"
444 print >>output, "--"
445 print >>output, '\n'.join(script)
446 print >>output, "--"
447 print >>output, "Exit Code: %r" % exitCode
448 print >>output, "Command Output (stdout):"
449 print >>output, "--"
450 output.write(out)
451 print >>output, "--"
452 print >>output, "Command Output (stderr):"
453 print >>output, "--"
454 output.write(err)
455 print >>output, "--"
456 return (status, output.getvalue())
457
458def executeTclTest(test, litConfig):
459 if test.config.unsupported:
460 return (Test.UNSUPPORTED, 'Test is unsupported')
461
Daniel Dunbar42543b72009-11-03 07:26:38 +0000462 res = parseIntegratedTestScript(test, False)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000463 if len(res) == 2:
464 return res
465
Daniel Dunbar42543b72009-11-03 07:26:38 +0000466 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000467
468 if litConfig.noExecute:
469 return (Test.PASS, '')
470
471 # Create the output directory if it does not already exist.
472 Util.mkdir_p(os.path.dirname(tmpBase))
473
474 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
475 if len(res) == 2:
476 return res
477
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000478 out,err,exitCode = res
479 if isXFail:
480 ok = exitCode != 0
481 status = (Test.XPASS, Test.XFAIL)[ok]
482 else:
483 ok = exitCode == 0
484 status = (Test.FAIL, Test.PASS)[ok]
485
486 if ok:
487 return (status,'')
488
489 return formatTestOutput(status, out, err, exitCode, script)
490
491def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
492 if test.config.unsupported:
493 return (Test.UNSUPPORTED, 'Test is unsupported')
494
Daniel Dunbar42543b72009-11-03 07:26:38 +0000495 res = parseIntegratedTestScript(test, requireAndAnd)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000496 if len(res) == 2:
497 return res
498
Daniel Dunbar42543b72009-11-03 07:26:38 +0000499 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000500
501 if litConfig.noExecute:
502 return (Test.PASS, '')
503
504 # Create the output directory if it does not already exist.
505 Util.mkdir_p(os.path.dirname(tmpBase))
506
507 if useExternalSh:
508 res = executeScript(test, litConfig, tmpBase, script, execdir)
509 else:
510 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
511 if len(res) == 2:
512 return res
513
514 out,err,exitCode = res
Daniel Dunbar42543b72009-11-03 07:26:38 +0000515 if isXFail:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000516 ok = exitCode != 0
517 status = (Test.XPASS, Test.XFAIL)[ok]
518 else:
519 ok = exitCode == 0
520 status = (Test.FAIL, Test.PASS)[ok]
521
522 if ok:
523 return (status,'')
524
525 return formatTestOutput(status, out, err, exitCode, script)