]> git.proxmox.com Git - rustc.git/blame - src/llvm/utils/lit/lit/TestRunner.py
Imported Upstream version 0.7
[rustc.git] / src / llvm / utils / lit / lit / TestRunner.py
CommitLineData
223e47cc
LB
1import os, signal, subprocess, sys
2import StringIO
3
4import ShUtil
5import Test
6import Util
7
8import platform
9import tempfile
10
11import re
12
13class InternalShellError(Exception):
14 def __init__(self, command, message):
15 self.command = command
16 self.message = message
17
18kIsWindows = platform.system() == 'Windows'
19
20# Don't use close_fds on Windows.
21kUseCloseFDs = not kIsWindows
22
23# Use temporary files to replace /dev/null on Windows.
24kAvoidDevNull = kIsWindows
25
26def executeCommand(command, cwd=None, env=None):
27 # Close extra file handles on UNIX (on Windows this cannot be done while
28 # also redirecting input).
29 close_fds = not kIsWindows
30
31 p = subprocess.Popen(command, cwd=cwd,
32 stdin=subprocess.PIPE,
33 stdout=subprocess.PIPE,
34 stderr=subprocess.PIPE,
35 env=env, close_fds=close_fds)
36 out,err = p.communicate()
37 exitCode = p.wait()
38
39 # Detect Ctrl-C in subprocess.
40 if exitCode == -signal.SIGINT:
41 raise KeyboardInterrupt
42
43 return out, err, exitCode
44
45def executeShCmd(cmd, cfg, cwd, results):
46 if isinstance(cmd, ShUtil.Seq):
47 if cmd.op == ';':
48 res = executeShCmd(cmd.lhs, cfg, cwd, results)
49 return executeShCmd(cmd.rhs, cfg, cwd, results)
50
51 if cmd.op == '&':
970d7e83 52 raise InternalShellError(cmd,"unsupported shell operator: '&'")
223e47cc
LB
53
54 if cmd.op == '||':
55 res = executeShCmd(cmd.lhs, cfg, cwd, results)
56 if res != 0:
57 res = executeShCmd(cmd.rhs, cfg, cwd, results)
58 return res
970d7e83 59
223e47cc
LB
60 if cmd.op == '&&':
61 res = executeShCmd(cmd.lhs, cfg, cwd, results)
62 if res is None:
63 return res
64
65 if res == 0:
66 res = executeShCmd(cmd.rhs, cfg, cwd, results)
67 return res
68
69 raise ValueError,'Unknown shell command: %r' % cmd.op
70
71 assert isinstance(cmd, ShUtil.Pipeline)
72 procs = []
73 input = subprocess.PIPE
74 stderrTempFiles = []
75 opened_files = []
76 named_temp_files = []
77 # To avoid deadlock, we use a single stderr stream for piped
78 # output. This is null until we have seen some output using
79 # stderr.
80 for i,j in enumerate(cmd.commands):
970d7e83 81 # Apply the redirections, we use (N,) as a sentinel to indicate stdin,
223e47cc
LB
82 # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
83 # from a file are represented with a list [file, mode, file-object]
84 # where file-object is initially None.
85 redirects = [(0,), (1,), (2,)]
86 for r in j.redirects:
87 if r[0] == ('>',2):
88 redirects[2] = [r[1], 'w', None]
89 elif r[0] == ('>>',2):
90 redirects[2] = [r[1], 'a', None]
91 elif r[0] == ('>&',2) and r[1] in '012':
92 redirects[2] = redirects[int(r[1])]
93 elif r[0] == ('>&',) or r[0] == ('&>',):
94 redirects[1] = redirects[2] = [r[1], 'w', None]
95 elif r[0] == ('>',):
96 redirects[1] = [r[1], 'w', None]
97 elif r[0] == ('>>',):
98 redirects[1] = [r[1], 'a', None]
99 elif r[0] == ('<',):
100 redirects[0] = [r[1], 'r', None]
101 else:
970d7e83 102 raise InternalShellError(j,"Unsupported redirect: %r" % (r,))
223e47cc
LB
103
104 # Map from the final redirections to something subprocess can handle.
105 final_redirects = []
106 for index,r in enumerate(redirects):
107 if r == (0,):
108 result = input
109 elif r == (1,):
110 if index == 0:
970d7e83 111 raise InternalShellError(j,"Unsupported redirect for stdin")
223e47cc
LB
112 elif index == 1:
113 result = subprocess.PIPE
114 else:
115 result = subprocess.STDOUT
116 elif r == (2,):
117 if index != 2:
970d7e83 118 raise InternalShellError(j,"Unsupported redirect on stdout")
223e47cc
LB
119 result = subprocess.PIPE
120 else:
121 if r[2] is None:
122 if kAvoidDevNull and r[0] == '/dev/null':
123 r[2] = tempfile.TemporaryFile(mode=r[1])
124 else:
125 r[2] = open(r[0], r[1])
126 # Workaround a Win32 and/or subprocess bug when appending.
127 #
128 # FIXME: Actually, this is probably an instance of PR6753.
129 if r[1] == 'a':
130 r[2].seek(0, 2)
131 opened_files.append(r[2])
132 result = r[2]
133 final_redirects.append(result)
134
135 stdin, stdout, stderr = final_redirects
136
137 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
138 # stderr on a pipe and treat it as stdout.
139 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
140 stderr = subprocess.PIPE
141 stderrIsStdout = True
142 else:
143 stderrIsStdout = False
144
145 # Don't allow stderr on a PIPE except for the last
146 # process, this could deadlock.
147 #
148 # FIXME: This is slow, but so is deadlock.
149 if stderr == subprocess.PIPE and j != cmd.commands[-1]:
150 stderr = tempfile.TemporaryFile(mode='w+b')
151 stderrTempFiles.append((i, stderr))
152
153 # Resolve the executable path ourselves.
154 args = list(j.args)
155 args[0] = Util.which(args[0], cfg.environment['PATH'])
156 if not args[0]:
157 raise InternalShellError(j, '%r: command not found' % j.args[0])
158
159 # Replace uses of /dev/null with temporary files.
160 if kAvoidDevNull:
161 for i,arg in enumerate(args):
162 if arg == "/dev/null":
163 f = tempfile.NamedTemporaryFile(delete=False)
164 f.close()
165 named_temp_files.append(f.name)
166 args[i] = f.name
167
168 procs.append(subprocess.Popen(args, cwd=cwd,
169 stdin = stdin,
170 stdout = stdout,
171 stderr = stderr,
172 env = cfg.environment,
173 close_fds = kUseCloseFDs))
174
175 # Immediately close stdin for any process taking stdin from us.
176 if stdin == subprocess.PIPE:
177 procs[-1].stdin.close()
178 procs[-1].stdin = None
179
180 # Update the current stdin source.
181 if stdout == subprocess.PIPE:
182 input = procs[-1].stdout
183 elif stderrIsStdout:
184 input = procs[-1].stderr
185 else:
186 input = subprocess.PIPE
187
188 # Explicitly close any redirected files. We need to do this now because we
189 # need to release any handles we may have on the temporary files (important
190 # on Win32, for example). Since we have already spawned the subprocess, our
191 # handles have already been transferred so we do not need them anymore.
192 for f in opened_files:
193 f.close()
194
195 # FIXME: There is probably still deadlock potential here. Yawn.
196 procData = [None] * len(procs)
197 procData[-1] = procs[-1].communicate()
198
199 for i in range(len(procs) - 1):
200 if procs[i].stdout is not None:
201 out = procs[i].stdout.read()
202 else:
203 out = ''
204 if procs[i].stderr is not None:
205 err = procs[i].stderr.read()
206 else:
207 err = ''
208 procData[i] = (out,err)
209
210 # Read stderr out of the temp files.
211 for i,f in stderrTempFiles:
212 f.seek(0, 0)
213 procData[i] = (procData[i][0], f.read())
214
215 exitCode = None
216 for i,(out,err) in enumerate(procData):
217 res = procs[i].wait()
218 # Detect Ctrl-C in subprocess.
219 if res == -signal.SIGINT:
220 raise KeyboardInterrupt
221
222 results.append((cmd.commands[i], out, err, res))
223 if cmd.pipe_err:
224 # Python treats the exit code as a signed char.
225 if res < 0:
226 exitCode = min(exitCode, res)
227 else:
228 exitCode = max(exitCode, res)
229 else:
230 exitCode = res
231
232 # Remove any named temporary files we created.
233 for f in named_temp_files:
234 try:
235 os.remove(f)
236 except OSError:
237 pass
238
239 if cmd.negate:
240 exitCode = not exitCode
241
242 return exitCode
243
244def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
223e47cc
LB
245 cmds = []
246 for ln in commands:
223e47cc 247 try:
970d7e83 248 cmds.append(ShUtil.ShParser(ln, litConfig.isWindows).parse())
223e47cc 249 except:
970d7e83 250 return (Test.FAIL, "shell parser error on: %r" % ln)
223e47cc
LB
251
252 cmd = cmds[0]
253 for c in cmds[1:]:
254 cmd = ShUtil.Seq(cmd, '&&', c)
255
970d7e83
LB
256 results = []
257 try:
258 exitCode = executeShCmd(cmd, test.config, cwd, results)
259 except InternalShellError,e:
260 exitCode = 127
261 results.append((e.command, '', e.message, exitCode))
223e47cc
LB
262
263 out = err = ''
970d7e83 264 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
223e47cc
LB
265 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
266 out += 'Command %d Result: %r\n' % (i, res)
267 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
268 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
269
270 return out, err, exitCode
271
272def executeScript(test, litConfig, tmpBase, commands, cwd):
273 bashPath = litConfig.getBashPath();
274 isWin32CMDEXE = (litConfig.isWindows and not bashPath)
275 script = tmpBase + '.script'
276 if isWin32CMDEXE:
277 script += '.bat'
278
279 # Write script file
280 f = open(script,'w')
281 if isWin32CMDEXE:
282 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
283 else:
970d7e83 284 f.write('{ ' + '; } &&\n{ '.join(commands) + '; }')
223e47cc
LB
285 f.write('\n')
286 f.close()
287
288 if isWin32CMDEXE:
289 command = ['cmd','/c', script]
290 else:
291 if bashPath:
292 command = [bashPath, script]
293 else:
294 command = ['/bin/sh', script]
295 if litConfig.useValgrind:
296 # FIXME: Running valgrind on sh is overkill. We probably could just
297 # run on clang with no real loss.
298 command = litConfig.valgrindArgs + command
299
300 return executeCommand(command, cwd=cwd, env=test.config.environment)
301
970d7e83
LB
302def isExpectedFail(test, xfails):
303 # Check if any of the xfails match an available feature or the target.
223e47cc 304 for item in xfails:
970d7e83
LB
305 # If this is the wildcard, it always fails.
306 if item == '*':
307 return True
223e47cc 308
970d7e83
LB
309 # If this is an exact match for one of the features, it fails.
310 if item in test.config.available_features:
311 return True
223e47cc 312
970d7e83
LB
313 # If this is a part of the target triple, it fails.
314 if item in test.suite.config.target_triple:
315 return True
316
317 return False
223e47cc
LB
318
319def parseIntegratedTestScript(test, normalize_slashes=False,
320 extra_substitutions=[]):
321 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
970d7e83 322 script and extract the lines to 'RUN' as well as 'XFAIL' and 'REQUIRES'
223e47cc
LB
323 information. The RUN lines also will have variable substitution performed.
324 """
325
326 # Get the temporary location, this is always relative to the test suite
327 # root, not test source root.
328 #
329 # FIXME: This should not be here?
330 sourcepath = test.getSourcePath()
331 sourcedir = os.path.dirname(sourcepath)
332 execpath = test.getExecPath()
333 execdir,execbase = os.path.split(execpath)
334 tmpDir = os.path.join(execdir, 'Output')
335 tmpBase = os.path.join(tmpDir, execbase)
336 if test.index is not None:
337 tmpBase += '_%d' % test.index
338
339 # Normalize slashes, if requested.
340 if normalize_slashes:
341 sourcepath = sourcepath.replace('\\', '/')
342 sourcedir = sourcedir.replace('\\', '/')
343 tmpDir = tmpDir.replace('\\', '/')
344 tmpBase = tmpBase.replace('\\', '/')
345
346 # We use #_MARKER_# to hide %% while we do the other substitutions.
347 substitutions = list(extra_substitutions)
348 substitutions.extend([('%%', '#_MARKER_#')])
349 substitutions.extend(test.config.substitutions)
350 substitutions.extend([('%s', sourcepath),
351 ('%S', sourcedir),
352 ('%p', sourcedir),
353 ('%{pathsep}', os.pathsep),
354 ('%t', tmpBase + '.tmp'),
355 ('%T', tmpDir),
223e47cc
LB
356 ('#_MARKER_#', '%')])
357
358 # Collect the test lines from the script.
359 script = []
360 xfails = []
223e47cc 361 requires = []
970d7e83 362 line_number = 0
223e47cc 363 for ln in open(sourcepath):
970d7e83 364 line_number += 1
223e47cc
LB
365 if 'RUN:' in ln:
366 # Isolate the command to run.
367 index = ln.index('RUN:')
368 ln = ln[index+4:]
369
370 # Trim trailing whitespace.
371 ln = ln.rstrip()
372
970d7e83
LB
373 # Substitute line number expressions
374 ln = re.sub('%\(line\)', str(line_number), ln)
375 def replace_line_number(match):
376 if match.group(1) == '+':
377 return str(line_number + int(match.group(2)))
378 if match.group(1) == '-':
379 return str(line_number - int(match.group(2)))
380 ln = re.sub('%\(line *([\+-]) *(\d+)\)', replace_line_number, ln)
381
223e47cc
LB
382 # Collapse lines with trailing '\\'.
383 if script and script[-1][-1] == '\\':
384 script[-1] = script[-1][:-1] + ln
385 else:
386 script.append(ln)
387 elif 'XFAIL:' in ln:
388 items = ln[ln.index('XFAIL:') + 6:].split(',')
389 xfails.extend([s.strip() for s in items])
223e47cc
LB
390 elif 'REQUIRES:' in ln:
391 items = ln[ln.index('REQUIRES:') + 9:].split(',')
392 requires.extend([s.strip() for s in items])
393 elif 'END.' in ln:
394 # Check for END. lines.
395 if ln[ln.index('END.'):].strip() == 'END.':
396 break
397
398 # Apply substitutions to the script. Allow full regular
399 # expression syntax. Replace each matching occurrence of regular
400 # expression pattern a with substitution b in line ln.
401 def processLine(ln):
402 # Apply substitutions
403 for a,b in substitutions:
404 if kIsWindows:
405 b = b.replace("\\","\\\\")
406 ln = re.sub(a, b, ln)
407
408 # Strip the trailing newline and any extra whitespace.
409 return ln.strip()
410 script = map(processLine, script)
411
412 # Verify the script contains a run line.
413 if not script:
414 return (Test.UNRESOLVED, "Test has no run line!")
415
416 # Check for unterminated run lines.
417 if script[-1][-1] == '\\':
418 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
419
420 # Check that we have the required features:
421 missing_required_features = [f for f in requires
422 if f not in test.config.available_features]
423 if missing_required_features:
424 msg = ', '.join(missing_required_features)
425 return (Test.UNSUPPORTED,
426 "Test requires the following features: %s" % msg)
427
970d7e83 428 isXFail = isExpectedFail(test, xfails)
223e47cc
LB
429 return script,isXFail,tmpBase,execdir
430
970d7e83 431def formatTestOutput(status, out, err, exitCode, script):
223e47cc
LB
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,
970d7e83 438 print >>output
223e47cc
LB
439 if out:
440 print >>output, "Command Output (stdout):"
441 print >>output, "--"
442 output.write(out)
443 print >>output, "--"
444 if err:
445 print >>output, "Command Output (stderr):"
446 print >>output, "--"
447 output.write(err)
448 print >>output, "--"
449 return (status, output.getvalue())
450
223e47cc
LB
451def executeShTest(test, litConfig, useExternalSh,
452 extra_substitutions=[]):
453 if test.config.unsupported:
454 return (Test.UNSUPPORTED, 'Test is unsupported')
455
456 res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
457 if len(res) == 2:
458 return res
459
460 script, isXFail, tmpBase, execdir = res
461
462 if litConfig.noExecute:
463 return (Test.PASS, '')
464
465 # Create the output directory if it does not already exist.
466 Util.mkdir_p(os.path.dirname(tmpBase))
467
468 if useExternalSh:
469 res = executeScript(test, litConfig, tmpBase, script, execdir)
470 else:
471 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
472 if len(res) == 2:
473 return res
474
475 out,err,exitCode = res
476 if isXFail:
477 ok = exitCode != 0
478 if ok:
479 status = Test.XFAIL
480 else:
481 status = Test.XPASS
482 else:
483 ok = exitCode == 0
484 if ok:
485 status = Test.PASS
486 else:
487 status = Test.FAIL
488
489 if ok:
490 return (status,'')
491
970d7e83 492 return formatTestOutput(status, out, err, exitCode, script)