Skip to content

Commit

Permalink
Merge remote-tracking branch 'oesmith/pretty_nose_output'
Browse files Browse the repository at this point in the history
  • Loading branch information
rlisagor committed Jun 6, 2011
2 parents 7dba375 + dde4532 commit e30899b
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 52 deletions.
34 changes: 34 additions & 0 deletions features/error_steps.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Feature: Error steps
In order to speed up development
It is useful to see a verbose report of which step has failed

Scenario: Error steps
When I run nose -v --tags @one --error-steps examples/self_test
Then it should fail with
"""
Sample: Missing ... UNDEFINED: "missing" # examples{sep}self_test{sep}features{sep}sample.feature:7
Sample: Passing ... ok
Sample: Failing ... ERROR
======================================================================
ERROR: Sample: Failing
----------------------------------------------------------------------
Traceback (most recent call last):
File "{cwd}{sep}examples{sep}self_test{sep}features{sep}steps.py", line 14, in failing
flunker()
File "{cwd}{sep}examples{sep}self_test{sep}features{sep}steps.py", line 5, in flunker
raise Exception("FAIL")
Exception: FAIL
@one
Feature: Sample
@four
Scenario: Failing
given failing  # examples{sep}self_test{sep}features{sep}sample.feature:18
----------------------------------------------------------------------
Ran 3 tests in {time}
FAILED (UNDEFINED=1, errors=1)
"""

45 changes: 45 additions & 0 deletions features/list_undefined.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Feature: List undefined steps
In order to speed up development
It is useful to see a report of undefined steps encountered in a test run

Scenario: List undefined
When I run nose -v --tags @one --list-undefined examples/self_test
Then it should fail with
"""
Sample: Missing ... UNDEFINED: "missing" # examples{sep}self_test{sep}features{sep}sample.feature:7
Sample: Passing ... ok
Sample: Failing ... ERROR
======================================================================
ERROR: Sample: Failing
----------------------------------------------------------------------
Traceback (most recent call last):
File "{cwd}{sep}examples{sep}self_test{sep}features{sep}steps.py", line 14, in failing
flunker()
File "{cwd}{sep}examples{sep}self_test{sep}features{sep}steps.py", line 5, in flunker
raise Exception("FAIL")
Exception: FAIL
>> in "failing" # examples{sep}self_test{sep}features{sep}sample.feature:18
======================================================================
Tests with undefined steps
----------------------------------------------------------------------
@one
Feature: Sample
@two @three
Scenario: Missing
given missing  # examples{sep}self_test{sep}features{sep}sample.feature:7
You can implement step definitions for the missing steps with these snippets:
@Given(r"^missing$")
def given_missing():
# code here
----------------------------------------------------------------------
Ran 3 tests in {time}
FAILED (UNDEFINED=1, errors=1)
"""

58 changes: 9 additions & 49 deletions freshen/handlers.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,33 @@
#-*- coding: utf8 -*-

from freshen.cuke import FreshenHandler
import sys
import traceback

COLORS = {
'bold': '1',
'grey': '2',
'underline': '4',
'red': '31',
'green': '32',
'yellow': '33',
'blue': '34',
'magenta': '35',
'cyan': '36',
'white': '37'
}

UNDEFINED = 'yellow'
AMBIGUOUS = 'cyan'
FAILED = 'red'
ERROR = 'red,bold'
PASSED = 'green'
TAG = 'cyan'
COMMENT = 'grey'

def colored(text, colorspec):
colors = [c.strip() for c in colorspec.split(',')]
result = ""
for c in colors:
result += "\033[%sm" % COLORS[c]
result += text + "\033[0m"
return result
from freshen.prettyprint import FreshenPrettyPrint

class ConsoleHandler(FreshenHandler):

def before_feature(self, feature):
if feature.tags:
print colored(" ".join(('@' + t) for t in feature.tags), TAG)
print 'Feature:', feature.name
if feature.description != ['']:
print "\n".join((' ' + l) for l in feature.description)
print
print FreshenPrettyPrint.feature(feature)
print

def before_scenario(self, scenario):
if scenario.tags:
print ' ' + colored(" ".join(('@' + t) for t in scenario.tags), TAG)
print " Scenario:", scenario.name
print FreshenPrettyPrint.scenario(scenario)

def after_scenario(self, scenario):
print

def _step(self, step, color):
print " " + colored('%-40s' % (step.step_type + " " + step.match), color) + \
colored(" # " + step.source_location(), COMMENT)

def step_failed(self, step, e):
self._step(step, FAILED)
print FreshenPrettyPrint.step_failed(step)

def step_ambiguous(self, step, e):
self._step(step, AMBIGUOUS)
print FreshenPrettyPrint.step_ambiguous(step)

def step_undefined(self, step, e):
self._step(step, UNDEFINED)
print FreshenPrettyPrint.step_undefined(step)

def step_exception(self, step, e):
self._step(step, ERROR)
print FreshenPrettyPrint.step_exception(step)

def after_step(self, step):
self._step(step, PASSED)
print FreshenPrettyPrint.step_passed(step)


96 changes: 93 additions & 3 deletions freshen/noseplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import sys
import os
import logging
import re
import traceback
from new import instancemethod

from pyparsing import ParseException

Expand All @@ -12,11 +15,20 @@
from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
from nose.selector import TestAddress
from nose.failure import Failure
from nose.util import isclass

from freshen.core import TagMatcher, load_language, load_feature, StepsRunner
from freshen.context import *
from freshen.prettyprint import FreshenPrettyPrint
from freshen.stepregistry import StepImplLoader, StepImplRegistry, UndefinedStepImpl

try:
# use colorama for cross-platform colored text, if available
import colorama
colorama.init()
except ImportError:
colorama = None

log = logging.getLogger('nose.plugins.freshen')

# This line ensures that frames from this file will not be shown in tracebacks
Expand All @@ -27,6 +39,9 @@ class ExceptionWrapper(Exception):
def __init__(self, e, step):
self.e = e
self.step = step

def __str__(self):
return "\n".join(traceback.format_exception(*self.e))

class FeatureSuite(object):

Expand All @@ -45,6 +60,7 @@ class FreshenTestCase(unittest.TestCase):
required_sane_plugins = ["django", "http"]
django_plugin_started = False
http_plugin_started = False
last_step = None

test_type = "http"

Expand All @@ -67,6 +83,7 @@ def setUp(self):
def runTest(self):
for step in self.scenario.iter_steps():
try:
self.last_step = step
self.step_runner.run_step(step)
except (AssertionError, UndefinedStepImpl, ExceptionWrapper):
raise
Expand All @@ -75,6 +92,7 @@ def runTest(self):

for hook_impl in reversed(self.step_registry.get_hooks('after_step', self.scenario.get_tags())):
hook_impl.run(self.scenario)
self.last_step = None

def tearDown(self):
for hook_impl in reversed(self.step_registry.get_hooks('after', self.scenario.get_tags())):
Expand Down Expand Up @@ -111,6 +129,19 @@ def options(self, parser, env):
default='en',
help='Change the language used when reading the feature files',
)
parser.add_option('--list-undefined',
action="store_true",
default=env.get('NOSE_FRESHEN_LIST_UNDEFINED')=='1',
dest="list_undefined",
help="Make a report of all undefined steps that "
"freshen encounters when running scenarios. "
"[NOSE_FRESHEN_LIST_UNDEFINED]")
parser.add_option('--error-steps',
action="store_true",
default=env.get('NOSE_FRESHEN_ERROR_STEPS')=='1',
dest='error_steps',
help="Show the location of steps that fail/error. "
"[NOSE_FRESHEN_ERROR_STEPS]")

def configure(self, options, config):
super(FreshenNosePlugin, self).configure(options, config)
Expand All @@ -121,6 +152,11 @@ def configure(self, options, config):
if not self.language:
print >> sys.stderr, "Error: language '%s' not available" % options.language
exit(1)
if options.list_undefined:
self.undefined_steps = []
else:
self.undefined_steps = None
self.error_steps = options.error_steps

def wantDirectory(self, dirname):
if not os.path.exists(os.path.join(dirname, ".freshenignore")):
Expand Down Expand Up @@ -194,8 +230,62 @@ def formatFailure(self, test, err):
ec, ev, tb = err
if ec is ExceptionWrapper and isinstance(ev, Exception):
orig_ec, orig_ev, orig_tb = ev.e
return (orig_ec, str(orig_ev) + '\n\n>> in "%s" # %s' % (ev.step.match, ev.step.source_location()), orig_tb)
if self.error_steps:
message = "%s\n\n%s" % (str(orig_ev), self._formatSteps(test, ev.step))
return (orig_ec, message, orig_tb)
else:
return (orig_ec, str(orig_ev) + '\n\n>> in "%s" # %s' % (ev.step.match, ev.step.source_location()), orig_tb)
elif not ec is UndefinedStepImpl and hasattr(test.test, 'last_step'):
if self.error_steps:
message = "%s\n\n%s" % (str(ev), self._formatSteps(test, test.test.last_step))
return (ec, message, tb)

formatError = formatFailure



def prepareTestResult(self, result):
# Patch the result handler with an addError method that saves
# UndefinedStepImpl exceptions for reporting later.
if self.undefined_steps is not None:
plugin = self
def _addError(self, test, err):
ec,ev,tb = err
if isclass(ec) and issubclass(ec, UndefinedStepImpl):
plugin.undefined_steps.append((test,ec,ev,tb))
self._old_addError(test, err)
result._old_addError = result.addError
result.addError = instancemethod(_addError, result, result.__class__)

def report(self, stream):
if self.undefined_steps:
stream.write("======================================================================\n")
stream.write("Tests with undefined steps\n")
stream.write("----------------------------------------------------------------------\n")
for test, ec, ev, tb in self.undefined_steps:
stream.write(self._formatSteps(test, ev.step, False)+"\n\n")
stream.write("You can implement step definitions for the missing steps with these snippets:\n\n")
uniq_steps = set(s[2].step for s in self.undefined_steps)
for step in uniq_steps:
stream.write('@%s(r"^%s$")\n' % (self.language.words(step.step_type)[0],
step.match))
stream.write('def %s_%s():\n' % (step.step_type,
re.sub('[^\w]', '_', step.match).lower()))
stream.write(' # code here\n\n')

def _formatSteps(self, test, failed_step, failure=True):
ret = []
ret.append(FreshenPrettyPrint.feature(test.test.feature))
ret.append(FreshenPrettyPrint.scenario(test.test.scenario))
found = False
for step in test.test.scenario.iter_steps():
if step == failed_step:
found = True
if failure:
ret.append(FreshenPrettyPrint.step_failed(step))
else:
ret.append(FreshenPrettyPrint.step_undefined(step))
elif found:
ret.append(FreshenPrettyPrint.step_notrun(step))
else:
ret.append(FreshenPrettyPrint.step_passed(step))
return "\n".join(ret)

Loading

0 comments on commit e30899b

Please sign in to comment.