Skip to content

Instantly share code, notes, and snippets.

@oakfang
Created March 23, 2014 04:55
Show Gist options
  • Save oakfang/9719014 to your computer and use it in GitHub Desktop.
Save oakfang/9719014 to your computer and use it in GitHub Desktop.
pytest-tree
import colorama as color
from os import path
import pytest
from _pytest.terminal import TerminalReporter
PASSED = 0
FAILED = 2
OTHER = 1
COLORS = {PASSED: "GREEN",
FAILED: "RED",
OTHER: "YELLOW"}
STATE = {PASSED: "PASSED",
FAILED: "BROKEN",
OTHER: "INCOMPLETE"}
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group._addoption(
'--tree', action="store_true", dest="vtest", default=False,
help=(
"show test results as a virtual tree (disabled by "
"default)."
)
)
@pytest.mark.trylast
def pytest_configure(config):
if hasattr(config, 'slaveinput'):
return # xdist slave, we are already active on the master
if config.option.vtest:
# Get the standard terminal reporter plugin...
standard_reporter = config.pluginmanager.getplugin('terminalreporter')
vtest_reporter = VtestTerminalReporter(standard_reporter)
config.pluginmanager.unregister(standard_reporter)
config.pluginmanager.register(vtest_reporter, 'terminalreporter')
class VtestTerminalReporter(TerminalReporter):
def __init__(self, reporter):
TerminalReporter.__init__(self, reporter.config)
self._tw = reporter._tw
def pytest_runtestloop(self, session):
if session.config.option.collectonly:
return True
#self.suite = Suite()
self.suite = ProjectNode("ROOT")
for i, item in enumerate(session.items):
try:
nextitem = session.items[i+1]
except IndexError:
nextitem = None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if nextitem is None:
print_test_tree(self.write_line, self.suite, session.config.option.verbose)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
def pytest_runtest_logstart(nodeid, location):
pass
def pytest_runtest_logreport(self, report):
# Show failures and errors occuring during running a test
# instantly.
if report.when != 'call':
return
import pdb; pdb.set_trace()
test_path, line, test_name = report.location
result = report.outcome
directory, module = path.split(test_path)
self.suite.add_test(directory, module, test_name, result.upper())
class Colorize(object):
def __enter__(self):
color.init()
return self
def __exit__(self, *args):
color.deinit()
def cprint(self, text, clr):
return getattr(color.Fore, clr) + text
def colorize_list(clz, lst, chr="#", prefix="[", suffix="]"):
buff = ""
if prefix:
buff += clz.cprint(prefix, "WHITE")
for item in lst:
buff += clz.cprint(chr, COLORS.get(item, "BLUE"))
if suffix:
buff += clz.cprint(suffix, "WHITE")
return buff
class Node(object):
def __init__(self, value, data=None):
self.value = value
self.__children = {}
self.parent = None
self.data = data
@property
def children(self):
return self.__children.values()
def add(self, node):
node.parent = self
if node.value not in self.__children:
self.__children[node.value] = node
return self.__children[node.value]
@property
def is_leaf(self):
return not bool(self.__children)
@property
def is_root(self):
return self.parent is None
@property
def nodes(self):
return filter(lambda n: not n.is_leaf, self.children)
@property
def leaves(self):
return filter(lambda n: n.is_leaf, not self.children)
@property
def root(self):
if self.parent is None:
return self
parent = self.parent
while parent.parent is not None:
parent = parent.parent
return parent
def __str__(self):
buff = str(self.value)
if self.data:
buff += "\t" + str(self.data)
return buff
def __hash__(self):
return hash(self.value)
class TestLeaf(Node):
def __init__(self, test, result):
Node.__init__(self, test, data=result)
@property
def state(self):
return COLORS[self.summary]
@property
def summary(self):
if self.data == "PASSED":
return PASSED
if self.data in ("ERROR", "FAILED"):
return FAILED
return OTHER
class ModuleNode(Node):
def add_test(self, test, result):
self.add(TestLeaf(test, result))
@property
def state(self):
return COLORS[max(self.summary)]
@property
def summary(self):
return [node.summary for node in self.children]
class DirectoryNode(ModuleNode):
def add_test(self, module, test, result):
mod = self.add(ModuleNode(module))
mod.add_test(test, result)
@property
def summary(self):
return [max(node.summary) for node in self.children]
class ProjectNode(DirectoryNode):
def add_test(self, directory, module, test, result):
dr = self.add(DirectoryNode(directory))
dr.add_test(module, test, result)
def __str__(self):
return "PROJECT " + STATE[max(self.summary)]
def print_test_tree(writer, root, verbose=False, indent=""):
with Colorize() as clz:
title = clz.cprint(indent + str(root), root.state)
if verbose and not root.is_leaf:
title += " " + colorize_list(clz, root.summary)
writer(title)
if not root.is_root:
indent = indent + '\t'
for node in root.children:
print_test_tree(writer, node, verbose, indent)
#SETUP
from setuptools import setup
setup(
name="vtest",
py_modules=['vtest'],
# the following makes a plugin available to pytest
entry_points={
'pytest11': [
'vtest = vtest',
]
},
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment