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