blob: 7abc60a1f0045b0ba379ccd4576057858b62d0fd [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':
117 r[2].seek(0, os.SEEK_END)
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
356def parseIntegratedTestScript(test, requireAndAnd):
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
428 # Validate interior lines for '&&', a lovely historical artifact.
429 if requireAndAnd:
430 for i in range(len(script) - 1):
431 ln = script[i]
432
433 if not ln.endswith('&&'):
434 return (Test.FAIL,
435 ("MISSING \'&&\': %s\n" +
436 "FOLLOWED BY : %s\n") % (ln, script[i + 1]))
437
438 # Strip off '&&'
439 script[i] = ln[:-2]
440
Daniel Dunbar42543b72009-11-03 07:26:38 +0000441 isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
442 return script,isXFail,tmpBase,execdir
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000443
444def formatTestOutput(status, out, err, exitCode, script):
445 output = StringIO.StringIO()
446 print >>output, "Script:"
447 print >>output, "--"
448 print >>output, '\n'.join(script)
449 print >>output, "--"
450 print >>output, "Exit Code: %r" % exitCode
451 print >>output, "Command Output (stdout):"
452 print >>output, "--"
453 output.write(out)
454 print >>output, "--"
455 print >>output, "Command Output (stderr):"
456 print >>output, "--"
457 output.write(err)
458 print >>output, "--"
459 return (status, output.getvalue())
460
461def executeTclTest(test, litConfig):
462 if test.config.unsupported:
463 return (Test.UNSUPPORTED, 'Test is unsupported')
464
Daniel Dunbar42543b72009-11-03 07:26:38 +0000465 res = parseIntegratedTestScript(test, False)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000466 if len(res) == 2:
467 return res
468
Daniel Dunbar42543b72009-11-03 07:26:38 +0000469 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000470
471 if litConfig.noExecute:
472 return (Test.PASS, '')
473
474 # Create the output directory if it does not already exist.
475 Util.mkdir_p(os.path.dirname(tmpBase))
476
477 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
478 if len(res) == 2:
479 return res
480
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000481 out,err,exitCode = res
482 if isXFail:
483 ok = exitCode != 0
484 status = (Test.XPASS, Test.XFAIL)[ok]
485 else:
486 ok = exitCode == 0
487 status = (Test.FAIL, Test.PASS)[ok]
488
489 if ok:
490 return (status,'')
491
492 return formatTestOutput(status, out, err, exitCode, script)
493
494def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
495 if test.config.unsupported:
496 return (Test.UNSUPPORTED, 'Test is unsupported')
497
Daniel Dunbar42543b72009-11-03 07:26:38 +0000498 res = parseIntegratedTestScript(test, requireAndAnd)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000499 if len(res) == 2:
500 return res
501
Daniel Dunbar42543b72009-11-03 07:26:38 +0000502 script, isXFail, tmpBase, execdir = res
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000503
504 if litConfig.noExecute:
505 return (Test.PASS, '')
506
507 # Create the output directory if it does not already exist.
508 Util.mkdir_p(os.path.dirname(tmpBase))
509
510 if useExternalSh:
511 res = executeScript(test, litConfig, tmpBase, script, execdir)
512 else:
513 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
514 if len(res) == 2:
515 return res
516
517 out,err,exitCode = res
Daniel Dunbar42543b72009-11-03 07:26:38 +0000518 if isXFail:
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000519 ok = exitCode != 0
520 status = (Test.XPASS, Test.XFAIL)[ok]
521 else:
522 ok = exitCode == 0
523 status = (Test.FAIL, Test.PASS)[ok]
524
525 if ok:
526 return (status,'')
527
528 return formatTestOutput(status, out, err, exitCode, script)