Commit 4b5db1bb by Gabriel Falcao

just some brainstorming

parent 87d8afbf
before 0.2.0:
* https://github.com/gabrielfalcao/lettuce/pull/224
* https://github.com/gabrielfalcao/lettuce/pull/255
* https://github.com/gabrielfalcao/lettuce/pull/256
* all the "high-priority" labeled tickets
* https://github.com/gabrielfalcao/lettuce/issues/103 (maybe)
* https://github.com/gabrielfalcao/lettuce/issues/198
...@@ -31,7 +31,7 @@ from lettuce.terrain import world ...@@ -31,7 +31,7 @@ from lettuce.terrain import world
from lettuce.decorators import step from lettuce.decorators import step
from lettuce.registry import call_hook from lettuce.registry import call_hook
from lettuce.registry import STEP_REGISTRY from lettuce.registry import STEP_REGISTRY, CASTER_REGISTRY
from lettuce.registry import CALLBACK_REGISTRY from lettuce.registry import CALLBACK_REGISTRY
from lettuce.exceptions import StepLoadingError from lettuce.exceptions import StepLoadingError
from lettuce.plugins import xunit_output from lettuce.plugins import xunit_output
......
...@@ -24,7 +24,7 @@ from copy import deepcopy ...@@ -24,7 +24,7 @@ from copy import deepcopy
from lettuce import strings from lettuce import strings
from lettuce import languages from lettuce import languages
from lettuce.fs import FileSystem from lettuce.fs import FileSystem
from lettuce.registry import STEP_REGISTRY from lettuce.registry import STEP_REGISTRY, CASTER_REGISTRY
from lettuce.registry import call_hook from lettuce.registry import call_hook
from lettuce.exceptions import ReasonToFail from lettuce.exceptions import ReasonToFail
from lettuce.exceptions import NoDefinitionFound from lettuce.exceptions import NoDefinitionFound
...@@ -110,16 +110,29 @@ class StepDefinition(object): ...@@ -110,16 +110,29 @@ class StepDefinition(object):
"""A step definition is a wrapper for user-defined callbacks. It """A step definition is a wrapper for user-defined callbacks. It
gets a few metadata from file, such as filename and line number""" gets a few metadata from file, such as filename and line number"""
def __init__(self, step, function): def __init__(self, step, function):
self.step = step
self.function = function self.function = function
self.file = fs.relpath(function.func_code.co_filename) self.file = fs.relpath(function.func_code.co_filename)
self.line = function.func_code.co_firstlineno + 1 self.line = function.func_code.co_firstlineno + 1
self.step = step
def _apply_casters(self, step, args):
sentence = step.sentence
if not args:
return ()
for regex, caster in CASTER_REGISTRY.items():
matched = re.search(regex, sentence)
if matched:
args = (caster(*args), )
return args
def __call__(self, *args, **kw): def __call__(self, *args, **kw):
"""Method that actually wrapps the call to step definition """Method that actually wrapps the call to step definition
callback. Sends step object as first argument callback. Sends step object as first argument
""" """
try: try:
args = self._apply_casters(self.step, args)
ret = self.function(self.step, *args, **kw) ret = self.function(self.step, *args, **kw)
self.step.passed = True self.step.passed = True
except Exception, e: except Exception, e:
......
...@@ -15,8 +15,43 @@ ...@@ -15,8 +15,43 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re import re
from lettuce.core import STEP_REGISTRY from lettuce.core import STEP_REGISTRY, CASTER_REGISTRY
from lettuce.exceptions import StepLoadingError from lettuce.exceptions import CasterLoadingError, StepLoadingError
def caster(regex):
"""Decorates a function that will be responsible for casting
matched regex into object, leveraging more flexibility on step
definitions and simplifying the test ecosystem.
Example::
>>> from lettuce import step
>>> from models import Contact
>>>
>>> @step.caster(r'the person "(.*)"')
... def into_a_contact_object(value):
... return Contact.objects.get(name=value)
...
>>> @step(r'Given the person "(.*)" is erased from my address book')
... def given_i_do_something(step, user):
... user.delete()
Notice the name matched in the regex below being cast into a
`Contact` object
"""
def wrap(func):
try:
re.compile(regex)
except re.error, e:
raise CasterLoadingError("Error when trying to compile:\n"
" regex: %r\n"
" for function: %s\n"
" error: %s" % (regex, func, e))
CASTER_REGISTRY[regex] = func
return func
return wrap
def step(regex): def step(regex):
...@@ -36,6 +71,7 @@ def step(regex): ...@@ -36,6 +71,7 @@ def step(regex):
Notice that all step definitions take a step object as argument. Notice that all step definitions take a step object as argument.
""" """
def wrap(func): def wrap(func):
try: try:
re.compile(regex) re.compile(regex)
...@@ -48,3 +84,5 @@ def step(regex): ...@@ -48,3 +84,5 @@ def step(regex):
return func return func
return wrap return wrap
step.caster = caster
...@@ -50,3 +50,8 @@ class LettuceSyntaxError(SyntaxError): ...@@ -50,3 +50,8 @@ class LettuceSyntaxError(SyntaxError):
class StepLoadingError(Exception): class StepLoadingError(Exception):
"""Raised when a step cannot be loaded.""" """Raised when a step cannot be loaded."""
pass pass
class CasterLoadingError(Exception):
"""Raised when a caster's regex cannot be compiled."""
pass
...@@ -38,8 +38,8 @@ class CallbackDict(dict): ...@@ -38,8 +38,8 @@ class CallbackDict(dict):
for callback_list in action_dict.values(): for callback_list in action_dict.values():
callback_list[:] = [] callback_list[:] = []
STEP_REGISTRY = {} STEP_REGISTRY = {}
CASTER_REGISTRY = {}
CALLBACK_REGISTRY = CallbackDict( CALLBACK_REGISTRY = CallbackDict(
{ {
'all': { 'all': {
...@@ -91,4 +91,5 @@ def call_hook(situation, kind, *args, **kw): ...@@ -91,4 +91,5 @@ def call_hook(situation, kind, *args, **kw):
def clear(): def clear():
STEP_REGISTRY.clear() STEP_REGISTRY.clear()
CASTER_REGISTRY.clear()
CALLBACK_REGISTRY.clear() CALLBACK_REGISTRY.clear()
# -*- coding: utf-8 -*-
# <Lettuce - Behaviour Driven Development for python>
# Copyright (C) <2010-2012> Gabriel Falcão <gabriel@nacaolivre.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from lettuce import step, world, Feature
from sure import that, scenario
FEATURE1 = '''
Feature: Transformations
Scenario: Simple matching
Given the following users:
| name | email |
| Gabriel | gabriel@lettuce.it |
| Lincoln | lincoln@comum.org |
When the user "Gabriel" is mentioned
Then it becomes available in `world` as "last_user"
And it is an `User` instance
'''
THIS_MODULE = sys.modules[__name__]
def step_runner_environ(context):
"Make sure the test environment is what is expected"
from lettuce import registry
registry.clear()
world.users = {}
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save(self):
world.users[self.name] = self
@step('Given the following users')
def collect_users(step):
for data in step.hashes:
user = User(**data)
user.save()
@step(r'When the user "(\w+)" is mentioned')
def mention_user(step, user):
world.last_user = user
@step(r'Then it becomes available.* as "([\w_]+)"')
def becomes_available(step, attribute):
assert (hasattr(world, attribute),
'world should contain the attribute %s' % attribute)
@step(r'And it is an `(\w+)` instance')
def and_is_instance_of(step, klass):
import ipdb;ipdb.set_trace()
assert that(world.last_user).is_a(klass)
@scenario(step_runner_environ)
def test_transformations(context):
@step.caster(r'the user "(\w+)" is mentioned')
def capture_users_by_name(name):
return world.users[name]
@step.caster(r'it is an `(\w+)`')
def get_class_by_name(name):
return THIS_MODULE[name]
f = Feature.from_string(FEATURE1)
feature_result = f.run()
scenario_result = feature_result.scenario_results[0]
assert that(scenario_result.steps_undefined).equals([])
assert that(scenario_result.steps_failed).equals([])
assert feature_result.passed
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment