""" This file intends to gather all methods of representing failures/tracebacks etc. which should be used among all terminal-based reporters. This methods should be general, to allow further use outside the pylib """ import py from py.__.code import safe_repr def getrelpath(source, dest): base = source.common(dest) if not base: return None # with posix local paths '/' is always a common base relsource = source.relto(base) reldest = dest.relto(base) n = relsource.count(source.sep) target = dest.sep.join(('..', )*n + (reldest, )) return target class Presenter(object): """ Class used for presentation of various objects, sharing common output style """ def __init__(self, out, config): """ out is a file-like object (we can write to it) """ assert hasattr(out, 'write') self.out = out self.config = config def repr_source(self, source, marker=">", marker_location=-1): """ This one represents piece of source with possible marker at requested position """ if isinstance(source, str): # why the hell, string is iterable? source = source.split("\n") if marker_location < 0: marker_location += len(source) if marker_location < 0: marker_location = 0 if marker_location >= len(source): marker_location = len(source) - 1 for i in range(len(source)): if i == marker_location: prefix = marker + " " else: prefix = " " self.out.line(prefix + source[i]) def repr_item_info(self, item): """ This method represents py.test.collect.Item info (path and module) """ root = item.fspath modpath = item._getmodpath() try: fn, lineno = item._getpathlineno() except TypeError: assert isinstance(item.parent, py.test.collect.Generator) # a generative test yielded a non-callable fn, lineno = item.parent._getpathlineno() if root == fn: self.out.sep("_", "entrypoint: %s" %(modpath)) else: self.out.sep("_", "entrypoint: %s %s" %(root.basename, modpath)) def repr_failure_explanation(self, excinfo, source): try: s = str(source.getstatement(len(source)-1)) except KeyboardInterrupt: raise except: try: s = str(source[-1]) except IndexError: s = "" indent = " " * (4 + (len(s) - len(s.lstrip()))) # get the real exception information out lines = excinfo.exconly(tryshort=True).split('\n') self.out.line('>' + indent[:-1] + lines.pop(0)) for x in lines: self.out.line(indent + x) def getentrysource(self, entry): try: source = entry.getsource() except py.error.ENOENT: source = py.code.Source("[failure to get at sourcelines from %r]\n" % entry) return source.deindent() def repr_locals(self, f_locals): if self.config.option.showlocals: self.out.sep('- ', 'locals') for name, value in f_locals.items(): if name == '__builtins__': self.out.line("__builtins__ = ") else: # This formatting could all be handled by the _repr() function, which is # only repr.Repr in disguise, so is very configurable. str_repr = safe_repr._repr(value) if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): self.out.line("%-10s = %s" %(name, str_repr)) else: self.out.line("%-10s =\\" % (name,)) py.std.pprint.pprint(value, stream=self.out) def repr_failure_tblong(self, item, excinfo, traceback, out_err_reporter): if not self.config.option.nomagic and excinfo.errisinstance(RuntimeError): recursionindex = traceback.recursionindex() else: recursionindex = None last = traceback[-1] first = traceback[0] for index, entry in py.builtin.enumerate(traceback): if entry == first: if item: self.repr_item_info(item) self.out.line() else: self.out.line("") source = self.getentrysource(entry) firstsourceline = entry.getfirstlinesource() marker_location = entry.lineno - firstsourceline if entry == last: self.repr_source(source, 'E', marker_location) self.repr_failure_explanation(excinfo, source) else: self.repr_source(source, '>', marker_location) self.out.line("") self.out.line("[%s:%d]" %(entry.path, entry.lineno+1)) self.repr_locals(entry.locals) # trailing info if entry == last: out_err_reporter() self.out.sep("_") else: self.out.sep("_ ") if index == recursionindex: self.out.line("Recursion detected (same locals & position)") self.out.sep("!") break def repr_failure_tbshort(self, item, excinfo, traceback, out_err_reporter): # print a Python-style short traceback if not self.config.option.nomagic and excinfo.errisinstance(RuntimeError): recursionindex = traceback.recursionindex() else: recursionindex = None last = traceback[-1] first = traceback[0] self.out.line() for index, entry in py.builtin.enumerate(traceback): path = entry.path.basename firstsourceline = entry.getfirstlinesource() relline = entry.lineno - firstsourceline self.out.line(' File "%s", line %d, in %s' % ( path, entry.lineno+1, entry.name)) try: source = entry.getsource().lines except py.error.ENOENT: source = ["?"] else: try: if len(source) > 1: source = source[relline] except IndexError: source = [] if entry == last: if source: self.repr_source(source, 'E') self.repr_failure_explanation(excinfo, source) else: if source: self.repr_source(source, ' ') self.repr_locals(entry.locals) # trailing info if entry == last: out_err_reporter() self.out.sep("_") else: if index == recursionindex: self.out.line("Recursion detected (same locals & position)") self.out.sep("!") break # the following is only used by the combination '--pdb --tb=no' repr_failure_tbno = repr_failure_tbshort def repr_pythonversion(): v = py.std.sys.version_info try: return "%s.%s.%s-%s-%s" % v except ValueError: return str(v)