Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pytest-bdd for
openSUSE:Factory checked in at 2021-03-02 12:32:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pytest-bdd (Old)
and /work/SRC/openSUSE:Factory/.python-pytest-bdd.new.2378 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-bdd"
Tue Mar 2 12:32:12 2021 rev:9 rq:875535 version:4.0.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pytest-bdd/python-pytest-bdd.changes
2020-09-23 18:48:19.373758629 +0200
+++
/work/SRC/openSUSE:Factory/.python-pytest-bdd.new.2378/python-pytest-bdd.changes
2021-03-02 12:44:38.948313631 +0100
@@ -1,0 +2,9 @@
+Fri Feb 26 20:50:41 UTC 2021 - Ben Greiner <[email protected]>
+
+- update to 4.0.2
+ * Fix a bug that prevents using comments in the Examples:
+ section. (youtux)
+- provide the correct u-a conrolled command to the tests
+- Skip failing tests: test_at_scenario and test_step_trace
+
+-------------------------------------------------------------------
Old:
----
pytest-bdd-4.0.1.tar.gz
New:
----
pytest-bdd-4.0.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pytest-bdd.spec ++++++
--- /var/tmp/diff_new_pack.Jog5zD/_old 2021-03-02 12:44:39.376314001 +0100
+++ /var/tmp/diff_new_pack.Jog5zD/_new 2021-03-02 12:44:39.380314005 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-pytest-bdd
#
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%bcond_without python2
Name: python-pytest-bdd
-Version: 4.0.1
+Version: 4.0.2
Release: 0
Summary: BDD for pytest
License: MIT
@@ -80,9 +80,14 @@
%check
export LANG=en_US.UTF-8
-export PYTHONDONTWRITEBYTECODE=1
-# test_generate_with_quotes and test_unicode_characters require ptyest-bdd
binary which we handle with u-a
-%pytest -k 'not test_generate_with_quotes and not test_unicode_characters'
+%{python_expand # provide the u-a controlled command in PATH
+mkdir -p build/testbin
+ln -s %{buildroot}%{_bindir}/pytest-bdd-%{$python_bin_suffix}
build/testbin/pytest-bdd
+}
+export PATH=$PWD/build/testbin:$PATH
+# test_at_in_scenario: the result footer looks slightly different
+# test_step_trace: unraisable exception in the obs environment
+%pytest -k "not (test_at_in_scenario or test_step_trace)" -ra
%post
%python_install_alternative pytest-bdd
++++++ pytest-bdd-4.0.1.tar.gz -> pytest-bdd-4.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/CHANGES.rst
new/pytest-bdd-4.0.2/CHANGES.rst
--- old/pytest-bdd-4.0.1/CHANGES.rst 2020-09-08 12:03:15.000000000 +0200
+++ new/pytest-bdd-4.0.2/CHANGES.rst 2020-12-07 13:38:07.000000000 +0100
@@ -1,6 +1,11 @@
Changelog
=========
+4.0.2
+-----
+- Fix a bug that prevents using comments in the ``Examples:`` section. (youtux)
+
+
4.0.1
-----
- Fixed performance regression introduced in 4.0.0 where collection time of
tests would take way longer than before. (youtux)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/pytest_bdd/__init__.py
new/pytest-bdd-4.0.2/pytest_bdd/__init__.py
--- old/pytest-bdd-4.0.1/pytest_bdd/__init__.py 2020-09-08 12:03:15.000000000
+0200
+++ new/pytest-bdd-4.0.2/pytest_bdd/__init__.py 2020-12-07 13:38:07.000000000
+0100
@@ -3,6 +3,6 @@
from pytest_bdd.steps import given, when, then
from pytest_bdd.scenario import scenario, scenarios
-__version__ = "4.0.1"
+__version__ = "4.0.2"
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__,
scenarios.__name__]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/pytest_bdd/feature.py
new/pytest-bdd-4.0.2/pytest_bdd/feature.py
--- old/pytest-bdd-4.0.1/pytest_bdd/feature.py 2020-09-08 12:03:15.000000000
+0200
+++ new/pytest-bdd-4.0.2/pytest_bdd/feature.py 2020-12-07 13:38:07.000000000
+0100
@@ -23,84 +23,18 @@
:note: There're no multiline steps, the description of the step must fit in
one line.
"""
-
-from collections import OrderedDict
-from os import path as op
-import codecs
-import re
+import os.path
import sys
-import textwrap
import glob2
-import six
-from . import types
-from . import exceptions
+from .parser import parse_feature
# Global features dictionary
features = {}
-STEP_PREFIXES = [
- ("Feature: ", types.FEATURE),
- ("Scenario Outline: ", types.SCENARIO_OUTLINE),
- ("Examples: Vertical", types.EXAMPLES_VERTICAL),
- ("Examples:", types.EXAMPLES),
- ("Scenario: ", types.SCENARIO),
- ("Background:", types.BACKGROUND),
- ("Given ", types.GIVEN),
- ("When ", types.WHEN),
- ("Then ", types.THEN),
- ("@", types.TAG),
- # Continuation of the previously mentioned step type
- ("And ", None),
- ("But ", None),
-]
-
-STEP_PARAM_RE = re.compile(r"\<(.+?)\>")
-COMMENT_RE = re.compile(r"(^|(?<=\s))#")
-SPLIT_LINE_RE = re.compile(r"(?<!\\)\|")
-
-
-def get_step_type(line):
- """Detect step type by the beginning of the line.
-
- :param str line: Line of the Feature file.
-
- :return: SCENARIO, GIVEN, WHEN, THEN, or `None` if can't be detected.
- """
- for prefix, _type in STEP_PREFIXES:
- if line.startswith(prefix):
- return _type
-
-
-def strip_comments(line):
- """Remove comments.
-
- :param str line: Line of the Feature file.
-
- :return: Stripped line.
- """
- res = COMMENT_RE.search(line)
- if res:
- line = line[: res.start()]
- return line.strip()
-
-
-def parse_line(line):
- """Parse step line to get the step prefix (Scenario, Given, When, Then or
And) and the actual step name.
-
- :param line: Line of the Feature file.
-
- :return: `tuple` in form ("<prefix>", "<Line without the prefix>").
- """
- for prefix, _ in STEP_PREFIXES:
- if line.startswith(prefix):
- return prefix.strip(), line[len(prefix) :].strip()
- return "", line
-
-
def force_unicode(obj, encoding="utf-8"):
"""Get the unicode string out of given object (python 2 and python 3).
@@ -131,26 +65,26 @@
return string
-def get_tags(line):
- """Get tags out of the given line.
-
- :param str line: Feature file text line.
-
- :return: List of tags.
- """
- if not line or not line.strip().startswith("@"):
- return set()
- return set((tag.lstrip("@") for tag in line.strip().split(" @") if
len(tag) > 1))
-
+def get_feature(base_path, filename, encoding="utf-8"):
+ """Get a feature by the filename.
-def split_line(line):
- """Split the given Examples line.
-
- :param str|unicode line: Feature file Examples line.
-
- :return: List of strings.
- """
- return [cell.replace("\\|", "|").strip() for cell in
SPLIT_LINE_RE.split(line[1:-1])]
+ :param str base_path: Base feature directory.
+ :param str filename: Filename of the feature file.
+ :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 = os.path.abspath(os.path.join(base_path, filename))
+ feature = features.get(full_name)
+ if not feature:
+ feature = parse_feature(base_path, filename, encoding=encoding)
+ features[full_name] = feature
+ return feature
def get_features(paths, **kwargs):
@@ -165,388 +99,11 @@
for path in paths:
if path not in seen_names:
seen_names.add(path)
- if op.isdir(path):
- features.extend(get_features(glob2.iglob(op.join(path, "**",
"*.feature")), **kwargs))
+ if os.path.isdir(path):
+ features.extend(get_features(glob2.iglob(os.path.join(path,
"**", "*.feature")), **kwargs))
else:
- base, name = op.split(path)
- feature = Feature.get_feature(base, name, **kwargs)
+ base, name = os.path.split(path)
+ feature = get_feature(base, name, **kwargs)
features.append(feature)
features.sort(key=lambda feature: feature.name or feature.filename)
return features
-
-
-class Examples(object):
-
- """Example table."""
-
- def __init__(self):
- """Initialize examples instance."""
- self.example_params = []
- self.examples = []
- self.vertical_examples = []
- self.line_number = None
- self.name = None
-
- def set_param_names(self, keys):
- """Set parameter names.
-
- :param names: `list` of `string` parameter names.
- """
- self.example_params = [str(key) for key in keys]
-
- def add_example(self, values):
- """Add example.
-
- :param values: `list` of `string` parameter values.
- """
- self.examples.append(values)
-
- def add_example_row(self, param, values):
- """Add example row.
-
- :param param: `str` parameter name
- :param values: `list` of `string` parameter values
- """
- if param in self.example_params:
- raise exceptions.ExamplesNotValidError(
- """Example rows should contain unique parameters. "{0}"
appeared more than once""".format(param)
- )
- self.example_params.append(param)
- self.vertical_examples.append(values)
-
- def get_params(self, converters, builtin=False):
- """Get scenario pytest parametrization table.
-
- :param converters: `dict` of converter functions to convert parameter
values
- """
- param_count = len(self.example_params)
- if self.vertical_examples and not self.examples:
- for value_index in range(len(self.vertical_examples[0])):
- example = []
- for param_index in range(param_count):
-
example.append(self.vertical_examples[param_index][value_index])
- self.examples.append(example)
-
- if self.examples:
- params = []
- for example in self.examples:
- example = list(example)
- for index, param in enumerate(self.example_params):
- raw_value = example[index]
- if converters and param in converters:
- value = converters[param](raw_value)
- if not builtin or value.__class__.__module__ in
{"__builtin__", "builtins"}:
- example[index] = value
- params.append(example)
- return [self.example_params, params]
- else:
- return []
-
- def __bool__(self):
- """Bool comparison."""
- return bool(self.vertical_examples or self.examples)
-
- if six.PY2:
- __nonzero__ = __bool__
-
-
-class Feature(object):
- """Feature."""
-
- def __init__(self, basedir, filename, encoding="utf-8"):
- """Parse the feature file.
-
- :param str basedir: Feature files base directory.
- :param str filename: Relative path to the feature file.
- :param str encoding: Feature file encoding (utf-8 by default).
- """
- self.scenarios = OrderedDict()
- self.rel_filename = op.join(op.basename(basedir), filename)
- self.filename = filename = op.abspath(op.join(basedir, filename))
- self.line_number = 1
- self.name = None
- self.tags = set()
- self.examples = Examples()
- scenario = None
- mode = None
- prev_mode = None
- description = []
- step = None
- multiline_step = False
- prev_line = None
- self.background = None
-
- with codecs.open(filename, encoding=encoding) as f:
- content = force_unicode(f.read(), encoding)
- 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 or ((not
unindented_line) and multiline_step)):
- multiline_step = True
- # multiline step, so just add line and continue
- step.add_line(line)
- continue
- else:
- step = None
- multiline_step = False
- stripped_line = line.strip()
- clean_line = strip_comments(line)
- if not clean_line and (not prev_mode or prev_mode not in
types.FEATURE):
- continue
- mode = get_step_type(clean_line) or mode
-
- allowed_prev_mode = (types.BACKGROUND, types.GIVEN, types.WHEN)
-
- if not scenario and prev_mode not in allowed_prev_mode and
mode in types.STEP_TYPES:
- raise exceptions.FeatureError(
- "Step definition outside of a Scenario or a
Background", line_number, clean_line, filename
- )
-
- if mode == types.FEATURE:
- if prev_mode is None or prev_mode == types.TAG:
- _, self.name = parse_line(clean_line)
- self.line_number = line_number
- self.tags = get_tags(prev_line)
- elif prev_mode == types.FEATURE:
- description.append(clean_line)
- else:
- raise exceptions.FeatureError(
- "Multiple features are not allowed in a single
feature file",
- line_number,
- clean_line,
- filename,
- )
-
- prev_mode = mode
-
- # Remove Feature, Given, When, Then, And
- keyword, parsed_line = parse_line(clean_line)
- if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
- tags = get_tags(prev_line)
- self.scenarios[parsed_line] = scenario = Scenario(self,
parsed_line, line_number, tags=tags)
- elif mode == types.BACKGROUND:
- self.background = Background(feature=self,
line_number=line_number)
- elif mode == types.EXAMPLES:
- mode = types.EXAMPLES_HEADERS
- (scenario or self).examples.line_number = line_number
- elif mode == types.EXAMPLES_VERTICAL:
- mode = types.EXAMPLE_LINE_VERTICAL
- (scenario or self).examples.line_number = line_number
- elif mode == types.EXAMPLES_HEADERS:
- (scenario or self).examples.set_param_names([l for l in
split_line(parsed_line) if l])
- mode = types.EXAMPLE_LINE
- elif mode == types.EXAMPLE_LINE:
- (scenario or self).examples.add_example([l for l in
split_line(stripped_line)])
- elif mode == types.EXAMPLE_LINE_VERTICAL:
- param_line_parts = [l for l in split_line(stripped_line)]
- try:
- (scenario or
self).examples.add_example_row(param_line_parts[0], param_line_parts[1:])
- except exceptions.ExamplesNotValidError as exc:
- if scenario:
- raise exceptions.FeatureError(
- """Scenario has not valid examples.
{0}""".format(exc.args[0]),
- line_number,
- clean_line,
- filename,
- )
- else:
- raise exceptions.FeatureError(
- """Feature has not valid examples.
{0}""".format(exc.args[0]),
- line_number,
- clean_line,
- filename,
- )
- elif mode and mode not in (types.FEATURE, types.TAG):
- step = Step(
- name=parsed_line, type=mode, indent=line_indent,
line_number=line_number, keyword=keyword
- )
- if self.background and not scenario:
- target = self.background
- else:
- target = scenario
- target.add_step(step)
- prev_line = clean_line
-
- self.description = u"\n".join(description).strip()
-
- @classmethod
- def get_feature(cls, base_path, filename, 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.
-
- :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))
- feature = features.get(full_name)
- if not feature:
- feature = Feature(base_path, filename, encoding=encoding)
- features[full_name] = feature
- return feature
-
-
-class Scenario(object):
-
- """Scenario."""
-
- def __init__(self, feature, name, line_number, example_converters=None,
tags=None):
- """Scenario constructor.
-
- :param pytest_bdd.feature.Feature feature: Feature.
- :param str name: Scenario name.
- :param int line_number: Scenario line number.
- :param dict example_converters: Example table parameter converters.
- :param set tags: Set of tags.
- """
- self.feature = feature
- self.name = name
- self._steps = []
- self.examples = Examples()
- self.line_number = line_number
- self.example_converters = example_converters
- self.tags = tags or set()
- self.failed = False
- self.test_function = None
-
- def add_step(self, step):
- """Add step to the scenario.
-
- :param pytest_bdd.feature.Step step: Step.
- """
- step.scenario = self
- self._steps.append(step)
-
- @property
- def steps(self):
- """Get scenario steps including background steps.
-
- :return: List of steps.
- """
- result = []
- if self.feature.background:
- result.extend(self.feature.background.steps)
- result.extend(self._steps)
- return result
-
- @property
- def params(self):
- """Get parameter names.
-
- :return: Parameter names.
- :rtype: frozenset
- """
- return frozenset(sum((list(step.params) for step in self.steps), []))
-
- def get_example_params(self):
- """Get example parameter names."""
- return set(self.examples.example_params +
self.feature.examples.example_params)
-
- def get_params(self, builtin=False):
- """Get converted example params."""
- for examples in [self.feature.examples, self.examples]:
- yield examples.get_params(self.example_converters, builtin=builtin)
-
- def validate(self):
- """Validate the scenario.
-
- :raises ScenarioValidationError: when scenario is not valid
- """
- params = self.params
- example_params = self.get_example_params()
- if params and example_params and params != example_params:
- raise exceptions.ScenarioExamplesNotValidError(
- """Scenario "{0}" in the feature "{1}" has not valid examples.
"""
- """Set of step parameters {2} should match set of example
values {3}.""".format(
- self.name, self.feature.filename, sorted(params),
sorted(example_params)
- )
- )
-
-
[email protected]_2_unicode_compatible
-class Step(object):
-
- """Step."""
-
- def __init__(self, name, type, indent, line_number, keyword):
- """Step constructor.
-
- :param str name: step name.
- :param str type: step type.
- :param int indent: step text indent.
- :param int line_number: line number.
- :param str keyword: step keyword.
- """
- self.name = name
- self.keyword = keyword
- self.lines = []
- self.indent = indent
- self.type = type
- self.line_number = line_number
- self.failed = False
- self.start = 0
- self.stop = 0
- self.scenario = None
- self.background = None
-
- def add_line(self, line):
- """Add line to the multiple step.
-
- :param str line: Line of text - the continuation of the step name.
- """
- self.lines.append(line)
-
- @property
- def name(self):
- """Get step name."""
- multilines_content = textwrap.dedent("\n".join(self.lines)) if
self.lines else ""
-
- # Remove the multiline quotes, if present.
- multilines_content = re.sub(
- pattern=r'^"""\n(?P<content>.*)\n"""$',
- repl=r"\g<content>",
- string=multilines_content,
- flags=re.DOTALL, # Needed to make the "." match also new lines
- )
-
- lines = [self._name] + [multilines_content]
- return "\n".join(lines).strip()
-
- @name.setter
- def name(self, value):
- """Set step name."""
- self._name = value
-
- def __str__(self):
- """Full step name including the type."""
- return '{type} "{name}"'.format(type=self.type.capitalize(),
name=self.name)
-
- @property
- def params(self):
- """Get step params."""
- return tuple(frozenset(STEP_PARAM_RE.findall(self.name)))
-
-
-class Background(object):
-
- """Background."""
-
- def __init__(self, feature, line_number):
- """Background constructor.
-
- :param pytest_bdd.feature.Feature feature: Feature.
- :param int line_number: Line number.
- """
- self.feature = feature
- self.line_number = line_number
- self.steps = []
-
- def add_step(self, step):
- """Add step to the background."""
- step.background = self
- self.steps.append(step)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest-bdd-4.0.1/pytest_bdd/gherkin_terminal_reporter.py
new/pytest-bdd-4.0.2/pytest_bdd/gherkin_terminal_reporter.py
--- old/pytest-bdd-4.0.1/pytest_bdd/gherkin_terminal_reporter.py
2020-09-08 12:03:15.000000000 +0200
+++ new/pytest-bdd-4.0.2/pytest_bdd/gherkin_terminal_reporter.py
2020-12-07 13:38:07.000000000 +0100
@@ -6,7 +6,7 @@
from _pytest.terminal import TerminalReporter
-from .feature import STEP_PARAM_RE
+from .parser import STEP_PARAM_RE
def add_options(parser):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/pytest_bdd/parser.py
new/pytest-bdd-4.0.2/pytest_bdd/parser.py
--- old/pytest-bdd-4.0.1/pytest_bdd/parser.py 1970-01-01 01:00:00.000000000
+0100
+++ new/pytest-bdd-4.0.2/pytest_bdd/parser.py 2020-12-07 13:38:07.000000000
+0100
@@ -0,0 +1,466 @@
+import io
+import os.path
+import re
+import textwrap
+from collections import OrderedDict
+
+import six
+
+from . import types, exceptions
+
+SPLIT_LINE_RE = re.compile(r"(?<!\\)\|")
+COMMENT_RE = re.compile(r"(^|(?<=\s))#")
+STEP_PREFIXES = [
+ ("Feature: ", types.FEATURE),
+ ("Scenario Outline: ", types.SCENARIO_OUTLINE),
+ ("Examples: Vertical", types.EXAMPLES_VERTICAL),
+ ("Examples:", types.EXAMPLES),
+ ("Scenario: ", types.SCENARIO),
+ ("Background:", types.BACKGROUND),
+ ("Given ", types.GIVEN),
+ ("When ", types.WHEN),
+ ("Then ", types.THEN),
+ ("@", types.TAG),
+ # Continuation of the previously mentioned step type
+ ("And ", None),
+ ("But ", None),
+]
+
+
+def split_line(line):
+ """Split the given Examples line.
+
+ :param str|unicode line: Feature file Examples line.
+
+ :return: List of strings.
+ """
+ return [cell.replace("\\|", "|").strip() for cell in
SPLIT_LINE_RE.split(line)[1:-1]]
+
+
+def parse_line(line):
+ """Parse step line to get the step prefix (Scenario, Given, When, Then or
And) and the actual step name.
+
+ :param line: Line of the Feature file.
+
+ :return: `tuple` in form ("<prefix>", "<Line without the prefix>").
+ """
+ for prefix, _ in STEP_PREFIXES:
+ if line.startswith(prefix):
+ return prefix.strip(), line[len(prefix) :].strip()
+ return "", line
+
+
+def strip_comments(line):
+ """Remove comments.
+
+ :param str line: Line of the Feature file.
+
+ :return: Stripped line.
+ """
+ res = COMMENT_RE.search(line)
+ if res:
+ line = line[: res.start()]
+ return line.strip()
+
+
+def get_step_type(line):
+ """Detect step type by the beginning of the line.
+
+ :param str line: Line of the Feature file.
+
+ :return: SCENARIO, GIVEN, WHEN, THEN, or `None` if can't be detected.
+ """
+ for prefix, _type in STEP_PREFIXES:
+ if line.startswith(prefix):
+ return _type
+
+
+def parse_feature(basedir, filename, encoding="utf-8"):
+ """Parse the feature file.
+
+ :param str basedir: Feature files base directory.
+ :param str filename: Relative path to the feature file.
+ :param str encoding: Feature file encoding (utf-8 by default).
+ """
+ abs_filename = os.path.abspath(os.path.join(basedir, filename))
+ rel_filename = os.path.join(os.path.basename(basedir), filename)
+ feature = Feature(
+ scenarios=OrderedDict(),
+ filename=abs_filename,
+ rel_filename=rel_filename,
+ line_number=1,
+ name=None,
+ tags=set(),
+ examples=Examples(),
+ background=None,
+ description="",
+ )
+ scenario = None
+ mode = None
+ prev_mode = None
+ description = []
+ step = None
+ multiline_step = False
+ prev_line = None
+
+ with io.open(abs_filename, "rt", encoding=encoding) as f:
+ content = f.read()
+
+ 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 or ((not unindented_line) and
multiline_step)):
+ multiline_step = True
+ # multiline step, so just add line and continue
+ step.add_line(line)
+ continue
+ else:
+ step = None
+ multiline_step = False
+ stripped_line = line.strip()
+ clean_line = strip_comments(line)
+ if not clean_line and (not prev_mode or prev_mode not in
types.FEATURE):
+ continue
+ mode = get_step_type(clean_line) or mode
+
+ allowed_prev_mode = (types.BACKGROUND, types.GIVEN, types.WHEN)
+
+ if not scenario and prev_mode not in allowed_prev_mode and mode in
types.STEP_TYPES:
+ raise exceptions.FeatureError(
+ "Step definition outside of a Scenario or a Background",
line_number, clean_line, filename
+ )
+
+ if mode == types.FEATURE:
+ if prev_mode is None or prev_mode == types.TAG:
+ _, feature.name = parse_line(clean_line)
+ feature.line_number = line_number
+ feature.tags = get_tags(prev_line)
+ elif prev_mode == types.FEATURE:
+ description.append(clean_line)
+ else:
+ raise exceptions.FeatureError(
+ "Multiple features are not allowed in a single feature
file",
+ line_number,
+ clean_line,
+ filename,
+ )
+
+ prev_mode = mode
+
+ # Remove Feature, Given, When, Then, And
+ keyword, parsed_line = parse_line(clean_line)
+ if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
+ tags = get_tags(prev_line)
+ feature.scenarios[parsed_line] = scenario = Scenario(feature,
parsed_line, line_number, tags=tags)
+ elif mode == types.BACKGROUND:
+ feature.background = Background(feature=feature,
line_number=line_number)
+ elif mode == types.EXAMPLES:
+ mode = types.EXAMPLES_HEADERS
+ (scenario or feature).examples.line_number = line_number
+ elif mode == types.EXAMPLES_VERTICAL:
+ mode = types.EXAMPLE_LINE_VERTICAL
+ (scenario or feature).examples.line_number = line_number
+ elif mode == types.EXAMPLES_HEADERS:
+ (scenario or feature).examples.set_param_names([l for l in
split_line(parsed_line) if l])
+ mode = types.EXAMPLE_LINE
+ elif mode == types.EXAMPLE_LINE:
+ (scenario or feature).examples.add_example([l for l in
split_line(stripped_line)])
+ elif mode == types.EXAMPLE_LINE_VERTICAL:
+ param_line_parts = [l for l in split_line(stripped_line)]
+ try:
+ (scenario or
feature).examples.add_example_row(param_line_parts[0], param_line_parts[1:])
+ except exceptions.ExamplesNotValidError as exc:
+ if scenario:
+ raise exceptions.FeatureError(
+ """Scenario has not valid examples.
{0}""".format(exc.args[0]),
+ line_number,
+ clean_line,
+ filename,
+ )
+ else:
+ raise exceptions.FeatureError(
+ """Feature has not valid examples.
{0}""".format(exc.args[0]),
+ line_number,
+ clean_line,
+ filename,
+ )
+ elif mode and mode not in (types.FEATURE, types.TAG):
+ step = Step(name=parsed_line, type=mode, indent=line_indent,
line_number=line_number, keyword=keyword)
+ if feature.background and not scenario:
+ target = feature.background
+ else:
+ target = scenario
+ target.add_step(step)
+ prev_line = clean_line
+
+ feature.description = u"\n".join(description).strip()
+ return feature
+
+
+class Feature(object):
+ """Feature."""
+
+ def __init__(self, scenarios, filename, rel_filename, name, tags,
examples, background, line_number, description):
+ self.scenarios = scenarios
+ self.rel_filename = rel_filename
+ self.filename = filename
+ self.name = name
+ self.tags = tags
+ self.examples = examples
+ self.name = name
+ self.line_number = line_number
+ self.tags = tags
+ self.scenarios = scenarios
+ self.description = description
+ self.background = background
+
+
+class Scenario(object):
+
+ """Scenario."""
+
+ def __init__(self, feature, name, line_number, example_converters=None,
tags=None):
+ """Scenario constructor.
+
+ :param pytest_bdd.parser.Feature feature: Feature.
+ :param str name: Scenario name.
+ :param int line_number: Scenario line number.
+ :param dict example_converters: Example table parameter converters.
+ :param set tags: Set of tags.
+ """
+ self.feature = feature
+ self.name = name
+ self._steps = []
+ self.examples = Examples()
+ self.line_number = line_number
+ self.example_converters = example_converters
+ self.tags = tags or set()
+ self.failed = False
+ self.test_function = None
+
+ def add_step(self, step):
+ """Add step to the scenario.
+
+ :param pytest_bdd.parser.Step step: Step.
+ """
+ step.scenario = self
+ self._steps.append(step)
+
+ @property
+ def steps(self):
+ """Get scenario steps including background steps.
+
+ :return: List of steps.
+ """
+ result = []
+ if self.feature.background:
+ result.extend(self.feature.background.steps)
+ result.extend(self._steps)
+ return result
+
+ @property
+ def params(self):
+ """Get parameter names.
+
+ :return: Parameter names.
+ :rtype: frozenset
+ """
+ return frozenset(sum((list(step.params) for step in self.steps), []))
+
+ def get_example_params(self):
+ """Get example parameter names."""
+ return set(self.examples.example_params +
self.feature.examples.example_params)
+
+ def get_params(self, builtin=False):
+ """Get converted example params."""
+ for examples in [self.feature.examples, self.examples]:
+ yield examples.get_params(self.example_converters, builtin=builtin)
+
+ def validate(self):
+ """Validate the scenario.
+
+ :raises ScenarioValidationError: when scenario is not valid
+ """
+ params = self.params
+ example_params = self.get_example_params()
+ if params and example_params and params != example_params:
+ raise exceptions.ScenarioExamplesNotValidError(
+ """Scenario "{0}" in the feature "{1}" has not valid examples.
"""
+ """Set of step parameters {2} should match set of example
values {3}.""".format(
+ self.name, self.feature.filename, sorted(params),
sorted(example_params)
+ )
+ )
+
+
[email protected]_2_unicode_compatible
+class Step(object):
+
+ """Step."""
+
+ def __init__(self, name, type, indent, line_number, keyword):
+ """Step constructor.
+
+ :param str name: step name.
+ :param str type: step type.
+ :param int indent: step text indent.
+ :param int line_number: line number.
+ :param str keyword: step keyword.
+ """
+ self.name = name
+ self.keyword = keyword
+ self.lines = []
+ self.indent = indent
+ self.type = type
+ self.line_number = line_number
+ self.failed = False
+ self.start = 0
+ self.stop = 0
+ self.scenario = None
+ self.background = None
+
+ def add_line(self, line):
+ """Add line to the multiple step.
+
+ :param str line: Line of text - the continuation of the step name.
+ """
+ self.lines.append(line)
+
+ @property
+ def name(self):
+ """Get step name."""
+ multilines_content = textwrap.dedent("\n".join(self.lines)) if
self.lines else ""
+
+ # Remove the multiline quotes, if present.
+ multilines_content = re.sub(
+ pattern=r'^"""\n(?P<content>.*)\n"""$',
+ repl=r"\g<content>",
+ string=multilines_content,
+ flags=re.DOTALL, # Needed to make the "." match also new lines
+ )
+
+ lines = [self._name] + [multilines_content]
+ return "\n".join(lines).strip()
+
+ @name.setter
+ def name(self, value):
+ """Set step name."""
+ self._name = value
+
+ def __str__(self):
+ """Full step name including the type."""
+ return '{type} "{name}"'.format(type=self.type.capitalize(),
name=self.name)
+
+ @property
+ def params(self):
+ """Get step params."""
+ return tuple(frozenset(STEP_PARAM_RE.findall(self.name)))
+
+
+class Background(object):
+
+ """Background."""
+
+ def __init__(self, feature, line_number):
+ """Background constructor.
+
+ :param pytest_bdd.parser.Feature feature: Feature.
+ :param int line_number: Line number.
+ """
+ self.feature = feature
+ self.line_number = line_number
+ self.steps = []
+
+ def add_step(self, step):
+ """Add step to the background."""
+ step.background = self
+ self.steps.append(step)
+
+
+class Examples(object):
+
+ """Example table."""
+
+ def __init__(self):
+ """Initialize examples instance."""
+ self.example_params = []
+ self.examples = []
+ self.vertical_examples = []
+ self.line_number = None
+ self.name = None
+
+ def set_param_names(self, keys):
+ """Set parameter names.
+
+ :param names: `list` of `string` parameter names.
+ """
+ self.example_params = [str(key) for key in keys]
+
+ def add_example(self, values):
+ """Add example.
+
+ :param values: `list` of `string` parameter values.
+ """
+ self.examples.append(values)
+
+ def add_example_row(self, param, values):
+ """Add example row.
+
+ :param param: `str` parameter name
+ :param values: `list` of `string` parameter values
+ """
+ if param in self.example_params:
+ raise exceptions.ExamplesNotValidError(
+ """Example rows should contain unique parameters. "{0}"
appeared more than once""".format(param)
+ )
+ self.example_params.append(param)
+ self.vertical_examples.append(values)
+
+ def get_params(self, converters, builtin=False):
+ """Get scenario pytest parametrization table.
+
+ :param converters: `dict` of converter functions to convert parameter
values
+ """
+ param_count = len(self.example_params)
+ if self.vertical_examples and not self.examples:
+ for value_index in range(len(self.vertical_examples[0])):
+ example = []
+ for param_index in range(param_count):
+
example.append(self.vertical_examples[param_index][value_index])
+ self.examples.append(example)
+
+ if self.examples:
+ params = []
+ for example in self.examples:
+ example = list(example)
+ for index, param in enumerate(self.example_params):
+ raw_value = example[index]
+ if converters and param in converters:
+ value = converters[param](raw_value)
+ if not builtin or value.__class__.__module__ in
{"__builtin__", "builtins"}:
+ example[index] = value
+ params.append(example)
+ return [self.example_params, params]
+ else:
+ return []
+
+ def __bool__(self):
+ """Bool comparison."""
+ return bool(self.vertical_examples or self.examples)
+
+ if six.PY2:
+ __nonzero__ = __bool__
+
+
+def get_tags(line):
+ """Get tags out of the given line.
+
+ :param str line: Feature file text line.
+
+ :return: List of tags.
+ """
+ if not line or not line.strip().startswith("@"):
+ return set()
+ return set((tag.lstrip("@") for tag in line.strip().split(" @") if
len(tag) > 1))
+
+
+STEP_PARAM_RE = re.compile(r"\<(.+?)\>")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/pytest_bdd/reporting.py
new/pytest-bdd-4.0.2/pytest_bdd/reporting.py
--- old/pytest-bdd-4.0.1/pytest_bdd/reporting.py 2020-09-08
12:03:15.000000000 +0200
+++ new/pytest-bdd-4.0.2/pytest_bdd/reporting.py 2020-12-07
13:38:07.000000000 +0100
@@ -19,7 +19,7 @@
def __init__(self, step):
"""Step report constructor.
- :param pytest_bdd.feature.Step step: Step.
+ :param pytest_bdd.parser.Step step: Step.
"""
self.step = step
self.started = time.time()
@@ -66,7 +66,7 @@
def __init__(self, scenario, node):
"""Scenario report constructor.
- :param pytest_bdd.feature.Scenario scenario: Scenario.
+ :param pytest_bdd.parser.Scenario scenario: Scenario.
:param node: pytest test node object
"""
self.scenario = scenario
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/pytest_bdd/scenario.py
new/pytest-bdd-4.0.2/pytest_bdd/scenario.py
--- old/pytest-bdd-4.0.1/pytest_bdd/scenario.py 2020-09-08 12:03:15.000000000
+0200
+++ new/pytest-bdd-4.0.2/pytest_bdd/scenario.py 2020-12-07 13:38:07.000000000
+0100
@@ -11,10 +11,8 @@
)
"""
import collections
-import inspect
import os
import re
-import sys
import pytest
@@ -24,7 +22,7 @@
from _pytest import python as pytest_fixtures
from . import exceptions
-from .feature import Feature, force_unicode, get_features
+from .feature import force_unicode, get_feature, get_features
from .steps import get_step_fixture_name, inject_fixture
from .utils import CONFIG_STACK, get_args, get_caller_module_locals,
get_caller_module_path
@@ -213,7 +211,7 @@
# Get the feature
if features_base_dir is None:
features_base_dir = get_features_base_dir(caller_module_path)
- feature = Feature.get_feature(features_base_dir, feature_name,
encoding=encoding)
+ feature = get_feature(features_base_dir, feature_name, encoding=encoding)
# Get the scenario
try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/tests/feature/test_outline.py
new/pytest-bdd-4.0.2/tests/feature/test_outline.py
--- old/pytest-bdd-4.0.1/tests/feature/test_outline.py 2020-09-08
12:03:15.000000000 +0200
+++ new/pytest-bdd-4.0.2/tests/feature/test_outline.py 2020-12-07
13:38:07.000000000 +0100
@@ -42,7 +42,7 @@
Examples:
| start | eat | left |
- | 12 | 5 | 7 |
+ | 12 | 5 | 7 | # a comment
| 5 | 4 | 1 |
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest-bdd-4.0.1/tests/feature/test_tags.py
new/pytest-bdd-4.0.2/tests/feature/test_tags.py
--- old/pytest-bdd-4.0.1/tests/feature/test_tags.py 2020-09-08
12:03:15.000000000 +0200
+++ new/pytest-bdd-4.0.2/tests/feature/test_tags.py 2020-12-07
13:38:07.000000000 +0100
@@ -3,7 +3,7 @@
import pytest
-from pytest_bdd import feature
+from pytest_bdd.parser import get_tags
def test_tags_selector(testdir):
@@ -251,4 +251,4 @@
],
)
def test_get_tags(line, expected):
- assert feature.get_tags(line) == expected
+ assert get_tags(line) == expected