Skip to content

Fixes #276: configure and unconfigure Feature class #282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
37 changes: 31 additions & 6 deletions pytest_bdd/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def get_features(paths, **kwargs):
)
else:
base, name = op.split(path)
feature = Feature.get_feature(base, name, **kwargs)
feature = Feature.get_feature(name, base, **kwargs)
features.append(feature)
features.sort(key=lambda feature: feature.name or feature.filename)
return features
Expand Down Expand Up @@ -250,6 +250,8 @@ def __bool__(self):
class Feature(object):

"""Feature."""
strict_gherkin = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the Feature class should contain configuration options like strict_gherkin or base_dir. Instead, callers of Feature(...) should consult the current config from the stack.

base_dir = None

def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
"""Parse the feature file.
Expand Down Expand Up @@ -390,28 +392,51 @@ def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
self.description = u"\n".join(description).strip()

@classmethod
def get_feature(cls, base_path, filename, encoding="utf-8", strict_gherkin=True):
def configure(cls, config):
cls.base_dir = config.getini('bdd_features_base_dir')
cls.strict_gherkin = config.getini('bdd_strict_gherkin')

@classmethod
def unconfigure(cls, config):
if config:
cls.configure(config)
else:
cls.base_dir = None
cls.strict_gherkin = None

@classmethod
def get_feature(cls, filename, base_dir=None, caller_module=None, strict_gherkin=True, encoding="utf-8"):
"""Get a feature by the filename.

:param str base_path: Base feature directory.
:param str filename: Filename of the feature file.
:param str encoding: Feature file encoding.
:param str base_dir: Base feature directory.
:param module caller_module
:param bool strict_gherkin: Flag whether it's a strictly gherkin scenario or not (e.g. it will validate correct
gherkin language (given-when-then))
:param str encoding: Feature file encoding.

:return: `Feature` instance from the parsed feature cache.

:note: The features are parsed on the execution of the test and
stored in the global variable cache to improve the performance
when multiple scenarios are referencing the same file.
"""
full_name = op.abspath(op.join(base_path, filename))
if base_dir is None:
base_dir = cls._get_default_base_dir(caller_module)
if strict_gherkin is None:
strict_gherkin = cls.strict_gherkin
full_name = op.abspath(op.join(base_dir, filename))

feature = features.get(full_name)
if not feature:
feature = Feature(base_path, filename, encoding=encoding, strict_gherkin=strict_gherkin)
feature = Feature(base_dir, filename, encoding=encoding, strict_gherkin=strict_gherkin)
features[full_name] = feature
return feature

@classmethod
def _get_default_base_dir(cls, caller_module):
return cls.base_dir if cls.base_dir else op.dirname(caller_module.__file__)


class Scenario(object):

Expand Down
8 changes: 8 additions & 0 deletions pytest_bdd/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from . import generation
from . import reporting
from . import gherkin_terminal_reporter
from .feature import Feature
from .utils import CONFIG_STACK


def pytest_addhooks(pluginmanager):
Expand Down Expand Up @@ -47,14 +49,20 @@ def add_bdd_ini(parser):
@pytest.mark.trylast
def pytest_configure(config):
"""Configure all subplugins."""
CONFIG_STACK.append(config)
cucumber_json.configure(config)
gherkin_terminal_reporter.configure(config)
Feature.configure(config)


def pytest_unconfigure(config):
"""Unconfigure all subplugins."""
CONFIG_STACK.pop()
cucumber_json.unconfigure(config)

previous_config = CONFIG_STACK[-1] if CONFIG_STACK else None
Feature.unconfigure(previous_config)


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
Expand Down
37 changes: 9 additions & 28 deletions pytest_bdd/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
recreate_function,
)
from .types import GIVEN
from .utils import get_args, get_fixture_value
from .utils import CONFIG_STACK, get_args, get_fixture_value

if six.PY3: # pragma: no cover
import runpy
Expand Down Expand Up @@ -246,7 +246,8 @@ def decorator(_pytestbdd_function):
_scenario = pytest.mark.parametrize(*param_set)(_scenario)

for tag in scenario.tags.union(feature.tags):
pytest.config.hook.pytest_bdd_apply_tag(tag=tag, function=_scenario)
config = CONFIG_STACK[-1]
config.hook.pytest_bdd_apply_tag(tag=tag, function=_scenario)

_scenario.__doc__ = "{feature_name}: {scenario_name}".format(
feature_name=feature_name, scenario_name=scenario_name)
Expand All @@ -272,11 +273,9 @@ def scenario(feature_name, scenario_name, encoding="utf-8", example_converters=N
caller_function = caller_function or get_caller_function()

# Get the feature
if features_base_dir is None:
features_base_dir = get_features_base_dir(caller_module)
if strict_gherkin is None:
strict_gherkin = get_strict_gherkin()
feature = Feature.get_feature(features_base_dir, feature_name, encoding=encoding, strict_gherkin=strict_gherkin)
feature = Feature.get_feature(
feature_name, features_base_dir, caller_module, strict_gherkin=strict_gherkin, encoding=encoding
)

# Get the sc_enario
try:
Expand Down Expand Up @@ -306,24 +305,6 @@ def scenario(feature_name, scenario_name, encoding="utf-8", example_converters=N
)


def get_features_base_dir(caller_module):
default_base_dir = os.path.dirname(caller_module.__file__)
return get_from_ini('bdd_features_base_dir', default_base_dir)


def get_from_ini(key, default):
"""Get value from ini config. Return default if value has not been set.

Use if the default value is dynamic. Otherwise set default on addini call.
"""
value = pytest.config.getini(key)
return value if value != '' else default


def get_strict_gherkin():
return pytest.config.getini('bdd_strict_gherkin')


def make_python_name(string):
"""Make python attribute name out of a given string."""
string = re.sub(PYTHON_REPLACE_REGEX, "", string.replace(" ", "_"))
Expand Down Expand Up @@ -354,11 +335,11 @@ def scenarios(*feature_paths, **kwargs):

features_base_dir = kwargs.get('features_base_dir')
if features_base_dir is None:
features_base_dir = get_features_base_dir(module)
features_base_dir = Feature._get_default_base_dir(module)

strict_gherkin = kwargs.get('strict_gherkin')
if strict_gherkin is None:
strict_gherkin = get_strict_gherkin()
strict_gherkin = Feature.strict_gherkin

abs_feature_paths = []
for path in feature_paths:
Expand All @@ -372,7 +353,7 @@ def scenarios(*feature_paths, **kwargs):
for name, attr in module.__dict__.items() if hasattr(attr, '__scenario__'))

index = 10
for feature in get_features(abs_feature_paths, strict_gherkin=strict_gherkin):
for feature in get_features(abs_feature_paths, caller_module=module, strict_gherkin=strict_gherkin):
for scenario_name, scenario_object in feature.scenarios.items():
# skip already bound scenarios
if (scenario_object.feature.filename, scenario_name) not in module_scenarios:
Expand Down
2 changes: 2 additions & 0 deletions pytest_bdd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import inspect

CONFIG_STACK = []


def get_args(func):
"""Get a list of argument names for a function.
Expand Down