blob: 34e828bf96098c831de52f3edb830979dabc34d3 [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
Daniel Dunbar7723d452009-10-19 03:54:21 +0000240 # FIXME: This is lame, we shouldn't need bash. See PR5240.
241 bashPath = litConfig.getBashPath()
242 if litConfig.useTclAsSh and bashPath:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000243 script = tmpBase + '.script'
244
245 # Write script file
246 f = open(script,'w')
247 print >>f, 'set -o pipefail'
248 cmd.toShell(f, pipefail = True)
249 f.close()
250
251 if 0:
252 print >>sys.stdout, cmd
253 print >>sys.stdout, open(script).read()
254 print >>sys.stdout
255 return '', '', 0
256
Daniel Dunbar7723d452009-10-19 03:54:21 +0000257 command = [litConfig.getBashPath(), script]
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000258 out,err,exitCode = executeCommand(command, cwd=cwd,
259 env=test.config.environment)
260
261 # Tcl commands fail on standard error output.
262 if err:
263 exitCode = 1
264 out = 'Command has output on stderr!\n\n' + out
265
266 return out,err,exitCode
267 else:
268 results = []
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000269 try:
270 exitCode = executeShCmd(cmd, test.config, cwd, results)
271 except InternalShellError,e:
272 results.append((e.command, '', e.message + '\n', 255))
273 exitCode = 255
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000274
275 out = err = ''
276
277 # Tcl commands fail on standard error output.
278 if [True for _,_,err,res in results if err]:
279 exitCode = 1
280 out += 'Command has output on stderr!\n\n'
281
282 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
283 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
284 out += 'Command %d Result: %r\n' % (i, res)
285 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
286 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
287
288 return out, err, exitCode
289
290def executeScript(test, litConfig, tmpBase, commands, cwd):
291 script = tmpBase + '.script'
292 if litConfig.isWindows:
293 script += '.bat'
294
295 # Write script file
296 f = open(script,'w')
297 if litConfig.isWindows:
298 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
299 else:
300 f.write(' &&\n'.join(commands))
301 f.write('\n')
302 f.close()
303
304 if litConfig.isWindows:
305 command = ['cmd','/c', script]
306 else:
307 command = ['/bin/sh', script]
308 if litConfig.useValgrind:
309 # FIXME: Running valgrind on sh is overkill. We probably could just
310 # run on clang with no real loss.
311 valgrindArgs = ['valgrind', '-q',
312 '--tool=memcheck', '--trace-children=yes',
313 '--error-exitcode=123']
314 valgrindArgs.extend(litConfig.valgrindArgs)
315
316 command = valgrindArgs + command
317
318 return executeCommand(command, cwd=cwd, env=test.config.environment)
319
320def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
321 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
322 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
323 information. The RUN lines also will have variable substitution performed.
324 """
325
326 # Get the temporary location, this is always relative to the test suite
327 # root, not test source root.
328 #
329 # FIXME: This should not be here?
330 sourcepath = test.getSourcePath()
331 execpath = test.getExecPath()
332 execdir,execbase = os.path.split(execpath)
333 tmpBase = os.path.join(execdir, 'Output', execbase)
334
335 # We use #_MARKER_# to hide %% while we do the other substitutions.
336 substitutions = [('%%', '#_MARKER_#')]
337 substitutions.extend(test.config.substitutions)
338 substitutions.extend([('%s', sourcepath),
339 ('%S', os.path.dirname(sourcepath)),
340 ('%p', os.path.dirname(sourcepath)),
341 ('%t', tmpBase + '.tmp'),
Daniel Dunbar51106092009-09-13 01:39:50 +0000342 # FIXME: Remove this once we kill DejaGNU.
343 ('%abs_tmp', tmpBase + '.tmp'),
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000344 ('#_MARKER_#', '%')])
345
346 # Collect the test lines from the script.
347 script = []
348 xfails = []
349 xtargets = []
350 for ln in open(sourcepath):
351 if 'RUN:' in ln:
352 # Isolate the command to run.
353 index = ln.index('RUN:')
354 ln = ln[index+4:]
355
356 # Trim trailing whitespace.
357 ln = ln.rstrip()
358
359 # Collapse lines with trailing '\\'.
360 if script and script[-1][-1] == '\\':
361 script[-1] = script[-1][:-1] + ln
362 else:
363 script.append(ln)
364 elif xfailHasColon and 'XFAIL:' in ln:
365 items = ln[ln.index('XFAIL:') + 6:].split(',')
366 xfails.extend([s.strip() for s in items])
367 elif not xfailHasColon and 'XFAIL' in ln:
368 items = ln[ln.index('XFAIL') + 5:].split(',')
369 xfails.extend([s.strip() for s in items])
370 elif 'XTARGET:' in ln:
371 items = ln[ln.index('XTARGET:') + 8:].split(',')
372 xtargets.extend([s.strip() for s in items])
373 elif 'END.' in ln:
374 # Check for END. lines.
375 if ln[ln.index('END.'):].strip() == 'END.':
376 break
377
378 # Apply substitutions to the script.
379 def processLine(ln):
380 # Apply substitutions
381 for a,b in substitutions:
382 ln = ln.replace(a,b)
383
384 # Strip the trailing newline and any extra whitespace.
385 return ln.strip()
386 script = map(processLine, script)
387
388 # Verify the script contains a run line.
389 if not script:
390 return (Test.UNRESOLVED, "Test has no run line!")
391
392 if script[-1][-1] == '\\':
393 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
394
395 # Validate interior lines for '&&', a lovely historical artifact.
396 if requireAndAnd:
397 for i in range(len(script) - 1):
398 ln = script[i]
399
400 if not ln.endswith('&&'):
401 return (Test.FAIL,
402 ("MISSING \'&&\': %s\n" +
403 "FOLLOWED BY : %s\n") % (ln, script[i + 1]))
404
405 # Strip off '&&'
406 script[i] = ln[:-2]
407
408 return script,xfails,xtargets,tmpBase,execdir
409
410def formatTestOutput(status, out, err, exitCode, script):
411 output = StringIO.StringIO()
412 print >>output, "Script:"
413 print >>output, "--"
414 print >>output, '\n'.join(script)
415 print >>output, "--"
416 print >>output, "Exit Code: %r" % exitCode
417 print >>output, "Command Output (stdout):"
418 print >>output, "--"
419 output.write(out)
420 print >>output, "--"
421 print >>output, "Command Output (stderr):"
422 print >>output, "--"
423 output.write(err)
424 print >>output, "--"
425 return (status, output.getvalue())
426
427def executeTclTest(test, litConfig):
428 if test.config.unsupported:
429 return (Test.UNSUPPORTED, 'Test is unsupported')
430
431 res = parseIntegratedTestScript(test, True, False)
432 if len(res) == 2:
433 return res
434
435 script, xfails, xtargets, tmpBase, execdir = res
436
437 if litConfig.noExecute:
438 return (Test.PASS, '')
439
440 # Create the output directory if it does not already exist.
441 Util.mkdir_p(os.path.dirname(tmpBase))
442
443 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
444 if len(res) == 2:
445 return res
446
447 isXFail = False
448 for item in xfails:
449 if item == '*' or item in test.suite.config.target_triple:
450 isXFail = True
451 break
452
453 # If this is XFAIL, see if it is expected to pass on this target.
454 if isXFail:
455 for item in xtargets:
456 if item == '*' or item in test.suite.config.target_triple:
457 isXFail = False
458 break
459
460 out,err,exitCode = res
461 if isXFail:
462 ok = exitCode != 0
463 status = (Test.XPASS, Test.XFAIL)[ok]
464 else:
465 ok = exitCode == 0
466 status = (Test.FAIL, Test.PASS)[ok]
467
468 if ok:
469 return (status,'')
470
471 return formatTestOutput(status, out, err, exitCode, script)
472
473def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
474 if test.config.unsupported:
475 return (Test.UNSUPPORTED, 'Test is unsupported')
476
477 res = parseIntegratedTestScript(test, False, requireAndAnd)
478 if len(res) == 2:
479 return res
480
481 script, xfails, xtargets, tmpBase, execdir = res
482
483 if litConfig.noExecute:
484 return (Test.PASS, '')
485
486 # Create the output directory if it does not already exist.
487 Util.mkdir_p(os.path.dirname(tmpBase))
488
489 if useExternalSh:
490 res = executeScript(test, litConfig, tmpBase, script, execdir)
491 else:
492 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
493 if len(res) == 2:
494 return res
495
496 out,err,exitCode = res
497 if xfails:
498 ok = exitCode != 0
499 status = (Test.XPASS, Test.XFAIL)[ok]
500 else:
501 ok = exitCode == 0
502 status = (Test.FAIL, Test.PASS)[ok]
503
504 if ok:
505 return (status,'')
506
507 return formatTestOutput(status, out, err, exitCode, script)