blob: 1501d287d9ff12554ea90b4f1873bbc6b48659d8 [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
9
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +000010class InternalShellError(Exception):
11 def __init__(self, command, message):
12 self.command = command
13 self.message = message
14
Daniel Dunbar58c661c2009-09-22 04:44:37 +000015# Don't use close_fds on Windows.
16kUseCloseFDs = platform.system() != 'Windows'
Daniel Dunbarbe7ada72009-09-08 05:31:18 +000017def executeCommand(command, cwd=None, env=None):
18 p = subprocess.Popen(command, cwd=cwd,
19 stdin=subprocess.PIPE,
20 stdout=subprocess.PIPE,
21 stderr=subprocess.PIPE,
22 env=env)
23 out,err = p.communicate()
24 exitCode = p.wait()
25
26 # Detect Ctrl-C in subprocess.
27 if exitCode == -signal.SIGINT:
28 raise KeyboardInterrupt
29
30 return out, err, exitCode
31
32def executeShCmd(cmd, cfg, cwd, results):
33 if isinstance(cmd, ShUtil.Seq):
34 if cmd.op == ';':
35 res = executeShCmd(cmd.lhs, cfg, cwd, results)
36 return executeShCmd(cmd.rhs, cfg, cwd, results)
37
38 if cmd.op == '&':
39 raise NotImplementedError,"unsupported test command: '&'"
40
41 if cmd.op == '||':
42 res = executeShCmd(cmd.lhs, cfg, cwd, results)
43 if res != 0:
44 res = executeShCmd(cmd.rhs, cfg, cwd, results)
45 return res
46 if cmd.op == '&&':
47 res = executeShCmd(cmd.lhs, cfg, cwd, results)
48 if res is None:
49 return res
50
51 if res == 0:
52 res = executeShCmd(cmd.rhs, cfg, cwd, results)
53 return res
54
55 raise ValueError,'Unknown shell command: %r' % cmd.op
56
57 assert isinstance(cmd, ShUtil.Pipeline)
58 procs = []
59 input = subprocess.PIPE
60 for j in cmd.commands:
61 redirects = [(0,), (1,), (2,)]
62 for r in j.redirects:
63 if r[0] == ('>',2):
64 redirects[2] = [r[1], 'w', None]
65 elif r[0] == ('>&',2) and r[1] in '012':
66 redirects[2] = redirects[int(r[1])]
67 elif r[0] == ('>&',) or r[0] == ('&>',):
68 redirects[1] = redirects[2] = [r[1], 'w', None]
69 elif r[0] == ('>',):
70 redirects[1] = [r[1], 'w', None]
71 elif r[0] == ('<',):
72 redirects[0] = [r[1], 'r', None]
73 else:
74 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
75
76 final_redirects = []
77 for index,r in enumerate(redirects):
78 if r == (0,):
79 result = input
80 elif r == (1,):
81 if index == 0:
82 raise NotImplementedError,"Unsupported redirect for stdin"
83 elif index == 1:
84 result = subprocess.PIPE
85 else:
86 result = subprocess.STDOUT
87 elif r == (2,):
88 if index != 2:
89 raise NotImplementedError,"Unsupported redirect on stdout"
90 result = subprocess.PIPE
91 else:
92 if r[2] is None:
93 r[2] = open(r[0], r[1])
94 result = r[2]
95 final_redirects.append(result)
96
97 stdin, stdout, stderr = final_redirects
98
99 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
100 # stderr on a pipe and treat it as stdout.
101 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
102 stderr = subprocess.PIPE
103 stderrIsStdout = True
104 else:
105 stderrIsStdout = False
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000106
107 # Resolve the executable path ourselves.
108 args = list(j.args)
109 args[0] = Util.which(args[0], cfg.environment['PATH'])
110 if not args[0]:
111 raise InternalShellError(j, '%r: command not found' % j.args[0])
112
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000113 procs.append(subprocess.Popen(j.args, cwd=cwd,
114 stdin = stdin,
115 stdout = stdout,
116 stderr = stderr,
117 env = cfg.environment,
Daniel Dunbar58c661c2009-09-22 04:44:37 +0000118 close_fds = kUseCloseFDs))
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000119
120 # Immediately close stdin for any process taking stdin from us.
121 if stdin == subprocess.PIPE:
122 procs[-1].stdin.close()
123 procs[-1].stdin = None
124
125 # Update the current stdin source.
126 if stdout == subprocess.PIPE:
127 input = procs[-1].stdout
128 elif stderrIsStdout:
129 input = procs[-1].stderr
130 else:
131 input = subprocess.PIPE
132
133 # FIXME: There is a potential for deadlock here, when we have a pipe and
134 # some process other than the last one ends up blocked on stderr.
135 procData = [None] * len(procs)
136 procData[-1] = procs[-1].communicate()
137 for i in range(len(procs) - 1):
138 if procs[i].stdout is not None:
139 out = procs[i].stdout.read()
140 else:
141 out = ''
142 if procs[i].stderr is not None:
143 err = procs[i].stderr.read()
144 else:
145 err = ''
146 procData[i] = (out,err)
147
148 exitCode = None
149 for i,(out,err) in enumerate(procData):
150 res = procs[i].wait()
151 # Detect Ctrl-C in subprocess.
152 if res == -signal.SIGINT:
153 raise KeyboardInterrupt
154
155 results.append((cmd.commands[i], out, err, res))
156 if cmd.pipe_err:
157 # Python treats the exit code as a signed char.
158 if res < 0:
159 exitCode = min(exitCode, res)
160 else:
161 exitCode = max(exitCode, res)
162 else:
163 exitCode = res
164
165 if cmd.negate:
166 exitCode = not exitCode
167
168 return exitCode
169
170def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
171 ln = ' &&\n'.join(commands)
172 try:
173 cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
174 except:
175 return (Test.FAIL, "shell parser error on: %r" % ln)
176
177 results = []
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000178 try:
179 exitCode = executeShCmd(cmd, test.config, cwd, results)
180 except InternalShellError,e:
181 out = ''
182 err = e.message
183 exitCode = 255
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000184
185 out = err = ''
186 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
187 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
188 out += 'Command %d Result: %r\n' % (i, res)
189 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
190 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
191
192 return out, err, exitCode
193
194def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
195 import TclUtil
196 cmds = []
197 for ln in commands:
198 # Given the unfortunate way LLVM's test are written, the line gets
199 # backslash substitution done twice.
200 ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
201
202 try:
203 tokens = list(TclUtil.TclLexer(ln).lex())
204 except:
205 return (Test.FAIL, "Tcl lexer error on: %r" % ln)
206
207 # Validate there are no control tokens.
208 for t in tokens:
209 if not isinstance(t, str):
210 return (Test.FAIL,
211 "Invalid test line: %r containing %r" % (ln, t))
212
213 try:
214 cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
215 except:
Daniel Dunbar2c0a49c2009-09-08 05:37:51 +0000216 return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000217
218 cmd = cmds[0]
219 for c in cmds[1:]:
220 cmd = ShUtil.Seq(cmd, '&&', c)
221
222 if litConfig.useTclAsSh:
223 script = tmpBase + '.script'
224
225 # Write script file
226 f = open(script,'w')
227 print >>f, 'set -o pipefail'
228 cmd.toShell(f, pipefail = True)
229 f.close()
230
231 if 0:
232 print >>sys.stdout, cmd
233 print >>sys.stdout, open(script).read()
234 print >>sys.stdout
235 return '', '', 0
236
Daniel Dunbarf0724322009-09-08 06:08:07 +0000237 command = ['/bin/bash', script]
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000238 out,err,exitCode = executeCommand(command, cwd=cwd,
239 env=test.config.environment)
240
241 # Tcl commands fail on standard error output.
242 if err:
243 exitCode = 1
244 out = 'Command has output on stderr!\n\n' + out
245
246 return out,err,exitCode
247 else:
248 results = []
Daniel Dunbar6bd2b2e2009-09-22 04:44:26 +0000249 try:
250 exitCode = executeShCmd(cmd, test.config, cwd, results)
251 except InternalShellError,e:
252 results.append((e.command, '', e.message + '\n', 255))
253 exitCode = 255
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000254
255 out = err = ''
256
257 # Tcl commands fail on standard error output.
258 if [True for _,_,err,res in results if err]:
259 exitCode = 1
260 out += 'Command has output on stderr!\n\n'
261
262 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
263 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
264 out += 'Command %d Result: %r\n' % (i, res)
265 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
266 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
267
268 return out, err, exitCode
269
270def executeScript(test, litConfig, tmpBase, commands, cwd):
271 script = tmpBase + '.script'
272 if litConfig.isWindows:
273 script += '.bat'
274
275 # Write script file
276 f = open(script,'w')
277 if litConfig.isWindows:
278 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
279 else:
280 f.write(' &&\n'.join(commands))
281 f.write('\n')
282 f.close()
283
284 if litConfig.isWindows:
285 command = ['cmd','/c', script]
286 else:
287 command = ['/bin/sh', script]
288 if litConfig.useValgrind:
289 # FIXME: Running valgrind on sh is overkill. We probably could just
290 # run on clang with no real loss.
291 valgrindArgs = ['valgrind', '-q',
292 '--tool=memcheck', '--trace-children=yes',
293 '--error-exitcode=123']
294 valgrindArgs.extend(litConfig.valgrindArgs)
295
296 command = valgrindArgs + command
297
298 return executeCommand(command, cwd=cwd, env=test.config.environment)
299
300def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
301 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
302 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
303 information. The RUN lines also will have variable substitution performed.
304 """
305
306 # Get the temporary location, this is always relative to the test suite
307 # root, not test source root.
308 #
309 # FIXME: This should not be here?
310 sourcepath = test.getSourcePath()
311 execpath = test.getExecPath()
312 execdir,execbase = os.path.split(execpath)
313 tmpBase = os.path.join(execdir, 'Output', execbase)
314
315 # We use #_MARKER_# to hide %% while we do the other substitutions.
316 substitutions = [('%%', '#_MARKER_#')]
317 substitutions.extend(test.config.substitutions)
318 substitutions.extend([('%s', sourcepath),
319 ('%S', os.path.dirname(sourcepath)),
320 ('%p', os.path.dirname(sourcepath)),
321 ('%t', tmpBase + '.tmp'),
Daniel Dunbar51106092009-09-13 01:39:50 +0000322 # FIXME: Remove this once we kill DejaGNU.
323 ('%abs_tmp', tmpBase + '.tmp'),
Daniel Dunbarbe7ada72009-09-08 05:31:18 +0000324 ('#_MARKER_#', '%')])
325
326 # Collect the test lines from the script.
327 script = []
328 xfails = []
329 xtargets = []
330 for ln in open(sourcepath):
331 if 'RUN:' in ln:
332 # Isolate the command to run.
333 index = ln.index('RUN:')
334 ln = ln[index+4:]
335
336 # Trim trailing whitespace.
337 ln = ln.rstrip()
338
339 # Collapse lines with trailing '\\'.
340 if script and script[-1][-1] == '\\':
341 script[-1] = script[-1][:-1] + ln
342 else:
343 script.append(ln)
344 elif xfailHasColon and 'XFAIL:' in ln:
345 items = ln[ln.index('XFAIL:') + 6:].split(',')
346 xfails.extend([s.strip() for s in items])
347 elif not xfailHasColon and 'XFAIL' in ln:
348 items = ln[ln.index('XFAIL') + 5:].split(',')
349 xfails.extend([s.strip() for s in items])
350 elif 'XTARGET:' in ln:
351 items = ln[ln.index('XTARGET:') + 8:].split(',')
352 xtargets.extend([s.strip() for s in items])
353 elif 'END.' in ln:
354 # Check for END. lines.
355 if ln[ln.index('END.'):].strip() == 'END.':
356 break
357
358 # Apply substitutions to the script.
359 def processLine(ln):
360 # Apply substitutions
361 for a,b in substitutions:
362 ln = ln.replace(a,b)
363
364 # Strip the trailing newline and any extra whitespace.
365 return ln.strip()
366 script = map(processLine, script)
367
368 # Verify the script contains a run line.
369 if not script:
370 return (Test.UNRESOLVED, "Test has no run line!")
371
372 if script[-1][-1] == '\\':
373 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
374
375 # Validate interior lines for '&&', a lovely historical artifact.
376 if requireAndAnd:
377 for i in range(len(script) - 1):
378 ln = script[i]
379
380 if not ln.endswith('&&'):
381 return (Test.FAIL,
382 ("MISSING \'&&\': %s\n" +
383 "FOLLOWED BY : %s\n") % (ln, script[i + 1]))
384
385 # Strip off '&&'
386 script[i] = ln[:-2]
387
388 return script,xfails,xtargets,tmpBase,execdir
389
390def formatTestOutput(status, out, err, exitCode, script):
391 output = StringIO.StringIO()
392 print >>output, "Script:"
393 print >>output, "--"
394 print >>output, '\n'.join(script)
395 print >>output, "--"
396 print >>output, "Exit Code: %r" % exitCode
397 print >>output, "Command Output (stdout):"
398 print >>output, "--"
399 output.write(out)
400 print >>output, "--"
401 print >>output, "Command Output (stderr):"
402 print >>output, "--"
403 output.write(err)
404 print >>output, "--"
405 return (status, output.getvalue())
406
407def executeTclTest(test, litConfig):
408 if test.config.unsupported:
409 return (Test.UNSUPPORTED, 'Test is unsupported')
410
411 res = parseIntegratedTestScript(test, True, False)
412 if len(res) == 2:
413 return res
414
415 script, xfails, xtargets, tmpBase, execdir = res
416
417 if litConfig.noExecute:
418 return (Test.PASS, '')
419
420 # Create the output directory if it does not already exist.
421 Util.mkdir_p(os.path.dirname(tmpBase))
422
423 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
424 if len(res) == 2:
425 return res
426
427 isXFail = False
428 for item in xfails:
429 if item == '*' or item in test.suite.config.target_triple:
430 isXFail = True
431 break
432
433 # If this is XFAIL, see if it is expected to pass on this target.
434 if isXFail:
435 for item in xtargets:
436 if item == '*' or item in test.suite.config.target_triple:
437 isXFail = False
438 break
439
440 out,err,exitCode = res
441 if isXFail:
442 ok = exitCode != 0
443 status = (Test.XPASS, Test.XFAIL)[ok]
444 else:
445 ok = exitCode == 0
446 status = (Test.FAIL, Test.PASS)[ok]
447
448 if ok:
449 return (status,'')
450
451 return formatTestOutput(status, out, err, exitCode, script)
452
453def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
454 if test.config.unsupported:
455 return (Test.UNSUPPORTED, 'Test is unsupported')
456
457 res = parseIntegratedTestScript(test, False, requireAndAnd)
458 if len(res) == 2:
459 return res
460
461 script, xfails, xtargets, tmpBase, execdir = res
462
463 if litConfig.noExecute:
464 return (Test.PASS, '')
465
466 # Create the output directory if it does not already exist.
467 Util.mkdir_p(os.path.dirname(tmpBase))
468
469 if useExternalSh:
470 res = executeScript(test, litConfig, tmpBase, script, execdir)
471 else:
472 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
473 if len(res) == 2:
474 return res
475
476 out,err,exitCode = res
477 if xfails:
478 ok = exitCode != 0
479 status = (Test.XPASS, Test.XFAIL)[ok]
480 else:
481 ok = exitCode == 0
482 status = (Test.FAIL, Test.PASS)[ok]
483
484 if ok:
485 return (status,'')
486
487 return formatTestOutput(status, out, err, exitCode, script)