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