Commit e9c41f64 by Gabriel Falcão

Huge commit with 3 features:

* Lettuce always try to load a "terrain.py" file. closes #14
* Can define callbacks to run before and after each step. closes #19
* Can define callbacks to run before and after each scenario. closes #15
parent c4d162de
......@@ -2,10 +2,10 @@ all: unit functional
unit:
@echo "Running unit tests ..."
@nosetests -s --verbosity=2 --with-coverage --cover-inclusive tests/unit/
@nosetests -s --verbosity=2 --with-coverage --cover-erase --cover-inclusive tests/unit/ --cover-package=lettuce
functional:
@echo "Running functional tests ..."
@nosetests -s --verbosity=2 --with-coverage --cover-inclusive tests/functional/
@nosetests -s --verbosity=2 --with-coverage --cover-erase --cover-inclusive tests/functional/ --cover-package=lettuce
clean:
@echo -n "Cleaning up files that are already in .gitignore... "
......
......@@ -16,5 +16,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
version = '0.1'
import sys
from lettuce.decorators import step
from lettuce.terrain import *
from lettuce.fs import FileSystem
#from lettuce.core import Feature
class Runner(object):
def __init__(self, base_path):
sys.path.insert(0, base_path)
FileSystem.pushd(base_path)
self.terrain = None
try:
self.terrain = __import__("terrain")
except ImportError, e:
if not "No module named terrain" in str(e):
raise e
sys.path.remove(base_path)
FileSystem.popd()
......@@ -14,14 +14,14 @@
#
# 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 re
from lettuce import strings
from lettuce.terrain import before, after
from lettuce.registry import STEP_REGISTRY
from lettuce.registry import CALLBACK_REGISTRY
from lettuce.exceptions import ReasonToFail
from lettuce.exceptions import NoDefinitionFound
STEP_REGISTRY = {}
def parse_data_list(lines):
keys = []
data_list = []
......@@ -105,14 +105,7 @@ class Step(object):
self.has_definition = True
self.defined_at = step_definition
try:
for callback in before.EACH_STEP_CALLBACKS:
callback(self)
step_definition()
for callback in after.EACH_STEP_CALLBACKS:
callback(self)
except Exception, e:
self.why = ReasonToFail(e)
raise
......@@ -158,9 +151,19 @@ class Scenario(object):
steps_failed = []
steps_undefined = []
for callback in CALLBACK_REGISTRY['scenario']['before_each']:
callback(self)
for step in self.steps:
try:
for callback in CALLBACK_REGISTRY['step']['before_each']:
callback(step)
step.run(ignore_case)
for callback in CALLBACK_REGISTRY['step']['after_each']:
callback(step)
steps_passed.append(step)
except AssertionError:
steps_passed.append(step)
......@@ -170,6 +173,9 @@ class Scenario(object):
steps_undefined.append(e.step)
continue
for callback in CALLBACK_REGISTRY['scenario']['after_each']:
callback(self)
skip = lambda x: x not in steps_passed and x not in steps_undefined
steps_skipped = filter(skip, self.steps)
......
......@@ -14,19 +14,151 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from os import walk
from os.path import dirname, abspath, join
import os
import sys
import codecs
import fnmatch
import zipfile
from glob import glob
from os.path import abspath, join, dirname, curdir, exists
class FeatureLoader(object):
def __init__(self, base_dir):
self.base_dir = abspath(base_dir)
self.base_dir = FileSystem.abspath(base_dir)
def find_feature_files(self):
paths = []
for root, dirs, files in walk(self.base_dir):
for root, dirs, files in FileSystem.walk(self.base_dir):
for filename in files:
if filename.endswith(".feature"):
path = join(root, filename)
path = FileSystem.join(root, filename)
paths.append(path)
return paths
class FileSystem(object):
stack = []
def __init__(self):
self.stack = []
@classmethod
def pushd(cls, path):
if not len(cls.stack):
cls.stack.append(cls.current_dir())
cls.stack.append(path)
os.chdir(path)
@classmethod
def popd(cls):
if cls.stack:
cls.stack.pop()
if cls.stack:
os.chdir(cls.stack[-1])
@classmethod
def filename(cls, path, with_extension=True):
fname = os.path.split(path)[1]
if not with_extension:
fname = os.path.splitext(fname)[0]
return fname
@classmethod
def exists(cls, path):
return exists(path)
@classmethod
def mkdir(cls, path):
try:
os.makedirs(path)
except OSError, e:
# ignore if path already exists
if e.errno not in (17, ):
raise e
else:
if not os.path.isdir(path):
# but the path must be a dir to ignore its creation
raise e
@classmethod
def current_dir(cls, path=""):
'''Returns the absolute path for current dir, also join the
current path with the given, if so.'''
to_return = cls.abspath(curdir)
if path:
return cls.join(to_return, path)
return to_return
@classmethod
def abspath(cls, path):
'''Returns the absolute path for the given path.'''
return abspath(path)
@classmethod
def join(cls, *args):
'''Returns the concatenated path for the given arguments.'''
return join(*args)
@classmethod
def dirname(cls, path):
'''Returns the directory name for the given file.'''
return dirname(path)
@classmethod
def walk(cls, path):
'''Walks through filesystem'''
return os.walk(path)
@classmethod
def locate(cls, path, match, recursive=True):
root_path = cls.abspath(path)
if recursive:
return_files = []
for path, dirs, files in cls.walk(root_path):
for filename in fnmatch.filter(files, match):
return_files.append(cls.join(path, filename))
return return_files
else:
return glob(cls.join(root_path, match))
@classmethod
def extract_zip(cls, filename, base_path='.', verbose=False):
base_path = cls.abspath(base_path)
output = lambda x: verbose and sys.stdout.write("%s\n" % x)
cls.pushd(base_path)
zfile = zipfile.ZipFile(filename)
output("Extracting files to %s" % base_path)
for file_name in zfile.namelist():
try:
output(" -> Unpacking %s" % file_name)
f = cls.open_raw(file_name, 'w')
f.write(zfile.read(file_name))
f.close()
except IOError:
output("---> Creating directory %s" % file_name)
cls.mkdir(file_name)
cls.popd()
@classmethod
def open(cls, name, mode):
path = name
if not os.path.isabs(path):
path = cls.current_dir(name)
return codecs.open(path, mode, 'utf-8')
@classmethod
def open_raw(cls, name, mode):
path = name
if not os.path.isabs(path):
path = cls.current_dir(name)
return open(path, mode)
......@@ -14,24 +14,29 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from lettuce.registry import world
from lettuce.registry import CALLBACK_REGISTRY
world._set = True
from threading import local
world = local()
class before:
@classmethod
def each_step(cls, function):
CALLBACK_REGISTRY['step']['%s_each' % cls.__name__].append(function)
return function
class before_and_after_base(object):
@classmethod
def each_step(cls, callback):
cls.EACH_STEP_CALLBACKS.append(callback)
def each_scenario(cls, function):
CALLBACK_REGISTRY['scenario']['%s_each' % cls.__name__].append(function)
return function
class after:
@classmethod
def each_scenario(cls, callback):
cls.EACH_SCENARIO_CALLBACKS.append(callback)
def each_step(cls, function):
CALLBACK_REGISTRY['step']['%s_each' % cls.__name__].append(function)
return function
class before(before_and_after_base):
EACH_STEP_CALLBACKS = []
EACH_SCENARIO_CALLBACKS = []
@classmethod
def each_scenario(cls, function):
CALLBACK_REGISTRY['scenario']['%s_each' % cls.__name__].append(function)
return function
class after(before_and_after_base):
EACH_STEP_CALLBACKS = []
EACH_SCENARIO_CALLBACKS = []
......@@ -19,23 +19,29 @@ from nose.tools import assert_equals
from lettuce import step
from lettuce.terrain import after
from lettuce.terrain import before
from lettuce.terrain import world
from lettuce.core import Feature
FEATURE = '''
FEATURE1 = '''
Feature: Before and After callbacks all along lettuce
Scenario: Before and After steps
Given I append "during" to states
'''
FEATURE2 = '''
Feature: Before and After callbacks all along lettuce
Scenario: Before and After scenarios
Given I append "during" to states
Scenario: Again
Given I append "during" to states
'''
def test_world():
"lettuce.terrain.world can be monkey patched at will"
def set_world():
from lettuce.terrain import world
world.was_set = True
def test_does_not_have():
......@@ -50,59 +56,53 @@ def test_world():
set_world()
test_does_have()
def _test_after_each_step_is_executed_before_each_step():
def test_after_each_step_is_executed_before_each_step():
"terrain.before.each_step and terrain.after.each_step decorators"
step_states = []
world.step_states = []
@before.each_step
def set_state_to_before(step):
assert_equals(step.sentence, 'Given I append "during" to step_states')
step_states.append('before')
world.step_states.append('before')
expected = 'Given I append "during" to states'
if step.sentence != expected:
raise TypeError('%r != %r' % (step.sentence, expected))
@step('append "during" to step_states')
@step('append "during" to states')
def append_during_to_step_states():
step_states.append("during")
world.step_states.append("during")
@after.each_step
def set_state_to_after(step):
assert_equals(step.sentence, 'Given I append "during" to step_states')
step_states.append('after')
world.step_states.append('after')
expected = 'Given I append "during" to states'
if step.sentence != expected:
raise TypeError('%r != %r' % (step.sentence, expected))
feature = Feature.from_string(FEATURE)
feature = Feature.from_string(FEATURE1)
feature.run()
assert_equals(step_states, ['before', 'during', 'after'])
assert_equals(world.step_states, ['before', 'during', 'after'])
def _test_after_each_scenario_is_executed_before_each_scenario():
def test_after_each_scenario_is_executed_before_each_scenario():
"terrain.before.each_scenario and terrain.after.each_scenario decorators"
scenario_steps = []
world.scenario_steps = []
@before.each_scenario
def set_state_to_before(scenario):
if scenario.name == 'Before and After steps':
scenario_steps.append('before')
else:
scenario_steps.append('almost during')
world.scenario_steps.append('before')
@step('append "during" to scenario_steps')
@step('append "during" to states')
def append_during_to_scenario_steps():
scenario_steps.append("during")
world.scenario_steps.append("during")
@after.each_scenario
def set_state_to_after(scenario):
if scenario.name == 'Before and After scenarios':
scenario_steps.append('almost after')
else:
scenario_steps.append('after')
world.scenario_steps.append('after')
feature = Feature.from_string(FEATURE)
feature = Feature.from_string(FEATURE2)
feature.run()
assert_equals(
scenario_steps,
['before', 'almost during', 'during', 'almost after', 'after']
world.scenario_steps,
['before', 'during', 'after', 'before', 'during', 'after']
)
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