Skip to content

Commit

Permalink
Add feature autoload for StructBDD
Browse files Browse the repository at this point in the history
  • Loading branch information
elchupanebrej committed Oct 6, 2022
1 parent d4dfc85 commit fe00c2f
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 20 deletions.
2 changes: 2 additions & 0 deletions pytest_bdd/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pathlib import Path
from uuid import uuid4

from attr import attrib, attrs
from pytest import Module as PytestModule

from pytest_bdd.scenario import _scenarios
Expand Down Expand Up @@ -30,6 +31,7 @@ def _build_test_module(self):
feature_paths=[self.fspath],
scenario_filter_or_scenario_name=None,
return_test_decorator=False,
parser=getattr(self, "parser", None),
_caller_module_locals=module.__dict__,
_caller_module_path=self.fspath,
)
Expand Down
27 changes: 24 additions & 3 deletions pytest_bdd/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from collections import deque
from contextlib import suppress
from functools import partial
from operator import attrgetter, contains, methodcaller
from pathlib import Path
from types import ModuleType
from typing import Collection
Expand All @@ -21,6 +23,7 @@
from pytest_bdd.scenario import add_options as scenario_add_options
from pytest_bdd.steps import StepHandler
from pytest_bdd.typing.pytest import Config, Mark, MarkDecorator, Metafunc, Parser, PytestPluginManager
from pytest_bdd.typing.struct_bdd import STRUCT_BDD_INSTALLED


def pytest_addhooks(pluginmanager: PytestPluginManager) -> None:
Expand Down Expand Up @@ -119,11 +122,29 @@ def pytest_collect_file(parent, path, file_path=None):
is_enabled_feature_autoload = config.getoption("feature_autoload")
if is_enabled_feature_autoload is None:
is_enabled_feature_autoload = config.getini("feature_autoload")
if file_path.suffix in {".gherkin", ".feature"} and is_enabled_feature_autoload:
if not is_enabled_feature_autoload:
return
if any(map(partial(contains, {".gherkin", ".feature"}), file_path.suffixes)):
if hasattr(FeatureFileCollector, "from_parent"):
return FeatureFileCollector.from_parent(parent, fspath=py.path.local(file_path))
collector = FeatureFileCollector.from_parent(parent, fspath=py.path.local(file_path))
else:
return FeatureFileCollector(parent=parent, fspath=py.path.local(file_path))
collector = FeatureFileCollector(parent=parent, fspath=py.path.local(file_path))

if STRUCT_BDD_INSTALLED:
from pytest_bdd.struct_bdd.parser import StructBDDParser

struct_bdd_parser_kind = next(
filter(
partial(contains, list(map(methodcaller("strip", "."), file_path.suffixes))),
list(map(attrgetter("value"), StructBDDParser.KIND)),
),
None,
)

if struct_bdd_parser_kind is not None:
collector.parser = StructBDDParser(kind=struct_bdd_parser_kind)

return collector


@pytest.mark.trylast
Expand Down
17 changes: 9 additions & 8 deletions pytest_bdd/struct_bdd/parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from functools import partial
from operator import methodcaller
from pathlib import Path
Expand All @@ -11,7 +12,7 @@

@attrs
class StructBDDParser(ParserProtocol):
class KIND:
class KIND(Enum):
HOCON = "hocon"
HJSON = "hjson"
JSON = "json"
Expand All @@ -25,7 +26,7 @@ class KIND:

@kind.default
def kind_default(self):
return self.KIND.YAML if getattr(self, "loader", None) is None else None
return self.KIND.YAML.value if getattr(self, "loader", None) is None else None

@glob.default
def glob_default(self):
Expand All @@ -46,28 +47,28 @@ def parse(self, path: Path, uri: str, *args, **kwargs):
)

def build_loader(self):
if self.kind == self.KIND.YAML:
if self.kind == self.KIND.YAML.value:
from yaml import FullLoader
from yaml import load as load_yaml

return partial(load_yaml, Loader=FullLoader)
elif self.kind == self.KIND.TOML:
elif self.kind == self.KIND.TOML.value:
from tomli import loads as load_toml

return load_toml
elif self.kind == self.KIND.JSON:
elif self.kind == self.KIND.JSON.value:
from json import loads as load_json

return load_json
elif self.kind == self.KIND.JSON5:
elif self.kind == self.KIND.JSON5.value:
from json5 import loads as load_json5

return load_json5
elif self.kind == self.KIND.HJSON:
elif self.kind == self.KIND.HJSON.value:
from hjson import loads as load_hjson

return load_hjson
elif self.kind == self.KIND.HOCON:
elif self.kind == self.KIND.HOCON.value:
from json import loads

from pyhocon import ConfigFactory, HOCONConverter
Expand Down
96 changes: 87 additions & 9 deletions tests/struct_bdd/test_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"kind,file_content",
[
partial(param, id="plain-yaml")(
StructBDDParser.KIND.YAML,
StructBDDParser.KIND.YAML.value,
"""\
Name: Steps are executed one by one
Description: |
Expand All @@ -30,7 +30,7 @@
""",
),
partial(param, id="plain-hocon")(
StructBDDParser.KIND.HOCON,
StructBDDParser.KIND.HOCON.value,
r"""
Name = Steps are executed one by one
Description: "Steps are executed one by one. Given and When sections\nare not mandatory in some cases.\n",
Expand All @@ -54,7 +54,7 @@
""",
),
partial(param, id="plain-json")(
StructBDDParser.KIND.JSON,
StructBDDParser.KIND.JSON.value,
r"""{
"Name": "Steps are executed one by one",
"Description": "Steps are executed one by one. Given and When sections\nare not mandatory in some cases.\n",
Expand All @@ -79,7 +79,7 @@
""",
),
partial(param, id="plain-hjson")(
StructBDDParser.KIND.HJSON,
StructBDDParser.KIND.HJSON.value,
r"""{
Name: Steps are executed one by one
Description:
Expand Down Expand Up @@ -111,7 +111,7 @@
""",
),
partial(param, id="plain-json5")(
StructBDDParser.KIND.JSON5,
StructBDDParser.KIND.JSON5.value,
r"""{
Name: 'Steps are executed one by one',
Description: 'Steps are executed one by one. Given and When sections\nare not mandatory in some cases.\n',
Expand All @@ -136,7 +136,7 @@
""",
),
partial(param, id="plain-toml")(
StructBDDParser.KIND.TOML,
StructBDDParser.KIND.TOML.value,
"""\
Name = "Steps are executed one by one"
Description='''
Expand All @@ -159,7 +159,7 @@
""",
),
partial(param, id="complex-yaml")(
StructBDDParser.KIND.YAML,
StructBDDParser.KIND.YAML.value,
"""\
Name: Steps are executed one by one
Description: |
Expand All @@ -185,7 +185,7 @@
""",
),
partial(param, id="complex-toml")(
StructBDDParser.KIND.TOML,
StructBDDParser.KIND.TOML.value,
"""\
Name = "Steps are executed one by one"
Description='''
Expand Down Expand Up @@ -283,7 +283,7 @@ def check_results(results):
"kind,file_content",
[
partial(param, id="plain-yaml")(
StructBDDParser.KIND.YAML,
StructBDDParser.KIND.YAML.value,
"""\
Name: Steps are executed one by one
Description: |
Expand Down Expand Up @@ -366,6 +366,84 @@ def check_results(results):
result.assert_outcomes(passed=1, failed=0)


@mark.parametrize(
"kind,file_content",
[
partial(param, id="plain-yaml")(
StructBDDParser.KIND.YAML.value,
"""\
Name: Steps are executed one by one
Description: |
Steps are executed one by one. Given and When sections
are not mandatory in some cases.
Steps:
- Step:
Name: Executed step by step
Description: Scenario description
Steps:
- Given: I have a foo fixture with value "foo"
- And: there is a list
- When: I append 1 to the list
- And: I append 2 to the list
- And: I append 3 to the list
- Then: foo should have value "foo"
- But: the list should be [1, 2, 3]
""",
),
],
)
def test_autoload_feature_yaml(testdir, kind, file_content):
testdir.makefile(
f".bdd.feature.{kind}",
steps=file_content,
)

testdir.makeconftest(
"""\
from textwrap import dedent
from pytest_bdd import given, when, then, scenario
from pytest_bdd.parser import StructBDDParser
@given('I have a foo fixture with value "foo"', target_fixture="foo")
def foo():
return "foo"
@given("there is a list", target_fixture="results")
def results():
return []
@when("I append 1 to the list")
def append_1(results):
results.append(1)
@when("I append 2 to the list")
def append_2(results):
results.append(2)
@when("I append 3 to the list")
def append_3(results):
results.append(3)
@then('foo should have value "foo"')
def foo_is_foo(foo):
assert foo == "foo"
@then("the list should be [1, 2, 3]")
def check_results(results):
assert results == [1, 2, 3]
"""
)
result = testdir.runpytest("--feature-autoload")
result.assert_outcomes(passed=1, failed=0)


@mark.parametrize(
"file_content",
[
Expand Down

0 comments on commit fe00c2f

Please sign in to comment.