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