PR10867. lit would interpret
[oota-llvm.git] / utils / lit / lit / TestRunner.py
1 import os, signal, subprocess, sys
2 import StringIO
3
4 import ShUtil
5 import Test
6 import Util
7
8 import platform
9 import tempfile
10
11 import re
12
13 class InternalShellError(Exception):
14     def __init__(self, command, message):
15         self.command = command
16         self.message = message
17
18 kIsWindows = platform.system() == 'Windows'
19
20 # Don't use close_fds on Windows.
21 kUseCloseFDs = not kIsWindows
22
23 # Use temporary files to replace /dev/null on Windows.
24 kAvoidDevNull = kIsWindows
25
26 def 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
45 def 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 == '&':
52             raise NotImplementedError,"unsupported test command: '&'"
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
59         if cmd.op == '&&':
60             res = executeShCmd(cmd.lhs, cfg, cwd, results)
61             if res is None:
62                 return res
63
64             if res == 0:
65                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
66             return res
67
68         raise ValueError,'Unknown shell command: %r' % cmd.op
69
70     assert isinstance(cmd, ShUtil.Pipeline)
71     procs = []
72     input = subprocess.PIPE
73     stderrTempFiles = []
74     opened_files = []
75     named_temp_files = []
76     # To avoid deadlock, we use a single stderr stream for piped
77     # output. This is null until we have seen some output using
78     # stderr.
79     for i,j in enumerate(cmd.commands):
80         # Apply the redirections, we use (N,) as a sentinal to indicate stdin,
81         # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
82         # from a file are represented with a list [file, mode, file-object]
83         # where file-object is initially None.
84         redirects = [(0,), (1,), (2,)]
85         for r in j.redirects:
86             if r[0] == ('>',2):
87                 redirects[2] = [r[1], 'w', None]
88             elif r[0] == ('>>',2):
89                 redirects[2] = [r[1], 'a', None]
90             elif r[0] == ('>&',2) and r[1] in '012':
91                 redirects[2] = redirects[int(r[1])]
92             elif r[0] == ('>&',) or r[0] == ('&>',):
93                 redirects[1] = redirects[2] = [r[1], 'w', None]
94             elif r[0] == ('>',):
95                 redirects[1] = [r[1], 'w', None]
96             elif r[0] == ('>>',):
97                 redirects[1] = [r[1], 'a', None]
98             elif r[0] == ('<',):
99                 redirects[0] = [r[1], 'r', None]
100             else:
101                 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
102
103         # Map from the final redirections to something subprocess can handle.
104         final_redirects = []
105         for index,r in enumerate(redirects):
106             if r == (0,):
107                 result = input
108             elif r == (1,):
109                 if index == 0:
110                     raise NotImplementedError,"Unsupported redirect for stdin"
111                 elif index == 1:
112                     result = subprocess.PIPE
113                 else:
114                     result = subprocess.STDOUT
115             elif r == (2,):
116                 if index != 2:
117                     raise NotImplementedError,"Unsupported redirect on stdout"
118                 result = subprocess.PIPE
119             else:
120                 if r[2] is None:
121                     if kAvoidDevNull and r[0] == '/dev/null':
122                         r[2] = tempfile.TemporaryFile(mode=r[1])
123                     else:
124                         r[2] = open(r[0], r[1])
125                     # Workaround a Win32 and/or subprocess bug when appending.
126                     #
127                     # FIXME: Actually, this is probably an instance of PR6753.
128                     if r[1] == 'a':
129                         r[2].seek(0, 2)
130                     opened_files.append(r[2])
131                 result = r[2]
132             final_redirects.append(result)
133
134         stdin, stdout, stderr = final_redirects
135
136         # If stderr wants to come from stdout, but stdout isn't a pipe, then put
137         # stderr on a pipe and treat it as stdout.
138         if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
139             stderr = subprocess.PIPE
140             stderrIsStdout = True
141         else:
142             stderrIsStdout = False
143
144             # Don't allow stderr on a PIPE except for the last
145             # process, this could deadlock.
146             #
147             # FIXME: This is slow, but so is deadlock.
148             if stderr == subprocess.PIPE and j != cmd.commands[-1]:
149                 stderr = tempfile.TemporaryFile(mode='w+b')
150                 stderrTempFiles.append((i, stderr))
151
152         # Resolve the executable path ourselves.
153         args = list(j.args)
154         args[0] = Util.which(args[0], cfg.environment['PATH'])
155         if not args[0]:
156             raise InternalShellError(j, '%r: command not found' % j.args[0])
157
158         # Replace uses of /dev/null with temporary files.
159         if kAvoidDevNull:
160             for i,arg in enumerate(args):
161                 if arg == "/dev/null":
162                     f = tempfile.NamedTemporaryFile(delete=False)
163                     f.close()
164                     named_temp_files.append(f.name)
165                     args[i] = f.name
166
167         procs.append(subprocess.Popen(args, cwd=cwd,
168                                       stdin = stdin,
169                                       stdout = stdout,
170                                       stderr = stderr,
171                                       env = cfg.environment,
172                                       close_fds = kUseCloseFDs))
173
174         # Immediately close stdin for any process taking stdin from us.
175         if stdin == subprocess.PIPE:
176             procs[-1].stdin.close()
177             procs[-1].stdin = None
178
179         # Update the current stdin source.
180         if stdout == subprocess.PIPE:
181             input = procs[-1].stdout
182         elif stderrIsStdout:
183             input = procs[-1].stderr
184         else:
185             input = subprocess.PIPE
186
187     # Explicitly close any redirected files. We need to do this now because we
188     # need to release any handles we may have on the temporary files (important
189     # on Win32, for example). Since we have already spawned the subprocess, our
190     # handles have already been transferred so we do not need them anymore.
191     for f in opened_files:
192         f.close()
193
194     # FIXME: There is probably still deadlock potential here. Yawn.
195     procData = [None] * len(procs)
196     procData[-1] = procs[-1].communicate()
197
198     for i in range(len(procs) - 1):
199         if procs[i].stdout is not None:
200             out = procs[i].stdout.read()
201         else:
202             out = ''
203         if procs[i].stderr is not None:
204             err = procs[i].stderr.read()
205         else:
206             err = ''
207         procData[i] = (out,err)
208
209     # Read stderr out of the temp files.
210     for i,f in stderrTempFiles:
211         f.seek(0, 0)
212         procData[i] = (procData[i][0], f.read())
213
214     exitCode = None
215     for i,(out,err) in enumerate(procData):
216         res = procs[i].wait()
217         # Detect Ctrl-C in subprocess.
218         if res == -signal.SIGINT:
219             raise KeyboardInterrupt
220
221         results.append((cmd.commands[i], out, err, res))
222         if cmd.pipe_err:
223             # Python treats the exit code as a signed char.
224             if res < 0:
225                 exitCode = min(exitCode, res)
226             else:
227                 exitCode = max(exitCode, res)
228         else:
229             exitCode = res
230
231     # Remove any named temporary files we created.
232     for f in named_temp_files:
233         try:
234             os.remove(f)
235         except OSError:
236             pass
237
238     if cmd.negate:
239         exitCode = not exitCode
240
241     return exitCode
242
243 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
244     cmds = []
245     for ln in commands:
246         try:
247             cmds.append(ShUtil.ShParser(ln, litConfig.isWindows).parse())
248         except:
249             return (Test.FAIL, "shell parser error on: %r" % ln)
250
251     cmd = cmds[0]
252     for c in cmds[1:]:
253         cmd = ShUtil.Seq(cmd, '&&', c)
254
255     results = []
256     try:
257         exitCode = executeShCmd(cmd, test.config, cwd, results)
258     except InternalShellError,e:
259         out = ''
260         err = e.message
261         exitCode = 255
262
263     out = err = ''
264     for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
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
272 def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
273     import TclUtil
274     cmds = []
275     for ln in commands:
276         # Given the unfortunate way LLVM's test are written, the line gets
277         # backslash substitution done twice.
278         ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
279
280         try:
281             tokens = list(TclUtil.TclLexer(ln).lex())
282         except:
283             return (Test.FAIL, "Tcl lexer error on: %r" % ln)
284
285         # Validate there are no control tokens.
286         for t in tokens:
287             if not isinstance(t, str):
288                 return (Test.FAIL,
289                         "Invalid test line: %r containing %r" % (ln, t))
290
291         try:
292             cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
293         except:
294             return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
295
296     if litConfig.useValgrind:
297         for pipeline in cmds:
298             if pipeline.commands:
299                 # Only valgrind the first command in each pipeline, to avoid
300                 # valgrinding things like grep, not, and FileCheck.
301                 cmd = pipeline.commands[0]
302                 cmd.args = litConfig.valgrindArgs + cmd.args
303
304     cmd = cmds[0]
305     for c in cmds[1:]:
306         cmd = ShUtil.Seq(cmd, '&&', c)
307
308     # FIXME: This is lame, we shouldn't need bash. See PR5240.
309     bashPath = litConfig.getBashPath()
310     if litConfig.useTclAsSh and bashPath:
311         script = tmpBase + '.script'
312
313         # Write script file
314         f = open(script,'w')
315         print >>f, 'set -o pipefail'
316         cmd.toShell(f, pipefail = True)
317         f.close()
318
319         if 0:
320             print >>sys.stdout, cmd
321             print >>sys.stdout, open(script).read()
322             print >>sys.stdout
323             return '', '', 0
324
325         command = [litConfig.getBashPath(), script]
326         out,err,exitCode = executeCommand(command, cwd=cwd,
327                                           env=test.config.environment)
328
329         return out,err,exitCode
330     else:
331         results = []
332         try:
333             exitCode = executeShCmd(cmd, test.config, cwd, results)
334         except InternalShellError,e:
335             results.append((e.command, '', e.message + '\n', 255))
336             exitCode = 255
337
338     out = err = ''
339
340     for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
341         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
342         out += 'Command %d Result: %r\n' % (i, res)
343         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
344         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
345
346     return out, err, exitCode
347
348 def executeScript(test, litConfig, tmpBase, commands, cwd):
349     bashPath = litConfig.getBashPath();
350     isWin32CMDEXE = (litConfig.isWindows and not bashPath)
351     script = tmpBase + '.script'
352     if isWin32CMDEXE:
353         script += '.bat'
354
355     # Write script file
356     f = open(script,'w')
357     if isWin32CMDEXE:
358         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
359     else:
360         f.write(' &&\n'.join(commands))
361     f.write('\n')
362     f.close()
363
364     if isWin32CMDEXE:
365         command = ['cmd','/c', script]
366     else:
367         if bashPath:
368             command = [bashPath, script]
369         else:
370             command = ['/bin/sh', script]
371         if litConfig.useValgrind:
372             # FIXME: Running valgrind on sh is overkill. We probably could just
373             # run on clang with no real loss.
374             command = litConfig.valgrindArgs + command
375
376     return executeCommand(command, cwd=cwd, env=test.config.environment)
377
378 def isExpectedFail(test, xfails):
379     # Check if any of the xfails match an available feature or the target.
380     for item in xfails:
381         # If this is the wildcard, it always fails.
382         if item == '*':
383             return True
384
385         # If this is an exact match for one of the features, it fails.
386         if item in test.config.available_features:
387             return True
388
389         # If this is a part of the target triple, it fails.
390         if item in test.suite.config.target_triple:
391             return True
392
393     return False
394
395 def parseIntegratedTestScript(test, normalize_slashes=False,
396                               extra_substitutions=[]):
397     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
398     script and extract the lines to 'RUN' as well as 'XFAIL' and 'REQUIRES'
399     information. The RUN lines also will have variable substitution performed.
400     """
401
402     # Get the temporary location, this is always relative to the test suite
403     # root, not test source root.
404     #
405     # FIXME: This should not be here?
406     sourcepath = test.getSourcePath()
407     sourcedir = os.path.dirname(sourcepath)
408     execpath = test.getExecPath()
409     execdir,execbase = os.path.split(execpath)
410     tmpDir = os.path.join(execdir, 'Output')
411     tmpBase = os.path.join(tmpDir, execbase)
412     if test.index is not None:
413         tmpBase += '_%d' % test.index
414
415     # Normalize slashes, if requested.
416     if normalize_slashes:
417         sourcepath = sourcepath.replace('\\', '/')
418         sourcedir = sourcedir.replace('\\', '/')
419         tmpDir = tmpDir.replace('\\', '/')
420         tmpBase = tmpBase.replace('\\', '/')
421
422     # We use #_MARKER_# to hide %% while we do the other substitutions.
423     substitutions = list(extra_substitutions)
424     substitutions.extend([('%%', '#_MARKER_#')])
425     substitutions.extend(test.config.substitutions)
426     substitutions.extend([('%s', sourcepath),
427                           ('%S', sourcedir),
428                           ('%p', sourcedir),
429                           ('%{pathsep}', os.pathsep),
430                           ('%t', tmpBase + '.tmp'),
431                           ('%T', tmpDir),
432                           # FIXME: Remove this once we kill DejaGNU.
433                           ('%abs_tmp', tmpBase + '.tmp'),
434                           ('#_MARKER_#', '%')])
435
436     # Collect the test lines from the script.
437     script = []
438     xfails = []
439     requires = []
440     line_number = 0
441     for ln in open(sourcepath):
442         line_number += 1
443         if 'RUN:' in ln:
444             # Isolate the command to run.
445             index = ln.index('RUN:')
446             ln = ln[index+4:]
447
448             # Trim trailing whitespace.
449             ln = ln.rstrip()
450
451             # Substitute line number expressions
452             ln = re.sub('%\(line\)', str(line_number), ln)
453             def replace_line_number(match):
454                 if match.group(1) == '+':
455                     return str(line_number + int(match.group(2)))
456                 if match.group(1) == '-':
457                     return str(line_number - int(match.group(2)))
458             ln = re.sub('%\(line *([\+-]) *(\d+)\)', replace_line_number, ln)
459
460             # Collapse lines with trailing '\\'.
461             if script and script[-1][-1] == '\\':
462                 script[-1] = script[-1][:-1] + ln
463             else:
464                 script.append(ln)
465         elif 'XFAIL:' in ln:
466             items = ln[ln.index('XFAIL:') + 6:].split(',')
467             xfails.extend([s.strip() for s in items])
468         elif 'REQUIRES:' in ln:
469             items = ln[ln.index('REQUIRES:') + 9:].split(',')
470             requires.extend([s.strip() for s in items])
471         elif 'END.' in ln:
472             # Check for END. lines.
473             if ln[ln.index('END.'):].strip() == 'END.':
474                 break
475
476     # Apply substitutions to the script.  Allow full regular
477     # expression syntax.  Replace each matching occurrence of regular
478     # expression pattern a with substitution b in line ln.
479     def processLine(ln):
480         # Apply substitutions
481         for a,b in substitutions:
482             if kIsWindows:
483                 b = b.replace("\\","\\\\")
484             ln = re.sub(a, b, ln)
485
486         # Strip the trailing newline and any extra whitespace.
487         return ln.strip()
488     script = map(processLine, script)
489
490     # Verify the script contains a run line.
491     if not script:
492         return (Test.UNRESOLVED, "Test has no run line!")
493
494     # Check for unterminated run lines.
495     if script[-1][-1] == '\\':
496         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
497
498     # Check that we have the required features:
499     missing_required_features = [f for f in requires
500                                  if f not in test.config.available_features]
501     if missing_required_features:
502         msg = ', '.join(missing_required_features)
503         return (Test.UNSUPPORTED,
504                 "Test requires the following features: %s" % msg)
505
506     isXFail = isExpectedFail(test, xfails)
507     return script,isXFail,tmpBase,execdir
508
509 def formatTestOutput(status, out, err, exitCode, failDueToStderr, script):
510     output = StringIO.StringIO()
511     print >>output, "Script:"
512     print >>output, "--"
513     print >>output, '\n'.join(script)
514     print >>output, "--"
515     print >>output, "Exit Code: %r" % exitCode,
516     if failDueToStderr:
517         print >>output, "(but there was output on stderr)"
518     else:
519         print >>output
520     if out:
521         print >>output, "Command Output (stdout):"
522         print >>output, "--"
523         output.write(out)
524         print >>output, "--"
525     if err:
526         print >>output, "Command Output (stderr):"
527         print >>output, "--"
528         output.write(err)
529         print >>output, "--"
530     return (status, output.getvalue())
531
532 def executeTclTest(test, litConfig):
533     if test.config.unsupported:
534         return (Test.UNSUPPORTED, 'Test is unsupported')
535
536     # Parse the test script, normalizing slashes in substitutions on Windows
537     # (since otherwise Tcl style lexing will treat them as escapes).
538     res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
539     if len(res) == 2:
540         return res
541
542     script, isXFail, tmpBase, execdir = res
543
544     if litConfig.noExecute:
545         return (Test.PASS, '')
546
547     # Create the output directory if it does not already exist.
548     Util.mkdir_p(os.path.dirname(tmpBase))
549
550     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
551     if len(res) == 2:
552         return res
553
554     # Test for failure. In addition to the exit code, Tcl commands are
555     # considered to fail if there is any standard error output.
556     out,err,exitCode = res
557     if isXFail:
558         ok = exitCode != 0 or err and not litConfig.ignoreStdErr
559         if ok:
560             status = Test.XFAIL
561         else:
562             status = Test.XPASS
563     else:
564         ok = exitCode == 0 and (not err or litConfig.ignoreStdErr)
565         if ok:
566             status = Test.PASS
567         else:
568             status = Test.FAIL
569
570     if ok:
571         return (status,'')
572
573     # Set a flag for formatTestOutput so it can explain why the test was
574     # considered to have failed, despite having an exit code of 0.
575     failDueToStderr = exitCode == 0 and err and not litConfig.ignoreStdErr
576
577     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
578
579 def executeShTest(test, litConfig, useExternalSh,
580                   extra_substitutions=[]):
581     if test.config.unsupported:
582         return (Test.UNSUPPORTED, 'Test is unsupported')
583
584     res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
585     if len(res) == 2:
586         return res
587
588     script, isXFail, tmpBase, execdir = res
589
590     if litConfig.noExecute:
591         return (Test.PASS, '')
592
593     # Create the output directory if it does not already exist.
594     Util.mkdir_p(os.path.dirname(tmpBase))
595
596     if useExternalSh:
597         res = executeScript(test, litConfig, tmpBase, script, execdir)
598     else:
599         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
600     if len(res) == 2:
601         return res
602
603     out,err,exitCode = res
604     if isXFail:
605         ok = exitCode != 0
606         if ok:
607             status = Test.XFAIL
608         else:
609             status = Test.XPASS
610     else:
611         ok = exitCode == 0
612         if ok:
613             status = Test.PASS
614         else:
615             status = Test.FAIL
616
617     if ok:
618         return (status,'')
619
620     # Sh tests are not considered to fail just from stderr output.
621     failDueToStderr = False
622
623     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)