Hello community, here is the log from the commit of package python-pytest-bdd for openSUSE:Factory checked in at 2019-09-10 00:03:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytest-bdd (Old) and /work/SRC/openSUSE:Factory/.python-pytest-bdd.new.7948 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-bdd" Tue Sep 10 00:03:56 2019 rev:3 rq:729485 version:3.2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytest-bdd/python-pytest-bdd.changes 2019-07-21 11:33:42.632783723 +0200 +++ /work/SRC/openSUSE:Factory/.python-pytest-bdd.new.7948/python-pytest-bdd.changes 2019-09-10 00:04:03.257205193 +0200 @@ -1,0 +2,6 @@ +Mon Sep 9 14:09:26 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 3.2.1: + * python 3.8 support + +------------------------------------------------------------------- Old: ---- pytest-bdd-3.1.1.tar.gz New: ---- pytest-bdd-3.2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytest-bdd.spec ++++++ --- /var/tmp/diff_new_pack.JlWfZN/_old 2019-09-10 00:04:03.997205144 +0200 +++ /var/tmp/diff_new_pack.JlWfZN/_new 2019-09-10 00:04:04.001205143 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-pytest-bdd -Version: 3.1.1 +Version: 3.2.1 Release: 0 Summary: BDD for pytest License: MIT ++++++ pytest-bdd-3.1.1.tar.gz -> pytest-bdd-3.2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/.travis.yml new/pytest-bdd-3.2.1/.travis.yml --- old/pytest-bdd-3.1.1/.travis.yml 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/.travis.yml 2019-08-21 13:21:08.000000000 +0200 @@ -5,6 +5,7 @@ - "3.5" - "3.6" - "3.7" + - "3.8-dev" install: pip install tox tox-travis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/CHANGES.rst new/pytest-bdd-3.2.1/CHANGES.rst --- old/pytest-bdd-3.1.1/CHANGES.rst 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/CHANGES.rst 2019-08-21 13:21:08.000000000 +0200 @@ -4,6 +4,17 @@ Unreleased ---------- +3.2.1 +---------- + +- Fix regression introduced in 3.2.0 where pytest-bdd would break in presence of test items that are not functions. + +3.2.0 +---------- + +- Fix Python 3.8 support +- Remove code that rewrites code. This should help with the maintenance of this project and make debugging easier. + 3.1.1 ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/pytest_bdd/__init__.py new/pytest-bdd-3.2.1/pytest_bdd/__init__.py --- old/pytest-bdd-3.1.1/pytest_bdd/__init__.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/pytest_bdd/__init__.py 2019-08-21 13:21:08.000000000 +0200 @@ -3,6 +3,6 @@ from pytest_bdd.steps import given, when, then from pytest_bdd.scenario import scenario, scenarios -__version__ = '3.1.1' +__version__ = '3.2.1' __all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, scenarios.__name__] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/pytest_bdd/plugin.py new/pytest-bdd-3.2.1/pytest_bdd/plugin.py --- old/pytest-bdd-3.1.1/pytest_bdd/plugin.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/pytest_bdd/plugin.py 2019-08-21 13:21:08.000000000 +0200 @@ -13,12 +13,7 @@ def pytest_addhooks(pluginmanager): """Register plugin hooks.""" from pytest_bdd import hooks - try: - # pytest >= 2.8 - pluginmanager.add_hookspecs(hooks) - except AttributeError: - # pytest < 2.8 - pluginmanager.addhooks(hooks) + pluginmanager.add_hookspecs(hooks) @given('trace') @@ -92,3 +87,23 @@ def pytest_bdd_apply_tag(tag, function): mark = getattr(pytest.mark, tag) return mark(function) + + +@pytest.mark.tryfirst +def pytest_collection_modifyitems(session, config, items): + """Re-order items using the creation counter as fallback. + + Pytest has troubles to correctly order the test items for python < 3.6. + For this reason, we have to apply some better ordering for pytest_bdd scenario-decorated test functions. + + This is not needed for python 3.6+, but this logic is safe to apply in that case as well. + """ + # TODO: Try to only re-sort the items that have __pytest_bdd_counter__, and not the others, + # since there may be other hooks that are executed before this and that want to reorder item as well + def item_key(item): + if isinstance(item, pytest.Function): + declaration_order = getattr(item.function, '__pytest_bdd_counter__', 0) + else: + declaration_order = 0 + return (item.reportinfo()[:2], declaration_order) + items.sort(key=item_key) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/pytest_bdd/scenario.py new/pytest-bdd-3.2.1/pytest_bdd/scenario.py --- old/pytest-bdd-3.1.1/pytest_bdd/scenario.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/pytest_bdd/scenario.py 2019-08-21 13:21:08.000000000 +0200 @@ -20,39 +20,31 @@ from _pytest import fixtures as pytest_fixtures except ImportError: from _pytest import python as pytest_fixtures -import six from . import exceptions from .feature import ( Feature, - force_encode, force_unicode, get_features, ) from .steps import ( - execute, - get_caller_function, get_caller_module, get_step_fixture_name, inject_fixture, - recreate_function, ) from .types import GIVEN from .utils import CONFIG_STACK, get_args -if six.PY3: # pragma: no cover - import runpy - - def execfile(filename, init_globals): - """Execute given file as a python script in given globals environment.""" - result = runpy.run_path(filename, init_globals=init_globals) - init_globals.update(result) - PYTHON_REPLACE_REGEX = re.compile(r"\W") ALPHA_REGEX = re.compile(r"^\d+_*") +# We have to keep track of the invocation of @scenario() so that we can reorder test item accordingly. +# In python 3.6+ this is no longer necessary, as the order is automatically retained. +_py2_scenario_creation_counter = 0 + + def find_argumented_step_fixture_name(name, type_, fixturemanager, request=None): """Find argumented step fixture name.""" # happens to be that _arg2fixturedefs is changed during the iteration so we use a copy @@ -88,9 +80,11 @@ """ name = step.name try: + # Simple case where no parser is used for the step return request.getfixturevalue(get_step_fixture_name(name, step.type, encoding)) except pytest_fixtures.FixtureLookupError: try: + # Could not find a fixture with the same name, let's see if there is a parser involved name = find_argumented_step_fixture_name(name, step.type, request._fixturemanager, request) if name: return request.getfixturevalue(name) @@ -204,63 +198,53 @@ FakeRequest = collections.namedtuple("FakeRequest", ["module"]) -def _get_scenario_decorator(feature, feature_name, scenario, scenario_name, caller_module, caller_function, encoding): - """Get scenario decorator.""" - g = locals() - g["_execute_scenario"] = _execute_scenario +def _get_scenario_decorator(feature, feature_name, scenario, scenario_name, encoding): + global _py2_scenario_creation_counter - scenario_name = force_encode(scenario_name, encoding) + counter = _py2_scenario_creation_counter + _py2_scenario_creation_counter += 1 - def decorator(_pytestbdd_function): - if isinstance(_pytestbdd_function, pytest_fixtures.FixtureRequest): + # HACK: Ideally we would use `def decorator(fn)`, but we want to return a custom exception + # when the decorator is misused. + # Pytest inspect the signature to determine the required fixtures, and in that case it would look + # for a fixture called "fn" that doesn't exist (if it exists then it's even worse). + # It will error with a "fixture 'fn' not found" message instead. + # We can avoid this hack by using a pytest hook and check for misuse instead. + def decorator(*args): + if not args: raise exceptions.ScenarioIsDecoratorOnly( "scenario function can only be used as a decorator. Refer to the documentation.", ) - - g.update(locals()) - - args = get_args(_pytestbdd_function) + [fn] = args + args = get_args(fn) function_args = list(args) for arg in scenario.get_example_params(): if arg not in function_args: function_args.append(arg) - if "request" not in function_args: - function_args.append("request") - code = """def {name}({function_args}): + @pytest.mark.usefixtures(*function_args) + def scenario_wrapper(request): _execute_scenario(feature, scenario, request, encoding) - _pytestbdd_function({args})""".format( - name=_pytestbdd_function.__name__, - function_args=", ".join(function_args), - args=", ".join(args)) - - execute(code, g) - - _scenario = recreate_function( - g[_pytestbdd_function.__name__], - module=caller_module, - firstlineno=caller_function.f_lineno, - ) + return fn(*[request.getfixturevalue(arg) for arg in args]) for param_set in scenario.get_params(): if param_set: - _scenario = pytest.mark.parametrize(*param_set)(_scenario) - + scenario_wrapper = pytest.mark.parametrize(*param_set)(scenario_wrapper) for tag in scenario.tags.union(feature.tags): config = CONFIG_STACK[-1] - config.hook.pytest_bdd_apply_tag(tag=tag, function=_scenario) + config.hook.pytest_bdd_apply_tag(tag=tag, function=scenario_wrapper) - _scenario.__doc__ = "{feature_name}: {scenario_name}".format( + scenario_wrapper.__doc__ = u"{feature_name}: {scenario_name}".format( feature_name=feature_name, scenario_name=scenario_name) - _scenario.__scenario__ = scenario - scenario.test_function = _scenario - return _scenario - - return recreate_function(decorator, module=caller_module, firstlineno=caller_function.f_lineno) + scenario_wrapper.__scenario__ = scenario + scenario_wrapper.__pytest_bdd_counter__ = counter + scenario.test_function = scenario_wrapper + return scenario_wrapper + return decorator def scenario(feature_name, scenario_name, encoding="utf-8", example_converters=None, - caller_module=None, caller_function=None, features_base_dir=None, strict_gherkin=None): + caller_module=None, features_base_dir=None, strict_gherkin=None): """Scenario decorator. :param str feature_name: Feature file name. Absolute or relative to the configured feature base path. @@ -269,9 +253,9 @@ :param dict example_converters: optional `dict` of example converter function, where key is the name of the example parameter, and value is the converter function. """ + scenario_name = force_unicode(scenario_name, encoding) caller_module = caller_module or get_caller_module() - caller_function = caller_function or get_caller_function() # Get the feature if features_base_dir is None: @@ -280,7 +264,7 @@ strict_gherkin = get_strict_gherkin() feature = Feature.get_feature(features_base_dir, feature_name, encoding=encoding, strict_gherkin=strict_gherkin) - # Get the sc_enario + # Get the scenario try: scenario = feature.scenarios[scenario_name] except KeyError: @@ -298,13 +282,11 @@ scenario.validate() return _get_scenario_decorator( - feature, - feature_name, - scenario, - scenario_name, - caller_module, - caller_function, - encoding, + feature=feature, + feature_name=feature_name, + scenario=scenario, + scenario_name=scenario_name, + encoding=encoding, ) @@ -375,7 +357,6 @@ (attr.__scenario__.feature.filename, attr.__scenario__.name) 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 scenario_name, scenario_object in feature.scenarios.items(): # skip already bound scenarios @@ -386,9 +367,6 @@ for test_name in get_python_name_generator(scenario_name): if test_name not in module.__dict__: # found an unique test name - # recreate function to set line number - _scenario = recreate_function(_scenario, module=module, firstlineno=index * 4) - index += 1 module.__dict__[test_name] = _scenario break found = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/pytest_bdd/steps.py new/pytest-bdd-3.2.1/pytest_bdd/steps.py --- old/pytest-bdd-3.1.1/pytest_bdd/steps.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/pytest_bdd/steps.py 2019-08-21 13:21:08.000000000 +0200 @@ -32,7 +32,6 @@ """ from __future__ import absolute_import -from types import CodeType import inspect import sys @@ -41,7 +40,6 @@ from _pytest import fixtures as pytest_fixtures except ImportError: from _pytest import python as pytest_fixtures -import six from .feature import parse_line, force_encode from .types import GIVEN, WHEN, THEN @@ -90,7 +88,7 @@ func = pytest.fixture(scope=scope)(lambda: step_func) func.__doc__ = 'Alias for the "{0}" fixture.'.format(fixture) _, name = parse_line(name) - contribute_to_module(module, get_step_fixture_name(name, GIVEN), func) + setattr(module, get_step_fixture_name(name, GIVEN), func) return _not_a_fixture_decorator return _step_decorator(GIVEN, name, converters=converters, scope=scope, target_fixture=target_fixture) @@ -185,84 +183,12 @@ step_func.converters = lazy_step_func.converters = converters lazy_step_func = pytest.fixture(scope=scope)(lazy_step_func) - contribute_to_module( - module=get_caller_module(), - name=get_step_fixture_name(parsed_step_name, step_type), - func=lazy_step_func, - ) - + setattr(get_caller_module(), get_step_fixture_name(parsed_step_name, step_type), lazy_step_func) return func return decorator -def recreate_function(func, module=None, name=None, add_args=[], firstlineno=None): - """Recreate a function, replacing some info. - - :param func: Function object. - :param module: Module to contribute to. - :param add_args: Additional arguments to add to function. - - :return: Function copy. - """ - def get_code(func): - return func.__code__ if six.PY3 else func.func_code - - def set_code(func, code): - if six.PY3: - func.__code__ = code - else: - func.func_code = code - - argnames = [ - "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code", "co_consts", "co_names", - "co_varnames", "co_filename", "co_name", "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars", - ] - if six.PY3: - argnames.insert(1, "co_kwonlyargcount") - - for arg in get_args(func): - if arg in add_args: - add_args.remove(arg) - - args = [] - code = get_code(func) - for arg in argnames: - if module is not None and arg == "co_filename": - args.append(module.__file__) - elif name is not None and arg == "co_name": - args.append(name) - elif arg == "co_argcount": - args.append(getattr(code, arg) + len(add_args)) - elif arg == "co_varnames": - co_varnames = getattr(code, arg) - args.append(co_varnames[:code.co_argcount] + tuple(add_args) + co_varnames[code.co_argcount:]) - elif arg == "co_firstlineno": - args.append(firstlineno if firstlineno else 1) - else: - args.append(getattr(code, arg)) - - set_code(func, CodeType(*args)) - if name is not None: - func.__name__ = name - return func - - -def contribute_to_module(module, name, func): - """Contribute a function to a module. - - :param module: Module to contribute to. - :param name: Attribute name. - :param func: Function object. - - :return: New function copy contributed to the module - """ - name = force_encode(name) - func = recreate_function(func, module=module) - setattr(module, name, func) - return func - - def get_caller_module(depth=2): """Return the module of the caller.""" frame = sys._getframe(depth) @@ -272,16 +198,6 @@ return module -def get_caller_function(depth=2): - """Return caller function.""" - return sys._getframe(depth) - - -def execute(code, g): - """Execute given code in given globals environment.""" - exec(code, g) - - def inject_fixture(request, arg, value): """Inject fixture into pytest fixture request. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/tests/feature/test_scenario.py new/pytest-bdd-3.2.1/tests/feature/test_scenario.py --- old/pytest-bdd-3.1.1/tests/feature/test_scenario.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/tests/feature/test_scenario.py 2019-08-21 13:21:08.000000000 +0200 @@ -57,11 +57,19 @@ test2(request) -def test_scenario_not_decorator(request): +def test_scenario_not_decorator(testdir): """Test scenario function is used not as decorator.""" - func = scenario( - 'comments.feature', - 'Strings that are not comments') + testdir.makefile('.feature', foo=""" + Scenario: Foo + Given I have a bar + """) + testdir.makepyfile(""" + from pytest_bdd import scenario - with pytest.raises(exceptions.ScenarioIsDecoratorOnly): - func(request) + test_foo = scenario('foo.feature', 'Foo') + """) + + result = testdir.runpytest() + + result.assert_outcomes(failed=1) + result.stdout.fnmatch_lines("*ScenarioIsDecoratorOnly: scenario function can only be used as a decorator*") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/tests/feature/test_steps.py new/pytest-bdd-3.2.1/tests/feature/test_steps.py --- old/pytest-bdd-3.1.1/tests/feature/test_steps.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/tests/feature/test_steps.py 2019-08-21 13:21:08.000000000 +0200 @@ -246,20 +246,20 @@ """) result = testdir.runpytest('-k test_when_fails_inline', '-vv') assert result.ret == 1 - result.stdout.fnmatch_lines(['*test_when_fails_inline FAILED']) + result.stdout.fnmatch_lines(['*test_when_fails_inline*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_fails_decorated', '-vv') assert result.ret == 1 - result.stdout.fnmatch_lines(['*test_when_fails_decorated FAILED']) + result.stdout.fnmatch_lines(['*test_when_fails_decorated*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_not_found', '-vv') assert result.ret == 1 - result.stdout.fnmatch_lines(['*test_when_not_found FAILED']) + result.stdout.fnmatch_lines(['*test_when_not_found*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_step_validation_error', '-vv') assert result.ret == 1 - result.stdout.fnmatch_lines(['*test_when_step_validation_error FAILED']) + result.stdout.fnmatch_lines(['*test_when_step_validation_error*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/tests/test_hooks.py new/pytest-bdd-3.2.1/tests/test_hooks.py --- old/pytest-bdd-3.1.1/tests/test_hooks.py 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/tests/test_hooks.py 2019-08-21 13:21:08.000000000 +0200 @@ -30,3 +30,26 @@ assert result.stdout.lines.count('pytest_pyfunc_call hook') == 1 assert result.stdout.lines.count('pytest_generate_tests hook') == 1 + + +def test_item_collection_does_not_break_on_non_function_items(testdir): + """Regression test for https://github.com/pytest-dev/pytest-bdd/issues/317""" + testdir.makeconftest(""" + import pytest + + @pytest.mark.tryfirst + def pytest_collection_modifyitems(session, config, items): + items[:] = [CustomItem(name=item.name, parent=item.parent) for item in items] + + class CustomItem(pytest.Item): + def runtest(self): + assert True + """) + + testdir.makepyfile(""" + def test_convert_me_to_custom_item_and_assert_true(): + assert False + """) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-bdd-3.1.1/tox.ini new/pytest-bdd-3.2.1/tox.ini --- old/pytest-bdd-3.1.1/tox.ini 2019-07-08 11:55:49.000000000 +0200 +++ new/pytest-bdd-3.2.1/tox.ini 2019-08-21 13:21:08.000000000 +0200 @@ -3,7 +3,7 @@ envlist = py27-pytestlatest-linters, py27-pytest{36,37,38,39,310,4,41,42,43,44,45,46}, py37-pytest{36,37,38,39,310,4,41,42,43,44,45,46,5,latest}, - py{35,36}-pytestlatest, + py{35,36,38}-pytestlatest, py27-pytestlatest-xdist skip_missing_interpreters = true