Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

py.test: hide stacktrace lines from unittest module

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.

like image 812
guettli Avatar asked Jun 25 '14 07:06

guettli


2 Answers

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
like image 140
Jeremy Allen Avatar answered Nov 03 '22 00:11

Jeremy Allen


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"
like image 42
Mattias Backman Avatar answered Nov 03 '22 00:11

Mattias Backman