Permalink
Browse files

Execute deferred steps in sequence rather than in parallel, don't har…

…d-depend on twisted, support custom twisted reactor implementation, prepare userspace selection of test class (refs #25)
  • Loading branch information...
1 parent b0461bc commit 395b9c29dddd3cc222f34ddf17eb84d72959e851 @jerico-dev jerico-dev committed Jun 18, 2011
Showing with 156 additions and 90 deletions.
  1. +32 −90 freshen/noseplugin.py
  2. 0 freshen/test/__init__.py
  3. +81 −0 freshen/test/base.py
  4. +19 −0 freshen/test/pyunit.py
  5. +24 −0 freshen/test/twisted.py
View
@@ -1,38 +1,28 @@
#-*- coding: utf8 -*-
-try:
- # use twisted tests if available (support for async testing)
- from twisted.trial.unittest import TestCase
- from twisted.internet.defer import Deferred, DeferredList
-except:
- from unittest import TestCase
-
-
import sys
import os
import logging
import re
-import traceback
from new import instancemethod
from pyparsing import ParseException
from nose.plugins import Plugin
-from nose.plugins.skip import SkipTest
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
from freshen.stepregistry import UndefinedStepImpl, StepImplLoadException
+from freshen.test.base import FeatureSuite, FreshenTestCase, ExceptionWrapper
try:
# use colorama for cross-platform colored text, if available
- import colorama
+ import colorama # pylint: disable=F0401
colorama.init()
except ImportError:
colorama = None
@@ -42,82 +32,6 @@
# This line ensures that frames from this file will not be shown in tracebacks
__unittest = 1
-class ExceptionWrapper(Exception):
-
- def __init__(self, e, step):
- self.e = e
- self.step = step
-
- def __str__(self):
- return "".join(traceback.format_exception(*self.e))
-
-class FeatureSuite(object):
-
- def setUp(self):
- #log.debug("Clearing feature context")
- ftc.clear()
-
-class FreshenTestCase(TestCase):
-
- start_live_server = True
- database_single_transaction = True
- database_flush = True
- selenium_start = False
- no_database_interaction = False
- make_translations = True
- required_sane_plugins = ["django", "http"]
- django_plugin_started = False
- http_plugin_started = False
- last_step = None
-
- test_type = "http"
-
- def __init__(self, step_runner, step_registry, feature, scenario, feature_suite):
- self.feature = feature
- self.scenario = scenario
- self.context = feature_suite
- self.step_registry = step_registry
- self.step_runner = step_runner
-
- self.description = feature.name + ": " + scenario.name
- super(FreshenTestCase, self).__init__()
-
- def setUp(self):
- #log.debug("Clearing scenario context")
- scc.clear()
- for hook_impl in self.step_registry.get_hooks('before', self.scenario.get_tags()):
- hook_impl.run(self.scenario)
-
- def runTest(self):
- deferreds = []
- for step in self.scenario.iter_steps():
- try:
- self.last_step = step
- deferred = self.step_runner.run_step(step)
- try:
- if isinstance(deferred, Deferred):
- deferreds.append(deferred)
- except NameError:
- pass
- except (AssertionError, UndefinedStepImpl, ExceptionWrapper):
- raise
- except:
- raise ExceptionWrapper(sys.exc_info(), step)
-
- 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
- if len(deferreds) == 1:
- return deferreds[0]
- elif len(deferreds) > 1:
- return DeferredList(deferreds)
- else:
- return None
-
- def tearDown(self):
- for hook_impl in reversed(self.step_registry.get_hooks('after', self.scenario.get_tags())):
- hook_impl.run(self.scenario)
-
class FreshenErrorPlugin(ErrorClassPlugin):
@@ -190,6 +104,7 @@ def configure(self, options, config):
self.undefined_steps = []
else:
self.undefined_steps = None
+ self._test_class = None
def wantDirectory(self, dirname):
if not os.path.exists(os.path.join(dirname, ".freshenignore")):
@@ -199,6 +114,33 @@ def wantDirectory(self, dirname):
def wantFile(self, filename):
return filename.endswith(".feature") or None
+ def _loadTestForFeature(self, feature):
+ """Chooses the test base class appropriate
+ for the given feature.
+
+ This method supports late import of the
+ test base class so that userspace code (e.g.
+ in the support environment) can configure
+ the test framework first (e.g. in the case
+ of twisted tests to install a custom
+ reactor implementation).
+
+ The current simplistic implementation chooses
+ a twisted-enabled test class if twisted is
+ present and returns a PyUnit-based test otherwise.
+
+ In the future this can be extended to support
+ more flexible (e.g. user-defined) test classes
+ on a per-feature basis."""
+ if self._test_class is None:
+ try:
+ from freshen.test.twisted import TwistedTestCase
+ self._test_class = TwistedTestCase
+ except ImportError:
+ from freshen.test.pyunit import PyunitTestCase
+ self._test_class = PyunitTestCase
+ return self._test_class
+
def loadTestsFromFile(self, filename, indexes=[]):
log.debug("Loading from file %s" % filename)
@@ -222,7 +164,8 @@ def loadTestsFromFile(self, filename, indexes=[]):
for i, sc in enumerate(feat.iter_scenarios()):
if (not indexes or (i + 1) in indexes):
if self.tagmatcher.check_match(sc.tags + feat.tags):
- yield FreshenTestCase(StepsRunner(step_registry), step_registry, feat, sc, ctx)
+ testclass = self._loadTestForFeature(feat)
+ yield testclass(StepsRunner(step_registry), step_registry, feat, sc, ctx)
cnt += 1
if not cnt:
@@ -235,7 +178,6 @@ def loadTestsFromName(self, name, _=None):
return # let nose take care of it
name_without_indexes, indexes = self._split_file_in_indexes(name)
-
if not os.path.exists(name_without_indexes):
return
View
No changes.
View
@@ -0,0 +1,81 @@
+#-*- coding: utf8 -*-
+
+import traceback
+import sys
+
+from freshen.context import ftc, scc
+from freshen.stepregistry import UndefinedStepImpl
+
+
+class ExceptionWrapper(Exception):
+
+ def __init__(self, e, step, discard_frames=0):
+ e = list(e)
+ while discard_frames:
+ e[2] = e[2].tb_next
+ discard_frames -= 1
+ self.e = e
+ self.step = step
+
+ def __str__(self):
+ return "".join(traceback.format_exception(*self.e))
+
+
+class FeatureSuite(object):
+
+ def setUp(self):
+ #log.debug("Clearing feature context")
+ ftc.clear()
+
+
+class FreshenTestCase(object):
+
+ start_live_server = True
+ database_single_transaction = True
+ database_flush = True
+ selenium_start = False
+ no_database_interaction = False
+ make_translations = True
+ required_sane_plugins = ["django", "http"]
+ django_plugin_started = False
+ http_plugin_started = False
+ last_step = None
+
+ test_type = "http"
+
+ def __init__(self, step_runner, step_registry, feature, scenario, feature_suite):
+ self.feature = feature
+ self.scenario = scenario
+ self.context = feature_suite
+ self.step_registry = step_registry
+ self.step_runner = step_runner
+
+ self.description = feature.name + ": " + scenario.name
+ super(FreshenTestCase, self).__init__()
+
+ def setUp(self):
+ #log.debug("Clearing scenario context")
+ scc.clear()
+ for hook_impl in self.step_registry.get_hooks('before', self.scenario.get_tags()):
+ hook_impl.run(self.scenario)
+
+ def runAfterStepHooks(self):
+ for hook_impl in reversed(self.step_registry.get_hooks('after_step', self.scenario.get_tags())):
+ hook_impl.run(self.scenario)
+
+ def runStep(self, step, discard_frames=0):
+ try:
+ self.last_step = step
+ return self.step_runner.run_step(step)
+ except (AssertionError, UndefinedStepImpl, ExceptionWrapper):
+ raise
+ except:
+ raise ExceptionWrapper(sys.exc_info(), step, discard_frames)
+ self.runAfterStepHooks()
+
+ def runTest(self):
+ raise NotImplementedError('Must be implemented by subclasses')
+
+ def tearDown(self):
+ for hook_impl in reversed(self.step_registry.get_hooks('after', self.scenario.get_tags())):
+ hook_impl.run(self.scenario)
View
@@ -0,0 +1,19 @@
+#-*- coding: utf8 -*-
+
+from freshen.test.base import FreshenTestCase
+
+from unittest import TestCase
+
+
+class PyunitTestCase(FreshenTestCase, TestCase):
+ """Support PyUnit tests."""
+
+ def __init__(self, step_runner, step_registry, feature, scenario, feature_suite):
+ FreshenTestCase.__init__(self, step_runner, step_registry,
+ feature, scenario, feature_suite)
+ TestCase.__init__(self)
+
+ def runTest(self):
+ for step in self.scenario.iter_steps():
+ self.runStep(step, 3)
+ self.last_step = None
View
@@ -0,0 +1,24 @@
+#-*- coding: utf8 -*-
+
+from freshen.test.base import FreshenTestCase
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import inlineCallbacks, Deferred
+
+class TwistedTestCase(FreshenTestCase, TestCase):
+ """Support asynchronous feature tests."""
+
+ # pylint: disable=R0913
+ def __init__(self, step_runner, step_registry,
+ feature, scenario, feature_suite):
+ FreshenTestCase.__init__(self, step_runner, step_registry,
+ feature, scenario, feature_suite)
+ TestCase.__init__(self)
+
+ @inlineCallbacks
+ def runTest(self):
+ for step in self.scenario.iter_steps():
+ result = self.runStep(step)
+ if isinstance(result, Deferred):
+ yield result
+ self.last_step = None

0 comments on commit 395b9c2

Please sign in to comment.