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