Index: utils/lit/lit/TestRunner.py =================================================================== --- utils/lit/lit/TestRunner.py +++ utils/lit/lit/TestRunner.py @@ -110,6 +110,18 @@ self._procs = [] # Python2 doesn't have list.clear() self._doneKillPass = True +class ShellCommandResult(object): + """Captures the result of an individual command.""" + + def __init__(self, command, stdout, stderr, exitCode, timeoutReached, + outputFiles = []): + self.command = command + self.stdout = stdout + self.stderr = stderr + self.exitCode = exitCode + self.timeoutReached = timeoutReached + self.outputFiles = outputFiles + def executeShCmd(cmd, shenv, results, timeout=0): """ Wrapper around _executeShCmd that handles @@ -258,7 +270,7 @@ # FIXME: Actually, this is probably an instance of PR6753. if r[1] == 'a': r[2].seek(0, 2) - opened_files.append(r[2]) + opened_files.append(tuple(r) + (redir_filename,)) result = r[2] final_redirects.append(result) @@ -332,7 +344,7 @@ # need to release any handles we may have on the temporary files (important # on Win32, for example). Since we have already spawned the subprocess, our # handles have already been transferred so we do not need them anymore. - for f in opened_files: + for (name, mode, f, path) in opened_files: f.close() # FIXME: There is probably still deadlock potential here. Yawn. @@ -369,15 +381,35 @@ # Ensure the resulting output is always of string type. try: - out = to_string(out.decode('utf-8')) + if out is None: + out = '' + else: + out = to_string(out.decode('utf-8')) except: out = str(out) try: - err = to_string(err.decode('utf-8')) + if err is None: + err = '' + else: + err = to_string(err.decode('utf-8')) except: err = str(err) - results.append((cmd.commands[i], out, err, res, timeoutHelper.timeoutReached())) + # Gather the redirected output files. + output_files = [] + for (name, mode, f, path) in sorted(opened_files): + if mode in ('w', 'a'): + try: + with open(path) as f: + data = f.read() + except: + data = None + if data != None: + output_files.append((name, path, data)) + + results.append(ShellCommandResult( + cmd.commands[i], out, err, res, timeoutHelper.timeoutReached(), + output_files)) if cmd.pipe_err: # Python treats the exit code as a signed char. if exitCode is None: @@ -422,16 +454,49 @@ except InternalShellError: e = sys.exc_info()[1] exitCode = 127 - results.append((e.command, '', e.message, exitCode, False)) + results.append( + ShellCommandResult(e.command, '', e.message, exitCode, False)) out = err = '' - for i,(cmd, cmd_out, cmd_err, res, timeoutReached) in enumerate(results): - out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args)) - out += 'Command %d Result: %r\n' % (i, res) + for i,result in enumerate(results): + # Write the command line run. + out += '$ %s\n' % (' '.join('"%s"' % s + for s in result.command.args),) + + # If nothing interesting happened, move on. + if litConfig.maxIndividualTestTime == 0 and \ + result.exitCode == 0 and \ + not result.stdout.strip() and not result.stderr.strip(): + continue + + # Otherwise, something failed or was printed, show it. + + # Add the command output, if redirected. + for (name, path, data) in result.outputFiles: + if data.strip(): + out += "# redirected output from %r:\n" % (name,) + data = to_string(data.decode('utf-8')) + if len(data) > 1024: + out += data[:1024] + "\n...\n" + out += "note: data was truncated\n" + else: + out += data + out += "\n" + + if result.stdout.strip(): + out += '# command output:\n%s\n' % (result.stdout,) + if result.stderr.strip(): + out += '# command stderr:\n%s\n' % (result.stderr,) + if not result.stdout.strip() and not result.stderr.strip(): + out += "note: command had no output on stdout or stderr\n" + + # Show the error conditions: + if result.exitCode != 0: + out += "error: command failed with exit status: %d\n" % ( + result.exitCode,) if litConfig.maxIndividualTestTime > 0: - out += 'Command %d Reached Timeout: %s\n\n' % (i, str(timeoutReached)) - out += 'Command %d Output:\n%s\n\n' % (i, cmd_out) - out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err) + out += 'error: command reached timeout: %s\n' % ( + i, str(result.timeoutReached)) return out, err, exitCode, timeoutInfo Index: utils/lit/tests/Inputs/shtest-output-printing/basic.txt =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-output-printing/basic.txt @@ -0,0 +1,3 @@ +# RUN: true +# RUN: echo hi +# RUN: wc missing-file &> %t.out Index: utils/lit/tests/Inputs/shtest-output-printing/lit.cfg =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-output-printing/lit.cfg @@ -0,0 +1,4 @@ +import lit.formats +config.name = 'shtest-output-printing' +config.suffixes = ['.txt'] +config.test_format = lit.formats.ShTest(execute_external=False) Index: utils/lit/tests/shtest-format.py =================================================================== --- utils/lit/tests/shtest-format.py +++ utils/lit/tests/shtest-format.py @@ -39,9 +39,8 @@ # # CHECK: Command Output (stdout): # CHECK-NEXT: -- -# CHECK-NEXT: Command 0: "printf" -# CHECK-NEXT: Command 0 Result: 0 -# CHECK-NEXT: Command 0 Output: +# CHECK-NEXT: $ "printf" +# CHECK-NEXT: # command output: # CHECK-NEXT: line 1: failed test output on stdout # CHECK-NEXT: line 2: failed test output on stdout Index: utils/lit/tests/shtest-output-printing.py =================================================================== --- /dev/null +++ utils/lit/tests/shtest-output-printing.py @@ -0,0 +1,28 @@ +# Check the various features of the ShTest format. +# +# RUN: not %{lit} -j 1 -v %{inputs}/shtest-output-printing > %t.out +# RUN: FileCheck --input-file %t.out %s +# +# END. + +# CHECK: -- Testing: + +# CHECK: FAIL: shtest-output-printing :: basic.txt +# CHECK-NEXT: *** TEST 'shtest-output-printing :: basic.txt' FAILED *** +# CHECK-NEXT: Script: +# CHECK-NEXT: -- +# CHECK: -- +# CHECK-NEXT: Exit Code: 1 +# +# CHECK: Command Output +# CHECK-NEXT: -- +# CHECK-NEXT: $ "true" +# CHECK-NEXT: $ "echo" "hi" +# CHECK-NEXT: # command output: +# CHECK-NEXT: hi +# +# CHECK: $ "wc" "missing-file" +# CHECK-NEXT: # redirected output from '{{.*}}/basic.txt.tmp.out': +# CHECK-NEXT: missing-file{{.*}} No such file or directory +# CHECK: note: command had no output on stdout or stderr +# CHECK-NEXT: error: command failed with exit status: 1 Index: utils/lit/tests/shtest-shell.py =================================================================== --- utils/lit/tests/shtest-shell.py +++ utils/lit/tests/shtest-shell.py @@ -1,7 +1,7 @@ # Check the internal shell handling component of the ShTest format. # # RUN: not %{lit} -j 1 -v %{inputs}/shtest-shell > %t.out -# RUN: FileCheck < %t.out %s +# RUN: FileCheck --input-file %t.out %s # # END. @@ -9,10 +9,10 @@ # CHECK: FAIL: shtest-shell :: error-0.txt # CHECK: *** TEST 'shtest-shell :: error-0.txt' FAILED *** -# CHECK: Command 0: "not-a-real-command" -# CHECK: Command 0 Result: 127 -# CHECK: Command 0 Stderr: +# CHECK: $ "not-a-real-command" +# CHECK: # command stderr: # CHECK: 'not-a-real-command': command not found +# CHECK: error: command failed with exit status: 127 # CHECK: *** # FIXME: The output here sucks.