blob: 5b4538f7d291cb201008729ed0022536f439062e [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 Dunbar474f0df2009-11-08 03:43:06 +0000115 # Workaround a Win32 and/or subprocess bug when appending.
116 if r[1] == 'a':
Daniel Dunbarbf477df2009-11-08 09:07:33 +0000117 r[2].seek(0, 2)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000118 result = r[2]
119 final_redirects.append(result)
120
121 stdin, stdout, stderr = final_redirects
122
123 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
124 # stderr on a pipe and treat it as stdout.
125 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
126 stderr = subprocess.PIPE
127 stderrIsStdout = True
128 else:
129 stderrIsStdout = False
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000130
Daniel Dunbar5a461dd2009-09-22 09:50:38 +0000131 # Don't allow stderr on a PIPE except for the last
132 # process, this could deadlock.
133 #
134 # FIXME: This is slow, but so is deadlock.
135 if stderr == subprocess.PIPE and j != cmd.commands[-1]:
136 stderr = tempfile.TemporaryFile(mode='w+b')
137 stderrTempFiles.append((i, stderr))
138
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000139 # Resolve the executable path ourselves.
140 args = list(j.args)
141 args[0] = Util.which(args[0], cfg.environment['PATH'])
142 if not args[0]:
143 raise InternalShellError(j, '%r: command not found' % j.args[0])
144
Daniel Dunbar4b78aa32009-09-22 06:09:13 +0000145 procs.append(subprocess.Popen(args, cwd=cwd,
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000146 stdin = stdin,
147 stdout = stdout,
148 stderr = stderr,
149 env = cfg.environment,
Daniel Dunbar58c661c2009-09-22 04:44:37 +0000150 close_fds = kUseCloseFDs))
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000151
152 # Immediately close stdin for any process taking stdin from us.
153 if stdin == subprocess.PIPE:
154 procs[-1].stdin.close()
155 procs[-1].stdin = None
156
157 # Update the current stdin source.
158 if stdout == subprocess.PIPE:
159 input = procs[-1].stdout
160 elif stderrIsStdout:
161 input = procs[-1].stderr
162 else:
163 input = subprocess.PIPE
164
Daniel Dunbar5a461dd2009-09-22 09:50:38 +0000165 # FIXME: There is probably still deadlock potential here. Yawn.
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000166 procData = [None] * len(procs)
167 procData[-1] = procs[-1].communicate()
Daniel Dunbar5a461dd2009-09-22 09:50:38 +0000168
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000169 for i in range(len(procs) - 1):
170 if procs[i].stdout is not None:
171 out = procs[i].stdout.read()
172 else:
173 out = ''
174 if procs[i].stderr is not None:
175 err = procs[i].stderr.read()
176 else:
177 err = ''
178 procData[i] = (out,err)
Daniel Dunbar5a461dd2009-09-22 09:50:38 +0000179
180 # Read stderr out of the temp files.
181 for i,f in stderrTempFiles:
182 f.seek(0, 0)
183 procData[i] = (procData[i][0], f.read())
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000184
185 exitCode = None
186 for i,(out,err) in enumerate(procData):
187 res = procs[i].wait()
188 # Detect Ctrl-C in subprocess.
189 if res == -signal.SIGINT:
190 raise KeyboardInterrupt
191
192 results.append((cmd.commands[i], out, err, res))
193 if cmd.pipe_err:
194 # Python treats the exit code as a signed char.
195 if res < 0:
196 exitCode = min(exitCode, res)
197 else:
198 exitCode = max(exitCode, res)
199 else:
200 exitCode = res
201
202 if cmd.negate:
203 exitCode = not exitCode
204
205 return exitCode
206
207def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
208 ln = ' &&\n'.join(commands)
209 try:
210 cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
211 except:
212 return (Test.FAIL, "shell parser error on: %r" % ln)
213
214 results = []
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000215 try:
216 exitCode = executeShCmd(cmd, test.config, cwd, results)
217 except InternalShellError,e:
218 out = ''
219 err = e.message
220 exitCode = 255
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000221
222 out = err = ''
223 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
224 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
225 out += 'Command %d Result: %r\n' % (i, res)
226 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
227 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
228
229 return out, err, exitCode
230
231def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
232 import TclUtil
233 cmds = []
234 for ln in commands:
235 # Given the unfortunate way LLVM's test are written, the line gets
236 # backslash substitution done twice.
237 ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
238
239 try:
240 tokens = list(TclUtil.TclLexer(ln).lex())
241 except:
242 return (Test.FAIL, "Tcl lexer error on: %r" % ln)
243
244 # Validate there are no control tokens.
245 for t in tokens:
246 if not isinstance(t, str):
247 return (Test.FAIL,
248 "Invalid test line: %r containing %r" % (ln, t))
249
250 try:
251 cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
252 except:
Daniel Dunbar2c0a49c2009-09-08 05:37:51 +0000253 return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000254
255 cmd = cmds[0]
256 for c in cmds[1:]:
257 cmd = ShUtil.Seq(cmd, '&&', c)
258
Daniel Dunbar7723d452009-10-19 03:54:21 +0000259 # FIXME: This is lame, we shouldn't need bash. See PR5240.
260 bashPath = litConfig.getBashPath()
261 if litConfig.useTclAsSh and bashPath:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000262 script = tmpBase + '.script'
263
264 # Write script file
265 f = open(script,'w')
266 print >>f, 'set -o pipefail'
267 cmd.toShell(f, pipefail = True)
268 f.close()
269
270 if 0:
271 print >>sys.stdout, cmd
272 print >>sys.stdout, open(script).read()
273 print >>sys.stdout
274 return '', '', 0
275
Daniel Dunbar7723d452009-10-19 03:54:21 +0000276 command = [litConfig.getBashPath(), script]
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000277 out,err,exitCode = executeCommand(command, cwd=cwd,
278 env=test.config.environment)
279
280 # Tcl commands fail on standard error output.
281 if err:
282 exitCode = 1
283 out = 'Command has output on stderr!\n\n' + out
284
285 return out,err,exitCode
286 else:
287 results = []
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000288 try:
289 exitCode = executeShCmd(cmd, test.config, cwd, results)
290 except InternalShellError,e:
291 results.append((e.command, '', e.message + '\n', 255))
292 exitCode = 255
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000293
294 out = err = ''
295
296 # Tcl commands fail on standard error output.
297 if [True for _,_,err,res in results if err]:
298 exitCode = 1
299 out += 'Command has output on stderr!\n\n'
300
301 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
302 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
303 out += 'Command %d Result: %r\n' % (i, res)
304 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
305 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
306
307 return out, err, exitCode
308
309def executeScript(test, litConfig, tmpBase, commands, cwd):
310 script = tmpBase + '.script'
311 if litConfig.isWindows:
312 script += '.bat'
313
314 # Write script file
315 f = open(script,'w')
316 if litConfig.isWindows:
317 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
318 else:
319 f.write(' &&\n'.join(commands))
320 f.write('\n')
321 f.close()
322
323 if litConfig.isWindows:
324 command = ['cmd','/c', script]
325 else:
326 command = ['/bin/sh', script]
327 if litConfig.useValgrind:
328 # FIXME: Running valgrind on sh is overkill. We probably could just
329 # run on clang with no real loss.
330 valgrindArgs = ['valgrind', '-q',
331 '--tool=memcheck', '--trace-children=yes',
332 '--error-exitcode=123']
333 valgrindArgs.extend(litConfig.valgrindArgs)
334
335 command = valgrindArgs + command
336
337 return executeCommand(command, cwd=cwd, env=test.config.environment)
338
Daniel Dunbar42543b72009-11-03 07:26:38 +0000339def isExpectedFail(xfails, xtargets, target_triple):
340 # Check if any xfail matches this target.
341 for item in xfails:
342 if item == '*' or item in target_triple:
343 break
344 else:
345 return False
346
347 # If so, see if it is expected to pass on this target.
348 #
349 # FIXME: Rename XTARGET to something that makes sense, like XPASS.
350 for item in xtargets:
351 if item == '*' or item in target_triple:
352 return False
353
354 return True
355
Daniel Dunbaree504b82009-11-08 09:07:13 +0000356def parseIntegratedTestScript(test):
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000357 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
358 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
359 information. The RUN lines also will have variable substitution performed.
360 """
361
362 # Get the temporary location, this is always relative to the test suite
363 # root, not test source root.
364 #
365 # FIXME: This should not be here?
366 sourcepath = test.getSourcePath()
367 execpath = test.getExecPath()
368 execdir,execbase = os.path.split(execpath)
369 tmpBase = os.path.join(execdir, 'Output', execbase)
370
371 # We use #_MARKER_# to hide %% while we do the other substitutions.
372 substitutions = [('%%', '#_MARKER_#')]
373 substitutions.extend(test.config.substitutions)
374 substitutions.extend([('%s', sourcepath),
375 ('%S', os.path.dirname(sourcepath)),
376 ('%p', os.path.dirname(sourcepath)),
377 ('%t', tmpBase + '.tmp'),
Daniel Dunbar51106092009-09-13 01:39:50 +0000378 # FIXME: Remove this once we kill DejaGNU.
379 ('%abs_tmp', tmpBase + '.tmp'),
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000380 ('#_MARKER_#', '%')])
381
382 # Collect the test lines from the script.
383 script = []
384 xfails = []
385 xtargets = []
386 for ln in open(sourcepath):
387 if 'RUN:' in ln:
388 # Isolate the command to run.
389 index = ln.index('RUN:')
390 ln = ln[index+4:]
391
392 # Trim trailing whitespace.
393 ln = ln.rstrip()
394
395 # Collapse lines with trailing '\\'.
396 if script and script[-1][-1] == '\\':
397 script[-1] = script[-1][:-1] + ln
398 else:
399 script.append(ln)
Daniel Dunbar42543b72009-11-03 07:26:38 +0000400 elif 'XFAIL:' in ln:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000401 items = ln[ln.index('XFAIL:') + 6:].split(',')
402 xfails.extend([s.strip() for s in items])
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000403 elif 'XTARGET:' in ln:
404 items = ln[ln.index('XTARGET:') + 8:].split(',')
405 xtargets.extend([s.strip() for s in items])
406 elif 'END.' in ln:
407 # Check for END. lines.
408 if ln[ln.index('END.'):].strip() == 'END.':
409 break
410
411 # Apply substitutions to the script.
412 def processLine(ln):
413 # Apply substitutions
414 for a,b in substitutions:
415 ln = ln.replace(a,b)
416
417 # Strip the trailing newline and any extra whitespace.
418 return ln.strip()
419 script = map(processLine, script)
420
421 # Verify the script contains a run line.
422 if not script:
423 return (Test.UNRESOLVED, "Test has no run line!")
424
425 if script[-1][-1] == '\\':
426 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
427
Daniel Dunbar42543b72009-11-03 07:26:38 +0000428 isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
429 return script,isXFail,tmpBase,execdir
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000430
431def formatTestOutput(status, out, err, exitCode, script):
432 output = StringIO.StringIO()
433 print >>output, "Script:"
434 print >>output, "--"
435 print >>output, '\n'.join(script)
436 print >>output, "--"
437 print >>output, "Exit Code: %r" % exitCode
438 print >>output, "Command Output (stdout):"
439 print >>output, "--"
440 output.write(out)
441 print >>output, "--"
442 print >>output, "Command Output (stderr):"
443 print >>output, "--"
444 output.write(err)
445 print >>output, "--"
446 return (status, output.getvalue())
447
448def executeTclTest(test, litConfig):
449 if test.config.unsupported:
450 return (Test.UNSUPPORTED, 'Test is unsupported')
451
Daniel Dunbaree504b82009-11-08 09:07:13 +0000452 res = parseIntegratedTestScript(test)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000453 if len(res) == 2:
454 return res
455
Daniel Dunbar42543b72009-11-03 07:26:38 +0000456 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000457
458 if litConfig.noExecute:
459 return (Test.PASS, '')
460
461 # Create the output directory if it does not already exist.
462 Util.mkdir_p(os.path.dirname(tmpBase))
463
464 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
465 if len(res) == 2:
466 return res
467
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000468 out,err,exitCode = res
469 if isXFail:
470 ok = exitCode != 0
471 status = (Test.XPASS, Test.XFAIL)[ok]
472 else:
473 ok = exitCode == 0
474 status = (Test.FAIL, Test.PASS)[ok]
475
476 if ok:
477 return (status,'')
478
479 return formatTestOutput(status, out, err, exitCode, script)
480
Daniel Dunbaree504b82009-11-08 09:07:13 +0000481def executeShTest(test, litConfig, useExternalSh):
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000482 if test.config.unsupported:
483 return (Test.UNSUPPORTED, 'Test is unsupported')
484
Daniel Dunbaree504b82009-11-08 09:07:13 +0000485 res = parseIntegratedTestScript(test)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000486 if len(res) == 2:
487 return res
488
Daniel Dunbar42543b72009-11-03 07:26:38 +0000489 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000490
491 if litConfig.noExecute:
492 return (Test.PASS, '')
493
494 # Create the output directory if it does not already exist.
495 Util.mkdir_p(os.path.dirname(tmpBase))
496
497 if useExternalSh:
498 res = executeScript(test, litConfig, tmpBase, script, execdir)
499 else:
500 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
501 if len(res) == 2:
502 return res
503
504 out,err,exitCode = res
Daniel Dunbar42543b72009-11-03 07:26:38 +0000505 if isXFail:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000506 ok = exitCode != 0
507 status = (Test.XPASS, Test.XFAIL)[ok]
508 else:
509 ok = exitCode == 0
510 status = (Test.FAIL, Test.PASS)[ok]
511
512 if ok:
513 return (status,'')
514
515 return formatTestOutput(status, out, err, exitCode, script)