more complex parsing of the multiline steps
This commit is contained in:
parent
60591ced61
commit
3370bcf714
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue