diff --git a/llvm/docs/CommandGuide/lit.rst b/llvm/docs/CommandGuide/lit.rst --- a/llvm/docs/CommandGuide/lit.rst +++ b/llvm/docs/CommandGuide/lit.rst @@ -97,6 +97,9 @@ option also causes those no-op commands to be echoed to stdout to help you locate the source line of the failed command. + The failing command and corresponding output are highlighted in color if the + terminal supports it. + .. option:: -vv, --echo-all-commands Alias for ``-v``/``--verbose`` (for backwards compatibility). diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -17,6 +17,7 @@ except ImportError: from io import StringIO +from lit.ProgressBar import TerminalController import lit.OutputSettings as OutputSettings from lit.ShCommands import GlobItem, Command import lit.ShUtil as ShUtil @@ -1578,7 +1579,12 @@ last_run_line_str = last_run_line.group(0)[delta:] return (last_run_line.start(), last_run_line_str) -def make_script_output(lit_config, script_lines, exit_code): +def highlight_failure_lines(text): + term = TerminalController() + return term.render('${BOLD}${RED}%s${NORMAL}' % text) + +def make_script_output(lit_config, script_lines, stdout, stderr, test_status, + exit_code): def make_output(script_display_lines): return ("""Script:\n--\n%s\n--\nExit Code: %d\n""" % ('\n'.join(script_display_lines), exit_code)) @@ -1586,11 +1592,36 @@ def default_output(): return make_output(script_lines) + def split_on_substring(strings, substring): + for i, s in enumerate(strings): + if substring in s: + return (strings[:i], s, strings[i+1:]) + return (None, None, None) + + def highlighted_output(last_run_line): + (pre, failing_line, post) = split_on_substring(script_lines, + last_run_line) + if failing_line is None: + return default_output() + highlighted_failing_line = highlight_failure_lines(failing_line) + return make_output(pre + [highlighted_failing_line] + post) + if lit_config.script_output_style == OutputSettings.NO_SCRIPT: return "" assert(lit_config.script_output_style == OutputSettings.FULL_SCRIPT) - return default_output() + + if not test_status.isFailure: + return default_output() + last_run_line = None + if stderr is not None: + _, last_run_line = locate_last_run_line(stderr) + if (last_run_line is None) and (stdout is not None): + _, last_run_line = locate_last_run_line(stdout) + if last_run_line is None: + return default_output() + + return highlighted_output(last_run_line) def make_command_output(lit_config, cmd_output, stream_name, test_status): def make_output(output_str, is_truncated=False): @@ -1621,7 +1652,9 @@ assert(lit_config.command_output_style == OutputSettings.UP_TO_AND_INCLUDING_FAILING_COMMAND) - return make_output(cmd_output, is_truncated=False) + failing_lines = highlight_failure_lines(cmd_output[line_start:]) + return make_output(cmd_output[:line_start] + failing_lines, + is_truncated=False) def _runShTest(test, litConfig, useExternalSh, script, tmpBase): def runOnce(execdir): @@ -1663,7 +1696,7 @@ status = Test.FLAKYPASS # Form the output log. - output = make_script_output(litConfig, script, exitCode) + output = make_script_output(litConfig, script, out, err, status, exitCode) if timeoutInfo is not None: output += """Timeout: %s\n""" % (timeoutInfo,)