more complex parsing of the multiline steps

This commit is contained in:
Anatoly Bubenkov 2014-04-01 01:58:50 +02:00
parent 60591ced61
commit 3370bcf714
6 changed files with 141 additions and 44 deletions

View File

@ -23,3 +23,7 @@ class StepTypeError(ScenarioValidationError): # pragma: no cover
class GivenAlreadyUsed(ScenarioValidationError): # pragma: no cover
"""Fixture that implements the Given has been already used."""
class StepDefinitionNotFoundError(Exception): # pragma: no cover
"""Step definition not found."""

View File

@ -25,6 +25,7 @@ one line.
"""
import re # pragma: no cover
import sys # pragma: no cover
import textwrap
from pytest_bdd import types # pragma: no cover
from pytest_bdd import exceptions # pragma: no cover
@ -147,13 +148,18 @@ class Feature(object):
with _open_file(filename, encoding) as f:
content = force_unicode(f.read(), encoding)
step = None
for line_number, line in enumerate(content.splitlines()):
multiline_step = False
for line_number, line in enumerate(content.splitlines(), start=1):
unindented_line = line.lstrip()
line_indent = len(line) - len(unindented_line)
if step and step.indent < line_indent:
if step and (step.indent < line_indent or ((not unindented_line) and multiline_step)):
multiline_step = True
# multiline step, so just add line and continue
step.add_line(line.lstrip())
step.add_line(line)
continue
else:
step = None
multiline_step = False
stripped_line = line.strip()
clean_line = strip_comments(line)
if not clean_line:
@ -185,9 +191,7 @@ class Feature(object):
clean_line = remove_prefix(clean_line)
if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
self.scenarios[clean_line] = scenario = Scenario(self, clean_line)
step = None
elif mode == types.EXAMPLES:
step = None
mode = types.EXAMPLES_HEADERS
elif mode == types.EXAMPLES_VERTICAL:
mode = types.EXAMPLE_LINE_VERTICAL
@ -197,11 +201,11 @@ class Feature(object):
elif mode == types.EXAMPLE_LINE:
scenario.add_example([l.strip() for l in stripped_line.split('|')[1:-1]])
elif mode == types.EXAMPLE_LINE_VERTICAL:
step = None
clean_line = [l.strip() for l in stripped_line.split('|')[1:-1]]
scenario.add_example_row(clean_line[0], clean_line[1:])
elif mode and mode != types.FEATURE:
step = scenario.add_step(step_name=clean_line, step_type=mode, indent=line_indent)
step = scenario.add_step(
step_name=clean_line, step_type=mode, indent=line_indent, line_number=line_number)
self.description = u'\n'.join(description)
@ -238,7 +242,7 @@ class Scenario(object):
self.vertical_examples = []
self.example_converters = example_converters
def add_step(self, step_name, step_type, indent):
def add_step(self, step_name, step_type, indent, line_number):
"""Add step to the scenario.
:param step_name: Step name.
@ -247,7 +251,8 @@ class Scenario(object):
"""
params = get_step_params(step_name)
self.params.update(params)
step = Step(name=step_name, type=step_type, params=params, scenario=self, indent=indent)
step = Step(
name=step_name, type=step_type, params=params, scenario=self, indent=indent, line_number=line_number)
self.steps.append(step)
return step
@ -323,13 +328,23 @@ class Scenario(object):
class Step(object):
"""Step."""
def __init__(self, name, type, params, scenario, indent):
def __init__(self, name, type, params, scenario, indent, line_number):
self.name = name
self.lines = []
self.indent = indent
self.type = type
self.params = params
self.scenario = scenario
self.line_number = line_number
def add_line(self, line):
"""Add line to the multiple step."""
self.name += '\n' + line
self.lines.append(line)
@property
def name(self):
return '\n'.join([self._name] + ([textwrap.dedent('\n'.join(self.lines))] if self.lines else []))
@name.setter
def name(self, value):
self._name = value

View File

@ -70,33 +70,39 @@ def _inject_fixture(request, arg, value):
request.fixturenames.append(arg)
def _find_step_function(request, name, encoding):
def _find_step_function(request, step, encoding):
"""Match the step defined by the regular expression pattern.
:param request: PyTest request object.
:param name: Step name.
:param step: `Step`.
:return: Step function.
"""
name = step.name
try:
return request.getfuncargvalue(force_encode(name, encoding))
except python.FixtureLookupError:
try:
for fixturename, fixturedefs in request._fixturemanager._arg2fixturedefs.items():
for fixturedef in fixturedefs:
for fixturename, fixturedefs in request._fixturemanager._arg2fixturedefs.items():
for fixturedef in fixturedefs:
pattern = getattr(fixturedef.func, 'pattern', None)
match = pattern.match(name) if pattern else None
if match:
converters = getattr(fixturedef.func, 'converters', {})
for arg, value in match.groupdict().items():
if arg in converters:
value = converters[arg](value)
_inject_fixture(request, arg, value)
return request.getfuncargvalue(pattern.pattern)
raise
pattern = getattr(fixturedef.func, 'pattern', None)
match = pattern.match(name) if pattern else None
if match:
converters = getattr(fixturedef.func, 'converters', {})
for arg, value in match.groupdict().items():
if arg in converters:
value = converters[arg](value)
_inject_fixture(request, arg, value)
return request.getfuncargvalue(pattern.pattern)
raise
except python.FixtureLookupError as e:
raise exceptions.StepDefinitionNotFoundError(
"""Step definition is not found: "{e.argname}"."""
""" Line {step.line_number} in scenario "{scenario.name}" in the feature "{feature.filename}""".format(
e=e, step=step, scenario=step.scenario, feature=step.scenario.feature)
)
def _execute_step_function(request, feature, step, step_func, example=None):
@ -133,8 +139,8 @@ def _execute_scenario(feature, scenario, request, encoding, example=None):
# Execute scenario steps
for step in scenario.steps:
try:
step_func = _find_step_function(request, step.name, encoding=encoding)
except python.FixtureLookupError as exception:
step_func = _find_step_function(request, step, encoding=encoding)
except exceptions.StepDefinitionNotFoundError as exception:
request.config.hook.pytest_bdd_step_func_lookup_error(
request=request, feature=feature, scenario=scenario, step=step, exception=exception)
raise

View File

@ -1,6 +1,7 @@
Scenario: Multiline step using sub indentation
Scenario: Multiline step using sub indentation wrong indent
Given I have a step with:
Some
Extra
Lines
Extra
Lines
Then the text should be parsed with correct indentation

View File

@ -1,15 +1,76 @@
"""Multiline steps tests."""
import re
import textwrap
from pytest_bdd import given, then, scenario
import pytest
from pytest_bdd import given, then, scenario, exceptions
@scenario(
'multiline.feature',
'Multiline step using sub indentation',
)
def test_multiline():
pass
@pytest.mark.parametrize(['feature_text', 'expected_text'], [
(
textwrap.dedent("""
Scenario: Multiline step using sub indentation
Given I have a step with:
Some
Extra
Lines
Then the text should be parsed with correct indentation
"""),
textwrap.dedent("""
Some
Extra
Lines
""")[1: -1]
),
(
textwrap.dedent("""
Scenario: Multiline step using sub indentation
Given I have a step with:
Some
Extra
Lines
Then the text should be parsed with correct indentation
"""),
textwrap.dedent("""
Some
Extra
Lines
""")[1:-1]
),
(
textwrap.dedent("""
Feature:
Scenario: Multiline step using sub indentation
Given I have a step with:
Some
Extra
Lines
"""),
textwrap.dedent("""
Some
Extra
Lines
""")[1:-1]
),
])
def test_multiline(request, tmpdir, feature_text, expected_text):
file_name = tmpdir.join('test.feature')
with file_name.open('w') as fd:
fd.write(feature_text)
@scenario(file_name.strpath, 'Multiline step using sub indentation')
def test_multiline(request):
assert request.getfuncargvalue('i_have_text') == expected_text
test_multiline(request)
@given(re.compile(r'I have a step with:\n(?P<text>.+)', re.DOTALL))
@ -18,7 +79,17 @@ def i_have_text(text):
@then('the text should be parsed with correct indentation')
def eat_cucumbers(i_have_text, text):
assert i_have_text == text == """Some
Extra
Lines"""
def text_should_be_correct(i_have_text, text, expected_text):
assert i_have_text == text == expected_text
def test_multiline_wrong_indent(request):
"""Multiline step using sub indentation wrong indent."""
@scenario(
'multiline.feature',
'Multiline step using sub indentation wrong indent',
)
def test_multiline():
pass
with pytest.raises(exceptions.StepDefinitionNotFoundError):
test_multiline(request)

View File

@ -71,5 +71,5 @@ def test_verbose_output(request):
msg, line_number, line = excinfo.value.args
assert line_number == 4
assert line_number == 5
assert line == 'When I do it again'