def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. Usually entries are lines like these: " x = 1" "> assert x == 2" "E assert 1 == 2" This function takes care of rendering the "source" portions of it (the lines without the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" character, as doing so might break line continuations. """ if not self.lines: return # separate indents and source lines that are not failures: we want to # highlight the code but not the indentation, which may contain markers # such as "> assert 0" fail_marker = "{} ".format(FormattedExcinfo.fail_marker) indent_size = len(fail_marker) indents = [] source_lines = [] failure_lines = [] seeing_failures = False for line in self.lines: is_source_line = not line.startswith(fail_marker) if is_source_line: assert not seeing_failures, ( "Unexpected failure lines between source lines:\n" + "\n".join(self.lines) ) if self.style == "value": source_lines.append(line) else: indents.append(line[:indent_size]) source_lines.append(line[indent_size:]) else: seeing_failures = True failure_lines.append(line) tw._write_source(source_lines, indents) # failure lines are always completely red and bold for line in failure_lines: tw.line(line, bold=True, red=True)
def _get_error_contents_from_report(report): if report.longrepr is not None: try: tw = TerminalWriter(stringio=True) stringio = tw.stringio except TypeError: import io stringio = io.StringIO() tw = TerminalWriter(file=stringio) tw.hasmarkup = False report.toterminal(tw) exc = stringio.getvalue() s = exc.strip() if s: return s return ''
def toterminal(self, out: TerminalWriter) -> None: if hasattr(self, "node"): out.line(getworkerinfoline(self.node)) longrepr = self.longrepr if longrepr is None: return if hasattr(longrepr, "toterminal"): longrepr_terminal = cast(TerminalRepr, longrepr) longrepr_terminal.toterminal(out) else: try: s = str(longrepr) except UnicodeEncodeError: s = "<unprintable longrepr>" out.line(s)
def main(args=None, plugins=None) -> Union[int, ExitCode]: """ return exit code, after performing an in-process test run. :arg args: list of command line arguments. :arg plugins: list of plugin objects to be auto-registered during initialization. """ try: try: config = _prepareconfig(args, plugins) except ConftestImportFailure as e: exc_info = ExceptionInfo(e.excinfo) tw = TerminalWriter(sys.stderr) tw.line( "ImportError while loading conftest '{e.path}'.".format(e=e), red=True ) exc_info.traceback = exc_info.traceback.filter( filter_traceback_for_conftest_import_failure ) exc_repr = ( exc_info.getrepr(style="short", chain=False) if exc_info.traceback else exc_info.exconly() ) formatted_tb = str(exc_repr) for line in formatted_tb.splitlines(): tw.line(line.rstrip(), red=True) return ExitCode.USAGE_ERROR else: try: ret = config.hook.pytest_cmdline_main( config=config ) # type: Union[ExitCode, int] try: return ExitCode(ret) except ValueError: return ret finally: config._ensure_unconfigure() except UsageError as e: tw = TerminalWriter(sys.stderr) for msg in e.args: tw.line("ERROR: {}\n".format(msg), red=True) return ExitCode.USAGE_ERROR
def test_exc_chain_repr_without_traceback(self, importasmod, reason, description): """ Handle representation of exception chains where one of the exceptions doesn't have a real traceback, such as those raised in a subprocess submitted by the multiprocessing module (#1984). """ exc_handling_code = " from e" if reason == "cause" else "" mod = importasmod( """ def f(): try: g() except Exception as e: raise RuntimeError('runtime problem'){exc_handling_code} def g(): raise ValueError('invalid value') """.format( exc_handling_code=exc_handling_code ) ) with pytest.raises(RuntimeError) as excinfo: mod.f() # emulate the issue described in #1984 attr = "__%s__" % reason getattr(excinfo.value, attr).__traceback__ = None r = excinfo.getrepr() file = io.StringIO() tw = TerminalWriter(file=file) tw.hasmarkup = False r.toterminal(tw) matcher = LineMatcher(file.getvalue().splitlines()) matcher.fnmatch_lines( [ "ValueError: invalid value", description, "* except Exception as e:", "> * raise RuntimeError('runtime problem')" + exc_handling_code, "E *RuntimeError: runtime problem", ] )
def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. Usually entries are lines like these: " x = 1" "> assert x == 2" "E assert 1 == 2" This function takes care of rendering the "source" portions of it (the lines without the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" character, as doing so might break line continuations. """ if not self.lines: return # separate indents and source lines that are not failures: we want to # highlight the code but not the indentation, which may contain markers # such as "> assert 0" fail_marker = f"{FormattedExcinfo.fail_marker} " indent_size = len(fail_marker) indents: List[str] = [] source_lines: List[str] = [] failure_lines: List[str] = [] for index, line in enumerate(self.lines): is_failure_line = line.startswith(fail_marker) if is_failure_line: # from this point on all lines are considered part of the failure failure_lines.extend(self.lines[index:]) break else: if self.style == "value": source_lines.append(line) else: indents.append(line[:indent_size]) source_lines.append(line[indent_size:]) tw._write_source(source_lines, indents) # failure lines are always completely red and bold for line in failure_lines: tw.line(line, bold=True, red=True)
def test_format_excinfo(self, importasmod, reproptions): mod = importasmod(""" def g(x): raise ValueError(x) def f(): g(3) """) excinfo = pytest.raises(ValueError, mod.f) tw = TerminalWriter(stringio=True) repr = excinfo.getrepr(**reproptions) repr.toterminal(tw) assert tw.stringio.getvalue()
def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": assert self.reprfileloc is not None self.reprfileloc.toterminal(tw) self._write_entry_lines(tw) if self.reprlocals: self.reprlocals.toterminal(tw, indent=" " * 8) return if self.reprfuncargs: self.reprfuncargs.toterminal(tw) self._write_entry_lines(tw) if self.reprlocals: tw.line("") self.reprlocals.toterminal(tw) if self.reprfileloc: if self.lines: tw.line("") self.reprfileloc.toterminal(tw)
def _write_entry_lines(self, tw: TerminalWriter) -> None: """Writes the source code portions of a list of traceback entries with syntax highlighting. Usually entries are lines like these: " x = 1" "> assert x == 2" "E assert 1 == 2" This function takes care of rendering the "source" portions of it (the lines without the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" character, as doing so might break line continuations. """ indent_size = 4 def is_fail(line): return line.startswith("{} ".format( FormattedExcinfo.fail_marker)) if not self.lines: return # separate indents and source lines that are not failures: we want to # highlight the code but not the indentation, which may contain markers # such as "> assert 0" indents = [] source_lines = [] for line in self.lines: if not is_fail(line): indents.append(line[:indent_size]) source_lines.append(line[indent_size:]) tw._write_source(source_lines, indents) # failure lines are always completely red and bold for line in (x for x in self.lines if is_fail(x)): tw.line(line, bold=True, red=True)
def test_colored_short_level() -> None: logfmt = "%(levelname).1s %(message)s" record = logging.LogRecord( name="dummy", level=logging.INFO, pathname="dummypath", lineno=10, msg="Test Message", args=(), exc_info=None, ) class ColorConfig: class option: pass tw = TerminalWriter() tw.hasmarkup = True formatter = ColoredLevelFormatter(tw, logfmt) output = formatter.format(record) # the I (of INFO) is colored assert output == ("\x1b[32mI\x1b[0m Test Message")
def test_coloredlogformatter() -> None: logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" record = logging.LogRecord( name="dummy", level=logging.INFO, pathname="dummypath", lineno=10, msg="Test Message", args=(), exc_info=None, ) tw = TerminalWriter() tw.hasmarkup = True formatter = ColoredLevelFormatter(tw, logfmt) output = formatter.format(record) assert output == ( "dummypath 10 \x1b[32mINFO \x1b[0m Test Message") tw.hasmarkup = False formatter = ColoredLevelFormatter(tw, logfmt) output = formatter.format(record) assert output == ("dummypath 10 INFO Test Message")
def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None: def bar(): assert False, "some error" def foo(): bar() # using inline functions as opposed to importasmod so we get source code lines # in the tracebacks (otherwise getinspect doesn't find the source code). with pytest.raises(AssertionError) as excinfo: foo() file = io.StringIO() tw = TerminalWriter(file=file) repr = excinfo.getrepr(**reproptions) repr.toterminal(tw) assert file.getvalue()
def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": assert self.reprfileloc is not None self.reprfileloc.toterminal(tw) for line in self.lines: red = line.startswith("E ") tw.line(line, bold=True, red=red) return if self.reprfuncargs: self.reprfuncargs.toterminal(tw) for line in self.lines: red = line.startswith("E ") tw.line(line, bold=True, red=red) if self.reprlocals: tw.line("") self.reprlocals.toterminal(tw) if self.reprfileloc: if self.lines: tw.line("") self.reprfileloc.toterminal(tw)
def toterminal(self, tw: TerminalWriter) -> None: """ Write the failure summary to given ``TerminalWriter``. Text includes failed step number (starting from ``1``), type of failure (explicit fail(), assertion failure, exception) and a traceback formatted in traditional Python style with hidden frames removed. """ exc: StepperException = self.excinfo.value tw.line(f"test execution failed at step #{exc.step_n}, reason: {exc.reason}", red=True) tb = self.format_traceback() tw.line("traceback:") tw.line("\n".join(map(lambda x: str(x).strip(), tb)))
def toterminal(self, tw: TerminalWriter) -> None: # The entries might have different styles. for i, entry in enumerate(self.reprentries): if entry.style == "long": tw.line("") entry.toterminal(tw) if i < len(self.reprentries) - 1: next_entry = self.reprentries[i + 1] if (entry.style == "long" or entry.style == "short" and next_entry.style == "long"): tw.sep(self.entrysep) if self.extraline: tw.line(self.extraline)
def pytest_exception_interact(self, report, call): """ Called when an exception was raised which can potentially be interactively handled. With the `--interactive` flag, outputs the full repr of the failed test and opens an interactive shell using `brownie._cli.console.Console`. Arguments --------- report : _pytest.reports.BaseReport Report object for the failed test. call : _pytest.runner.CallInfo Result/Exception info for the failed test. """ if self.config.getoption("interactive") and report.failed: capman = self.config.pluginmanager.get_plugin("capturemanager") if capman: capman.suspend_global_capture(in_=True) tw = TerminalWriter() report.longrepr.toterminal(tw) locals_dict = call.excinfo.traceback[-1].locals locals_dict = {k: v for k, v in locals_dict.items() if not k.startswith("@")} try: CONFIG.argv["cli"] = "console" shell = Console(self.project, extra_locals={"_callinfo": call, **locals_dict}) shell.interact( banner=f"\nInteractive mode enabled. Use quit() to continue running tests.", exitmsg="", ) except SystemExit: pass finally: CONFIG.argv["cli"] = "test" print("Continuing tests...") if capman: capman.resume_global_capture()
def toterminal(self, tw: TerminalWriter) -> None: if self.args: linesofar = "" for name, value in self.args: ns = f"{name} = {value}" if len(ns) + len(linesofar) + 2 > tw.fullwidth: if linesofar: tw.line(linesofar) linesofar = ns else: if linesofar: linesofar += ", " + ns else: linesofar = ns if linesofar: tw.line(linesofar) tw.line("")
def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._original_fmt = self._style._fmt self._level_to_fmt_mapping: Dict[int, str] = {} assert self._fmt is not None levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) if not levelname_fmt_match: return levelname_fmt = levelname_fmt_match.group() for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): formatted_levelname = levelname_fmt % { "levelname": logging.getLevelName(level) } # add ANSI escape sequences around the formatted levelname color_kwargs = {name: True for name in color_opts} colorized_formatted_levelname = terminalwriter.markup( formatted_levelname, **color_kwargs) self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub( colorized_formatted_levelname, self._fmt)
def create_terminal_writer( config: Config, file: Optional[TextIO] = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. Every code which requires a TerminalWriter object and has access to a config object should use this function. """ tw = TerminalWriter(file=file) if config.option.color == "yes": tw.hasmarkup = True elif config.option.color == "no": tw.hasmarkup = False if config.option.code_highlight == "yes": tw.code_highlight = True elif config.option.code_highlight == "no": tw.code_highlight = False return tw
def toterminal(self, tw: TerminalWriter, indent="") -> None: for line in self.lines: tw.line(indent + line)
def dump_json(self, reports): # Compute the final test outcome outcomes = [report.outcome for report in reports] if "failed" in outcomes: outcome = "failed" elif "skipped" in outcomes: outcome = "skipped" else: outcome = "passed" # Errors error = "" errors = {} for report in reports: if report.outcome == "failed" and report.longrepr: if hasattr(report.longrepr, "toterminal"): # Compute human repre tw = TerminalWriter(stringio=True) tw.hasmarkup = False report.longrepr.toterminal(tw) exc = tw.stringio.getvalue() else: exc = str(report.longrepr) humanrepr = exc.strip() errors[report.when] = {"humanrepr": humanrepr} # Take the first error if not error: error = humanrepr # Skipped skipped_messages = {} for report in reports: if report.outcome == "skipped" and report.longrepr: skipped_messages[report.when] = report.longrepr[2] # Durations total_duration = 0 durations = {} for report in reports: durations[report.when] = report.duration total_duration += report.duration report = reports[-1] # Get logs reports logs = "" for secname, content in report.sections: if secname == "Captured log call": logs = content raw_json_report = { "_type": "test_result", "file": report.fspath, "line": report.location[1] + 1, # Pytest lineno are 0-based "test_name": report.location[2], "duration": total_duration, "durations": durations, "outcome": outcome, "id": report.nodeid, "stdout": report.capstdout, "stderr": report.capstderr, "error": { "humanrepr": error }, "logs": logs, "skipped_messages": skipped_messages, } output(raw_json_report)
def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines))
def toterminal(self, tw: TerminalWriter) -> None: for reprlocation, lines in self.reprlocation_lines: for line in lines: tw.line(line) reprlocation.toterminal(tw)
def pytest_exception_interact(self, report, call): """ Called when an exception was raised which can potentially be interactively handled. With the `--interactive` flag, outputs the full repr of the failed test and opens an interactive shell using `brownie._cli.console.Console`. Arguments --------- report : _pytest.reports.BaseReport Report object for the failed test. call : _pytest.runner.CallInfo Result/Exception info for the failed test. """ if self.config.getoption("interactive") and report.failed: location = self._path(report.location[0]) if location not in self.node_map: # if the exception happened prior to collection it is likely a # SyntaxError and we cannot open an interactive debugger return capman = self.config.pluginmanager.get_plugin("capturemanager") if capman: capman.suspend_global_capture(in_=True) tw = TerminalWriter() report.longrepr.toterminal(tw) # find last traceback frame within the active test excinfo = call.excinfo traceback = next( (i for i in excinfo.traceback[::-1] if Path(i.path).as_posix().endswith(location)), excinfo.traceback[-1], ) # get global namespace globals_dict = traceback.frame.f_globals # filter python internals and pytest internals globals_dict = { k: v for k, v in globals_dict.items() if not k.startswith("__") } globals_dict = { k: v for k, v in globals_dict.items() if not k.startswith("@") } # filter test functions and fixtures test_names = self.node_map[location] globals_dict = { k: v for k, v in globals_dict.items() if k not in test_names } globals_dict = { k: v for k, v in globals_dict.items() if not hasattr(v, "_pytestfixturefunction") } # get local namespace locals_dict = traceback.locals locals_dict = { k: v for k, v in locals_dict.items() if not k.startswith("@") } namespace = {"_callinfo": call, **globals_dict, **locals_dict} if "tx" not in namespace and brownie.history: # make it easier to look at the most recent transaction namespace["tx"] = brownie.history[-1] try: CONFIG.argv["cli"] = "console" shell = Console(self.project, extra_locals=namespace, exit_on_continue=True) banner = ("\nInteractive mode enabled. Type `continue` to" " resume testing or `quit()` to halt execution.") shell.interact(banner=banner, exitmsg="") except SystemExit as exc: if exc.code != "continue": pytest.exit("Test execution halted due to SystemExit") finally: CONFIG.argv["cli"] = "test" print("Continuing tests...") if capman: capman.resume_global_capture()
def pytest_exception_interact(self, report, call): """ Called when an exception was raised which can potentially be interactively handled. With the `--interactive` flag, outputs the full repr of the failed test and opens an interactive shell using `brownie._cli.console.Console`. Arguments --------- report : _pytest.reports.BaseReport Report object for the failed test. call : _pytest.runner.CallInfo Result/Exception info for the failed test. """ if self.config.getoption("interactive") and report.failed: capman = self.config.pluginmanager.get_plugin("capturemanager") if capman: capman.suspend_global_capture(in_=True) tw = TerminalWriter() report.longrepr.toterminal(tw) location = self._path(report.location[0]) # find last traceback frame within the active test traceback = next( (i for i in call.excinfo.traceback[::-1] if i.path.strpath.endswith(location)), call.excinfo.traceback[-1], ) # get global namespace globals_dict = traceback.frame.f_globals # filter python internals and pytest internals globals_dict = { k: v for k, v in globals_dict.items() if not k.startswith("__") } globals_dict = { k: v for k, v in globals_dict.items() if not k.startswith("@") } # filter test functions test_names = self.node_map[location] globals_dict = { k: v for k, v in globals_dict.items() if k not in test_names } # get local namespace locals_dict = traceback.locals locals_dict = { k: v for k, v in locals_dict.items() if not k.startswith("@") } namespace = {"_callinfo": call, **globals_dict, **locals_dict} try: CONFIG.argv["cli"] = "console" shell = Console(self.project, extra_locals=namespace) shell.interact( banner= f"\nInteractive mode enabled. Use quit() to continue running tests.", exitmsg="", ) except SystemExit: pass finally: CONFIG.argv["cli"] = "test" print("Continuing tests...") if capman: capman.resume_global_capture()
def toterminal(self, out: TerminalWriter) -> None: out.line(self.longrepr, red=True)
def toterminal(self, tw: TerminalWriter) -> None: for name, content, sep in self.sections: tw.sep(sep, name) tw.line(content)
def toterminal(self, tw: TerminalWriter) -> None: for line in self.lines: tw.line(line)
def cacheshow(config: Config, session: Session) -> int: from pprint import pformat assert config.cache is not None tw = TerminalWriter() tw.line("cachedir: " + str(config.cache._cachedir)) if not config.cache._cachedir.is_dir(): tw.line("cache is empty") return 0 glob = config.option.cacheshow[0] if glob is None: glob = "*" dummy = object() basedir = config.cache._cachedir vdir = basedir / Cache._CACHE_PREFIX_VALUES tw.sep("-", "cache values for %r" % glob) for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): key = str(valpath.relative_to(vdir)) val = config.cache.get(key, dummy) if val is dummy: tw.line("%s contains unreadable content, will be ignored" % key) else: tw.line("%s contains:" % key) for line in pformat(val).splitlines(): tw.line(" " + line) ddir = basedir / Cache._CACHE_PREFIX_DIRS if ddir.is_dir(): contents = sorted(ddir.rglob(glob)) tw.sep("-", "cache directories for %r" % glob) for p in contents: # if p.check(dir=1): # print("%s/" % p.relto(basedir)) if p.is_file(): key = str(p.relative_to(basedir)) tw.line(f"{key} is a file of length {p.stat().st_size:d}") return 0
def toterminal(self, tw: TerminalWriter) -> None: tw.line("я")