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