7

py.test stacktraces look like this at the moment:

Traceback (most recent call last):
  File "/home/foo_tbz_di476/src/djangotools/djangotools/tests/ReadonlyModelTestCommon.py", line 788, in test_stale_or_missing_content_types
    self.assertEqual([], errors, 'Stale/Missing ContentTypes: %s' % '\n'.join(errors))
  File "/usr/lib64/python2.7/unittest/case.py", line 511, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/lib64/python2.7/unittest/case.py", line 740, in assertListEqual
    self.assertSequenceEqual(list1, list2, msg, seq_type=list)
  File "/usr/lib64/python2.7/unittest/case.py", line 722, in assertSequenceEqual
    self.fail(msg)
  File "/usr/lib64/python2.7/unittest/case.py", line 408, in fail
    raise self.failureException(msg)

It would be much easier for my human eyes if the output would skip the lines from the unittest module.

Example:

Traceback (most recent call last):
  File "/home/foo_tbz_di476/src/djangotools/djangotools/tests/ReadonlyModelTestCommon.py", line 788, in test_stale_or_missing_content_types
    self.assertEqual([], errors, 'Stale/Missing ContentTypes: %s' % '\n'.join(errors))

I tried option --tb=short but this does not do this.

Update

A solution without a unix pipe (like py.test ...| grep) is preferred.

guettli
  • 23,964
  • 63
  • 293
  • 556
  • Can you post an example test that produces this output, along with command you're using to run it. AFAIK, py.test shouldn't be outputting tracebacks like that at all. – Jamie Cockburn Jul 07 '14 at 11:30
  • I've added this as a feature request on the py.test github page: https://github.com/pytest-dev/pytest/issues/1434 – dbn Mar 04 '16 at 08:31

4 Answers4

11

It looks like you are invoking pytest like:

py.test --tb=native

This form will output a python stdlib stacktrace derived from traceback.format_exception

With pytest you can add a conftest.py file(s) to your project. Here you can add any bits of code to modify pytest behaviour.

Beware! In both the following approaches use monkey patching which people may consider evil.

Option 1: String matching

This is the simplest approach but could be a problem if the string you are searching for appears in a line that you don't want to hide.

This approach patches the ReprEntryNative class in the py package which is a dependancy of pytest.

Put the following code in you conftest.py

import py

def test_skip_line(line):
    """decide which lines to skip, the code below will also skip the next line if this returns true"""
    return 'unittest' in line

class PatchedReprEntryNative(py._code.code.ReprEntryNative):
    def __init__(self, tblines):
        self.lines = []
        while len(tblines) > 0:
            line = tblines.pop(0)
            if test_skip_line(line):
                # skip this line and the next
                tblines.pop(0)
            else:
                self.lines.append(line)
py._code.code.ReprEntryNative = PatchedReprEntryNative

Option 2: traceback frame inspection

If string matching isnt true enough for you, we can inspect the traceback before it gets dumped to strings and only output frames that aren't from a set of modules.

This approach patches the traceback.extract_tb function which probably kills puppies.

Put the following code in you conftest.py

import inspect
import linecache
import traceback
import unittest.case
import sys    

SKIPPED_MODULES = [
    unittest.case
]

def test_skip_frame(frame):
    module = inspect.getmodule(frame)
    return module in SKIPPED_MODULES

def tb_skipper(tb):
    tbnext = tb.tb_next
    while tbnext is not None:
        if test_skip_frame(tbnext.tb_frame):
            tbnext = tbnext.tb_next
        else:
            yield tbnext
    yield None

def new_extract_tb(tb, limit = None):
    if limit is None:
        if hasattr(sys, 'tracebacklimit'):
            limit = sys.tracebacklimit
    list = []
    n = 0
    new_tb_order = tb_skipper(tb) # <-- this line added
    while tb is not None and (limit is None or n < limit):
        f = tb.tb_frame
        lineno = tb.tb_lineno
        co = f.f_code
        filename = co.co_filename
        name = co.co_name
        linecache.checkcache(filename)
        line = linecache.getline(filename, lineno, f.f_globals)
        if line: line = line.strip()
        else: line = None
        list.append((filename, lineno, name, line))
        tb = next(new_tb_order) # <-- this line modified, was tb = tb.tb_next
        n = n+1
    return list
traceback.extract_tb = new_extract_tb
Community
  • 1
  • 1
Jeremy Allen
  • 6,078
  • 2
  • 23
  • 31
  • I think the hip thing to do is to skip modules with __unittest =True – dbn Mar 04 '16 at 08:31
  • Thanks much for the quality post! Would've commented my update but didn't have enough reputation to do so :( . You'll see my update of option 1 below. – neozen Apr 10 '21 at 04:39
2

Try piping the output to grep with an inverted pattern. That will print all the lines except those that match the pattern.

python all_tests.py | grep -v "usr/lib64/python2.7/unittest"
Mattias Backman
  • 907
  • 12
  • 25
  • Every stacktrace frame gets printed with two lines: first the source file name, and the next file shows the source code. Your solutions only filters out the lines which show the source file name. The output looks strange. – guettli Jun 25 '14 at 08:05
  • Add -A 1 to the command line. That includes one line after the match. I hope that works as intended with a reverse filter. – Mattias Backman Jun 25 '14 at 08:32
1

If you know what os your using and don't care for the directory splits, you can remove the import os and replace os.sep with the appropriate seperator

This will remove all traceback with wherever the unittest module is and the line after it.

import os
import sys
import unittest

class Stderr(object):
    def __init__(self):
        self.unittest_location = (os.sep).join(unittest.__file__.split(os.sep)[:-1])
        self.stderr = sys.__stderr__
        self.skip = False

    def write(self, text):
        if self.skip and text.find("\n") != -1: self.skip=False
        elif self.skip: pass
        else:
            self.skip = text.find(self.unittest_location) != -1
            if not self.skip: self.stderr.write(text)

sys.stderr = Stderr()
muddyfish
  • 3,161
  • 28
  • 35
1

All credit to https://stackoverflow.com/a/24679193/59412 I just took the liberty of porting it up to the latest pytest (6.2.3 as of writing). Stick this in your conftest.py. (won't work for unittest code though... since that's not in site-packages)

Option 1:

(filters all pip-installed packages by default)

# 5c4048fc-ccf1-44ab-a683-78a29c1a98a6
import _pytest._code.code
def should_skip_line(line):
    """
    decide which lines to skip
    """
    return 'site-packages' in line

class PatchedReprEntryNative(_pytest._code.code.ReprEntryNative):
    def __init__(self, tblines):
        self.lines = []
        while len(tblines) > 0:
            # [...yourfilter...]/test_thing.py", line 1, in test_thing
            line = tblines.pop(0)
            if should_skip_line(line):
                # some line of framework code you don't want to see either...
                tblines.pop(0)
            else:
                self.lines.append(line)
_pytest._code.code.ReprEntryNative = PatchedReprEntryNative
del _pytest._code.code

Only works with --tb=native. Just like the answer I ported up to this version.

Option 2:

TBD

neozen
  • 93
  • 7