From 815d53cd9a095b0b67dc6f98ad9a7fa251edefbf Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@moto-timo.dev>
Date: Tue, 11 Jul 2023 17:12:25 -0500
Subject: [PATCH] python3-behave: upgrade
1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6
---
...ioOutlineBuilder-was-not-copying-des.patch | 22 +
.../0002-UPDATE-FIXED-725.patch | 21 +
...ST-to-verify-that-issue-725-is-fixed.patch | 60 +
...ter-counts-computation-when-Rules-ar.patch | 342 +
...print_summary-Simplify-if-Rules-are-.patch | 60 +
...r-Add-basic-support-output-for-Rules.patch | 395 +
...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 +
.../0008-Correct-examples-and-docstring.patch | 41 +
...ure.run_items-processing-with-Rule-s.patch | 58 +
...-sphinx-intl-support-for-READTHEDOCS.patch | 58 +
...ent-package-versions-in-requirements.patch | 81 +
.../0012-docs-conf.py-tweaks.patch | 30 +
...lled-after_rule-hook-was-after_after.patch | 30 +
...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 +
.../0015-README-ReST-tweaks.patch | 18 +
...016-Example-using-Gherkin-v6-grammar.patch | 228 +
.../0017-PREPARE-Python-3.8-support.patch | 39 +
...x-logging.Formatter-validate-problem.patch | 22 +
...arily-move-py38-dev-to-front-build-f.patch | 28 +
...Tweaks-for-faster-builds-temporarily.patch | 35 +
...8-logging.Formatter.validate-problem.patch | 47 +
...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 +
.../0023-UPDATE-Add-755-info.patch | 24 +
...ted-to-docstring-example-and-weird-b.patch | 25 +
...pe-sequence-warnings-w-regex-pattern.patch | 29 +
...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++
.../python3-behave/0027-Comment-tweaks.patch | 30 +
...-related-to-invalid-escapes-in-regex.patch | 79 +
...ould-not-break-configured-rerun-sett.patch | 73 +
...les-and-failing-scenarios-enable-via.patch | 36 +
..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 +
.../0032-Add-info-on-merged-pull-588.patch | 21 +
...3-Tweak-tests-required-by-pytest-5.0.patch | 97 +
...st-instead-of-nose-to-remove-nose.im.patch | 180 +
...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++
...0036-FIX-Remove-test-from-pytest-run.patch | 22 +
...on-Add-support-for-Scenario-containe.patch | 652 ++
...tion-for-Select-by-location-for-Scen.patch | 58 +
.../0039-tests-Fix-warnings-for-python3.patch | 50 +
...ag-expressions-1.1.2-to-fix-warnings.patch | 55 +
...ENT-Support-emojis-in-feature-files-.patch | 91 +
...valid-escape-char-in-regex-w-python3.patch | 250 +
...3-Example-related-to-question-in-756.patch | 335 +
...X-python3.8-regressions-on-CI-server.patch | 489 +
.../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 +
...DATE-Cucumber-gherkin-languages.json.patch | 57 +
...ule-keyword-translation-in-portugues.patch | 202 +
...-generate-from-gherkin-languages.jso.patch | 141 +
...ming-to-fixture.behave.no_background.patch | 322 +
...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 +
...for-feature.background-inheritance-f.patch | 1510 +++
...-Add-support-for-runtime-constraints.patch | 269 +
.../0053-Use-runtime-constraints.patch | 196 +
...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++
...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++
...rform-more-Gherkin-v6-checks-and-run.patch | 155 +
...-and-python-module-old-was-broken-no.patch | 72 +
.../0058-UTIL-Formatting-tweaks.patch | 22 +
...e-use_fixture_by_tag-didn-t-return-t.patch | 23 +
.../0060-Added-issue-unit-test.patch | 62 +
...e-pull-request-767-with-minor-tweaks.patch | 60 +
...sue-766-PrettyFormatter-UnicodeError.patch | 83 +
...enarioOutline.Examples-without-table.patch | 74 +
...enarioOutline.Examples-without-table.patch | 21 +
.../0065-Nibble-TravisCI-to-wake-up.patch | 21 +
.../0066-Tweak-pytest-version-selection.patch | 37 +
...figuration-to-silence-JUnit-XML-dial.patch | 37 +
...e-test-for-wildcard-pattern-matching.patch | 56 +
...ATE-dependencies-path.py-path-pytest.patch | 141 +
...eprecatedWarning-from-distutils-pack.patch | 25 +
...-Add-ContextMode-enum-related-to-797.patch | 216 +
.../0072-Cleanup-comments.patch | 22 +
...phinx-build-problem-async_steps3x.py.patch | 29 +
...-parse_expressions-was-parse_builtin.patch | 185 +
...ssion-add-links-to-parse_type-module.patch | 40 +
...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 +
...leanups-related-to-question-in-800-P.patch | 223 +
...y-name-uses-regex-pattern-related-to.patch | 82 +
...nce-problem-copy-paste-in-Rule-class.patch | 34 +
...API-description-for-Runner-Operation.patch | 195 +
...0081-FIX-DOCS-Runner-operations-typo.patch | 22 +
...ement-Context.add_cleanup-with-layer.patch | 295 +
...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 +
...Duplicated-steps-AmbiguousStepErrors.patch | 34 +
.../0085-Add-renovate.json.patch | 21 +
...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 +
.../0087-Pin-dependencies.patch | 36 +
...te-Extend-pip-requirements-file-list.patch | 31 +
...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 +
..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++
...nge-code-blocks-from-bash-to-console.patch | 36 +
.../0092-Fix-typo-in-tutorial.patch | 24 +
...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 +
.../0094-UPDATE-PR-877-was-merged.patch | 21 +
.../0095-capitalizing-steps.patch | 28 +
...develop.update-gherkin-that-aborted-.patch | 56 +
...ainst-PowerPC-CPU-support-Travis-867.patch | 22 +
...098-Add-Context.attach-docs-and-test.patch | 132 +
.../0099-Add-Contributing-chapter.patch | 125 +
...apt-Tox-target-for-building-the-docs.patch | 34 +
...tion-HTML-formatter-in-documentation.patch | 83 +
...le-highlighting-for-pip-install-docs.patch | 22 +
...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 +
...t-for-python3.9-by-using-active-tags.patch | 227 +
.../0105-PREFER-python3-from-now-on.patch | 19 +
.../0106-REMOVE-invoke-scripts.patch | 41 +
...X-Deprecated-warnings-for-Python-3.x.patch | 124 +
...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 +
.../0109-FIX-Active-tag-logic.patch | 875 ++
.../0110-FIX-Tests-w-more.features.patch | 56 +
...FIX-Some-regressions-with-Python-3.9.patch | 741 ++
.../0112-docs-Update-new-and-noteworthy.patch | 84 +
...elper-function-to-print-the-current-.patch | 278 +
...kin-languages.json-from-cucumber-rep.patch | 541 ++
...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 +
...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 +
...-to-use-a-custom-runner-in-the-behav.patch | 126 +
...llow-forcing-color-with-color-always.patch | 59 +
...lor-with-no-value-followed-by-posarg.patch | 43 +
.../0120-Add-BEHAVE_COLOR-env-var.patch | 31 +
...121-fix-malformed-table-rows-warning.patch | 33 +
...-955-setup-Remove-attribute-use_2to3.patch | 42 +
.../0123-Add-info-for-fixed-issue-955.patch | 21 +
.../python/python3-behave_1.2.6.bb | 129 +-
124 files changed, 29808 insertions(+), 2 deletions(-)
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch
create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch
new file mode 100644
@@ -0,0 +1,22 @@
+From b941f353c129f73934853082f3f3a01cebe6f944 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:37:04 +0100
+Subject: [PATCH] FIXES #725: ScenarioOutlineBuilder was not copying
+ description to created Scenario.
+
+---
+ behave/model.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/behave/model.py b/behave/model.py
+index 4ad4b9d..9dd68fd 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -1196,6 +1196,7 @@ class ScenarioOutlineBuilder(object):
+ scenario.feature = scenario_outline.feature
+ scenario.parent = scenario_outline
+ scenario.background = scenario_outline.background
++ scenario.description = scenario_outline.description
+ scenario._row = row # pylint: disable=protected-access
+ scenarios.append(scenario)
+ return scenarios
new file mode 100644
@@ -0,0 +1,21 @@
+From 0e26bbae1f9f8d60c3ab9470b3685af1dde5b6d8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 22:40:13 +0100
+Subject: [PATCH] UPDATE: FIXED #725
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index c11840f..d6e96af 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -32,6 +32,7 @@ ENHANCEMENTS:
+
+ FIXED:
+
++* issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
+ * issue #631: ScenarioOutline variables not possible in table headings (provided by: mschnelle, pull #642)
new file mode 100644
@@ -0,0 +1,60 @@
+From 66324f8dc74715a5018d1eced225557c40bd7acd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 11 Mar 2019 23:08:00 +0100
+Subject: [PATCH] ADD TEST to verify that issue #725 is fixed.
+
+---
+ tests/issues/test_issue0725.py | 44 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+ create mode 100644 tests/issues/test_issue0725.py
+
+diff --git a/tests/issues/test_issue0725.py b/tests/issues/test_issue0725.py
+new file mode 100644
+index 0000000..7479f59
+--- /dev/null
++++ b/tests/issues/test_issue0725.py
+@@ -0,0 +1,44 @@
++# -*- coding: UTF-8 -*-
++"""
++https://github.com/behave/behave/issues/725
++
++ANALYSIS:
++----------
++
++ScenarioOutlineBuilder did not copy ScenarioOutline.description
++to the Scenarios that were created from the ScenarioOutline.
++"""
++
++from __future__ import absolute_import, print_function
++from behave.parser import parse_feature
++
++
++def test_issue():
++ """Verifies that issue #725 is fixed."""
++ text = u'''
++Feature: ScenarioOutline with description
++
++ Scenario Outline: SO_1
++ Description line 1 for ScenarioOutline.
++ Description line 2 for ScenarioOutline.
++
++ Given a step with "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
++'''.lstrip()
++ feature = parse_feature(text)
++ assert len(feature.scenarios) == 1
++ scenario_outline_1 = feature.scenarios[0]
++ assert len(scenario_outline_1.scenarios) == 2
++ # -- HINT: Last line triggers building of the Scenarios from ScenarioOutline.
++
++ expected_description = [
++ "Description line 1 for ScenarioOutline.",
++ "Description line 2 for ScenarioOutline.",
++ ]
++ assert scenario_outline_1.description == expected_description
++ assert scenario_outline_1.scenarios[0].description == expected_description
++ assert scenario_outline_1.scenarios[1].description == expected_description
new file mode 100644
@@ -0,0 +1,342 @@
+From 74d539b86ca52e83255183d96b93ff7492751b6f Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:29:02 +0100
+Subject: [PATCH] FIX: SummaryReporter counts computation when Rules are used.
+
+---
+ behave/model.py | 39 +++---
+ behave/reporter/summary.py | 114 ++++++++++++++----
+ .../unit/{reporters => reporter}/__init__.py | 0
+ .../{reporters => reporter}/test_summary.py | 4 +
+ 4 files changed, 116 insertions(+), 41 deletions(-)
+ rename tests/unit/{reporters => reporter}/__init__.py (100%)
+ rename tests/unit/{reporters => reporter}/test_summary.py (99%)
+
+diff --git a/behave/model.py b/behave/model.py
+index 9dd68fd..6238313 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -144,18 +144,18 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.hook_failed = False
+ self.run_starttime = 0
+ self.run_endtime = 0
+- for scenario in self.scenarios:
+- scenario.reset()
++ for run_item in self.run_items:
++ run_item.reset()
+
+ def __iter__(self):
+- return iter(self.scenarios)
++ return iter(self.run_items)
+
+ def add_scenario(self, scenario):
+ feature = getattr(self, "feature", None)
+ if isinstance(self, Feature):
+ feature = self
+
+- scenario.parent = self # XXX-NEW
++ scenario.parent = self
+ scenario.feature = feature
+ scenario.background = self.background
+ self.scenarios.append(scenario)
+@@ -174,17 +174,17 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ skipped = True
+ passed_count = 0
+- for scenario in self.scenarios:
+- scenario_status = scenario.status
+- if scenario_status == Status.failed:
++ for run_item in self.run_items:
++ run_item_status = run_item.status
++ if run_item_status == Status.failed:
+ return Status.failed
+- elif scenario_status == Status.untested:
++ elif run_item_status == Status.untested:
+ if passed_count > 0:
+ return Status.failed # ABORTED: Some passed, now untested.
+ return Status.untested
+- if scenario_status != Status.skipped:
++ if run_item_status != Status.skipped:
+ skipped = False
+- if scenario_status == Status.passed:
++ if run_item_status == Status.passed:
+ passed_count += 1
+
+ if skipped:
+@@ -217,14 +217,19 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ """
+ # TODO: Better use self.run_items
+ all_scenarios = []
+- for scenario in self.scenarios:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
++ # for scenario in self.scenarios:
++ for run_item in self.run_items:
++ if isinstance(run_item, Rule):
++ rule = run_item
++ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
++ if isinstance(run_item, ScenarioOutline):
++ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- all_scenarios.append(scenario)
++ assert isinstance(run_item, Scenario)
++ all_scenarios.append(run_item)
+ return all_scenarios
+
+ def should_run(self, config=None):
+@@ -285,9 +290,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ self.clear_status()
+ self.should_skip = True
+ self.skip_reason = reason
+- for scenario in self.scenarios:
+- scenario.skip(reason, require_not_executed)
+- if not self.scenarios:
++ for run_item in self.run_items:
++ run_item.skip(reason, require_not_executed)
++ if not self.run_items:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+ assert self.status in self.final_status #< skipped, failed or passed.
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index c82daa1..2ccdc8f 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -6,25 +6,52 @@ Provides a summary after each test run.
+ from __future__ import absolute_import, division, print_function
+ import sys
+ from time import time as time_now
+-from behave.model import ScenarioOutline
++from behave.model import Rule, ScenarioOutline # MAYBE: Scenario
+ from behave.model_core import Status
+ from behave.reporter.base import Reporter
+ from behave.formatter.base import StreamOpener
+
+
+-# -- DISABLED: optional_steps = ('untested', 'undefined')
+-optional_steps = (Status.untested,) # MAYBE: Status.undefined
+-status_order = (Status.passed, Status.failed, Status.skipped,
++# ---------------------------------------------------------------------------
++# CONSTANTS:
++# ---------------------------------------------------------------------------
++# -- DISABLED: OPTIONAL_STEPS = ('untested', 'undefined')
++OPTIONAL_STEPS = (Status.untested,) # MAYBE: Status.undefined
++STATUS_ORDER = (Status.passed, Status.failed, Status.skipped,
+ Status.undefined, Status.untested)
+
+
+-def format_summary(statement_type, summary):
++# ---------------------------------------------------------------------------
++# UTILITY FUNCTIONS:
++# ---------------------------------------------------------------------------
++def pluralize(word, count=1, suffix="s"):
++ if count == 1:
++ return word
++ # -- OTHERWISE:
++ return "{0}{1}".format(word, suffix)
++
++
++def compute_summary_sum(summary):
++ """Compute sum of all summary counts (except: all)
++
++ :param summary: Summary counts (as dict).
++ :return: Sum of all counts (as integer).
++ """
++ counts_sum = 0
++ for name, count in summary.items():
++ if name == "all":
++ continue # IGNORE IT.
++ counts_sum += count
++ return counts_sum
++
++
++def format_summary0(statement_type, summary):
+ parts = []
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+
+@@ -40,11 +67,23 @@ def format_summary(statement_type, summary):
+ return ", ".join(parts) + "\n"
+
+
+-def pluralize(word, count=1, suffix="s"):
+- if count == 1:
+- return word
+- # -- OTHERWISE:
+- return "{0}{1}".format(word, suffix)
++def format_summary(statement_type, summary):
++ parts = []
++ for status in STATUS_ORDER:
++ if status.name not in summary:
++ continue
++ counts = summary[status.name]
++ if status in OPTIONAL_STEPS and counts == 0:
++ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
++ continue
++
++ name = status.name
++ if status.name == "passed":
++ statement = pluralize(statement_type, counts)
++ name = u"%s passed" % statement
++ part = u"%d %s" % (counts, name)
++ parts.append(part)
++ return ", ".join(parts) + "\n"
+
+
+ # -- PREPARED:
+@@ -60,18 +99,16 @@ def format_summary2(statement_type, summary, end="\n"):
+ :return:
+ """
+ parts = []
+- counts_sum = 0
+- for status in status_order:
++ for status in STATUS_ORDER:
+ if status.name not in summary:
+ continue
+ counts = summary[status.name]
+- if status in optional_steps and counts == 0:
++ if status in OPTIONAL_STEPS and counts == 0:
+ # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc.
+ continue
+-
+- counts_sum += counts
+ parts.append((status.name, counts))
+
++ counts_sum = summary["all"]
+ statement = pluralize(statement_type, sum)
+ parts_text = ", ".join(["{0}: {1}".format(name, value)
+ for name, value in parts])
+@@ -79,6 +116,9 @@ def format_summary2(statement_type, summary, end="\n"):
+ count=counts_sum, statement=statement, parts=parts_text, end=end)
+
+
++# ---------------------------------------------------------------------------
++# REPORTERS:
++# ---------------------------------------------------------------------------
+ class SummaryReporter(Reporter):
+ show_failed_scenarios = True
+ output_stream_name = "stdout"
+@@ -88,6 +128,7 @@ class SummaryReporter(Reporter):
+ stream = getattr(sys, self.output_stream_name, sys.stderr)
+ self.stream = StreamOpener.ensure_stream_with_encoder(stream)
+ summary_zero_data = {
++ "all": 0,
+ Status.passed.name: 0,
+ Status.failed.name: 0,
+ Status.skipped.name: 0,
+@@ -122,10 +163,22 @@ class SummaryReporter(Reporter):
+ for scenario in self.failed_scenarios:
+ stream.write(u" %s %s\n" % (scenario.location, scenario.name))
+
++ def compute_summary_sums(self):
++ """(Re)Compute summary sum of all counts (except: all)."""
++ summaries = [
++ self.feature_summary,
++ self.rule_summary,
++ self.scenario_summary,
++ self.step_summary
++ ]
++ for summary in summaries:
++ summary["all"] = compute_summary_sum(summary)
++
+ def print_summary(self, stream=None, with_duration=True):
+ if stream is None:
+ stream = self.stream
+
++ self.compute_summary_sums()
+ stream.write(format_summary("feature", self.feature_summary))
+ rules_summary = format_summary("rule", self.rule_summary)
+ if self.show_rules and not rules_summary.strip().startswith("0"):
+@@ -145,13 +198,7 @@ class SummaryReporter(Reporter):
+ # -- DISCOVER: TEST-RUN started.
+ self.testrun_started()
+
+- self.feature_summary[feature.status.name] += 1
+- self.duration += feature.duration
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- self.process_scenario_outline(scenario)
+- else:
+- self.process_scenario(scenario)
++ self.process_feature(feature)
+
+ def end(self):
+ self.testrun_finished()
+@@ -164,6 +211,25 @@ class SummaryReporter(Reporter):
+ # -- SHOW SUMMARY COUNTS:
+ self.print_summary()
+
++ def process_run_items_for(self, parent):
++ for run_item in parent:
++ if isinstance(run_item, Rule):
++ self.process_rule(run_item)
++ elif isinstance(run_item, ScenarioOutline):
++ self.process_scenario_outline(run_item)
++ else:
++ # assert isinstance(run_item, Scenario)
++ self.process_scenario(run_item)
++
++ def process_feature(self, feature):
++ self.duration += feature.duration
++ self.feature_summary[feature.status.name] += 1
++ self.process_run_items_for(feature)
++
++ def process_rule(self, rule):
++ self.rule_summary[rule.status.name] += 1
++ self.process_run_items_for(rule)
++
+ def process_scenario(self, scenario):
+ if scenario.status == Status.failed:
+ self.failed_scenarios.append(scenario)
+diff --git a/tests/unit/reporters/__init__.py b/tests/unit/reporter/__init__.py
+similarity index 100%
+rename from tests/unit/reporters/__init__.py
+rename to tests/unit/reporter/__init__.py
+diff --git a/tests/unit/reporters/test_summary.py b/tests/unit/reporter/test_summary.py
+similarity index 99%
+rename from tests/unit/reporters/test_summary.py
+rename to tests/unit/reporter/test_summary.py
+index 02154db..97adbb5 100644
+--- a/tests/unit/reporters/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -120,6 +120,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
+@@ -156,6 +157,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 1,
+ Status.failed.name: 2,
+ Status.skipped.name: 1,
+@@ -201,6 +203,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 7,
+ Status.passed.name: 2,
+ Status.failed.name: 3,
+ Status.skipped.name: 2,
+@@ -241,6 +244,7 @@ class TestSummaryReporter(object):
+ reporter.end()
+
+ expected = {
++ "all": 5,
+ Status.passed.name: 2,
+ Status.failed.name: 1,
+ Status.skipped.name: 1,
new file mode 100644
@@ -0,0 +1,60 @@
+From db1ead991924fb71d87e02aa43ffa73eae60594e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 08:41:37 +0100
+Subject: [PATCH] SummaryReporter.print_summary: Simplify if Rules are used.
+
+---
+ behave/reporter/summary.py | 7 ++++---
+ tests/unit/reporter/test_summary.py | 6 +++---
+ 2 files changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/behave/reporter/summary.py b/behave/reporter/summary.py
+index 2ccdc8f..09285ea 100644
+--- a/behave/reporter/summary.py
++++ b/behave/reporter/summary.py
+@@ -179,11 +179,12 @@ class SummaryReporter(Reporter):
+ stream = self.stream
+
+ self.compute_summary_sums()
++ has_rules = (self.rule_summary["all"] > 0)
++
+ stream.write(format_summary("feature", self.feature_summary))
+- rules_summary = format_summary("rule", self.rule_summary)
+- if self.show_rules and not rules_summary.strip().startswith("0"):
++ if self.show_rules and has_rules:
+ # -- HINT: Show only rules, if any exists.
+- self.stream.write(rules_summary)
++ self.stream.write(format_summary("rule", self.rule_summary))
+ stream.write(format_summary("scenario", self.scenario_summary))
+ stream.write(format_summary("step", self.step_summary))
+
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index 97adbb5..d4e85b5 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -164,7 +164,7 @@ class TestSummaryReporter(object):
+ Status.untested.name: 1,
+ }
+
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -209,7 +209,7 @@ class TestSummaryReporter(object):
+ Status.skipped.name: 2,
+ Status.untested.name: 0,
+ }
+- scenario_index = 2
++ scenario_index = 1 # -- HINT: Index for scenarios if no Rules are used.
+ expected_parts = ("scenario", expected)
+ assert format_summary.call_args_list[scenario_index][0] == expected_parts
+
+@@ -252,6 +252,6 @@ class TestSummaryReporter(object):
+ Status.undefined.name: 1,
+ }
+
+- step_index = 3
++ step_index = 2 # HINT: Index for steps if not rules are used.
+ expected_parts = ("step", expected)
+ assert format_summary.call_args_list[step_index][0] == expected_parts
new file mode 100644
@@ -0,0 +1,395 @@
+From 24811b631e0eed92347880f1dac3f932f4b46f9d Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:08:19 +0100
+Subject: [PATCH] Formatter: Add basic support/output for Rules.
+
+---
+ behave/formatter/base.py | 18 +++++------
+ behave/formatter/plain.py | 63 +++++++++++++++++++++++++++++-------
+ behave/formatter/pretty.py | 33 +++++++++++++++----
+ behave/formatter/progress.py | 18 ++++++++++-
+ behave/model.py | 14 ++++----
+ 5 files changed, 110 insertions(+), 36 deletions(-)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index f7268fa..a8b9f7c 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -129,15 +129,6 @@ class Formatter(object):
+ """
+ pass
+
+- def background(self, background):
+- """Called when a (Feature) Background is provided.
+- Called after :method:`feature()` is called.
+- Called before processing any scenarios or scenario outlines.
+-
+- :param background: Background object (as :class:`behave.model.Background`)
+- """
+- pass
+-
+ def rule(self, rule):
+ """Called before a rule is executed.
+
+@@ -153,6 +144,15 @@ class Formatter(object):
+ # """
+ # pass
+
++ def background(self, background):
++ """Called when a (Feature) Background is provided.
++ Called after :method:`feature()` is called.
++ Called before processing any scenarios or scenario outlines.
++
++ :param background: Background object (as :class:`behave.model.Background`)
++ """
++ pass
++
+ def scenario(self, scenario):
+ """Called before a scenario is executed (or ScenarioOutline scenarios).
+
+diff --git a/behave/formatter/plain.py b/behave/formatter/plain.py
+index 9f1f833..e720829 100644
+--- a/behave/formatter/plain.py
++++ b/behave/formatter/plain.py
+@@ -23,6 +23,8 @@ class PlainFormatter(Formatter):
+
+ SHOW_MULTI_LINE = True
+ SHOW_TAGS = False
++ SHOW_RULES = True
++ SHOW_BACKGROUNDS = True
+ SHOW_ALIGNED_KEYWORDS = False
+ DEFAULT_INDENT_SIZE = 2
+ RAISE_OUTPUT_ERRORS = True
+@@ -35,6 +37,7 @@ class PlainFormatter(Formatter):
+ self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS
+ self.show_tags = self.SHOW_TAGS
+ self.indent_size = self.DEFAULT_INDENT_SIZE
++ self.current_rule = None
+ # -- ENSURE: Output stream is open.
+ self.stream = self.open()
+ self.printer = ModelPrinter(self.stream)
+@@ -49,6 +52,10 @@ class PlainFormatter(Formatter):
+ offset = 2
+ indentation = make_indentation(3 * self.indent_size + offset)
+ self._multiline_indentation = indentation
++
++ if self.current_rule:
++ indent_extra = make_indentation(self.indent_size)
++ return self._multiline_indentation + indent_extra
+ return self._multiline_indentation
+
+ def reset_steps(self):
+@@ -60,37 +67,69 @@ class PlainFormatter(Formatter):
+ text = " @".join(tags)
+ self.stream.write(u"%s@%s\n" % (indent, text))
+
++ def write_entity(self, entity, indent="", has_tags=True):
++ if has_tags:
++ self.write_tags(entity.tags, indent)
++ text = u"%s%s: %s\n" % (indent, entity.keyword, entity.name)
++ self.stream.write(text)
++
+ # -- IMPLEMENT-INTERFACE FOR: Formatter
+ def feature(self, feature):
++ self.current_rule = None
+ self.reset_steps()
+- self.write_tags(feature.tags)
+- self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
++ self.write_entity(feature)
++ # self.write_tags(feature.tags)
++ # self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name))
+
+- def background(self, background):
++ def rule(self, rule):
++ self.current_rule = rule
+ self.reset_steps()
+ indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
+- self.stream.write(text)
++ self.stream.write(u"\n")
++ self.write_entity(rule, indent)
++ # self.stream.write(u"%s%s: %s\n" % (indent, rule.keyword, rule.name))
++
++ def background(self, background):
++ self.reset_steps()
++ if not self.SHOW_BACKGROUNDS:
++ return
++
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(background, indent, has_tags=False)
++ # text = u"%s%s: %s\n" % (indent, background.keyword, background.name)
++ # self.stream.write(text)
+
+ def scenario(self, scenario):
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ self.reset_steps()
+ self.stream.write(u"\n")
+- indent = make_indentation(self.indent_size)
+- text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
+- self.write_tags(scenario.tags, indent)
+- self.stream.write(text)
++ indent = make_indentation(self.indent_size + indent_extra)
++ self.write_entity(scenario, indent)
++ # text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name)
++ # self.write_tags(scenario.tags, indent)
++ # self.stream.write(text)
+
+ def step(self, step):
+ self.steps.append(step)
+
+ def result(self, step):
+- """
+- Process the result of a step (after step execution).
++ """Process the result of a step (after step execution).
+
+ :param step: Step object with result to process.
+ """
++ indent_extra = 0
++ if self.current_rule:
++ indent_extra = self.indent_size
++
+ step = self.steps.pop(0)
+- indent = make_indentation(2 * self.indent_size)
++ indent = make_indentation(2 * self.indent_size + indent_extra)
+ if self.show_aligned_keywords:
+ # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6):
+ text = u"%s%6s %s ... " % (indent, step.keyword, step.name)
+diff --git a/behave/formatter/pretty.py b/behave/formatter/pretty.py
+index b6f0eac..794e1d7 100644
+--- a/behave/formatter/pretty.py
++++ b/behave/formatter/pretty.py
+@@ -6,7 +6,7 @@ from behave.formatter.ansi_escapes import escapes, up
+ from behave.formatter.base import Formatter
+ from behave.model_core import Status
+ from behave.model_describe import escape_cell, escape_triple_quotes
+-from behave.textutil import indent, text as _text
++from behave.textutil import indent, make_indentation, text as _text
+ import six
+ from six.moves import range, zip
+
+@@ -86,6 +86,7 @@ class PrettyFormatter(Formatter):
+
+ def reset(self):
+ # -- UNUSED: self.tag_statement = None
++ self.current_rule = None
+ self.steps = []
+ self._uri = None
+ self._match = None
+@@ -99,7 +100,9 @@ class PrettyFormatter(Formatter):
+
+ def feature(self, feature):
+ #self.print_comments(feature.comments, '')
+- self.print_tags(feature.tags, '')
++ self.current_rule = None
++ prefix = ""
++ self.print_tags(feature.tags, prefix)
+ self.stream.write(u"%s: %s" % (feature.keyword, feature.name))
+ if self.show_source:
+ # pylint: disable=redefined-builtin
+@@ -109,6 +112,11 @@ class PrettyFormatter(Formatter):
+ self.print_description(feature.description, " ", False)
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.replay()
++ self.current_rule = rule
++ self.statement = rule
++
+ def background(self, background):
+ self.replay()
+ self.statement = background
+@@ -176,6 +184,10 @@ class PrettyFormatter(Formatter):
+ self.stream.flush()
+
+ def table(self, table):
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
++
+ cell_lengths = []
+ all_rows = [table.headings] + table.rows
+ for row in all_rows:
+@@ -189,7 +201,7 @@ class PrettyFormatter(Formatter):
+ for i, row in enumerate(all_rows):
+ #for comment in row.comments:
+ # self.stream.write(" %s\n" % comment.value)
+- self.stream.write(" |")
++ self.stream.write(u"%s|" % prefix)
+ for j, (cell, max_length) in enumerate(zip(row, max_lengths)):
+ self.stream.write(" ")
+ self.stream.write(self.color(cell, None, j))
+@@ -202,6 +214,8 @@ class PrettyFormatter(Formatter):
+ #self.stream.write(' """' + doc_string.content_type + '\n')
+ doc_string = _text(doc_string)
+ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ self.stream.write(u'%s"""\n' % prefix)
+ doc_string = escape_triple_quotes(indent(doc_string, prefix))
+ self.stream.write(doc_string)
+@@ -251,12 +265,16 @@ class PrettyFormatter(Formatter):
+ if self.statement is None:
+ return
+
++ prefix = u" "
++ if self.current_rule and self.statement.type != "rule":
++ prefix += prefix
++
+ self.calculate_location_indentations()
+ self.stream.write(u"\n")
+ #self.print_comments(self.statement.comments, " ")
+ if hasattr(self.statement, "tags"):
+- self.print_tags(self.statement.tags, u" ")
+- self.stream.write(u" %s: %s " % (self.statement.keyword,
++ self.print_tags(self.statement.tags, prefix)
++ self.stream.write(u"%s%s: %s " % (prefix, self.statement.keyword,
+ self.statement.name))
+
+ location = self.indented_text(six.text_type(self.statement.location), True)
+@@ -279,8 +297,11 @@ class PrettyFormatter(Formatter):
+ text_format = self.format(status.name)
+ arg_format = self.arg_format(status.name)
+
++ prefix = u" "
++ if self.current_rule:
++ prefix += u" "
+ #self.print_comments(step.comments, " ")
+- self.stream.write(" ")
++ self.stream.write(prefix)
+ self.stream.write(text_format.text(step.keyword + " "))
+ line_length = 5 + len(step.keyword)
+
+diff --git a/behave/formatter/progress.py b/behave/formatter/progress.py
+index 6d8adf6..3b471ed 100644
+--- a/behave/formatter/progress.py
++++ b/behave/formatter/progress.py
+@@ -43,6 +43,7 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+ self.show_timings = config.show_timings and self.show_timings
+
+@@ -50,14 +51,19 @@ class ProgressFormatterBase(Formatter):
+ self.steps = []
+ self.failures = []
+ self.current_feature = None
++ self.current_rule = None
+ self.current_scenario = None
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write("%s " % six.text_type(feature.filename))
+ self.stream.flush()
+
++ def rule(self, rule):
++ self.current_rule = rule
++
+ def background(self, background):
+ pass
+
+@@ -219,9 +225,16 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+
+ # -- FORMATTER API:
+ def feature(self, feature):
++ self.current_rule = None
+ self.current_feature = feature
+ self.stream.write(u"%s # %s" % (feature.name, feature.filename))
+
++ def rule(self, rule):
++ self.current_rule = rule
++ self.stream.write(u"\n\n %s: %s # %s" %
++ (rule.keyword, rule.name, rule.location))
++ self.stream.flush()
++
+ def scenario(self, scenario):
+ """Process the next scenario."""
+ # -- LAST SCENARIO: Report failures (if any).
+@@ -231,9 +244,12 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
+ assert not self.failures
+ self.current_scenario = scenario
+ scenario_name = scenario.name
++ prefix = self.scenario_prefix
++ if self.current_rule:
++ prefix += u" "
+ if scenario_name:
+ scenario_name += " "
+- self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name))
++ self.stream.write(u"%s%s " % (prefix, scenario_name))
+ self.stream.flush()
+
+ # -- DISABLED:
+diff --git a/behave/model.py b/behave/model.py
+index 6238313..3084850 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -318,10 +318,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ runner.context.tags = set(self.tags)
+
+ skip_entity_untested = runner.aborted
+- run_entity = self.should_run(runner.config)
++ should_run_entity = self.should_run(runner.config)
+ failed_count = 0
+ hooks_called = False
+- if not runner.config.dry_run and run_entity:
++ if not runner.config.dry_run and should_run_entity:
+ hooks_called = True
+ for tag in self.tags:
+ runner.run_hook("before_tag", runner.context, tag)
+@@ -332,10 +332,10 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- RE-EVALUATE SHOULD-RUN STATE:
+ # Hook may call entity.mark_skipped() to exclude it.
+ skip_entity_untested = self.hook_failed or runner.aborted
+- run_entity = self.should_run()
++ should_run_entity = self.should_run()
+
+ # run this entity if the tags say so or any one of its scenarios
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ for formatter in runner.formatters:
+ formatter_callback = getattr(formatter, entity_name, None)
+ if formatter_callback:
+@@ -363,7 +363,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ break
+
+ self.clear_status() # -- ENFORCE: compute_status() after run.
+- if not self.run_items and not run_entity:
++ if not self.run_items and not should_run_entity:
+ # -- SPECIAL CASE: Feature without scenarios
+ self.set_status(Status.skipped)
+
+@@ -382,7 +382,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # -- CLEANUP-ERROR:
+ self.set_status(Status.failed)
+
+- if run_entity or runner.config.show_skipped:
++ if should_run_entity or runner.config.show_skipped:
+ callback_name = "{0}_finished".format(entity_name)
+ if entity_name == "feature":
+ callback_name = "eof"
+@@ -608,7 +608,6 @@ class Rule(ScenarioContainer):
+ .. versionadded:: 1.2.7
+ .. _`feature`: gherkin.html#rule
+ """
+-
+ type = "rule"
+
+ def __init__(self, filename, line, keyword, name, tags=None,
+@@ -625,7 +624,6 @@ class Rule(ScenarioContainer):
+ (self.name, len(self.scenarios))
+
+
+-
+ class Background(BasicStatement, Replayable):
+ """A `background`_ parsed from a *feature file*.
+
new file mode 100644
@@ -0,0 +1,67 @@
+From 19a4134596217540832ed394d790d7b509ec865a Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Wed, 13 Mar 2019 23:11:50 +0100
+Subject: [PATCH] BUMP-VERSION: 1.2.7.dev1 (was: 1.2.7.dev0)
+
+---
+ .bumpversion.cfg | 2 +-
+ VERSION.txt | 2 +-
+ behave/__init__.py | 2 +-
+ pytest.ini | 2 +-
+ setup.py | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/.bumpversion.cfg b/.bumpversion.cfg
+index f387d43..ac913c2 100644
+--- a/.bumpversion.cfg
++++ b/.bumpversion.cfg
+@@ -1,5 +1,5 @@
+ [bumpversion]
+-current_version = 1.2.7.dev0
++current_version = 1.2.7.dev1
+ files = behave/__init__.py setup.py VERSION.txt pytest.ini .bumpversion.cfg
+ parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<drop>\w*)
+ serialize = {major}.{minor}.{patch}{drop}
+diff --git a/VERSION.txt b/VERSION.txt
+index 4e63eef..c0ef36b 100644
+--- a/VERSION.txt
++++ b/VERSION.txt
+@@ -1 +1 @@
+-1.2.7.dev0
++1.2.7.dev1
+diff --git a/behave/__init__.py b/behave/__init__.py
+index 8888355..31e4e55 100644
+--- a/behave/__init__.py
++++ b/behave/__init__.py
+@@ -29,4 +29,4 @@ __all__ = [
+ # -- DEPRECATING:
+ "step_matcher"
+ ]
+-__version__ = "1.2.7.dev0"
++__version__ = "1.2.7.dev1"
+diff --git a/pytest.ini b/pytest.ini
+index 70e10cd..17ad388 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -20,7 +20,7 @@ minversion = 2.8
+ testpaths = test tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+- --metadata PACKAGE_VERSION 1.2.7.dev0
++ --metadata PACKAGE_VERSION 1.2.7.dev1
+ --html=build/testing/report.html --self-contained-html
+ --junit-xml=build/testing/report.xml
+ markers =
+diff --git a/setup.py b/setup.py
+index cb3b338..c5af262 100644
+--- a/setup.py
++++ b/setup.py
+@@ -55,7 +55,7 @@ def find_packages_by_root_package(where):
+ # -----------------------------------------------------------------------------
+ setup(
+ name="behave",
+- version="1.2.7.dev0",
++ version="1.2.7.dev1",
+ description="behave is behaviour-driven development, Python style",
+ long_description=description,
+ author="Jens Engel, Benno Rice and Richard Jones",
new file mode 100644
@@ -0,0 +1,41 @@
+From 29d3ef4d3ff8c836bc592b92687a28bf873d0e0c Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:14:54 +0100
+Subject: [PATCH] Correct examples and docstring
+
+---
+ behave/contrib/scenario_autoretry.py | 2 +-
+ behave/formatter/base.py | 3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/behave/contrib/scenario_autoretry.py b/behave/contrib/scenario_autoretry.py
+index 2b7f94f..2592d10 100644
+--- a/behave/contrib/scenario_autoretry.py
++++ b/behave/contrib/scenario_autoretry.py
+@@ -24,7 +24,7 @@ EXAMPLE:
+ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry
+
+ def before_feature(context, feature):
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ if "autoretry" in scenario.effective_tags:
+ patch_scenario_with_autoretry(scenario, max_attempts=2)
+
+diff --git a/behave/formatter/base.py b/behave/formatter/base.py
+index a8b9f7c..7f59ad4 100644
+--- a/behave/formatter/base.py
++++ b/behave/formatter/base.py
+@@ -74,11 +74,12 @@ class Formatter(object):
+
+ Processing Logic (simplified, without ScenarioOutline and skip logic)::
+
++ # -- HINT: Rule processing is missing.
+ for feature in runner.features:
+ formatter = make_formatters(...)
+ formatter.uri(feature.filename)
+ formatter.feature(feature)
+- for scenario in feature.scenarios:
++ for scenario in feature.walk_scenarios():
+ formatter.scenario(scenario)
+ for step in scenario.all_steps:
+ formatter.step(step)
new file mode 100644
@@ -0,0 +1,58 @@
+From dee5266820aabcfe09d103cf007bb26b9db54849 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 14 Mar 2019 22:15:34 +0100
+Subject: [PATCH] FIX: feature.run_items processing with Rule(s).
+
+---
+ behave/reporter/junit.py | 24 ++++++++++++++++--------
+ 1 file changed, 16 insertions(+), 8 deletions(-)
+
+diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py
+index 48e1411..9018399 100644
+--- a/behave/reporter/junit.py
++++ b/behave/reporter/junit.py
+@@ -75,7 +75,7 @@ import codecs
+ from xml.etree import ElementTree
+ from datetime import datetime
+ from behave.reporter.base import Reporter
+-from behave.model import Scenario, ScenarioOutline, Step
++from behave.model import Rule, Scenario, ScenarioOutline, Step
+ from behave.model_core import Status
+ from behave.formatter import ansi_escapes
+ from behave.model_describe import ModelDescriptor
+@@ -236,13 +236,8 @@ class JUnitReporter(Reporter):
+ feature_name = feature.name or feature_filename
+ suite.set(u'name', u'%s.%s' % (classname, feature_name))
+
+- # -- BUILD-TESTCASES: From scenarios
+- for scenario in feature:
+- if isinstance(scenario, ScenarioOutline):
+- scenario_outline = scenario
+- self._process_scenario_outline(scenario_outline, report)
+- else:
+- self._process_scenario(scenario, report)
++ # -- BUILD-TESTCASES: From run_items (and scenarios)
++ self._process_run_items_for(feature, report)
+
+ # -- ADD TESTCASES to testsuite:
+ for testcase in report.testcases:
+@@ -457,6 +452,19 @@ class JUnitReporter(Reporter):
+ if scenario.status != Status.skipped or self.show_skipped:
+ report.testcases.append(case)
+
++ def _process_run_items_for(self, parent, report):
++ for run_item in parent.run_items:
++ if isinstance(run_item, Rule):
++ self._process_rule(run_item, report)
++ elif isinstance(run_item, ScenarioOutline):
++ self._process_scenario_outline(run_item, report)
++ else:
++ assert isinstance(run_item, Scenario)
++ self._process_scenario(run_item, report)
++
++ def _process_rule(self, rule, report):
++ self._process_run_items_for(rule, report)
++
+ def _process_scenario_outline(self, scenario_outline, report):
+ assert isinstance(scenario_outline, ScenarioOutline)
+ for scenario in scenario_outline:
new file mode 100644
@@ -0,0 +1,58 @@
+From b4a40c4df5872b0c9c7293b3a9fa057e208361d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:40:38 +0200
+Subject: [PATCH] docs: Disable sphinx-intl support for READTHEDOCS.
+
+---
+ docs/conf.py | 17 +++++++++++++----
+ 1 file changed, 13 insertions(+), 4 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d38db7a..f9dfb6a 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -3,6 +3,7 @@
+ # SPHINX CONFIGURATION: behave documentation build configuration file
+ # =============================================================================
+
++from __future__ import print_function
+ import os.path
+ import sys
+ import importlib
+@@ -13,6 +14,13 @@ import importlib
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
+ sys.path.insert(0, os.path.abspath(".."))
+
++# ------------------------------------------------------------------------------
++# DETECT BUILD CONTEXT
++# ------------------------------------------------------------------------------
++ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
++USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++
++
+ # ------------------------------------------------------------------------------
+ # EXTENSIONS CONFIGURATION
+ # ------------------------------------------------------------------------------
+@@ -82,8 +90,10 @@ master_doc = "index"
+ # -- MULTI-LANGUAGE SUPPORT: en, ...
+ # SEE: https://pypi.org/project/sphinx-intl/
+ # SEE: https://github.com/sphinx-doc/sphinx-intl/
+-locale_dirs = ["locale/"] # path is example but recommended.
+-gettext_compact = False # optional.
++if USE_SPHINX_INTERNATIONAL:
++ locale_dirs = ["locale/"] # path is example but recommended.
++ gettext_compact = False # optional.
++ print("USE SPHINX-INTL: locale_dirs=%s" % ",".join(locale_dirs))
+
+ # STEPS:
+ # make gettext
+@@ -155,8 +165,7 @@ todo_include_todos = False
+ html_theme = "kr"
+ html_theme = "bootstrap"
+
+-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+-if on_rtd:
++if ON_READTHEDOCS:
+ html_theme = "default"
+
+ if html_theme == "bootstrap":
new file mode 100644
@@ -0,0 +1,81 @@
+From ffbd9840d2c2e273a0ce2ea8fe20afad034bdeb2 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Thu, 18 Apr 2019 19:02:43 +0200
+Subject: [PATCH] Cleanup: Dependent package versions in requirements.
+
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 4 ++--
+ py.requirements/testing.txt | 2 +-
+ setup.py | 6 +++---
+ 4 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 9eebcad..3b71bfb 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -11,7 +11,7 @@
+ cucumber-tag-expressions >= 1.1.1
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+-six >= 1.11.0
++six >= 1.12.0
+
+ traceback2; python_version < '3.0'
+ contextlib2 # MAYBE: python_version < '3.5'
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index c55d3cd..3deedc7 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -5,8 +5,8 @@
+ # -- BUILD-TOOL:
+ # PREPARE USAGE: invoke
+ # ALREADY: six >= 1.11.0
+-invoke >= 0.21.0
+-path.py >= 10.1
++invoke >= 1.2.0
++path.py >= 11.5.0
+ pathlib; python_version <= '3.4'
+ pycmd
+
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 5876e29..3806d39 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -12,4 +12,4 @@ PyHamcrest >= 1.9
+
+ # -- NEEDED: By some tests (as proof of concept)
+ # NOTE: path.py-10.1 is required for python2.6
+-path.py >= 10.1
++path.py >= 11.5.0
+diff --git a/setup.py b/setup.py
+index c5af262..ac7bddf 100644
+--- a/setup.py
++++ b/setup.py
+@@ -79,7 +79,7 @@ setup(
+ "cucumber-tag-expressions >= 1.1.1",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+- "six >= 1.11.0",
++ "six >= 1.12.0",
+ "traceback2; python_version < '3.0'",
+ "enum34; python_version < '3.4'",
+ # -- PREPARED:
+@@ -93,7 +93,7 @@ setup(
+ "nose >= 1.3",
+ "mock >= 1.1",
+ "PyHamcrest >= 1.8",
+- "path.py >= 10.1"
++ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+ "behave_test": behave_test,
+@@ -110,7 +110,7 @@ setup(
+ "pytest-cov",
+ "tox",
+ "invoke >= 1.2.0",
+- "path.py >= 10.1",
++ "path.py >= 11.5.0",
+ "pycmd",
+ "pathlib; python_version <= '3.4'",
+ "modernize >= 0.5",
new file mode 100644
@@ -0,0 +1,30 @@
+From 430d19123b3a7adc21075b1befda8b550b7eb641 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 26 May 2019 14:55:20 +0200
+Subject: [PATCH] docs: conf.py tweaks.
+
+---
+ docs/conf.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f9dfb6a..f7c2c24 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath(".."))
+ # DETECT BUILD CONTEXT
+ # ------------------------------------------------------------------------------
+ ON_READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True"
+-USE_SPHINX_INTERNATIONAL = not ON_READTHEDOCS
++USE_SPHINX_INTERNATIONAL = True
+
+
+ # ------------------------------------------------------------------------------
+@@ -164,7 +164,6 @@ todo_include_todos = False
+ # a list of builtin themes.
+ html_theme = "kr"
+ html_theme = "bootstrap"
+-
+ if ON_READTHEDOCS:
+ html_theme = "default"
+
new file mode 100644
@@ -0,0 +1,30 @@
+From 5ea1f1b47c14cf9aeabe7d8e22511d54b15e5f1e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:11:23 +0200
+Subject: [PATCH] FIX: Misspelled after_rule hook (was: after_after)
+
+---
+ docs/context_attributes.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/context_attributes.rst b/docs/context_attributes.rst
+index a4817d1..163664b 100644
+--- a/docs/context_attributes.rst
++++ b/docs/context_attributes.rst
+@@ -23,6 +23,7 @@ config test run :class:`~behave.configuration.Configuration` Configur
+ aborted test run bool Set to true if test run is aborted by the user.
+ failed test run bool Set to true if a step fails.
+ feature feature :class:`~behave.model.Feature` Current feature.
++rule rule :class:`~behave.model.Feature` Current rule.
+ tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, rule, scenario, scenario outline.
+ rule,
+ scenario
+@@ -62,7 +63,7 @@ Hook :func:`after_tags` feature, rule or scenario
+ Hook :func:`before_feature` feature
+ Hook :func:`after_feature` feature
+ Hook :func:`before_rule` rule
+-Hook :func:`after_after` rule
++Hook :func:`after_rule` rule
+ Hook :func:`before_scenario` scenario
+ Hook :func:`after_scenario` scenario
+ Hook :func:`before_step` scenario
new file mode 100644
@@ -0,0 +1,45 @@
+From 14e4c88e9bd1a12a2f081dfb2709df9f78106ca6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:12:23 +0200
+Subject: [PATCH] Add hints on Gherkin v6 grammar issues.
+
+---
+ docs/conf.py | 4 +++-
+ docs/new_and_noteworthy_v1.2.7.rst | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index f7c2c24..e55fb21 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -54,9 +54,11 @@ for optional_module_name in optional_extensions:
+ extlinks = {
+ "pypi": ("https://pypi.org/project/%s", ""),
+ "github": ("https://github.com/%s", "github:/"),
+- "issue": ("https://github.com/behave/behave/issue/%s", "issue #"),
++ "issue": ("https://github.com/behave/behave/issues/%s", "issue #"),
+ "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="),
+ "behave": ("https://github.com/behave/behave", None),
++ "cucumber": ("https://github.com/cucumber/cucumber/", None),
++ "cucumber.issue": ("https://github.com/cucumber/cucumber/issues/%s", "issue #"),
+ }
+
+ intersphinx_mapping = {
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 451ed8c..80d9576 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -92,5 +92,13 @@ Overview of the `Example Mapping`_ concepts:
+ * https://lisacrispin.com/2016/06/02/experiment-example-mapping/
+ * https://tobythetesterblog.wordpress.com/2016/05/25/how-to-do-example-mapping/
+
++.. hint:: **Gherkin v6 Grammar Issues**
++
++ * :cucumber.issue:`632`: Rule tags are currently only supported in `behave`.
++ The Cucumber Gherkin v6 grammar currently lacks this functionality.
++
++ * :cucumber.issue:`590`: Rule Background:
++ A proposal is pending to remove Rule Backgrounds again
++
+
+ .. include:: _content.tag_expressions_v2.rst
new file mode 100644
@@ -0,0 +1,18 @@
+From 12d37ec6af46d3edb806679183ab2138e4f1f5bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:13:08 +0200
+Subject: [PATCH] README: ReST tweaks
+
+---
+ etc/gherkin/README.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/etc/gherkin/README.rst b/etc/gherkin/README.rst
+index ad3cedb..7ec2108 100644
+--- a/etc/gherkin/README.rst
++++ b/etc/gherkin/README.rst
+@@ -1,3 +1,4 @@
+ SOURCE:
++
+ * https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json
+ * https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json
new file mode 100644
@@ -0,0 +1,228 @@
+From eabcf73f0e7f26ef021cef30950c7bb3d2442226 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:16:01 +0200
+Subject: [PATCH] Example using Gherkin v6 grammar.
+
+---
+ examples/gherkin_v6/README.rst | 18 ++++++++
+ examples/gherkin_v6/behave.ini | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_1.feature | 42 +++++++++++++++++++
+ examples/gherkin_v6/features/rule_2.feature | 42 +++++++++++++++++++
+ .../features/steps/example_steps.py | 21 ++++++++++
+ .../gherkin_v6/features/steps/person_steps.py | 7 ++++
+ 6 files changed, 172 insertions(+)
+ create mode 100644 examples/gherkin_v6/README.rst
+ create mode 100644 examples/gherkin_v6/behave.ini
+ create mode 100644 examples/gherkin_v6/features/rule_1.feature
+ create mode 100644 examples/gherkin_v6/features/rule_2.feature
+ create mode 100644 examples/gherkin_v6/features/steps/example_steps.py
+ create mode 100644 examples/gherkin_v6/features/steps/person_steps.py
+
+diff --git a/examples/gherkin_v6/README.rst b/examples/gherkin_v6/README.rst
+new file mode 100644
+index 0000000..58199dd
+--- /dev/null
++++ b/examples/gherkin_v6/README.rst
+@@ -0,0 +1,18 @@
++Gherkin v6 Examples
++=============================================================================
++
++
++SCRATCHPAD: Problems
++-----------------------------------------------------------------------------
++
++- SummaryReporter: Shows wrong counts when Rules are present::
++
++ ...
++ 0 features passed, 0 failed, 1 skipped XXX
++ 3 rules passed, 0 failed, 0 skipped
++ 5 scenarios passed, 0 failed, 0 skipped
++ 13 steps passed, 0 failed, 0 skipped, 0 undefined
++
++
++- Formatters: PrettyFormatter, PlainFormatter (at least) need Rule support
++
+diff --git a/examples/gherkin_v6/behave.ini b/examples/gherkin_v6/behave.ini
+new file mode 100644
+index 0000000..45c0f0d
+--- /dev/null
++++ b/examples/gherkin_v6/behave.ini
+@@ -0,0 +1,42 @@
++# =============================================================================
++# BEHAVE CONFIGURATION
++# =============================================================================
++# FILE: .behaverc, behave.ini, setup.cfg, tox.ini
++#
++# SEE ALSO:
++# * http://packages.python.org/behave/behave.html#configuration-files
++# * https://github.com/behave/behave
++# * http://pypi.python.org/pypi/behave/
++# =============================================================================
++
++[behave]
++default_tags = not (@xfail or @not_implemented)
++show_skipped = false
++format = rerun
++ progress3
++outfiles = rerun.txt
++ reports/report_progress3.txt
++junit = true
++logging_level = INFO
++# logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s
++# logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s
++
++# -- ALLURE-FORMATTER REQUIRES:
++# brew install allure
++# pip install allure-behave
++# ALLURE_REPORTS_DIR=allure.reports
++# behave -f allure -o $ALLURE_REPORTS_DIR ...
++# allure serve $ALLURE_REPORTS_DIR
++#
++# SEE ALSO:
++# * https://github.com/allure-framework/allure2
++# * https://github.com/allure-framework/allure-python
++[behave.formatters]
++allure = allure_behave.formatter:AllureFormatter
++
++# PREPARED:
++# [behave]
++# format = ... missing_steps ...
++# output = ... features/steps/missing_steps.py ...
++# [behave.formatters]
++# missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter
+diff --git a/examples/gherkin_v6/features/rule_1.feature b/examples/gherkin_v6/features/rule_1.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_1.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+new file mode 100644
+index 0000000..a802e19
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -0,0 +1,42 @@
++Feature: Gherkin v6 Example -- with Rules
++ Feature description line 1.
++
++ Background: Feature.Background
++ Given feature background step_1
++
++ Rule: R1 (with Rule.Background)
++ Rule R1 description line 1.
++
++ Background: R1.Background
++ Given rule R1 background step_1
++ When rule R1 background step_2
++
++ Example: R1.Scenario_1
++ When rule R1 scenario_1 step_1
++ Then rule R1 scenario_1 step_2
++
++ Example: R1.Scenario_2
++ Given rule R1 scenario_2 step_1
++ Then rule R1 scenario_2 step_2
++
++ Rule: R2 (without Rule.Background)
++ Rule R2 description line 1.
++
++ Example: R2.Scenario_1
++ When rule R2 scenario_1 step_1
++ Then rule R2 scenario_1 step_2
++
++
++ Rule: R3 (with empty Rule.Background)
++ Rule R3 description line 1.
++ Rule R3 description line 2.
++
++ Background: R3.EmptyBackground
++
++ Scenario Template: R3.Scenario
++ Given a person named "<name>"
++
++ Examples:
++ | name |
++ | Alice |
++ | Bob |
+diff --git a/examples/gherkin_v6/features/steps/example_steps.py b/examples/gherkin_v6/features/steps/example_steps.py
+new file mode 100644
+index 0000000..f4822f3
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/example_steps.py
+@@ -0,0 +1,21 @@
++# -*- coding: UTF-8 -*-
++from __future__ import absolute_import, print_function
++from behave import step
++
++
++@step(u'feature background step_{step_id:d}')
++def step_rule_background(ctx, step_id):
++ print("feature background step_{0}".format(step_id))
++
++
++@step(u'rule {rule_id:w} background step_{step_id:d}')
++def step_rule_background(ctx, rule_id, step_id):
++ print("rule {0} background step_{1}".format(rule_id, step_id))
++
++
++@step(u'rule {rule_id:w} scenario_{scenario_id:d} step_{step_id:d}')
++def step_rule_scenario(ctx, rule_id, scenario_id, step_id):
++ print("rule {0} scenario_{1} step_{2}".format(
++ rule_id, scenario_id, step_id))
++
++
+diff --git a/examples/gherkin_v6/features/steps/person_steps.py b/examples/gherkin_v6/features/steps/person_steps.py
+new file mode 100644
+index 0000000..714ac01
+--- /dev/null
++++ b/examples/gherkin_v6/features/steps/person_steps.py
+@@ -0,0 +1,7 @@
++# -*- coding: UTF-8 -*-
++from behave import given
++
++
++@given(u'a person named "{name}"')
++def step_given_person_with_name(ctx, name):
++ pass
new file mode 100644
@@ -0,0 +1,39 @@
+From 5e529b9ae17c15231f989c17fe1a09897edf6477 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 8 Jun 2019 20:39:42 +0200
+Subject: [PATCH] PREPARE: Python-3.8 support
+
+---
+ .travis.yml | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 7015b88..d8f2443 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -1,12 +1,14 @@
+ language: python
+ sudo: false
++dist: xenial # required for Python >= 3.7
+ python:
+- - "3.6"
++ - "3.7"
+ - "2.7"
++ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.7-dev"
++ - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
+@@ -19,7 +21,7 @@ python:
+ # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer
+ matrix:
+ allow_failures:
+- - python: "3.7-dev"
++ - python: "3.8-dev"
+ - python: "nightly"
+
+ cache:
new file mode 100644
@@ -0,0 +1,22 @@
+From c046c3a31322c7c29ff6d7f6172df41da3530683 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:17:10 +0200
+Subject: [PATCH] py3.8: Try to fix logging.Formatter validate problem
+
+---
+ tests/unit/test_capture.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tests/unit/test_capture.py b/tests/unit/test_capture.py
+index ac2655e..d9a3f3a 100644
+--- a/tests/unit/test_capture.py
++++ b/tests/unit/test_capture.py
+@@ -20,6 +20,8 @@ def create_capture_controller(config=None):
+ config.log_capture = True
+ config.logging_filter = None
+ config.logging_level = "INFO"
++ config.logging_format = "%(levelname)s:%(name)s:%(message)s"
++ config.logging_datefmt = None
+ return CaptureController(config)
+
+ def setup_capture_controller(capture_controller, context=None):
new file mode 100644
@@ -0,0 +1,28 @@
+From fbe6b2937087db11f96e21b36a540270d7c2b165 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:19:58 +0200
+Subject: [PATCH] travis.ci: Temporarily move py38-dev to front (build first).
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index d8f2443..35bce8c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,13 +2,13 @@ language: python
+ sudo: false
+ dist: xenial # required for Python >= 3.7
+ python:
++ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+ - "3.6"
+ - "3.5"
+ - "pypy"
+ - "pypy3"
+- - "3.8-dev"
+
+ # -- DISABLED:
+ # - "nightly"
new file mode 100644
@@ -0,0 +1,35 @@
+From 41525c748413405d0faf6c8fc9a345047e31a1a7 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:26:19 +0200
+Subject: [PATCH] travis.ci: Tweaks for faster builds (temporarily).
+
+---
+ .travis.yml | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index 35bce8c..fbc3520 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -5,15 +5,15 @@ python:
+ - "3.8-dev"
+ - "3.7"
+ - "2.7"
+- - "3.6"
+- - "3.5"
+- - "pypy"
+- - "pypy3"
++
++# -- DISABLE-TEMPORARILY: Ensure faster builds
++# - "3.6"
++# - "3.5"
++# - "pypy"
++# - "pypy3"
+
+ # -- DISABLED:
+ # - "nightly"
+-# - "3.4"
+-# - "3.3"
+ #
+ # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1)
+ # NOTE: nightly = 3.7-dev
new file mode 100644
@@ -0,0 +1,47 @@
+From fed4bb3273633e5f81fc8ba21c8b62255c7eefcd Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:42:20 +0200
+Subject: [PATCH] FIX py3.8: logging.Formatter.validate() problem.
+
+---
+ test/test_runner.py | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/test/test_runner.py b/test/test_runner.py
+index 57c9445..6647283 100644
+--- a/test/test_runner.py
++++ b/test/test_runner.py
+@@ -286,6 +286,7 @@ class TestContext(unittest.TestCase):
+ eq_("thing" in self.context, True)
+ del self.context.thing
+
++
+ class ExampleSteps(object):
+ text = None
+ table = None
+@@ -320,6 +321,7 @@ class ExampleSteps(object):
+ for keyword, pattern, func in step_definitions:
+ step_registry.add_step_definition(keyword, pattern, func)
+
++
+ class TestContext_ExecuteSteps(unittest.TestCase):
+ """
+ Test the behave.runner.Context.execute_steps() functionality.
+@@ -341,6 +343,8 @@ class TestContext_ExecuteSteps(unittest.TestCase):
+ runner_.config.stdout_capture = False
+ runner_.config.stderr_capture = False
+ runner_.config.log_capture = False
++ runner_.config.logging_format = None
++ runner_.config.logging_datefmt = None
+ runner_.step_registry = self.step_registry
+
+ self.context = runner.Context(runner_)
+@@ -658,6 +662,8 @@ class TestRunWithPaths(unittest.TestCase):
+ self.config.logging_filter = None
+ self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)]
+ self.config.format = ["plain", "progress"]
++ self.config.logging_format = None
++ self.config.logging_datefmt = None
+ self.runner = runner.Runner(self.config)
+ self.load_hooks = self.runner.load_hooks = Mock()
+ self.load_step_definitions = self.runner.load_step_definitions = Mock()
new file mode 100644
@@ -0,0 +1,42 @@
+From d54d8c038a7e5f9d2168daa6e13362d1df7d17a5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 13:58:22 +0200
+Subject: [PATCH] PREPARE FOR: Python 3.8, @asyncio.coroutine is deprecated
+ since py38.
+
+---
+ tests/api/_test_async_step34.py | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
+index 8242be7..1c0c31f 100644
+--- a/tests/api/_test_async_step34.py
++++ b/tests/api/_test_async_step34.py
+@@ -37,13 +37,16 @@ from .testing_support_async import AsyncStepTheory
+ # -----------------------------------------------------------------------------
+ # TEST MARKERS:
+ # -----------------------------------------------------------------------------
++# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+ _python_version = float("%s.%s" % sys.version_info[:2])
+-py34_or_newer = pytest.mark.skipif(_python_version < 3.4, reason="Needs Python >= 3.4")
++requires_py34_to_py37 = pytest.mark.skipif(not (3.4 <= _python_version < 3.8),
++ reason="Supported only for python.versions: 3.4 .. 3.7 (inclusive)")
++
+
+ # -----------------------------------------------------------------------------
+ # TESTSUITE:
+ # -----------------------------------------------------------------------------
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepDecoratorPy34(object):
+
+ def test_step_decorator_async_run_until_complete2(self):
+@@ -128,7 +131,7 @@ class TestAsyncContext(object):
+ assert async_context.loop is loop0
+
+
+-@py34_or_newer
++@requires_py34_to_py37
+ class TestAsyncStepRunPy34(object):
+ """Ensure that execution of async-steps works as expected."""
+
new file mode 100644
@@ -0,0 +1,24 @@
+From db66eecf4c5301ca45db9f71c7b2c3c26d5927a1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:06:14 +0200
+Subject: [PATCH] UPDATE: Add #755 info
+
+---
+ CHANGES.rst | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index d6e96af..a91e22a 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -30,6 +30,10 @@ ENHANCEMENTS:
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+
+
++PARTIALLY FIXED:
++
++* issue #755: Failures with Python 3.8 (submitted by: hroncok)
++
+ FIXED:
+
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
new file mode 100644
@@ -0,0 +1,25 @@
+From 467b223dc84f52933b2782ce9d116e89d48a26f1 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:27:23 +0200
+Subject: [PATCH] FIX-WARNING: Related to docstring-example and weird backslash
+ usage.
+
+---
+ behave/matchers.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/behave/matchers.py b/behave/matchers.py
+index c896f52..0fee0c7 100644
+--- a/behave/matchers.py
++++ b/behave/matchers.py
+@@ -261,9 +261,7 @@ class CFParseMatcher(ParseMatcher):
+
+
+ def register_type(**kw):
+- # pylint: disable=anomalous-backslash-in-string
+- # REQUIRED-BY: code example
+- """Registers a custom type that will be available to "parse"
++ r"""Registers a custom type that will be available to "parse"
+ for type conversion during step matching.
+
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
new file mode 100644
@@ -0,0 +1,29 @@
+From 11ea2c45d5b88c92b25ab8e8027a931df81c2abc Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 14:37:41 +0200
+Subject: [PATCH] FIX: invalid escape sequence warnings (w/ regex patterns).
+
+---
+ tests/unit/test_behave4cmd_command_shell_proc.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/tests/unit/test_behave4cmd_command_shell_proc.py b/tests/unit/test_behave4cmd_command_shell_proc.py
+index aae5e9f..c45ab3b 100644
+--- a/tests/unit/test_behave4cmd_command_shell_proc.py
++++ b/tests/unit/test_behave4cmd_command_shell_proc.py
+@@ -1,5 +1,5 @@
+ # -*- coding: UTF-8 -*-
+-"""
++r"""
+
+ Regular expressions for winpath:
+ http://regexlib.com/Search.aspx?k=file+name
+@@ -61,7 +61,7 @@ line_processor_ioerrors = [
+
+ line_processor_traceback = [
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ ' File "'),
+ BehaveWinCommandOutputProcessor.line_processors[4],
+ ]
new file mode 100644
@@ -0,0 +1,1020 @@
+From 72279d87372cf21d980c40d01fce2f59bb734884 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 16:04:43 +0200
+Subject: [PATCH] DEPRECATING-CLEANUP: Move deprecated tag matcher classes
+
+- behave.tag_matcher.OnlyWithCategoryTagMatcher
+- behave.tag_matcher.OnlyWithAnyCategoryTagMatcher
+
+to "behave.attic.tag_matcher".
+Move related unit tests to "tests.attic/unit/test_tag_matcher.py".
+---
+ behave/attic/__init__.py | 0
+ behave/attic/tag_matcher.py | 181 +++++++++++++++++
+ behave/tag_matcher.py | 189 +-----------------
+ tests.attic/__init__.py | 0
+ tests.attic/unit/__init__.py | 0
+ tests.attic/unit/test_tag_matcher.py | 280 +++++++++++++++++++++++++++
+ tests/unit/test_tag_matcher.py | 279 +-------------------------
+ 7 files changed, 470 insertions(+), 459 deletions(-)
+ create mode 100644 behave/attic/__init__.py
+ create mode 100644 behave/attic/tag_matcher.py
+ create mode 100644 tests.attic/__init__.py
+ create mode 100644 tests.attic/unit/__init__.py
+ create mode 100644 tests.attic/unit/test_tag_matcher.py
+
+diff --git a/behave/attic/__init__.py b/behave/attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/behave/attic/tag_matcher.py b/behave/attic/tag_matcher.py
+new file mode 100644
+index 0000000..f07dcbf
+--- /dev/null
++++ b/behave/attic/tag_matcher.py
+@@ -0,0 +1,181 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES: Should no longer be used
++# -----------------------------------------------------------------------------
++
++import warnings
++from behave.tag_matcher import TagMatcher
++
++
++class OnlyWithCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that allows to determine if feature/scenario
++ should run or should be excluded from the run-set (at runtime).
++
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only on MACOSX
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_os=darwin
++ Scenario: Bob (Run only on MACOSX)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithCategoryTagMatcher
++ import sys
++
++ # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
++ active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++ tag_prefix = "only.with_"
++ value_separator = "="
++
++ def __init__(self, category, value, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithCategoryTagMatcher, self).__init__()
++ self.active_tag = self.make_category_tag(category, value,
++ tag_prefix, value_sep)
++ self.category_tag_prefix = self.make_category_tag(category, None,
++ tag_prefix, value_sep)
++
++ def should_exclude_with(self, tags):
++ category_tags = self.select_category_tags(tags)
++ if category_tags and self.active_tag not in category_tags:
++ return True
++ # -- OTHERWISE: feature/scenario with theses tags should run.
++ return False
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.category_tag_prefix)]
++
++ @classmethod
++ def make_category_tag(cls, category, value=None, tag_prefix=None,
++ value_sep=None):
++ if tag_prefix is None:
++ tag_prefix = cls.tag_prefix
++ if value_sep is None:
++ value_sep = cls.value_separator
++ value = value or ""
++ return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
++
++
++class OnlyWithAnyCategoryTagMatcher(TagMatcher):
++ """
++ Provides a tag matcher that matches any category that follows the
++ "@only.with_" tag schema and determines if it should run or
++ should be excluded from the run-set (at runtime).
++
++ TAG SCHEMA: @only.with_{category}={value}
++
++ .. seealso:: OnlyWithCategoryTagMatcher
++ .. deprecated:: Use :class:`ActiveTagMatcher` instead.
++
++ EXAMPLE:
++ --------
++
++ Run some scenarios only when runtime conditions are met:
++
++ * Run scenario Alice only on Windows OS
++ * Run scenario Bob only with browser Chrome
++
++ .. code-block:: gherkin
++
++ # -- FILE: features/alice.feature
++ # TAG SCHEMA: @only.with_{category}={current_value}
++ Feature:
++
++ @only.with_os=win32
++ Scenario: Alice (Run only on Windows)
++ Given I do something
++ ...
++
++ @only.with_browser=chrome
++ Scenario: Bob (Run only with Web-Browser Chrome)
++ Given I do something else
++ ...
++
++
++ .. code-block:: python
++
++ # -- FILE: features/environment.py
++ from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
++ import sys
++
++ # -- MATCHES ANY TAGS: @only.with_{category}={value}
++ # NOTE: active_tag_value_provider provides current category values.
++ active_tag_value_provider = {
++ "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
++ "os": sys.platform,
++ }
++ active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
++
++ def before_scenario(context, scenario):
++ if active_tag_matcher.should_exclude_with(scenario.effective_tags):
++ scenario.skip() #< LATE-EXCLUDE from run-set.
++ """
++
++ def __init__(self, value_provider, tag_prefix=None, value_sep=None):
++ warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
++ super(OnlyWithAnyCategoryTagMatcher, self).__init__()
++ if value_sep is None:
++ value_sep = OnlyWithCategoryTagMatcher.value_separator
++ self.value_provider = value_provider
++ self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
++ self.value_separator = value_sep
++
++ def should_exclude_with(self, tags):
++ exclude_decision_map = {}
++ for category_tag in self.select_category_tags(tags):
++ category, value = self.parse_category_tag(category_tag)
++ active_value = self.value_provider.get(category, None)
++ if active_value is None:
++ # -- CASE: Unknown category, ignore it.
++ continue
++ elif active_value == value:
++ # -- CASE: Active category value selected, decision should run.
++ exclude_decision_map[category] = False
++ else:
++ # -- CASE: Inactive category value selected, may exclude it.
++ if category not in exclude_decision_map:
++ exclude_decision_map[category] = True
++ return any(exclude_decision_map.values())
++
++ def select_category_tags(self, tags):
++ return [tag for tag in tags
++ if tag.startswith(self.tag_prefix)]
++
++ def parse_category_tag(self, tag):
++ assert tag and tag.startswith(self.tag_prefix)
++ category_value = tag[len(self.tag_prefix):]
++ if self.value_separator in category_value:
++ category, value = category_value.split(self.value_separator, 1)
++ else:
++ # -- OOPS: TAG SCHEMA FORMAT MISMATCH
++ category = category_value
++ value = None
++ return category, value
+diff --git a/behave/tag_matcher.py b/behave/tag_matcher.py
+index f1f955b..5f9dce0 100644
+--- a/behave/tag_matcher.py
++++ b/behave/tag_matcher.py
+@@ -1,9 +1,12 @@
+-# -*- coding: utf-8 -*-
++# -*- coding: UTF-8 -*-
++"""
++Contains classes and functionality to provide a skip-if logic based on tags
++in feature files.
++"""
+
+ from __future__ import absolute_import
+ import re
+ import operator
+-import warnings
+ import six
+
+
+@@ -34,10 +37,10 @@ class ActiveTagMatcher(TagMatcher):
+ """Provides an active tag matcher for many categories.
+
+ TAG SCHEMA:
+- * active.with_{category}={value}
+- * not_active.with_{category}={value}
+ * use.with_{category}={value}
+ * not.with_{category}={value}
++ * active.with_{category}={value}
++ * not_active.with_{category}={value}
+ * only.with_{category}={value} (NOTE: For backward compatibility)
+
+ TAG LOGIC
+@@ -285,181 +288,3 @@ def setup_active_tag_values(active_tag_values, data):
+ for category in list(active_tag_values.keys()):
+ if category in data:
+ active_tag_values[category] = data[category]
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES:
+-# -----------------------------------------------------------------------------
+-class OnlyWithCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that allows to determine if feature/scenario
+- should run or should be excluded from the run-set (at runtime).
+-
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only on MACOSX
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_os=darwin
+- Scenario: Bob (Run only on MACOSX)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=*
+- active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+- tag_prefix = "only.with_"
+- value_separator = "="
+-
+- def __init__(self, category, value, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithCategoryTagMatcher, self).__init__()
+- self.active_tag = self.make_category_tag(category, value,
+- tag_prefix, value_sep)
+- self.category_tag_prefix = self.make_category_tag(category, None,
+- tag_prefix, value_sep)
+-
+- def should_exclude_with(self, tags):
+- category_tags = self.select_category_tags(tags)
+- if category_tags and self.active_tag not in category_tags:
+- return True
+- # -- OTHERWISE: feature/scenario with theses tags should run.
+- return False
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.category_tag_prefix)]
+-
+- @classmethod
+- def make_category_tag(cls, category, value=None, tag_prefix=None,
+- value_sep=None):
+- if tag_prefix is None:
+- tag_prefix = cls.tag_prefix
+- if value_sep is None:
+- value_sep = cls.value_separator
+- value = value or ""
+- return "%s%s%s%s" % (tag_prefix, category, value_sep, value)
+-
+-
+-class OnlyWithAnyCategoryTagMatcher(TagMatcher):
+- """
+- Provides a tag matcher that matches any category that follows the
+- "@only.with_" tag schema and determines if it should run or
+- should be excluded from the run-set (at runtime).
+-
+- TAG SCHEMA: @only.with_{category}={value}
+-
+- .. seealso:: OnlyWithCategoryTagMatcher
+- .. deprecated:: Use :class:`ActiveTagMatcher` instead.
+-
+- EXAMPLE:
+- --------
+-
+- Run some scenarios only when runtime conditions are met:
+-
+- * Run scenario Alice only on Windows OS
+- * Run scenario Bob only with browser Chrome
+-
+- .. code-block:: gherkin
+-
+- # -- FILE: features/alice.feature
+- # TAG SCHEMA: @only.with_{category}={current_value}
+- Feature:
+-
+- @only.with_os=win32
+- Scenario: Alice (Run only on Windows)
+- Given I do something
+- ...
+-
+- @only.with_browser=chrome
+- Scenario: Bob (Run only with Web-Browser Chrome)
+- Given I do something else
+- ...
+-
+-
+- .. code-block:: python
+-
+- # -- FILE: features/environment.py
+- from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher
+- import sys
+-
+- # -- MATCHES ANY TAGS: @only.with_{category}={value}
+- # NOTE: active_tag_value_provider provides current category values.
+- active_tag_value_provider = {
+- "browser": os.environ.get("BEHAVE_BROWSER", "chrome"),
+- "os": sys.platform,
+- }
+- active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider)
+-
+- def before_scenario(context, scenario):
+- if active_tag_matcher.should_exclude_with(scenario.effective_tags):
+- scenario.skip() #< LATE-EXCLUDE from run-set.
+- """
+-
+- def __init__(self, value_provider, tag_prefix=None, value_sep=None):
+- warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning)
+- super(OnlyWithAnyCategoryTagMatcher, self).__init__()
+- if value_sep is None:
+- value_sep = OnlyWithCategoryTagMatcher.value_separator
+- self.value_provider = value_provider
+- self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix
+- self.value_separator = value_sep
+-
+- def should_exclude_with(self, tags):
+- exclude_decision_map = {}
+- for category_tag in self.select_category_tags(tags):
+- category, value = self.parse_category_tag(category_tag)
+- active_value = self.value_provider.get(category, None)
+- if active_value is None:
+- # -- CASE: Unknown category, ignore it.
+- continue
+- elif active_value == value:
+- # -- CASE: Active category value selected, decision should run.
+- exclude_decision_map[category] = False
+- else:
+- # -- CASE: Inactive category value selected, may exclude it.
+- if category not in exclude_decision_map:
+- exclude_decision_map[category] = True
+- return any(exclude_decision_map.values())
+-
+- def select_category_tags(self, tags):
+- return [tag for tag in tags
+- if tag.startswith(self.tag_prefix)]
+-
+- def parse_category_tag(self, tag):
+- assert tag and tag.startswith(self.tag_prefix)
+- category_value = tag[len(self.tag_prefix):]
+- if self.value_separator in category_value:
+- category, value = category_value.split(self.value_separator, 1)
+- else:
+- # -- OOPS: TAG SCHEMA FORMAT MISMATCH
+- category = category_value
+- value = None
+- return category, value
+diff --git a/tests.attic/__init__.py b/tests.attic/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/__init__.py b/tests.attic/unit/__init__.py
+new file mode 100644
+index 0000000..e69de29
+diff --git a/tests.attic/unit/test_tag_matcher.py b/tests.attic/unit/test_tag_matcher.py
+new file mode 100644
+index 0000000..d767fa7
+--- /dev/null
++++ b/tests.attic/unit/test_tag_matcher.py
+@@ -0,0 +1,280 @@
++# -----------------------------------------------------------------------------
++# PROTOTYPING CLASSES (deprecating) -- Should no longer be used.
++# -----------------------------------------------------------------------------
++
++import warnings
++from unittest import TestCase
++from behave.attic.tag_matcher import \
++ OnlyWithCategoryTagMatcher, OnlyWithAnyCategoryTagMatcher
++
++
++class TestOnlyWithCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithCategoryTagMatcher
++
++ def setUp(self):
++ category = "xxx"
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
++ self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
++ self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
++ self.other_tag = self.TagMatcher.make_category_tag(category, "other")
++ self.category = category
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ tags = [ self.enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ test_patterns = [
++ ([ self.enabled_tag, self.other_tag ], "case: first"),
++ ([ self.other_tag, self.enabled_tag ], "case: last"),
++ ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ tags = [ self.other_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ test_patterns = [
++ ([ self.other_tag, "foo" ], "case: first"),
++ ([ "foo", self.other_tag ], "case: last"),
++ ([ "foo", self.other_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ tags = [ self.similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ test_patterns = [
++ ([ self.similar_tag, "foo" ], "case: first"),
++ ([ "foo", self.similar_tag ], "case: last"),
++ ([ "foo", self.similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ self.enabled_tag ], "case: enabled tag"),
++ ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
++ ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ self.other_tag ], "case: other tag"),
++ ([ self.other_tag, "foo" ], "case: other and foo tag"),
++ ([ self.similar_tag ], "case: similar tag"),
++ ([ "foo", self.similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
++
++ def test_make_category_tag__returns_category_tag_prefix_without_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
++ tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
++ self.assertEqual("only.with_xxx=", tag1)
++ self.assertEqual("only.with_xxx=", tag2)
++ self.assertEqual("only.with_xxx=", tag3)
++ self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
++
++ def test_make_category_tag__returns_category_tag_with_value(self):
++ category = "xxx"
++ tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
++ tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
++ self.assertEqual("only.with_xxx=alice", tag1)
++ self.assertEqual("only.with_xxx=bob", tag2)
++
++ def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
++ my_tag_prefix = "ONLY_WITH."
++ category = "xxx"
++ TagMatcher = OnlyWithCategoryTagMatcher
++ tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
++ tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
++ tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
++ self.assertEqual("ONLY_WITH.xxx=", tag0)
++ self.assertEqual("ONLY_WITH.xxx=alice", tag1)
++ self.assertEqual("ONLY_WITH.xxx=bob", tag2)
++ self.assertTrue(tag1.startswith(my_tag_prefix))
++
++ def test_ctor__with_tag_prefix(self):
++ tag_prefix = "ONLY_WITH."
++ tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
++
++ tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
++ actual_tags = tag_matcher.select_category_tags(tags)
++ self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
++
++
++class Traits4OnlyWithAnyCategoryTagMatcher(object):
++ """Test data for OnlyWithAnyCategoryTagMatcher."""
++
++ TagMatcher0 = OnlyWithCategoryTagMatcher
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
++ category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
++ category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
++ category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
++ category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
++ category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
++ unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
++
++
++class TestOnlyWithAnyCategoryTagMatcher(TestCase):
++ TagMatcher = OnlyWithAnyCategoryTagMatcher
++ traits = Traits4OnlyWithAnyCategoryTagMatcher
++
++ def setUp(self):
++ value_provider = {
++ "foo": "alice",
++ "bar": "BOB",
++ }
++ with warnings.catch_warnings():
++ warnings.simplefilter("ignore", DeprecationWarning)
++ self.tag_matcher = self.TagMatcher(value_provider)
++
++ # def test_deprecating_warning_is_issued(self):
++ # value_provider = {"foo": "alice"}
++ # with warnings.catch_warnings(record=True) as recorder:
++ # warnings.simplefilter("always", DeprecationWarning)
++ # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
++ # self.assertEqual(len(recorder), 1)
++ # last_warning = recorder[-1]
++ # assert issubclass(last_warning.category, DeprecationWarning)
++ # assert "deprecated" in str(last_warning.message)
++
++ def test_should_exclude_with__returns_false_with_enabled_tag(self):
++ traits = self.traits
++ tags1 = [ traits.category1_enabled_tag ]
++ tags2 = [ traits.category2_enabled_tag ]
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
++
++ def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
++ ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
++ ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_disabled_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_disabled_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_disabled_tag ], "case: last"),
++ ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_true_with_similar_tag(self):
++ traits = self.traits
++ tags = [ traits.category1_similar_tag ]
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__returns_true_with_similar_and_more(self):
++ traits = self.traits
++ test_patterns = [
++ ([ traits.category1_similar_tag, "foo" ], "case: first"),
++ ([ "foo", traits.category1_similar_tag ], "case: last"),
++ ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_without_category_tag(self):
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One tag"),
++ ([ "foo", "bar" ], "case: Two tags"),
++ ]
++ for tags, case in test_patterns:
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
++ """Tags from unknown categories, not supported by value_provider,
++ should not be excluded.
++ """
++ traits = self.traits
++ tags = [ traits.unknown_category_tag ]
++ self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
++ self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
++ self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
++
++ def test_should_exclude_with__combinations_of_2_categories(self):
++ traits = self.traits
++ test_patterns = [
++ ("case 00: 2 disabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
++ ("case 01: disabled and enabled category tags", True,
++ [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
++ ("case 10: enabled and disabled category tags", True,
++ [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
++ ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
++ # -- SPECIAL CASE: With unknown category
++ ("case 0x: disabled and unknown category tags", True,
++ [ traits.category1_disabled_tag, traits.unknown_category_tag]),
++ ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
++ [ traits.category1_enabled_tag, traits.unknown_category_tag]),
++ ]
++ for case, expected, tags in test_patterns:
++ actual_result = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(expected, actual_result,
++ "%s: tags=%s" % (case, tags))
++
++ def test_should_run_with__negates_result_of_should_exclude_with(self):
++ traits = self.traits
++ test_patterns = [
++ ([ ], "case: No tags"),
++ ([ "foo" ], "case: One non-category tag"),
++ ([ "foo", "bar" ], "case: Two non-category tags"),
++ ([ traits.category1_enabled_tag ], "case: enabled tag"),
++ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
++ ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
++ ([ traits.category1_disabled_tag ], "case: other tag"),
++ ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
++ ([ traits.category1_similar_tag ], "case: similar tag"),
++ ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
++ ]
++ for tags, case in test_patterns:
++ result1 = self.tag_matcher.should_run_with(tags)
++ result2 = self.tag_matcher.should_exclude_with(tags)
++ self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
++ self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+diff --git a/tests/unit/test_tag_matcher.py b/tests/unit/test_tag_matcher.py
+index c5d2266..a04c1d4 100644
+--- a/tests/unit/test_tag_matcher.py
++++ b/tests/unit/test_tag_matcher.py
+@@ -8,12 +8,13 @@ Unit tests for active tag-matcher (mod:`behave.tag_matcher`).
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_matcher import *
+ from mock import Mock
+ from unittest import TestCase
+ import warnings
+ import pytest
+
++from behave.tag_matcher import *
++
+
+ class Traits4ActiveTagMatcher(object):
+ TagMatcher = ActiveTagMatcher
+@@ -460,279 +461,3 @@ class TestCompositeTagMatcher(TestCase):
+ actual_true_count = self.count_tag_matcher_with_result(
+ self.ctag_matcher.tag_matchers, tags, True)
+ self.assertEqual(0, actual_true_count)
+-
+-
+-# -----------------------------------------------------------------------------
+-# PROTOTYPING CLASSES (deprecating)
+-# -----------------------------------------------------------------------------
+-class TestOnlyWithCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithCategoryTagMatcher
+-
+- def setUp(self):
+- category = "xxx"
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice")
+- self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice")
+- self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2")
+- self.other_tag = self.TagMatcher.make_category_tag(category, "other")
+- self.category = category
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- tags = [ self.enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- test_patterns = [
+- ([ self.enabled_tag, self.other_tag ], "case: first"),
+- ([ self.other_tag, self.enabled_tag ], "case: last"),
+- ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- tags = [ self.other_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- test_patterns = [
+- ([ self.other_tag, "foo" ], "case: first"),
+- ([ "foo", self.other_tag ], "case: last"),
+- ([ "foo", self.other_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- tags = [ self.similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- test_patterns = [
+- ([ self.similar_tag, "foo" ], "case: first"),
+- ([ "foo", self.similar_tag ], "case: last"),
+- ([ "foo", self.similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ self.enabled_tag ], "case: enabled tag"),
+- ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"),
+- ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ self.other_tag ], "case: other tag"),
+- ([ self.other_tag, "foo" ], "case: other and foo tag"),
+- ([ self.similar_tag ], "case: similar tag"),
+- ([ "foo", self.similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
+- def test_make_category_tag__returns_category_tag_prefix_without_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category)
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None)
+- tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None)
+- self.assertEqual("only.with_xxx=", tag1)
+- self.assertEqual("only.with_xxx=", tag2)
+- self.assertEqual("only.with_xxx=", tag3)
+- self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix))
+-
+- def test_make_category_tag__returns_category_tag_with_value(self):
+- category = "xxx"
+- tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice")
+- tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob")
+- self.assertEqual("only.with_xxx=alice", tag1)
+- self.assertEqual("only.with_xxx=bob", tag2)
+-
+- def test_make_category_tag__returns_category_tag_with_tag_prefix(self):
+- my_tag_prefix = "ONLY_WITH."
+- category = "xxx"
+- TagMatcher = OnlyWithCategoryTagMatcher
+- tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix)
+- tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix)
+- tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix)
+- self.assertEqual("ONLY_WITH.xxx=", tag0)
+- self.assertEqual("ONLY_WITH.xxx=alice", tag1)
+- self.assertEqual("ONLY_WITH.xxx=bob", tag2)
+- self.assertTrue(tag1.startswith(my_tag_prefix))
+-
+- def test_ctor__with_tag_prefix(self):
+- tag_prefix = "ONLY_WITH."
+- tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix)
+-
+- tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"]
+- actual_tags = tag_matcher.select_category_tags(tags)
+- self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags)
+-
+-
+-class Traits4OnlyWithAnyCategoryTagMatcher(object):
+- """Test data for OnlyWithAnyCategoryTagMatcher."""
+-
+- TagMatcher0 = OnlyWithCategoryTagMatcher
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice")
+- category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2")
+- category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob")
+- category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB")
+- category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2")
+- category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY")
+- unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one")
+-
+-
+-class TestOnlyWithAnyCategoryTagMatcher(TestCase):
+- TagMatcher = OnlyWithAnyCategoryTagMatcher
+- traits = Traits4OnlyWithAnyCategoryTagMatcher
+-
+- def setUp(self):
+- value_provider = {
+- "foo": "alice",
+- "bar": "BOB",
+- }
+- with warnings.catch_warnings():
+- warnings.simplefilter("ignore", DeprecationWarning)
+- self.tag_matcher = self.TagMatcher(value_provider)
+-
+- # def test_deprecating_warning_is_issued(self):
+- # value_provider = {"foo": "alice"}
+- # with warnings.catch_warnings(record=True) as recorder:
+- # warnings.simplefilter("always", DeprecationWarning)
+- # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider)
+- # self.assertEqual(len(recorder), 1)
+- # last_warning = recorder[-1]
+- # assert issubclass(last_warning.category, DeprecationWarning)
+- # assert "deprecated" in str(last_warning.message)
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag(self):
+- traits = self.traits
+- tags1 = [ traits.category1_enabled_tag ]
+- tags2 = [ traits.category2_enabled_tag ]
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2))
+-
+- def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"),
+- ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"),
+- ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_disabled_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_other_tag_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_disabled_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_disabled_tag ], "case: last"),
+- ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_tag(self):
+- traits = self.traits
+- tags = [ traits.category1_similar_tag ]
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__returns_true_with_similar_and_more(self):
+- traits = self.traits
+- test_patterns = [
+- ([ traits.category1_similar_tag, "foo" ], "case: first"),
+- ([ "foo", traits.category1_similar_tag ], "case: last"),
+- ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(True, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_without_category_tag(self):
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One tag"),
+- ([ "foo", "bar" ], "case: Two tags"),
+- ]
+- for tags, case in test_patterns:
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags),
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_exclude_with__returns_false_with_unknown_category_tag(self):
+- """Tags from unknown categories, not supported by value_provider,
+- should not be excluded.
+- """
+- traits = self.traits
+- tags = [ traits.unknown_category_tag ]
+- self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag)
+- self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN"))
+- self.assertEqual(False, self.tag_matcher.should_exclude_with(tags))
+-
+- def test_should_exclude_with__combinations_of_2_categories(self):
+- traits = self.traits
+- test_patterns = [
+- ("case 00: 2 disabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_disabled_tag]),
+- ("case 01: disabled and enabled category tags", True,
+- [ traits.category1_disabled_tag, traits.category2_enabled_tag]),
+- ("case 10: enabled and disabled category tags", True,
+- [ traits.category1_enabled_tag, traits.category2_disabled_tag]),
+- ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.category2_enabled_tag]),
+- # -- SPECIAL CASE: With unknown category
+- ("case 0x: disabled and unknown category tags", True,
+- [ traits.category1_disabled_tag, traits.unknown_category_tag]),
+- ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN
+- [ traits.category1_enabled_tag, traits.unknown_category_tag]),
+- ]
+- for case, expected, tags in test_patterns:
+- actual_result = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(expected, actual_result,
+- "%s: tags=%s" % (case, tags))
+-
+- def test_should_run_with__negates_result_of_should_exclude_with(self):
+- traits = self.traits
+- test_patterns = [
+- ([ ], "case: No tags"),
+- ([ "foo" ], "case: One non-category tag"),
+- ([ "foo", "bar" ], "case: Two non-category tags"),
+- ([ traits.category1_enabled_tag ], "case: enabled tag"),
+- ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"),
+- ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"),
+- ([ traits.category1_disabled_tag ], "case: other tag"),
+- ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"),
+- ([ traits.category1_similar_tag ], "case: similar tag"),
+- ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"),
+- ]
+- for tags, case in test_patterns:
+- result1 = self.tag_matcher.should_run_with(tags)
+- result2 = self.tag_matcher.should_exclude_with(tags)
+- self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags))
+- self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags))
+-
new file mode 100644
@@ -0,0 +1,30 @@
+From 362e778e5e57054b45099f1432abea181987d3c9 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:24:14 +0200
+Subject: [PATCH] Comment tweaks
+
+---
+ behave/runner.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/behave/runner.py b/behave/runner.py
+index f0f077a..f209cb0 100644
+--- a/behave/runner.py
++++ b/behave/runner.py
+@@ -167,10 +167,15 @@ class Context(object):
+ self._record = {}
+ self._origin = {}
+ self._mode = self.BEHAVE
++
++ # -- MODEL ENTITY REFERENCES/SUPPORT:
+ self.feature = None
+- # -- RECHECK: If needed
++ # DISABLED: self.rule = None
++ # DISABLED: self.scenario = None
+ self.text = None
+ self.table = None
++
++ # -- RUNTIME SUPPORT:
+ self.stdout_capture = None
+ self.stderr_capture = None
+ self.log_capture = None
new file mode 100644
@@ -0,0 +1,79 @@
+From d892f81e3bd3e979d3677c39c3a9efc40131b452 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sun, 9 Jun 2019 19:32:10 +0200
+Subject: [PATCH] FIX warnings related to invalid escapes in regex.
+
+---
+ behave4cmd0/command_shell_proc.py | 3 ++-
+ features/steps/behave_select_files_steps.py | 24 +++++++++++++--------
+ 2 files changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/behave4cmd0/command_shell_proc.py b/behave4cmd0/command_shell_proc.py
+index 07f1c84..03ca55a 100755
+--- a/behave4cmd0/command_shell_proc.py
++++ b/behave4cmd0/command_shell_proc.py
+@@ -251,6 +251,7 @@ class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor):
+ "No such file or directory: '(?P<path>.*)'",
+ "[Errno 2] No such file or directory:"), # IOError
+ ExceptionWithPathNormalizer(
+- '^\s*File "(?P<path>.*)", line \d+, in ',
++ # WAS: '^\s*File "(?P<path>.*)", line \d+, in ',
++ r'^\s*File "(?P<path>.*)", line \d+, in ',
+ 'File "'),
+ ]
+diff --git a/features/steps/behave_select_files_steps.py b/features/steps/behave_select_files_steps.py
+index 431674e..0d2cd2e 100644
+--- a/features/steps/behave_select_files_steps.py
++++ b/features/steps/behave_select_files_steps.py
+@@ -1,29 +1,34 @@
+ # -*- coding: utf-8 -*-
+-"""
++# DOCSTRING-NEEDS-REGEX-STRING-PREFIX: Due to example w/ wildcard pattern.
++r'''
+ Provides step definitions that test how the behave runner selects feature files.
+
+ EXAMPLE:
++
++.. code-block:: gherkin
++
+ Given behave has the following feature fileset:
+- '''
++ """
+ features/alice.feature
+ features/bob.feature
+ features/barbi.feature
+- '''
++ """
+ When behave includes feature files with "features/a.*\.feature"
+ And behave excludes feature files with "features/b.*\.feature"
+ Then the following feature files are selected:
+- '''
++ """
+ features/alice.feature
+- '''
+-"""
++ """
++'''
+
+ from __future__ import absolute_import
+-from behave import given, when, then
+-from behave.runner_util import FeatureListParser
+-from hamcrest import assert_that, equal_to
+ from copy import copy
+ import re
+ import six
++from hamcrest import assert_that, equal_to
++from behave import given, when, then
++from behave.runner_util import FeatureListParser
++
+
+ # -----------------------------------------------------------------------------
+ # STEP UTILS:
+@@ -47,6 +52,7 @@ class BasicBehaveRunner(object):
+ # -----------------------------------------------------------------------------
+ # STEP DEFINITIONS:
+ # -----------------------------------------------------------------------------
++# pylint: disable=invalid-name
+ @given('behave has the following feature fileset')
+ def step_given_behave_has_feature_fileset(context):
+ assert context.text is not None, "REQUIRE: text"
new file mode 100644
@@ -0,0 +1,73 @@
+From 11c4ac862bdce901ef19864532ef6d17a85799cd Mon Sep 17 00:00:00 2001
+From: Edvin Linge <edvin.linge@gmail.com>
+Date: Mon, 31 Jul 2017 17:05:18 +0200
+Subject: [PATCH] Steps-catalog should not break configured rerun settings
+
+---
+ behave/configuration.py | 5 ++++-
+ features/formatter.rerun.feature | 17 +++++++++++++++++
+ features/formatter.steps_catalog.feature | 13 +++++++++++++
+ 3 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/behave/configuration.py b/behave/configuration.py
+index 49b1dff..861f89f 100644
+--- a/behave/configuration.py
++++ b/behave/configuration.py
+@@ -601,7 +601,10 @@ class Configuration(object):
+ if self.steps_catalog:
+ # -- SHOW STEP-CATALOG: As step summary.
+ self.default_format = "steps.catalog"
+- self.format = ["steps.catalog"]
++ if self.format:
++ self.format.append("steps.catalog")
++ else:
++ self.format = ["steps.catalog"]
+ self.dry_run = True
+ self.summary = False
+ self.show_skipped = False
+diff --git a/features/formatter.rerun.feature b/features/formatter.rerun.feature
+index 1e645f1..b9d7e1b 100644
+--- a/features/formatter.rerun.feature
++++ b/features/formatter.rerun.feature
+@@ -294,3 +294,20 @@ Feature: Rerun Formatter
+ features/bob.feature:13
+ """
+ And note that "the second RerunFormatter overwrites the output of the first one"
++
++ @with.behave_configfile
++ Scenario: RerunFormatter with steps-catalog
++ Given a file named "behave.ini" with:
++ """
++ [behave]
++ format = rerun
++ outfiles = rerun.txt
++ """
++ When I run "behave --steps-catalog features/"
++
++ Then it should pass with:
++ """
++ Given a step passes
++ """
++ And a file named "rerun.txt" does not exist
++
+diff --git a/features/formatter.steps_catalog.feature b/features/formatter.steps_catalog.feature
+index 09591bf..936d7f4 100644
+--- a/features/formatter.steps_catalog.feature
++++ b/features/formatter.steps_catalog.feature
+@@ -98,3 +98,16 @@ Feature: Steps Catalog Formatter
+ """
+ But note that "the step definitions are ordered by step type"
+ And note that "'When I visit {person}' has no doc-string"
++
++
++ Scenario: Steps catalog formatter is used for output even when other formatter is specified
++ When I run "behave --steps-catalog -f plain features/"
++ Then it should pass with:
++ """
++ Given {person} lives in {city}
++ Setup the data where a person lives and store in the database.
++
++ :param person: Person's name (as string).
++ :param city: City where the person lives (as string).
++ """
++
new file mode 100644
@@ -0,0 +1,36 @@
+From 502382430f1bee8d0a9031fc70541488224a1029 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:07:19 +0200
+Subject: [PATCH] Add feature w/ rules and failing scenarios (enable via
+ tags=fail or tags=xfail).
+
+---
+ .../gherkin_v6/features/rule_fails.feature | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 examples/gherkin_v6/features/rule_fails.feature
+
+diff --git a/examples/gherkin_v6/features/rule_fails.feature b/examples/gherkin_v6/features/rule_fails.feature
+new file mode 100644
+index 0000000..7e3872e
+--- /dev/null
++++ b/examples/gherkin_v6/features/rule_fails.feature
+@@ -0,0 +1,19 @@
++@fail
++Feature: With Rule(s) and Failing Scenario(s)
++
++ HINT: Contains failing scenarios (by intention).
++
++ @xfail
++ Scenario: F0 -- Fails
++ When some step fails
++
++ Rule: Fails in Scenario F2
++
++ Scenario: F1
++ Given a step passes
++
++ @xfail
++ Scenario: F2 -- Fails
++ Given another step passes
++ When a step fails
++ Then another step passes
new file mode 100644
@@ -0,0 +1,27 @@
+From dcbba864209e8784ecaa5a1cf80b346b39b2a5d6 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 22:11:13 +0200
+Subject: [PATCH] examples/gherkin_v6: Tweak ScenarioOutline Examples titles.
+
+---
+ examples/gherkin_v6/features/rule_2.feature | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/examples/gherkin_v6/features/rule_2.feature b/examples/gherkin_v6/features/rule_2.feature
+index a802e19..8b8bb04 100644
+--- a/examples/gherkin_v6/features/rule_2.feature
++++ b/examples/gherkin_v6/features/rule_2.feature
+@@ -36,7 +36,12 @@ Feature: Gherkin v6 Example -- with Rules
+ Scenario Template: R3.Scenario
+ Given a person named "<name>"
+
+- Examples:
++ Examples: R3.E1
+ | name |
+ | Alice |
+ | Bob |
++
++ Examples: R3.E2
++ | name |
++ | Charly |
++ | Doro |
new file mode 100644
@@ -0,0 +1,21 @@
+From 9c9bb1e6320a6ffbac0b171d9cf98cbb311104eb Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Mon, 1 Jul 2019 08:06:43 +0200
+Subject: [PATCH] Add info on merged pull #588
+
+---
+ CHANGES.rst | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a91e22a..3d805b3 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -36,6 +36,7 @@ PARTIALLY FIXED:
+
+ FIXED:
+
++* pull #588: Steps-catalog argument should not break configured rerun settings (provided by: Lego3)
+ * issue #725: Scenario Outline description lines seem to be ignored (submitted by: nizwiz)
+ * issue #713: Background section doesn't support description (provided by: dgou)
+ * pull #657: Allow async steps with timeouts to fail when they raise exceptions (provided by: ALSchwalm)
new file mode 100644
@@ -0,0 +1,97 @@
+From 8fe1bc305aeea5d86de1b3c5dfcd2e7984fc5145 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:27:44 +0200
+Subject: [PATCH] Tweak tests, required by pytest >= 5.0. With pytest.raises
+ use str(e.value) instead of str(e) in some cases.
+
+---
+ tests/issues/test_issue0458.py | 2 +-
+ tests/unit/test_context_cleanups.py | 2 +-
+ tests/unit/test_textutil.py | 24 +++++++++++++++---------
+ 3 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/tests/issues/test_issue0458.py b/tests/issues/test_issue0458.py
+index 1853ad6..f66f6d3 100644
+--- a/tests/issues/test_issue0458.py
++++ b/tests/issues/test_issue0458.py
+@@ -48,7 +48,7 @@ def test_issue(exception_class, message):
+ raise_exception(exception_class, message)
+
+ # -- SHOULD NOT RAISE EXCEPTION HERE:
+- text = _text(e)
++ text = _text(e.value)
+ # -- DIAGNOSTICS:
+ print(u"text"+ text)
+ print(u"exception: %s" % e)
+diff --git a/tests/unit/test_context_cleanups.py b/tests/unit/test_context_cleanups.py
+index b0e8ae6..bf0ab50 100644
+--- a/tests/unit/test_context_cleanups.py
++++ b/tests/unit/test_context_cleanups.py
+@@ -153,7 +153,7 @@ class TestContextCleanup(object):
+ with pytest.raises(AssertionError) as e:
+ with scoped_context_layer(context):
+ context.add_cleanup(non_callable)
+- assert "REQUIRES: callable(cleanup_func)" in str(e)
++ assert "REQUIRES: callable(cleanup_func)" in str(e.value)
+
+ def test_on_cleanup_error__prints_error_by_default(self, capsys):
+ def bad_cleanup_func():
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index f7f642c..e05e9ad 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -214,9 +214,11 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(AssertionError) as e:
+ assert False, message
+
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
++ assert u"AssertionError" in text(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("message", [
+@@ -236,10 +238,11 @@ class TestObjectToTextConversion(object):
+ assert expected_decode_error in str(uni_error)
+ assert False, bytes_message.decode(self.ENCODING)
+
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
+ print("decode_error_occured(ascii)=%s" % decode_error_occured)
+- text2 = text(e)
+- expected = u"AssertionError: %s" % message
+- assert text2.endswith(expected)
++ text2 = text(e.value)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+@@ -251,10 +254,13 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(message)
+
+- text2 = text(e)
++ # -- FOR: pytest < 5.0
++ # expected = u"AssertionError: %s" % message
++ text2 = text(e.value)
+ expected = u"%s: %s" % (exception_class.__name__, message)
+ assert isinstance(text2, six.text_type)
+- assert text2.endswith(expected)
++ assert exception_class.__name__ in str(e)
++ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
+ @pytest.mark.parametrize("exception_class, message", [
+@@ -268,7 +274,7 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e)
++ text2 = text(e.value)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
new file mode 100644
@@ -0,0 +1,180 @@
+From dce0e40c9689a8d4fe71c9b2e8e4decd72dcf574 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Fri, 5 Jul 2019 08:45:51 +0200
+Subject: [PATCH] CLEANUP: Use pytest instead of nose to remove nose.importer
+ DeprecationWarning.
+
+---
+ tests/unit/test_importer.py | 163 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 163 insertions(+)
+ create mode 100644 tests/unit/test_importer.py
+
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+new file mode 100644
+index 0000000..f3f4e2c
+--- /dev/null
++++ b/tests/unit/test_importer.py
+@@ -0,0 +1,163 @@
++# -*- coding: utf-8 -*-
++"""
++Tests for behave.importing.
++The module provides a lazy-loading/importing mechanism.
++"""
++
++from __future__ import absolute_import
++import pytest
++from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
++from behave.formatter.base import Formatter
++import sys
++import types
++# import unittest
++
++
++class TestTheory(object):
++ """Marker for test-theory classes as syntactic sugar."""
++ pass
++
++
++class ImportModuleTheory(TestTheory):
++ """
++ Provides a test theory for importing modules.
++ """
++
++ @classmethod
++ def ensure_module_is_not_imported(cls, module_name):
++ if module_name in sys.modules:
++ del sys.modules[module_name]
++ cls.assert_module_is_not_imported(module_name)
++
++ @staticmethod
++ def assert_module_is_imported(module_name):
++ module = sys.modules.get(module_name, None)
++ assert module_name in sys.modules
++ assert module is not None
++
++ @staticmethod
++ def assert_module_is_not_imported(module_name):
++ assert module_name not in sys.modules
++
++ @staticmethod
++ def assert_module_with_name(module, name):
++ assert isinstance(module, types.ModuleType)
++ assert module.__name__ == name
++
++
++class TestLoadModule(object):
++ theory = ImportModuleTheory
++
++ def test_load_module__should_fail_for_unknown_module(self):
++ with pytest.raises(ImportError) as e:
++ load_module("__unknown_module__")
++ # OLD: assert_raises(ImportError, load_module, "__unknown_module__")
++
++ def test_load_module__should_succeed_for_already_imported_module(self):
++ module_name = "behave.importer"
++ self.theory.assert_module_is_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++ def test_load_module__should_succeed_for_existing_module(self):
++ module_name = "test._importer_candidate"
++ self.theory.ensure_module_is_not_imported(module_name)
++
++ module = load_module(module_name)
++ self.theory.assert_module_with_name(module, module_name)
++ self.theory.assert_module_is_imported(module_name)
++
++
++class TestLazyObject(object):
++
++ def test_get__should_succeed_for_known_object(self):
++ lazy = LazyObject("behave.importer", "LazyObject")
++ value = lazy.get()
++ assert value is LazyObject
++
++ lazy2 = LazyObject("behave.importer:LazyObject")
++ value2 = lazy2.get()
++ assert value2 is LazyObject
++
++ lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
++ value3 = lazy3.get()
++ assert issubclass(value3, Formatter)
++
++ def test_get__should_fail_for_unknown_module(self):
++ lazy = LazyObject("__unknown_module__", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++ def test_get__should_fail_for_unknown_object_in_module(self):
++ lazy = LazyObject("test._importer_candidate", "xxx")
++ with pytest.raises(ImportError):
++ lazy.get()
++
++
++class LazyDictTheory(TestTheory):
++
++ @staticmethod
++ def safe_getitem(data, key):
++ return dict.__getitem__(data, key)
++
++ @classmethod
++ def assert_item_is_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_lazy_object(value)
++
++ @classmethod
++ def assert_item_is_not_lazy(cls, data, key):
++ value = cls.safe_getitem(data, key)
++ cls.assert_is_not_lazy_object(value)
++
++ @staticmethod
++ def assert_is_lazy_object(obj):
++ assert isinstance(obj, LazyObject)
++
++ @staticmethod
++ def assert_is_not_lazy_object(obj):
++ assert not isinstance(obj, LazyObject)
++
++
++class TestLazyDict(object):
++ theory = LazyDictTheory
++
++ def test_unknown_item_access__should_raise_keyerror(self):
++ lazy_dict = LazyDict({"alice": 42})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(KeyError):
++ item_access("unknown")
++
++ def test_plain_item_access__should_succeed(self):
++ theory = LazyDictTheory
++ lazy_dict = LazyDict({"alice": 42})
++ theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ assert value == 42
++
++ def test_lazy_item_access__should_load_object(self):
++ ImportModuleTheory.ensure_module_is_not_imported("inspect")
++ lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++ self.theory.assert_item_is_lazy(lazy_dict, "alice")
++
++ value = lazy_dict["alice"]
++ self.theory.assert_is_not_lazy_object(value)
++ self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
++
++ def test_lazy_item_access__should_fail_with_unknown_module(self):
++ lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
++
++ def test_lazy_item_access__should_fail_with_unknown_object(self):
++ lazy_dict = LazyDict({
++ "bob": LazyObject("behave.importer", "XUnknown")
++ })
++ item_access = lambda key: lazy_dict[key]
++ with pytest.raises(ImportError):
++ item_access("bob")
new file mode 100644
@@ -0,0 +1,1815 @@
+From 65b17ccbea1a17b26e6191a1d7336088d0dd4181 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:10:52 +0200
+Subject: [PATCH] REMOVE-DEPENDENCY: nose (to avoid nose.importer warnings)
+
+Replace nose test-framework functionality w/ pytest.
+Move test/*.py => tests/unit/*.py
+---
+ MANIFEST.in | 2 -
+ conftest.py | 13 ++
+ invoke.yaml | 1 +
+ py.requirements/ci.tox.txt | 9 +
+ py.requirements/ci.travis.txt | 1 -
+ py.requirements/develop.txt | 1 -
+ py.requirements/testing.txt | 3 +-
+ pytest.ini | 7 +-
+ setup.py | 8 +-
+ tasks/test.py | 2 +-
+ test/__init__.py | 0
+ test/test_importer.py | 151 -------------
+ {test => tests/unit}/_importer_candidate.py | 0
+ tests/unit/reporter/test_summary.py | 2 -
+ .../test_tag_expression_v1_part1.py | 17 +-
+ .../test_tag_expression_v1_part2.py | 18 +-
+ {test => tests/unit}/test_ansi_escapes.py | 14 +-
+ {test => tests/unit}/test_configuration.py | 75 +++---
+ {test => tests/unit}/test_formatter.py | 15 +-
+ .../unit}/test_formatter_progress.py | 7 +
+ {test => tests/unit}/test_formatter_rerun.py | 12 +-
+ {test => tests/unit}/test_formatter_tags.py | 9 +
+ tests/unit/test_importer.py | 2 +-
+ {test => tests/unit}/test_log_capture.py | 9 +-
+ {test => tests/unit}/test_matchers.py | 24 +-
+ tests/unit/test_parser.py | 1 -
+ {test => tests/unit}/test_runner.py | 213 ++++++++++--------
+ {test => tests/unit}/test_step_registry.py | 5 +-
+ tests/unit/test_textutil.py | 12 +-
+ tox.ini | 19 +-
+ 30 files changed, 283 insertions(+), 369 deletions(-)
+ create mode 100644 py.requirements/ci.tox.txt
+ delete mode 100644 test/__init__.py
+ delete mode 100644 test/test_importer.py
+ rename {test => tests/unit}/_importer_candidate.py (100%)
+ rename {test => tests/unit}/test_ansi_escapes.py (84%)
+ rename {test => tests/unit}/test_configuration.py (72%)
+ rename {test => tests/unit}/test_formatter.py (94%)
+ rename {test => tests/unit}/test_formatter_progress.py (99%)
+ rename {test => tests/unit}/test_formatter_rerun.py (94%)
+ rename {test => tests/unit}/test_formatter_tags.py (99%)
+ rename {test => tests/unit}/test_log_capture.py (87%)
+ rename {test => tests/unit}/test_matchers.py (93%)
+ rename {test => tests/unit}/test_runner.py (82%)
+ rename {test => tests/unit}/test_step_registry.py (95%)
+
+diff --git a/MANIFEST.in b/MANIFEST.in
+index 49cb7c6..60c2601 100644
+--- a/MANIFEST.in
++++ b/MANIFEST.in
+@@ -33,5 +33,3 @@ recursive-include py.requirements *.txt
+
+ prune .tox
+ prune .venv*
+-prune paver_ext
+-exclude pavement.py
+diff --git a/conftest.py b/conftest.py
+index 7b3a883..71a3bd0 100644
+--- a/conftest.py
++++ b/conftest.py
+@@ -10,6 +10,10 @@ Add project-specific information.
+ import behave
+ import pytest
+
++
++# ---------------------------------------------------------------------------
++# PYTEST FIXTURES:
++# ---------------------------------------------------------------------------
+ @pytest.fixture(autouse=True)
+ def _annotate_environment(request):
+ """Add project-specific information to test-run environment:
+@@ -25,3 +29,12 @@ def _annotate_environment(request):
+ behave_version = behave.__version__
+ environment.append(("behave", behave_version))
+
++_pytest_version = pytest.__version__
++if _pytest_version >= "5.0":
++ # -- SUPPORTED SINCE: pytest 5.0
++ @pytest.fixture(scope="session", autouse=True)
++ def log_global_env_facts(record_testsuite_property):
++ # SEE: https://docs.pytest.org/en/latest/usage.html
++ behave_version = behave.__version__
++ record_testsuite_property("BEHAVE_VERSION", behave_version)
++
+diff --git a/invoke.yaml b/invoke.yaml
+index 4f21328..3e93cfc 100644
+--- a/invoke.yaml
++++ b/invoke.yaml
+@@ -23,6 +23,7 @@ sphinx:
+ languages:
+ - de
+ # PREPARED: - zh-CN
++
+ cleanup:
+ extra_directories:
+ - "build"
+diff --git a/py.requirements/ci.tox.txt b/py.requirements/ci.tox.txt
+new file mode 100644
+index 0000000..6b3b3ae
+--- /dev/null
++++ b/py.requirements/ci.tox.txt
+@@ -0,0 +1,9 @@
++# ============================================================================
++# BEHAVE: PYTHON PACKAGE REQUIREMENTS: ci.tox.txt
++# ============================================================================
++
++pytest >= 4.2
++pytest-html >= 1.19.0
++mock >= 2.0
++PyHamcrest >= 1.9
++path.py >= 10.1
+diff --git a/py.requirements/ci.travis.txt b/py.requirements/ci.travis.txt
+index 1cc0239..73d65f6 100644
+--- a/py.requirements/ci.travis.txt
++++ b/py.requirements/ci.travis.txt
+@@ -1,5 +1,4 @@
+ mock
+-nose
+ PyHamcrest >= 1.9
+ pytest >= 3.0
+ pytest-html >= 1.19.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index 3deedc7..d823389 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -14,7 +14,6 @@ pycmd
+ bumpversion >= 0.4.0
+
+ # -- DEVELOPMENT SUPPORT:
+-# PREPARED: nose-cov >= 1.4
+ tox >= 1.8.1
+ coverage >= 4.2
+ pytest-cov
+diff --git a/py.requirements/testing.txt b/py.requirements/testing.txt
+index 3806d39..a418739 100644
+--- a/py.requirements/testing.txt
++++ b/py.requirements/testing.txt
+@@ -4,9 +4,8 @@
+
+ # -- TESTING: Unit tests and behave self-tests.
+ # PREPARED-FUTURE: behave4cmd0, behave4cmd
+-pytest >= 3.0
++pytest >= 4.2
+ pytest-html >= 1.19.0
+-nose >= 1.3
+ mock >= 2.0
+ PyHamcrest >= 1.9
+
+diff --git a/pytest.ini b/pytest.ini
+index 17ad388..a686596 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,8 +16,8 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 2.8
+-testpaths = test tests
++minversion = 4.2
++testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+ --metadata PACKAGE_VERSION 1.2.7.dev1
+@@ -26,6 +26,7 @@ addopts = --metadata PACKAGE_UNDER_TEST behave
+ markers =
+ smoke
+ slow
++
+ # -- BACKWARD COMPATIBILITY: pytest < 2.8
+-norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
++# norecursedirs = .git .tox build dist py.requirements tmp* _WORKSPACE
+
+diff --git a/setup.py b/setup.py
+index ac7bddf..9c7560d 100644
+--- a/setup.py
++++ b/setup.py
+@@ -86,13 +86,11 @@ setup(
+ "win_unicode_console; python_version < '3.6'",
+ "colorama",
+ ],
+- test_suite="nose.collector",
+ tests_require=[
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+- "nose >= 1.3",
+ "mock >= 1.1",
+- "PyHamcrest >= 1.8",
++ "PyHamcrest >= 1.9",
+ "path.py >= 11.5.0"
+ ],
+ cmdclass = {
+@@ -105,7 +103,7 @@ setup(
+ ],
+ "develop": [
+ "coverage",
+- "pytest >= 3.0",
++ "pytest >= 4.2",
+ "pytest-html >= 1.19.0",
+ "pytest-cov",
+ "tox",
+diff --git a/tasks/test.py b/tasks/test.py
+index 06eb175..bfa2d80 100644
+--- a/tasks/test.py
++++ b/tasks/test.py
+@@ -172,7 +172,7 @@ namespace.configure({
+ },
+ },
+ "pytest": {
+- "scopes": ["test", "tests"],
++ "scopes": ["tests"],
+ "args": "",
+ "options": "", # -- NOTE: Overide in configfile "invoke.yaml"
+ },
+diff --git a/test/__init__.py b/test/__init__.py
+deleted file mode 100644
+index e69de29..0000000
+diff --git a/test/test_importer.py b/test/test_importer.py
+deleted file mode 100644
+index baca21a..0000000
+--- a/test/test_importer.py
++++ /dev/null
+@@ -1,151 +0,0 @@
+-# -*- coding: utf-8 -*-
+-"""
+-Tests for behave.importing.
+-The module provides a lazy-loading/importing mechanism.
+-"""
+-
+-from __future__ import absolute_import
+-from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name
+-from behave.formatter.base import Formatter
+-from nose.tools import eq_, assert_raises
+-import sys
+-import types
+-# import unittest
+-
+-
+-class TestTheory(object): pass
+-class ImportModuleTheory(TestTheory):
+- """
+- Provides a test theory for importing modules.
+- """
+-
+- @classmethod
+- def ensure_module_is_not_imported(cls, module_name):
+- if module_name in sys.modules:
+- del sys.modules[module_name]
+- cls.assert_module_is_not_imported(module_name)
+-
+- @staticmethod
+- def assert_module_is_imported(module_name):
+- module = sys.modules.get(module_name, None)
+- assert module_name in sys.modules
+- assert module is not None
+-
+- @staticmethod
+- def assert_module_is_not_imported(module_name):
+- assert module_name not in sys.modules
+-
+- @staticmethod
+- def assert_module_with_name(module, name):
+- assert isinstance(module, types.ModuleType)
+- eq_(module.__name__, name)
+-
+-
+-class TestLoadModule(object):
+- theory = ImportModuleTheory
+-
+- def test_load_module__should_fail_for_unknown_module(self):
+- assert_raises(ImportError, load_module, "__unknown_module__")
+-
+- def test_load_module__should_succeed_for_already_imported_module(self):
+- module_name = "behave.importer"
+- self.theory.assert_module_is_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+- def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
+- self.theory.ensure_module_is_not_imported(module_name)
+-
+- module = load_module(module_name)
+- self.theory.assert_module_with_name(module, module_name)
+- self.theory.assert_module_is_imported(module_name)
+-
+-class TestLazyObject(object):
+-
+- def test_get__should_succeed_for_known_object(self):
+- lazy = LazyObject("behave.importer", "LazyObject")
+- value = lazy.get()
+- assert value is LazyObject
+-
+- lazy2 = LazyObject("behave.importer:LazyObject")
+- value2 = lazy2.get()
+- assert value2 is LazyObject
+-
+- lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter")
+- value3 = lazy3.get()
+- assert issubclass(value3, Formatter)
+-
+- def test_get__should_fail_for_unknown_module(self):
+- lazy = LazyObject("__unknown_module__", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+- def test_get__should_fail_for_unknown_object_in_module(self):
+- lazy = LazyObject("test._importer_candidate", "xxx")
+- assert_raises(ImportError, lazy.get)
+-
+-
+-class LazyDictTheory(TestTheory):
+-
+- @staticmethod
+- def safe_getitem(data, key):
+- return dict.__getitem__(data, key)
+-
+- @classmethod
+- def assert_item_is_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_lazy_object(value)
+-
+- @classmethod
+- def assert_item_is_not_lazy(cls, data, key):
+- value = cls.safe_getitem(data, key)
+- cls.assert_is_not_lazy_object(value)
+-
+- @staticmethod
+- def assert_is_lazy_object(obj):
+- assert isinstance(obj, LazyObject)
+-
+- @staticmethod
+- def assert_is_not_lazy_object(obj):
+- assert not isinstance(obj, LazyObject)
+-
+-
+-class TestLazyDict(object):
+- theory = LazyDictTheory
+-
+- def test_unknown_item_access__should_raise_keyerror(self):
+- lazy_dict = LazyDict({"alice": 42})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(KeyError, item_access, "unknown")
+-
+- def test_plain_item_access__should_succeed(self):
+- theory = LazyDictTheory
+- lazy_dict = LazyDict({"alice": 42})
+- theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- eq_(value, 42)
+-
+- def test_lazy_item_access__should_load_object(self):
+- ImportModuleTheory.ensure_module_is_not_imported("inspect")
+- lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")})
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+- self.theory.assert_item_is_lazy(lazy_dict, "alice")
+-
+- value = lazy_dict["alice"]
+- self.theory.assert_is_not_lazy_object(value)
+- self.theory.assert_item_is_not_lazy(lazy_dict, "alice")
+-
+- def test_lazy_item_access__should_fail_with_unknown_module(self):
+- lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")})
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+-
+- def test_lazy_item_access__should_fail_with_unknown_object(self):
+- lazy_dict = LazyDict({
+- "bob": LazyObject("behave.importer", "XUnknown")
+- })
+- item_access = lambda key: lazy_dict[key]
+- assert_raises(ImportError, item_access, "bob")
+diff --git a/test/_importer_candidate.py b/tests/unit/_importer_candidate.py
+similarity index 100%
+rename from test/_importer_candidate.py
+rename to tests/unit/_importer_candidate.py
+diff --git a/tests/unit/reporter/test_summary.py b/tests/unit/reporter/test_summary.py
+index d4e85b5..6b947bd 100644
+--- a/tests/unit/reporter/test_summary.py
++++ b/tests/unit/reporter/test_summary.py
+@@ -4,8 +4,6 @@ from __future__ import absolute_import, division
+ import sys
+ import pytest
+ from mock import Mock, patch
+-# NOT-NEEDED: from nose.tools import *
+-
+ from behave.model import ScenarioOutline, Scenario
+ from behave.model_core import Status
+ from behave.reporter.summary import SummaryReporter, format_summary
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part1.py b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+index 619c710..56fb85d 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part1.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part1.py
+@@ -2,7 +2,7 @@
+
+ from __future__ import absolute_import
+ from behave.tag_expression import TagExpression
+-from nose import tools
++import pytest
+ import unittest
+
+
+@@ -476,31 +476,34 @@ class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase):
+ self.e = TagExpression(['foo:3,-bar', 'zap:5'])
+
+ def test_should_count_tags_for_positive_tags(self):
+- tools.eq_(self.e.limits, {'foo': 3, 'zap': 5})
++ assert self.e.limits == {'foo': 3, 'zap': 5}
+
+ def test_should_match_foo_zap(self):
+ assert self.e.check(['foo', 'zap'])
+
++
+ class TestTagExpressionParsing(unittest.TestCase):
+ def setUp(self):
+ self.e = TagExpression([' foo:3 , -bar ', ' zap:5 '])
+
+ def test_should_have_limits(self):
+- tools.eq_(self.e.limits, {'zap': 5, 'foo': 3})
++ assert self.e.limits == {'zap': 5, 'foo': 3}
++
+
+ class TestTagExpressionTagLimits(unittest.TestCase):
+ def test_should_be_counted_for_negative_tags(self):
+ e = TagExpression(['-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_be_counted_for_positive_tags(self):
+ e = TagExpression(['todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+ def test_should_raise_an_error_for_inconsistent_limits(self):
+- tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4'])
++ with pytest.raises(Exception):
++ _ = TagExpression(['todo:3', '-todo:4'])
+
+ def test_should_allow_duplicate_consistent_limits(self):
+ e = TagExpression(['todo:3', '-todo:3'])
+- tools.eq_(e.limits, {'todo': 3})
++ assert e.limits == {'todo': 3}
+
+diff --git a/tests/unit/tag_expression/test_tag_expression_v1_part2.py b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+index 690235b..cf619da 100644
+--- a/tests/unit/tag_expression/test_tag_expression_v1_part2.py
++++ b/tests/unit/tag_expression/test_tag_expression_v1_part2.py
+@@ -6,10 +6,11 @@ REQUIRES: Python >= 2.6, because itertools.combinations() is used.
+ """
+
+ from __future__ import absolute_import
+-from behave.tag_expression import TagExpression
+-from nose import tools
+ import itertools
+ from six.moves import range
++import pytest
++from behave.tag_expression import TagExpression
++
+
+ has_combinations = hasattr(itertools, "combinations")
+ if has_combinations:
+@@ -31,6 +32,7 @@ if has_combinations:
+ return "@" + " @".join(tags)
+ return NO_TAGS
+
++
+ TestCase = object
+ # ----------------------------------------------------------------------------
+ # TEST: all_combinations() test helper
+@@ -45,8 +47,8 @@ if has_combinations:
+ ('@one', '@two'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 4)
++ assert actual == expected
++ assert len(actual) == 4
+
+ def test_all_combinations_with_3values(self):
+ items = "@one @two @three".split()
+@@ -61,8 +63,8 @@ if has_combinations:
+ ('@one', '@two', '@three'),
+ ]
+ actual = all_combinations(items)
+- tools.eq_(actual, expected)
+- tools.eq_(len(actual), 8)
++ assert actual == expected
++ assert len(actual) == 8
+
+
+ # ----------------------------------------------------------------------------
+@@ -74,13 +76,13 @@ if has_combinations:
+ tag_combinations, expected):
+ matched = [ make_tags_line(c) for c in tag_combinations
+ if tag_expression.check(c) ]
+- tools.eq_(matched, expected)
++ assert matched == expected
+
+ def assert_tag_expression_mismatches(self, tag_expression,
+ tag_combinations, expected):
+ mismatched = [ make_tags_line(c) for c in tag_combinations
+ if not tag_expression.check(c) ]
+- tools.eq_(mismatched, expected)
++ assert mismatched == expected
+
+
+ class TestTagExpressionWith1Term(TagExpressionTestCase):
+diff --git a/test/test_ansi_escapes.py b/tests/unit/test_ansi_escapes.py
+similarity index 84%
+rename from test/test_ansi_escapes.py
+rename to tests/unit/test_ansi_escapes.py
+index 77564fd..969f3a9 100644
+--- a/test/test_ansi_escapes.py
++++ b/tests/unit/test_ansi_escapes.py
+@@ -7,7 +7,7 @@
+ # W0621 Redefining name ... from outer scope
+
+ from __future__ import absolute_import
+-from nose import tools
++import pytest
+ from behave.formatter import ansi_escapes
+ import unittest
+ from six.moves import range
+@@ -44,30 +44,30 @@ class StripEscapesTest(unittest.TestCase):
+
+ def test_should_return_same_text_without_escapes(self):
+ for text in self.TEXTS:
+- tools.eq_(text, ansi_escapes.strip_escapes(text))
++ assert text == ansi_escapes.strip_escapes(text)
+
+ def test_should_return_empty_string_for_any_ansi_escape(self):
+ # XXX-JE-CHECK-PY23: If list() is really needed.
+ for text in list(ansi_escapes.colors.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+ for text in list(ansi_escapes.escapes.values()):
+- tools.eq_("", ansi_escapes.strip_escapes(text))
++ assert "" == ansi_escapes.strip_escapes(text)
+
+
+ def test_should_strip_color_escapes_from_text(self):
+ for text in self.TEXTS:
+ colored_text = self.colorize_text(text, self.ALL_COLORS)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ for color in self.ALL_COLORS:
+ colored_text = self.colorize(text, color)
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+
+ def test_should_strip_cursor_up_escapes_from_text(self):
+ for text in self.TEXTS:
+ for cursor_up in self.CURSOR_UPS:
+ colored_text = cursor_up + text + ansi_escapes.escapes["reset"]
+- tools.eq_(text, ansi_escapes.strip_escapes(colored_text))
++ assert text == ansi_escapes.strip_escapes(colored_text)
+ self.assertNotEqual(text, colored_text)
+diff --git a/test/test_configuration.py b/tests/unit/test_configuration.py
+similarity index 72%
+rename from test/test_configuration.py
+rename to tests/unit/test_configuration.py
+index e6828e3..c96cf63 100644
+--- a/test/test_configuration.py
++++ b/tests/unit/test_configuration.py
+@@ -2,7 +2,7 @@ import os.path
+ import sys
+ import tempfile
+ import six
+-from nose.tools import *
++import pytest
+ from behave import configuration
+ from behave.configuration import Configuration, UserData
+ from unittest import TestCase
+@@ -36,6 +36,7 @@ if sys.platform.startswith("win"):
+ ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower()
+ ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT)
+
++
+ class TestConfiguration(object):
+
+ def test_read_file(self):
+@@ -46,19 +47,19 @@ class TestConfiguration(object):
+
+ # -- WINDOWS-REQUIRES: normpath
+ d = configuration.read_configuration(tn)
+- eq_(d["outfiles"], [
++ assert d["outfiles"] ==[
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"),
+ os.path.normpath(os.path.join(tndir, "relative/path2")),
+- ])
+- eq_(d["paths"], [
++ ]
++ assert d["paths"] == [
+ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"),
+ os.path.normpath(os.path.join(tndir, "relative/path4")),
+- ])
+- eq_(d["format"], ["pretty", "tag-counter"])
+- eq_(d["default_tags"], ["@foo,~@bar", "@zap"])
+- eq_(d["stdout_capture"], False)
+- ok_("bogus" not in d)
+- eq_(d["userdata"], {"foo": "bar", "answer": "42"})
++ ]
++ assert d["format"] == ["pretty", "tag-counter"]
++ assert d["default_tags"] == ["@foo,~@bar", "@zap"]
++ assert d["stdout_capture"] == False
++ assert "bogus" not in d
++ assert d["userdata"] == {"foo": "bar", "answer": "42"}
+
+ def ensure_stage_environment_is_not_set(self):
+ if "BEHAVE_STAGE" in os.environ:
+@@ -69,26 +70,26 @@ class TestConfiguration(object):
+ self.ensure_stage_environment_is_not_set()
+ assert "BEHAVE_STAGE" not in os.environ
+ config = Configuration("")
+- eq_("steps", config.steps_dir)
+- eq_("environment.py", config.environment_file)
++ assert "steps" == config.steps_dir
++ assert "environment.py" == config.environment_file
+
+ def test_settings_with_stage(self):
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+
+ def test_settings_with_stage_and_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration(["--stage=STAGE1"])
+- eq_("STAGE1_steps", config.steps_dir)
+- eq_("STAGE1_environment.py", config.environment_file)
++ assert "STAGE1_steps" == config.steps_dir
++ assert "STAGE1_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+ def test_settings_with_stage_from_envvar(self):
+ os.environ["BEHAVE_STAGE"] = "STAGE2"
+ config = Configuration("")
+- eq_("STAGE2_steps", config.steps_dir)
+- eq_("STAGE2_environment.py", config.environment_file)
++ assert "STAGE2_steps" == config.steps_dir
++ assert "STAGE2_environment.py" == config.environment_file
+ del os.environ["BEHAVE_STAGE"]
+
+
+@@ -101,33 +102,33 @@ class TestConfigurationUserData(TestCase):
+ "--define=bar=bar_value",
+ "--define", "baz=BAZ_VALUE",
+ ])
+- eq_("foo_value", config.userdata["foo"])
+- eq_("bar_value", config.userdata["bar"])
+- eq_("BAZ_VALUE", config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "bar_value" == config.userdata["bar"]
++ assert "BAZ_VALUE" == config.userdata["baz"]
+
+ def test_cmdline_defines_override_configfile(self):
+ userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42}
+ config = Configuration(
+ "-D foo=foo_value --define bar=123",
+ load_config=False, userdata=userdata_init)
+- eq_("foo_value", config.userdata["foo"])
+- eq_("123", config.userdata["bar"])
+- eq_(42, config.userdata["baz"])
++ assert "foo_value" == config.userdata["foo"]
++ assert "123" == config.userdata["bar"]
++ assert 42 == config.userdata["baz"]
+
+ def test_cmdline_defines_without_value_are_true(self):
+ config = Configuration("-D foo --define bar -Dbaz")
+- eq_("true", config.userdata["foo"])
+- eq_("true", config.userdata["bar"])
+- eq_("true", config.userdata["baz"])
+- eq_(True, config.userdata.getbool("foo"))
++ assert "true" == config.userdata["foo"]
++ assert "true" == config.userdata["bar"]
++ assert "true" == config.userdata["baz"]
++ assert True == config.userdata.getbool("foo")
+
+ def test_cmdline_defines_with_empty_value(self):
+ config = Configuration("-D foo=")
+- eq_("", config.userdata["foo"])
++ assert "" == config.userdata["foo"]
+
+ def test_cmdline_defines_with_assign_character_as_value(self):
+ config = Configuration("-D foo=bar=baz")
+- eq_("bar=baz", config.userdata["foo"])
++ assert "bar=baz" == config.userdata["foo"]
+
+ def test_cmdline_defines__with_quoted_name_value_pair(self):
+ cmdlines = [
+@@ -136,7 +137,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_cmdline_defines__with_quoted_value(self):
+ cmdlines = [
+@@ -145,7 +146,7 @@ class TestConfigurationUserData(TestCase):
+ ]
+ for cmdline in cmdlines:
+ config = Configuration(cmdline, load_config=False)
+- eq_(config.userdata, dict(person="Alice and Bob"))
++ assert config.userdata == dict(person="Alice and Bob")
+
+ def test_setup_userdata(self):
+ config = Configuration("", load_config=False)
+@@ -154,7 +155,7 @@ class TestConfigurationUserData(TestCase):
+ config.setup_userdata()
+
+ expected_data = dict(person1="Alice", person2="Charly")
+- eq_(config.userdata, expected_data)
++ assert config.userdata == expected_data
+
+ def test_update_userdata__with_cmdline_defines(self):
+ # -- NOTE: cmdline defines are reapplied.
+@@ -163,8 +164,8 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bea", person3="Charly")
+- eq_(config.userdata, expected_data)
+- eq_(config.userdata_defines, [("person2", "Bea")])
++ assert config.userdata == expected_data
++ assert config.userdata_defines == [("person2", "Bea")]
+
+ def test_update_userdata__without_cmdline_defines(self):
+ config = Configuration("", load_config=False)
+@@ -172,5 +173,5 @@ class TestConfigurationUserData(TestCase):
+ config.update_userdata(dict(person1="Alice", person2="Bob"))
+
+ expected_data = dict(person1="Alice", person2="Bob", person3="Charly")
+- eq_(config.userdata, expected_data)
+- self.assertFalse(config.userdata_defines)
++ assert config.userdata == expected_data
++ assert config.userdata_defines is None
+diff --git a/test/test_formatter.py b/tests/unit/test_formatter.py
+similarity index 94%
+rename from test/test_formatter.py
+rename to tests/unit/test_formatter.py
+index 42e5f0d..c1a0945 100644
+--- a/test/test_formatter.py
++++ b/tests/unit/test_formatter.py
+@@ -6,9 +6,8 @@ import sys
+ import tempfile
+ import unittest
+ import six
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave.formatter._registry import make_formatters
+ from behave.formatter import pretty
+ from behave.formatter.base import StreamOpener
+@@ -35,7 +34,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ platform = sys.platform
+ sys.platform = "windows"
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ sys.platform = platform
+
+@@ -46,7 +45,7 @@ class TestGetTerminalSize(unittest.TestCase):
+ except ImportError:
+ pass
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+
+ def test_exception_in_ioctl(self):
+ try:
+@@ -59,7 +58,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.side_effect = raiser
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_happy_path(self):
+@@ -70,7 +69,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5)
+
+- eq_(pretty.get_terminal_size(), (23, 17))
++ assert pretty.get_terminal_size() == (23, 17)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+ def test_zero_size_fallback(self):
+@@ -81,7 +80,7 @@ class TestGetTerminalSize(unittest.TestCase):
+
+ self.ioctl.return_value = self.zero_struct
+
+- eq_(pretty.get_terminal_size(), (80, 24))
++ assert pretty.get_terminal_size() == (80, 24)
+ self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct)
+
+
+@@ -204,7 +203,7 @@ class TestTagsCount(FormatterTests):
+ p.feature(f)
+ p.scenario(s)
+
+- eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]})
++ assert p.tag_counts == {"ham": [f, s], "spam": [f], "foo": [s]}
+
+
+ class MultipleFormattersTests(FormatterTests):
+diff --git a/test/test_formatter_progress.py b/tests/unit/test_formatter_progress.py
+similarity index 99%
+rename from test/test_formatter_progress.py
+rename to tests/unit/test_formatter_progress.py
+index 29c8e68..19cdf64 100644
+--- a/test/test_formatter_progress.py
++++ b/tests/unit/test_formatter_progress.py
+@@ -9,6 +9,7 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ class TestScenarioProgressFormatter(FormatterTest):
+ formatter_name = "progress"
+
+@@ -20,20 +21,26 @@ class TestStepProgressFormatter(FormatterTest):
+ class TestPrettyAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress']
+
++
+ class TestPlainAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress']
+
++
+ class TestJSONAndScenarioProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress']
+
++
+ class TestPrettyAndStepProgress(MultipleFormattersTest):
+ formatters = ['pretty', 'progress2']
+
++
+ class TestPlainAndStepProgress(MultipleFormattersTest):
+ formatters = ['plain', 'progress2']
+
++
+ class TestJSONAndStepProgress(MultipleFormattersTest):
+ formatters = ['json', 'progress2']
+
++
+ class TestScenarioProgressAndStepProgress(MultipleFormattersTest):
+ formatters = ['progress', 'progress2']
+diff --git a/test/test_formatter_rerun.py b/tests/unit/test_formatter_rerun.py
+similarity index 94%
+rename from test/test_formatter_rerun.py
+rename to tests/unit/test_formatter_rerun.py
+index 6357f92..154588f 100644
+--- a/test/test_formatter_rerun.py
++++ b/tests/unit/test_formatter_rerun.py
+@@ -8,7 +8,7 @@ from __future__ import absolute_import
+ from behave.model_core import Status
+ from .test_formatter import FormatterTests as FormatterTest, _tf
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+-from nose.tools import *
++
+
+ class TestRerunFormatter(FormatterTest):
+ formatter_name = "rerun"
+@@ -26,7 +26,7 @@ class TestRerunFormatter(FormatterTest):
+ p.scenario(scenario)
+ assert scenario.status == Status.passed
+ p.eof()
+- eq_([], p.failed_scenarios)
++ assert [] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -49,7 +49,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[0].status == Status.passed
+ assert scenarios[1].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario ], p.failed_scenarios)
++ assert [ failing_scenario ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -76,7 +76,7 @@ class TestRerunFormatter(FormatterTest):
+ assert scenarios[1].status == Status.passed
+ assert scenarios[2].status == Status.failed
+ p.eof()
+- eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios)
++ assert [ failing_scenario1, failing_scenario2 ] == p.failed_scenarios
+ # -- EMIT REPORT:
+ p.close()
+
+@@ -84,14 +84,18 @@ class TestRerunFormatter(FormatterTest):
+ class TestRerunAndPrettyFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "pretty"]
+
++
+ class TestRerunAndPlainFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "plain"]
+
++
+ class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress"]
+
++
+ class TestRerunAndStepProgressFormatters(MultipleFormattersTest):
+ formatters = ["rerun", "progress2"]
+
++
+ class TestRerunAndJsonFormatter(MultipleFormattersTest):
+ formatters = ["rerun", "json"]
+diff --git a/test/test_formatter_tags.py b/tests/unit/test_formatter_tags.py
+similarity index 99%
+rename from test/test_formatter_tags.py
+rename to tests/unit/test_formatter_tags.py
+index 5125d51..9f95374 100644
+--- a/test/test_formatter_tags.py
++++ b/tests/unit/test_formatter_tags.py
+@@ -9,12 +9,14 @@ from __future__ import absolute_import
+ from .test_formatter import FormatterTests as FormatterTest
+ from .test_formatter import MultipleFormattersTests as MultipleFormattersTest
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagCountFormatter
+ # -----------------------------------------------------------------------------
+ class TestTagsCountFormatter(FormatterTest):
+ formatter_name = "tags"
+
++
+ # -----------------------------------------------------------------------------
+ # FORMATTER TESTS: With TagLocationFormatter
+ # -----------------------------------------------------------------------------
+@@ -28,12 +30,15 @@ class TestTagsLocationFormatter(FormatterTest):
+ class TestPrettyAndTagsCount(MultipleFormattersTest):
+ formatters = ["pretty", "tags"]
+
++
+ class TestPlainAndTagsCount(MultipleFormattersTest):
+ formatters = ["plain", "tags"]
+
++
+ class TestJSONAndTagsCount(MultipleFormattersTest):
+ formatters = ["json", "tags"]
+
++
+ class TestRerunAndTagsCount(MultipleFormattersTest):
+ formatters = ["rerun", "tags"]
+
+@@ -44,14 +49,18 @@ class TestRerunAndTagsCount(MultipleFormattersTest):
+ class TestPrettyAndTagsLocation(MultipleFormattersTest):
+ formatters = ["pretty", "tags.location"]
+
++
+ class TestPlainAndTagsLocation(MultipleFormattersTest):
+ formatters = ["plain", "tags.location"]
+
++
+ class TestJSONAndTagsLocation(MultipleFormattersTest):
+ formatters = ["json", "tags.location"]
+
++
+ class TestRerunAndTagsLocation(MultipleFormattersTest):
+ formatters = ["rerun", "tags.location"]
+
++
+ class TestTagsCountAndTagsLocation(MultipleFormattersTest):
+ formatters = ["tags", "tags.location"]
+diff --git a/tests/unit/test_importer.py b/tests/unit/test_importer.py
+index f3f4e2c..055b9fb 100644
+--- a/tests/unit/test_importer.py
++++ b/tests/unit/test_importer.py
+@@ -62,7 +62,7 @@ class TestLoadModule(object):
+ self.theory.assert_module_is_imported(module_name)
+
+ def test_load_module__should_succeed_for_existing_module(self):
+- module_name = "test._importer_candidate"
++ module_name = "tests.unit._importer_candidate"
+ self.theory.ensure_module_is_not_imported(module_name)
+
+ module = load_module(module_name)
+diff --git a/test/test_log_capture.py b/tests/unit/test_log_capture.py
+similarity index 87%
+rename from test/test_log_capture.py
+rename to tests/unit/test_log_capture.py
+index bfdbed7..bf1874c 100644
+--- a/test/test_log_capture.py
++++ b/tests/unit/test_log_capture.py
+@@ -1,11 +1,10 @@
+ from __future__ import absolute_import, with_statement
+-
+-from nose.tools import *
++import pytest
+ from mock import patch
+-
+ from behave.log_capture import LoggingCapture
+ from six.moves import range
+
++
+ class TestLogCapture(object):
+ def test_get_value_returns_all_log_records(self):
+ class FakeConfig(object):
+@@ -23,7 +22,7 @@ class TestLogCapture(object):
+ format.return_value = 'foo'
+ expected = '\n'.join(['foo'] * len(fake_records))
+
+- eq_(handler.getvalue(), expected)
++ assert handler.getvalue() == expected
+
+ calls = [args[0][0] for args in format.call_args_list]
+- eq_(calls, fake_records)
++ assert calls == fake_records
+diff --git a/test/test_matchers.py b/tests/unit/test_matchers.py
+similarity index 93%
+rename from test/test_matchers.py
+rename to tests/unit/test_matchers.py
+index bfe04fc..815581c 100644
+--- a/test/test_matchers.py
++++ b/tests/unit/test_matchers.py
+@@ -1,7 +1,7 @@
+ # -*- coding: UTF-8 -*-
+ from __future__ import absolute_import, with_statement
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+ import parse
+ from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
+ SimplifiedRegexMatcher, CucumberRegexMatcher
+@@ -80,7 +80,7 @@ class TestParseMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+ def test_named_arguments(self):
+ text = "has a {string}, an {integer:d} and a {decimal:f}"
+@@ -89,11 +89,11 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context,), {
++ assert self.recorded_args, ((context,) == {
+ 'string': 'foo',
+ 'integer': 11,
+ 'decimal': 3.14159
+- }))
++ })
+
+ def test_positional_arguments(self):
+ text = "has a {}, an {:d} and a {:f}"
+@@ -102,7 +102,7 @@ class TestParseMatcher(object):
+
+ m = matcher.match("has a foo, an 11 and a 3.14159")
+ m.run(context)
+- eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {}))
++ assert self.recorded_args == ((context, 'foo', 11, 3.14159), {})
+
+ class TestRegexMatcher(object):
+ # pylint: disable=invalid-name, no-self-use
+@@ -151,7 +151,7 @@ class TestRegexMatcher(object):
+ assert m.func is func
+ args = m.arguments
+ have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
+- eq_(have, expected)
++ assert have == expected
+
+
+
+@@ -179,17 +179,17 @@ class TestSimplifiedRegexMatcher(TestRegexMatcher):
+ assert isinstance(matched1, Match)
+ assert isinstance(matched2, Match)
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_end_marker(self):
+- SimplifiedRegexMatcher(None, "I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "I do something$")
+
+- @raises(AssertionError)
+ def test_step_should_not_use_regex_begin_and_end_marker(self):
+- SimplifiedRegexMatcher(None, "^I do something$")
++ with pytest.raises(AssertionError):
++ SimplifiedRegexMatcher(None, "^I do something$")
+
+
+ class TestCucumberRegexMatcher(TestRegexMatcher):
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index 5603a4b..ecbb1bf 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -7,7 +7,6 @@ Unit tests for Gherkin parser: :mod:`behave.parser`.
+ from __future__ import absolute_import, print_function
+ import pytest
+ from behave import i18n, model, parser
+-# NOT-NEEDED: from nose.tools import *
+
+
+ # ---------------------------------------------------------------------------
+diff --git a/test/test_runner.py b/tests/unit/test_runner.py
+similarity index 82%
+rename from test/test_runner.py
+rename to tests/unit/test_runner.py
+index 6647283..030dffa 100644
+--- a/test/test_runner.py
++++ b/tests/unit/test_runner.py
+@@ -11,10 +11,8 @@ import tempfile
+ import unittest
+ import six
+ from six import StringIO
+-
++import pytest
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
+-
+ from behave import runner_util
+ from behave.model import Table
+ from behave.step_registry import StepRegistry
+@@ -39,31 +37,31 @@ class TestContext(unittest.TestCase):
+ def test_user_mode_shall_restore_behave_mode(self):
+ # -- CASE: No exception is raised.
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_assert_fails(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ assert False, "XFAIL"
+ except AssertionError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self):
+ initial_mode = runner.Context.BEHAVE
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -71,9 +69,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.BEHAVE
+ self.context._mode = initial_mode
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_user_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -82,10 +80,10 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context.use_with_user_mode():
+- eq_(self.context._mode, runner.Context.USER)
++ assert self.context._mode == runner.Context.USER
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode(self):
+ # -- CASE: No exception is raised.
+@@ -93,9 +91,9 @@ class TestContext(unittest.TestCase):
+ initial_mode = runner.Context.USER
+ self.context._mode = initial_mode
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ self.context.thing = "stuff"
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self):
+ # -- CASE: Exception is raised.
+@@ -104,22 +102,22 @@ class TestContext(unittest.TestCase):
+ self.context._mode = initial_mode
+ try:
+ with self.context._use_with_behave_mode():
+- eq_(self.context._mode, runner.Context.BEHAVE)
++ assert self.context._mode == runner.Context.BEHAVE
+ raise RuntimeError("XFAIL")
+ except RuntimeError:
+- eq_(self.context._mode, initial_mode)
++ assert self.context._mode == initial_mode
+
+ def test_context_contains(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+
+ def test_attribute_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+
+ def test_attribute_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context._push()
+@@ -130,16 +128,16 @@ class TestContext(unittest.TestCase):
+ def test_attributes_set_at_upper_level_visible_at_lower_level(self):
+ self.context.thing = "stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ self.context.other_thing = "more stuff"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ self.context.third_thing = "wombats"
+ self.context._push()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ def test_attributes_set_at_lower_level_not_visible_at_upper_level(self):
+ self.context.thing = "stuff"
+@@ -149,17 +147,17 @@ class TestContext(unittest.TestCase):
+
+ self.context._push()
+ self.context.third_thing = "wombats"
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
+- eq_(self.context.third_thing, "wombats")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
++ assert self.context.third_thing == "wombats"
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
+- eq_(self.context.other_thing, "more stuff")
++ assert self.context.thing == "stuff"
++ assert self.context.other_thing == "more stuff"
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+ self.context._pop()
+- eq_(self.context.thing, "stuff")
++ assert self.context.thing == "stuff"
+ assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing
+ assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing
+
+@@ -270,21 +268,22 @@ class TestContext(unittest.TestCase):
+ assert filename in info, "%r not in %r" % (filename, info)
+
+ def test_context_deletable(self):
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ del self.context.thing
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+
+- @raises(AttributeError)
++ # OLD: @raises(AttributeError)
+ def test_context_deletable_raises(self):
+ # pylint: disable=protected-access
+- eq_("thing" in self.context, False)
++ assert "thing" not in self.context
+ self.context.thing = "stuff"
+- eq_("thing" in self.context, True)
++ assert "thing" in self.context
+ self.context._push()
+- eq_("thing" in self.context, True)
+- del self.context.thing
++ assert "thing" in self.context
++ with pytest.raises(AttributeError):
++ del self.context.thing
+
+
+ class ExampleSteps(object):
+@@ -362,7 +361,7 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+- eq_(result, True)
++ assert result is True
+
+ def test_execute_steps_with_failing_step(self):
+ doc = u"""
+@@ -374,7 +373,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("FAILED SUB-STEP: When a step fails" in _text(e))
++ assert "FAILED SUB-STEP: When a step fails" in _text(e)
+
+ def test_execute_steps_with_undefined_step(self):
+ doc = u"""
+@@ -386,7 +385,7 @@ Then a step passes
+ try:
+ result = self.context.execute_steps(doc)
+ except AssertionError as e:
+- ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e))
++ assert "UNDEFINED SUB-STEP: When a step is undefined" in _text(e)
+
+ def test_execute_steps_with_text(self):
+ doc = u'''
+@@ -401,8 +400,8 @@ Then a step passes
+ with patch("behave.step_registry.registry", self.step_registry):
+ result = self.context.execute_steps(doc)
+ expected_text = "Lorem ipsum\nIpsum lorem"
+- eq_(result, True)
+- eq_(expected_text, ExampleSteps.text)
++ assert result is True
++ assert expected_text == ExampleSteps.text
+
+ def test_execute_steps_with_table(self):
+ doc = u"""
+@@ -419,8 +418,8 @@ Then a step passes
+ [u"Alice", u"12"],
+ [u"Bob", u"23"],
+ ])
+- eq_(result, True)
+- eq_(expected_table, ExampleSteps.table)
++ assert result is True
++ assert expected_table == ExampleSteps.table
+
+ def test_context_table_is_restored_after_execute_steps_without_table(self):
+ doc = u"""
+@@ -431,7 +430,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_table_is_restored_after_execute_steps_with_table(self):
+ doc = u"""
+@@ -445,7 +444,7 @@ Then a step passes
+ original_table = "<ORIGINAL_TABLE>"
+ self.context.table = original_table
+ self.context.execute_steps(doc)
+- eq_(self.context.table, original_table)
++ assert self.context.table == original_table
+
+ def test_context_text_is_restored_after_execute_steps_without_text(self):
+ doc = u"""
+@@ -456,7 +455,7 @@ Then a step passes
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+ def test_context_text_is_restored_after_execute_steps_with_text(self):
+ doc = u'''
+@@ -471,10 +470,10 @@ When a step with text:
+ original_text = "<ORIGINAL_TEXT>"
+ self.context.text = original_text
+ self.context.execute_steps(doc)
+- eq_(self.context.text, original_text)
++ assert self.context.text == original_text
+
+
+- @raises(ValueError)
++ # OLD: @raises(ValueError)
+ def test_execute_steps_should_fail_when_called_without_feature(self):
+ doc = u"""
+ Given a passes
+@@ -482,7 +481,8 @@ Then a step passes
+ """.lstrip()
+ with patch("behave.step_registry.registry", self.step_registry):
+ self.context.feature = None
+- self.context.execute_steps(doc)
++ with pytest.raises(ValueError):
++ self.context.execute_steps(doc)
+
+
+ def create_mock_config():
+@@ -588,11 +588,11 @@ class TestRunner(object):
+ r.setup_capture()
+ r.start_capture()
+
+- eq_(sys.stdout, r.capture_controller.stdout_capture)
++ assert sys.stdout == r.capture_controller.stdout_capture
+
+ r.stop_capture()
+
+- eq_(sys.stdout, new_stdout)
++ assert sys.stdout == new_stdout
+
+ sys.stdout = old_stdout
+
+@@ -605,11 +605,11 @@ class TestRunner(object):
+
+ r.start_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ r.stop_capture()
+
+- eq_(sys.stdout, old_stdout)
++ assert sys.stdout == old_stdout
+
+ def test_teardown_capture_removes_log_tap(self):
+ r = runner.Runner(Mock())
+@@ -633,7 +633,7 @@ class TestRunner(object):
+ # pylint: disable=too-many-format-args
+ assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l)
+ # pylint: enable=too-many-format-args
+- eq_(l["spam"], fn)
++ assert l["spam"] == fn
+
+ def test_run_returns_true_if_everything_passed(self):
+ r = runner.Runner(Mock())
+@@ -694,11 +694,11 @@ class TestRunWithPaths(unittest.TestCase):
+ self.runner.context = Mock()
+ self.runner.run_with_paths()
+
+- eq_(self.run_hook.call_args_list, [
++ assert self.run_hook.call_args_list == [
+ ((), {}),
+ (("before_all", self.runner.context), {}),
+ (("after_all", self.runner.context), {}),
+- ])
++ ]
+
+ @patch("behave.parser.parse_file")
+ @patch("os.path.abspath")
+@@ -724,8 +724,8 @@ class TestRunWithPaths(unittest.TestCase):
+
+ expected_parse_file_args = \
+ [((x.upper(),), {"language": "fritz"}) for x in feature_locations]
+- eq_(parse_file.call_args_list, expected_parse_file_args)
+- eq_(self.runner.features, [feature] * 3)
++ assert parse_file.call_args_list == expected_parse_file_args
++ assert self.runner.features == [feature] * 3
+
+
+ class FsMock(object):
+@@ -829,9 +829,12 @@ class TestFeatureDirectory(object):
+
+ # will look for a "features" directory and not find one
+ with patch("os.path", fs):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ # ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+
+ def test_default_path_no_features(self):
+ config = create_mock_config()
+@@ -842,7 +845,9 @@ class TestFeatureDirectory(object):
+ fs = FsMock("features/steps/")
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_default_path(self):
+ config = create_mock_config()
+@@ -857,7 +862,7 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_feature_file(self):
+ config = create_mock_config()
+@@ -872,10 +877,12 @@ class TestFeatureDirectory(object):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+ r.setup_paths()
+- ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls)
+
+- eq_(r.base_dir, fs.base)
++ assert r.base_dir == fs.base
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -888,7 +895,9 @@ class TestFeatureDirectory(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -903,9 +912,10 @@ class TestFeatureDirectory(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+- eq_(r.base_dir, os.path.join(fs.base, "spam"))
++ assert r.base_dir == os.path.join(fs.base, "spam")
+
+ def test_supplied_feature_directory_no_steps(self):
+ config = create_mock_config()
+@@ -917,9 +927,12 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls
++ # OLD: ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls)
+
+ def test_supplied_feature_directory_missing(self):
+ config = create_mock_config()
+@@ -931,7 +944,9 @@ class TestFeatureDirectory(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+
+ class TestFeatureDirectoryLayout2(object):
+@@ -955,7 +970,7 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- eq_(r.base_dir, os.path.abspath("features"))
++ assert r.base_dir == os.path.abspath("features")
+
+ def test_supplied_root_directory(self):
+ config = create_mock_config()
+@@ -975,8 +990,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+ def test_supplied_root_directory_no_steps(self):
+ config = create_mock_config()
+@@ -993,10 +1009,13 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, None)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir is None
+
+
+ def test_supplied_feature_file(self):
+@@ -1018,9 +1037,11 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
+- eq_(r.base_dir, fs.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert ("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls
++ assert r.base_dir == fs.join(fs.base, "features")
+
+ def test_supplied_feature_file_no_steps(self):
+ config = create_mock_config()
+@@ -1037,7 +1058,9 @@ class TestFeatureDirectoryLayout2(object):
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+ with r.path_manager:
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD assert_raises(ConfigError, r.setup_paths)
+
+ def test_supplied_feature_directory(self):
+ config = create_mock_config()
+@@ -1057,8 +1080,9 @@ class TestFeatureDirectoryLayout2(object):
+ with r.path_manager:
+ r.setup_paths()
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
+- eq_(r.base_dir, os.path.join(fs.base, "features"))
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
++ assert r.base_dir == os.path.join(fs.base, "features")
+
+
+ def test_supplied_feature_directory_no_steps(self):
+@@ -1075,6 +1099,9 @@ class TestFeatureDirectoryLayout2(object):
+
+ with patch("os.path", fs):
+ with patch("os.walk", fs.walk):
+- assert_raises(ConfigError, r.setup_paths)
++ with pytest.raises(ConfigError):
++ r.setup_paths()
++ # OLD: assert_raises(ConfigError, r.setup_paths)
+
+- ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ # OLD: ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls)
++ assert ("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls
+diff --git a/test/test_step_registry.py b/tests/unit/test_step_registry.py
+similarity index 95%
+rename from test/test_step_registry.py
+rename to tests/unit/test_step_registry.py
+index f5b5a4d..6f85729 100644
+--- a/test/test_step_registry.py
++++ b/tests/unit/test_step_registry.py
+@@ -2,7 +2,6 @@
+ # pylint: disable=unused-wildcard-import
+ from __future__ import absolute_import, with_statement
+ from mock import Mock, patch
+-from nose.tools import * # pylint: disable=wildcard-import
+ from six.moves import range # pylint: disable=redefined-builtin
+ from behave import step_registry
+
+@@ -26,7 +25,7 @@ class TestStepRegistry(object):
+
+ registry.add_step_definition(step_type.upper(), pattern, func)
+ get_matcher.assert_called_with(func, pattern)
+- eq_(l, [magic_object])
++ assert l == [magic_object]
+
+ def test_find_match_with_specific_step_type_also_searches_generic(self):
+ registry = step_registry.StepRegistry()
+@@ -80,7 +79,7 @@ class TestStepRegistry(object):
+
+ assert registry.find_match(step) is magic_object
+ for mock in step_defs[6:]:
+- eq_(mock.match.call_count, 0)
++ assert mock.match.call_count == 0
+
+ # pylint: disable=line-too-long
+ @patch.object(step_registry.registry, 'add_step_definition')
+diff --git a/tests/unit/test_textutil.py b/tests/unit/test_textutil.py
+index e05e9ad..3ffab3c 100644
+--- a/tests/unit/test_textutil.py
++++ b/tests/unit/test_textutil.py
+@@ -9,6 +9,10 @@ import pytest
+ import codecs
+ import six
+
++
++pytest_version = pytest.__version__
++
++
+ # -----------------------------------------------------------------------------
+ # TEST SUPPORT:
+ # -----------------------------------------------------------------------------
+@@ -263,6 +267,7 @@ class TestObjectToTextConversion(object):
+ assert message in text2, "OOPS: text=%r" % text2
+
+ @requires_python2
++ @pytest.mark.skipif(pytest_version >= "5.0", reason="Fails with pytest 5.0")
+ @pytest.mark.parametrize("exception_class, message", [
+ (AssertionError, u"Ärgernis"),
+ (RuntimeError, u"Übermütig"),
+@@ -274,10 +279,15 @@ class TestObjectToTextConversion(object):
+ with pytest.raises(exception_class) as e:
+ raise exception_class(bytes_message)
+
+- text2 = text(e.value)
++ # -- REQUIRES: pytest < 5.0
++ # HINT: pytest >= 5.0 needs: text(e.value)
++ # NEW: text2 = text(e.value) # Causes problems w/ decoding and comparison.
++ assert isinstance(e.value, Exception)
++ text2 = text(e)
+ unicode_message = bytes_message.decode(self.ENCODING)
+ expected = u"%s: %s" % (exception_class.__name__, unicode_message)
+ assert isinstance(text2, six.text_type)
++ assert unicode_message in text2
+ assert text2.endswith(expected)
+ # -- DIAGNOSTICS:
+ print(u"text2: "+ text2)
+diff --git a/tox.ini b/tox.ini
+index 392bb39..d2fbce2 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -67,17 +67,11 @@ deps=
+ install_command = pip install -U {opts} {packages}
+ changedir = {toxinidir}
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+-deps=
+- pytest>=3.0
+- pytest-html >= 1.19.0
+- nose>=1.3
+- mock>=2.0
+- PyHamcrest>=1.9
+- path.py >= 10.1
++deps= -r {toxinidir}/py.requirements/ci.tox.txt
+ setenv =
+ PYTHONPATH = {toxinidir}
+
+@@ -97,13 +91,12 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -119,18 +112,16 @@ changedir = {envdir}
+ commands=
+ behave --version
+ {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 .
+- {toxinidir}/bin/toxcmd.py copytree ../../test .
+ {toxinidir}/bin/toxcmd.py copytree ../../tests .
+ {toxinidir}/bin/toxcmd.py copytree ../../features .
+ {toxinidir}/bin/toxcmd.py copytree ../../tools .
+ {toxinidir}/bin/toxcmd.py copytree ../../issue.features .
+ {toxinidir}/bin/toxcmd.py copy ../../behave.ini .
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0
+- {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features
+ {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
+@@ -148,7 +139,7 @@ setenv =
+ [testenv:jy27]
+ basepython= jython
+ commands=
+- pytest {posargs:test tests}
++ pytest {posargs:tests}
+ behave --format=progress {posargs:features}
+ behave --format=progress {posargs:tools/test-features}
+ behave --format=progress {posargs:issue.features}
new file mode 100644
@@ -0,0 +1,22 @@
+From 2c886d201c19085ab52065d47e4f86a82a77f991 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:17:54 +0200
+Subject: [PATCH] FIX: Remove test/ from pytest run.
+
+---
+ .travis.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.travis.yml b/.travis.yml
+index fbc3520..c6027e0 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -35,7 +35,7 @@ install:
+
+ script:
+ - python --version
+- - pytest test tests
++ - pytest tests
+ - behave -f progress --junit features/
+ - behave -f progress --junit tools/test-features/
+ - behave -f progress --junit issue.features/
new file mode 100644
@@ -0,0 +1,652 @@
+From 57a9cc0e1c99b0ed2ec5dbfd3fcfce456cfe13c8 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 09:50:46 +0200
+Subject: [PATCH] Select-by-location: Add support for "Scenario container"
+ (Feature, Rule, ScenarioOutline) (related to: #391)
+
+---
+ CHANGES.rst | 2 +-
+ behave/model.py | 49 +++--
+ behave/runner_util.py | 186 +++++++++++++++++-
+ ....select_scenarios_by_file_location.feature | 27 ++-
+ pytest.ini | 2 +-
+ tests/unit/test_runner_util.py | 175 ++++++++++++++++
+ 6 files changed, 416 insertions(+), 25 deletions(-)
+ create mode 100644 tests/unit/test_runner_util.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 3d805b3..01bd1bd 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -28,7 +28,7 @@ ENHANCEMENTS:
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+-
++* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+
+ PARTIALLY FIXED:
+
+diff --git a/behave/model.py b/behave/model.py
+index 3084850..7fc534a 100644
+--- a/behave/model.py
++++ b/behave/model.py
+@@ -55,11 +55,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ .. attribute:: keyword
+
+ This is the keyword as seen in the *feature file*. In English this will
+- be "Feature".
++ be "Feature" or "Rule".
+
+ .. attribute:: name
+
+- The name of the feature (the text after "Feature".)
++ The name (or title) of the model entity (the text after the keyword.)
+
+ .. attribute:: description
+
+@@ -93,12 +93,16 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+
+ Status.untested
+ The feature was has not been completely tested yet.
++
+ Status.skipped
+- One or more steps of this feature was passed over during testing.
++ The execution of this model entity (feature or rule)
++ should be / was skipped (excluded from the test run).
++
+ Status.passed
+- The feature was tested successfully.
++ The model entity (feature or rule) was tested successfully.
++
+ Status.failed
+- One or more steps of this feature failed.
++ One or more run items of this model entity failed.
+
+ .. versionchanged:: 1.2.6
+ Use Status enum class (was: string).
+@@ -147,6 +151,11 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ for run_item in self.run_items:
+ run_item.reset()
+
++ def _setup_context_for_run(self, context):
++ """Setup/Init runner context for run."""
++ # -- OVERRIDDEN: By derived classes.
++ pass
++
+ def __iter__(self):
+ return iter(self.run_items)
+
+@@ -204,7 +213,7 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ # feature_duration = self.run_endtime - self.run_starttime
+ return feature_duration
+
+- def walk_scenarios(self, with_outlines=False):
++ def walk_scenarios(self, with_outlines=False, with_rules=False):
+ """Provides a flat list of all scenarios of this ScenarioContainer.
+ A ScenarioOutline element adds its scenarios to this list.
+ But the ScenarioOutline element itself is only added when specified.
+@@ -215,20 +224,20 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ :param with_outlines: If ScenarioOutline items should be added, too.
+ :return: List of all scenarios of this feature.
+ """
+- # TODO: Better use self.run_items
+ all_scenarios = []
+- # for scenario in self.scenarios:
+ for run_item in self.run_items:
+ if isinstance(run_item, Rule):
+ rule = run_item
++ if with_rules:
++ all_scenarios.append(rule)
+ all_scenarios.extend(rule.walk_scenarios(with_outlines=with_outlines))
+- if isinstance(run_item, ScenarioOutline):
++ elif isinstance(run_item, ScenarioOutline):
+ scenario_outline = run_item
+ if with_outlines:
+ all_scenarios.append(scenario_outline)
+ all_scenarios.extend(scenario_outline.scenarios)
+ else:
+- assert isinstance(run_item, Scenario)
++ assert isinstance(run_item, Scenario), "OOPS: %r" % run_item
+ all_scenarios.append(run_item)
+ return all_scenarios
+
+@@ -274,9 +283,9 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ assert self.status == Status.skipped or self.hook_failed
+
+ def skip(self, reason=None, require_not_executed=False):
+- """Skip executing this feature or the remaining parts of it.
+- Note that this feature may be already partly executed
+- when this function is called.
++ """Skip executing this model entity or the remaining parts of it.
++ Note that this model entity (feature or rule) may be already partly
++ executed when this function is called.
+
+ :param reason: Optional reason why feature should be skipped (as string).
+ :param require_not_executed: Optional, requires that feature is not
+@@ -314,8 +323,8 @@ class ScenarioContainer(TagAndStatusStatement, Replayable):
+ hook_after_entity = "after_{0}".format(entity_name)
+
+ runner.context._push(layer_name=entity_name) # pylint: disable=protected-access
+- runner.context.feature = self
+ runner.context.tags = set(self.tags)
++ self._setup_context_for_run(runner.context)
+
+ skip_entity_untested = runner.aborted
+ should_run_entity = self.should_run(runner.config)
+@@ -497,6 +506,9 @@ class Feature(ScenarioContainer):
+ (self.name, len(self.run_items),
+ len(self.rules), len(self.scenarios))
+
++ def _setup_context_for_run(self, context):
++ context.feature = self
++
+ def add_rule(self, rule):
+ """Add a rule to this feature."""
+ feature = self
+@@ -619,6 +631,9 @@ class Rule(ScenarioContainer):
+ self.parent = parent
+ self.feature = parent
+
++ def _setup_context_for_run(self, context):
++ context.rule = self
++
+ def __repr__(self):
+ return '<Rule "%s": %d scenario(s)>' % \
+ (self.name, len(self.scenarios))
+@@ -664,6 +679,10 @@ class Background(BasicStatement, Replayable):
+
+ .. _`background`: gherkin.html#backgrounds
+ """
++ # TODO: Background inheritance
++ # Rule.background should inherit its Feature.background steps (if available)
++ # Rule.background = Feature.background iff not Rule.background exists (ALREADY-SOLVED)
++ # Rule may override background inheritance mechanism
+ type = "background"
+
+ def __init__(self, filename, line, keyword, name, steps=None, description=None):
+@@ -796,7 +815,7 @@ class Scenario(TagAndStatusStatement, Replayable):
+
+ @property
+ def background_steps(self):
+- """Provide background steps if feature has a background.
++ """Provide background steps if feature/rule has a background.
+ Lazy init that copies the background steps.
+
+ Note that a copy of the background steps is needed to ensure
+diff --git a/behave/runner_util.py b/behave/runner_util.py
+index 2210f78..7e0807f 100644
+--- a/behave/runner_util.py
++++ b/behave/runner_util.py
+@@ -58,6 +58,100 @@ class FileLocationParser(object):
+ # -----------------------------------------------------------------------------
+ # CLASSES:
+ # -----------------------------------------------------------------------------
++from collections import OrderedDict
++from .model import Feature, Rule, ScenarioOutline, Scenario
++
++
++class FeatureLineDatabase(object):
++ """Helper class that supports select-by-location mechanism (FileLocation)
++ within a feature file by storing the feature line numbers for each entity.
++
++ RESPONSIBILITY(s):
++
++ * Can use the line number to select the best matching entity(s) in a feature
++ * Implements the select-by-location mechanism for each entity in the feature
++ """
++
++ def __init__(self, entity=None, line_data=None):
++ if entity and not line_data:
++ line_data = self.make_line_data_for(entity)
++ self.entity = entity
++ self.data = OrderedDict(line_data or [])
++ self._line_numbers = None
++ self._line_entities = None
++
++ def select_run_item_by_line(self, line):
++ """Select one run-items by using the line number.
++
++ * Exact match returns run-time entity (Feature, Rule, ScenarioOutline, Scenario)
++ * Any other line in between uses the predecessor entity
++
++ :param line: Line number in Feature file (as int)
++ :return: Selected run-item object.
++ """
++ run_item = self.data.get(line, None)
++ if run_item is None:
++ # -- CASE: BEST-MATCH in ordered line database
++ if self._line_numbers is None:
++ self._line_numbers = list(self.data.keys())
++ self._line_entities = list(self.data.values())
++
++ pos = bisect(self._line_numbers, line) - 1
++ if pos < 0:
++ pos = 0
++ run_item = self._line_entities[pos]
++ return run_item
++
++ def select_scenarios_by_line(self, line):
++ """Select one or more scenarios by using the line number.
++
++ * line = 0: Selects all scenarios in the Feature file
++ * Feature / Rule / ScenarioOutline.location.line selects its scenarios
++ * Scenario.location.line selects the Scenario
++ * Any other lines use the predecessor entity (and its scenarios)
++
++ :param line: Line number in Feature file (as int)
++ :return: List of selected scenarios
++ """
++ run_item = self.select_run_item_by_line(line)
++ scenarios = []
++ if isinstance(run_item, Feature):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, Rule):
++ scenarios = list(run_item.walk_scenarios())
++ elif isinstance(run_item, ScenarioOutline):
++ scenarios = list(run_item.scenarios)
++ elif isinstance(run_item, Scenario):
++ scenarios = [run_item]
++ return scenarios
++
++ @classmethod
++ def make_line_data_for(cls, entity):
++ line_data = []
++ run_items = []
++ if isinstance(entity, Feature):
++ line_data.append((0, entity))
++ run_items = entity.run_items
++ elif isinstance(entity, Rule):
++ run_items = entity.run_items
++ elif isinstance(entity, ScenarioOutline):
++ run_items = entity.scenarios
++
++ line_data.append((entity.location.line, entity))
++ for run_item in run_items:
++ line_data.extend(cls.make_line_data_for(run_item))
++ # -- MAYBE:
++ # if isinstance(entity, ScenarioOutline) and run_items:
++ # # -- SPECIAL CASE: Lines after last Examples row => Use ScenarioOutline
++ # line_data.append((run_items[-1].location.line + 1, entity))
++ return sorted(line_data)
++
++ @classmethod
++ def make(cls, entity):
++ return cls(entity, cls.make_line_data_for(entity))
++
++
++
+ class FeatureScenarioLocationCollector(object):
+ """
+ Collects FileLocation objects for a feature.
+@@ -200,6 +294,94 @@ class FeatureScenarioLocationCollector(object):
+ return self.feature
+
+
++class FeatureScenarioLocationCollector1(FeatureScenarioLocationCollector):
++
++ @staticmethod
++ def select_scenario_line_for(line, scenario_lines):
++ """
++ Select scenario line for any given line.
++
++ ALGORITHM: scenario.line <= line < next_scenario.line
++
++ :param line: A line number in the file (as number).
++ :param scenario_lines: Sorted list of scenario lines.
++ :return: Scenario.line (first line) for the given line.
++ """
++ if not scenario_lines:
++ return 0 # -- Select all scenarios.
++ pos = bisect(scenario_lines, line) - 1
++ if pos < 0:
++ pos = 0
++ return scenario_lines[pos]
++
++ def discover_selected_scenarios(self, strict=False):
++ """
++ Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ # -- STEP: Check if lines are correct.
++ existing_lines = [scenario.line for scenario in self.all_scenarios]
++ selected_lines = list(self.scenario_lines)
++ for line in selected_lines:
++ new_line = self.select_scenario_line_for(line, existing_lines)
++ if new_line != line:
++ # -- AUTO-CORRECT BAD-LINE:
++ self.scenario_lines.remove(line)
++ self.scenario_lines.add(new_line)
++ if strict:
++ msg = "Scenario location '...:%d' should be: '%s:%d'" % \
++ (line, self.filename, new_line)
++ raise InvalidFileLocationError(msg)
++
++ # -- STEP: Determine selected scenarios and store them.
++ scenario_lines = set(self.scenario_lines)
++ selected_scenarios = set()
++ for scenario in self.all_scenarios:
++ if scenario.line in scenario_lines:
++ selected_scenarios.add(scenario)
++ scenario_lines.remove(scenario.line)
++ # -- CHECK ALL ARE RESOLVED:
++ assert not scenario_lines
++ return selected_scenarios
++
++
++class FeatureScenarioLocationCollector2(FeatureScenarioLocationCollector):
++
++ def discover_selected_scenarios(self, strict=False):
++ """Discovers selected scenarios based on the provided file locations.
++ In addition:
++ * discover all scenarios
++ * auto-correct BAD LINE-NUMBERS
++
++ :param strict: If true, raises exception if file location is invalid.
++ :return: List of selected scenarios of this feature (as set).
++ :raises InvalidFileLocationError:
++ If file location is no exactly correct and strict is true.
++ """
++ assert self.feature
++ if not self.all_scenarios:
++ self.all_scenarios = self.feature.walk_scenarios()
++
++ line_database = FeatureLineDatabase.make(self.feature)
++ selected_lines = list(self.scenario_lines)
++ selected_scenarios = set()
++ for line in selected_lines:
++ more_scenarios = line_database.select_scenarios_by_line(line)
++ selected_scenarios.update(more_scenarios)
++ return selected_scenarios
++
++
+ class FeatureListParser(object):
+ """
+ Read textual file, ala '@features.txt'. This file contains:
+@@ -304,7 +486,7 @@ def parse_features(feature_files, language=None):
+ :param language: Default language to use.
+ :return: List of feature objects.
+ """
+- scenario_collector = FeatureScenarioLocationCollector()
++ scenario_collector = FeatureScenarioLocationCollector2()
+ features = []
+ for location in feature_files:
+ if not isinstance(location, FileLocation):
+@@ -315,7 +497,7 @@ def parse_features(feature_files, language=None):
+ scenario_collector.add_location(location)
+ continue
+ elif scenario_collector.feature:
+- # -- ADD CURRENT FEATURE: As collection of scenarios.
++ # -- NEW FEATURE DETECTED: Add current feature.
+ current_feature = scenario_collector.build_feature()
+ features.append(current_feature)
+ scenario_collector.clear()
+diff --git a/features/runner.select_scenarios_by_file_location.feature b/features/runner.select_scenarios_by_file_location.feature
+index f60c43f..69e23fe 100644
+--- a/features/runner.select_scenarios_by_file_location.feature
++++ b/features/runner.select_scenarios_by_file_location.feature
+@@ -13,15 +13,28 @@ Feature: Select Scenarios by File Location
+ . * A file location with filename but without line number
+ . refers to the complete file
+ . * A file location with line number 0 (zero) refers to the complete file
++ . * A file location within a scenario container (Feature, Rule, ScenarioOutline),
++ . that does not refer to the file location within a scenario,
++ . selects all scenarios of this scenario container.
+ .
+ . SPECIFICATION: Scenario selection by file locations
+ . * scenario.line == file_location.line selects scenario (preferred method).
+ . * Any line number in the following range is acceptable:
+- . scenario.line <= file_location.line < next_scenario.line
+- . * The first scenario is selected,
+- . if the file location line number is less than first scenario.line.
++ . scenario.line <= file_location.line < next_entity.line (maybe: scenario)
++ . * If the file location line number is less than first scenario.line,
++ . the preceeding scenario container (Feature or Rule) is selected.
+ . * The last scenario is selected,
+ . if the file location line number is greater than the lines in the file.
++ . * For ScenarioOutline.scenarios:
++ . scenario.line == ScenarioOutline.examples[x].row.line
++ . The line number of the Examples row that created the scenario is assigned to it.
++ .
++ . SPECIFICATION: "Scenario container" selection by file locations
++ . * Scenario containers are: Feature, Rule, ScenarioOutline
++ . * A file location that points into the matching range of a scenario container,
++ . selects all scenarios / run-items within this scenario container.
++ . * Any line number in the following range selects the scenario container:
++ . entity.line <= file_location.line < next_entity.line (maybe: child)
+ .
+ . SPECIFICATION: Runner with scenario locations (file locations)
+ . * Adjacent file locations are merged if they refer to the same file, like:
+@@ -162,22 +175,24 @@ Feature: Select Scenarios by File Location
+ """
+
+ @file_location.select_first
+- Scenario: Select first scenario if line number is smaller than first scenario line
++ Scenario: Select all scenarios if line number is smaller than first scenario line
+
+ CASE: 0 < file_location.line < first_scenario.line
++ HINT: Any line number outside of a scenario may point into a "scenario container".
++ In this case, all the scenarios of the scenario container are selected.
+
+ When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1"
+ Then it should pass with:
+ """
+ 0 features passed, 0 failed, 0 skipped, 1 untested
+- 0 scenarios passed, 0 failed, 1 skipped, 1 untested
++ 0 scenarios passed, 0 failed, 0 skipped, 2 untested
+ """
+ And the command output should contain:
+ """
+ Feature: Alice
+ Scenario: Alice First
+ """
+- But the command output should not contain:
++ But the command output should contain:
+ """
+ Scenario: Alice Last
+ """
+diff --git a/pytest.ini b/pytest.ini
+index a686596..ff2a8a2 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -16,7 +16,7 @@
+ # ============================================================================
+
+ [pytest]
+-minversion = 4.2
++minversion = 2.8
+ testpaths = tests
+ python_files = test_*.py
+ addopts = --metadata PACKAGE_UNDER_TEST behave
+diff --git a/tests/unit/test_runner_util.py b/tests/unit/test_runner_util.py
+new file mode 100644
+index 0000000..b5019b8
+--- /dev/null
++++ b/tests/unit/test_runner_util.py
+@@ -0,0 +1,175 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import absolute_import, print_function
++from collections import OrderedDict
++from behave.runner_util import FeatureLineDatabase
++from behave.parser import parse_feature
++from behave.model import Feature, Rule, ScenarioOutline, Scenario, Background
++import pytest
++
++
++# ---------------------------------------------------------------------------------------
++# TEST DATA: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++feature_text1 = u"""
++ Feature: Alice
++ Background: Alice.Background
++ Given a background step passes
++
++ Scenario: A1
++ Given a scenario step passes
++
++ Scenario: A2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_scenario_outline = u"""
++ Feature: Bob
++
++ Scenario Outline: Bob.SO_2_<row.id>
++ Given a person with name "<Name>"
++ Then the person is born in <Birthyear>
++
++ Examples:
++ | Name | Birthyear |
++ | Alice | 1990 |
++ | Bob | 1991 |
++
++ Scenario: Bob.S3
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_text_with_rule = u"""
++ Feature: Charly
++ Background: Charly.Background
++ Given a background step passes
++
++ Scenario: C1
++ Given a scenario step passes
++
++ Rule: Charly.Rule_1
++
++ Scenario: Rule_1.C2
++ Given a scenario step passes
++ When a scenario step passes
++ """
++
++feature_file_map = {
++ "basic.feature": feature_text1,
++ "scenario_outline.feature": feature_text_with_scenario_outline,
++ "rule.feature": feature_text_with_rule,
++}
++
++# ---------------------------------------------------------------------------------------
++# TEST SUITE FOR: FeatureLineDatabase
++# ---------------------------------------------------------------------------------------
++class TestFeatureLineDatabase(object):
++ def test_make(self):
++ feature = parse_feature(feature_text1.strip(),
++ filename="features/Alice.feature")
++ scenario_0 = feature.scenarios[0]
++ scenario_1 = feature.scenarios[1]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_0.line, scenario_0),
++ (scenario_1.line, scenario_1),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line == 1
++
++ def test_make__with_scenario_outline(self):
++ feature = parse_feature(feature_text_with_scenario_outline.strip(),
++ filename="features/Bob.feature")
++ scenarios = feature.walk_scenarios(with_outlines=True)
++ scenario_outline = scenarios[0]
++ assert scenario_outline is feature.run_items[0]
++ scenario_1 = scenarios[1]
++ scenario_2 = scenarios[2]
++ scenario_3 = scenarios[3]
++
++ line_database = FeatureLineDatabase.make(feature)
++ expected = OrderedDict([
++ (0, feature),
++ (feature.location.line, feature),
++ (scenario_outline.line, scenario_outline),
++ (scenario_1.line, scenario_1),
++ (scenario_2.line, scenario_2),
++ (scenario_3.line, scenario_3),
++ ])
++ assert line_database.data == expected
++ assert feature.location.line < scenario_outline.location.line
++ assert scenario_outline.location.line < scenario_1.location.line
++ assert scenario_1.location.line < scenario_2.location.line
++ assert scenario_2.location.line < scenario_3.location.line
++
++
++ def test_select_run_items_by_line__feature_line_selects_feature(self):
++ feature = parse_feature(feature_text1, filename="features/Alice.feature")
++ line_database = FeatureLineDatabase.make(feature)
++ selected = line_database.select_run_item_by_line(feature.location.line)
++ assert selected is feature
++ assert isinstance(selected, Feature)
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__entity_line_selects_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ last_line = 0
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ for run_item in all_run_items:
++ selected = line_database.select_run_item_by_line(run_item.location.line)
++ assert selected is run_item
++ assert last_line < selected.location.line
++ last_line = run_item.location.line
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_before_entity_selects_last_entity(self, filename):
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ last_run_item = feature
++ for run_item in all_run_items:
++ predecessor_line = run_item.location.line - 1
++ selected = line_database.select_run_item_by_line(predecessor_line)
++ assert selected is last_run_item
++ assert selected is not run_item
++ last_run_item = run_item
++
++ @pytest.mark.parametrize("filename", [
++ "basic.feature", "scenario_outline.feature", "rule.feature"
++ ])
++ def test_select_run_items_by_line__line_after_entity_selects_entity(self, filename):
++ # -- HINT: In most cases
++ # EXCEPT:
++ # * Scenarios of ScenarioOutline: scenario.line == SO.examples.row.line
++ # * Empty entity without steps is directly followed by other entity
++ feature_text = feature_file_map[filename]
++ feature = parse_feature(feature_text, filename=filename)
++ line_database = FeatureLineDatabase.make(feature)
++ all_run_items = feature.walk_scenarios(with_outlines=True, with_rules=True)
++ file_end_line = all_run_items[-1].location.line + 1000
++ for index, run_item in enumerate(all_run_items):
++ next_line = run_item.location.line + 1
++ next_entity_line = file_end_line
++ if index+1 < len(all_run_items):
++ next_entity = all_run_items[index+1]
++ next_entity_line = next_entity.line
++ if next_line >= next_entity_line:
++ # -- EXCLUDE: Scenarios in a ScenarioOutline
++ print("EXCLUDED: %s: %s (line=%s)" %
++ (run_item.keyword, run_item.name, run_item.line))
++ continue
++
++ selected = line_database.select_run_item_by_line(next_line)
++ assert selected is run_item
new file mode 100644
@@ -0,0 +1,58 @@
+From a6388c0e97e4378a4ef628361a46ff6d02d3159e Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 10:50:54 +0200
+Subject: [PATCH] docs: Add description for "Select-by-location for Scenario
+ Containers"
+
+---
+ docs/new_and_noteworthy_v1.2.7.rst | 33 ++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index 80d9576..ff1cd1f 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -7,6 +7,7 @@ Summary:
+ * Use/Support :pypi:`cucumber-tag-expressions` (superceed: old-style tag-expressions)
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
++* `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -102,3 +103,35 @@ Overview of the `Example Mapping`_ concepts:
+
+
+ .. include:: _content.tag_expressions_v2.rst
++
++
++Select-by-location for Scenario Containers
++-------------------------------------------------------------------------------
++
++In the past, it was already possible to scenario(s) by using its **file-location**.
++
++A **file-location** has the schema: ``<FILENAME>:<LINE_NUMBER>``.
++Example: ``features/alice.feature:12``
++(refers to ``line 12`` in ``features/alice.feature`` file).
++
++Rules to select **Scenarios** by using the file-location:
++
++* **Scenario:** Use a file-location that points to the keyword/title or its steps
++ (until next Scenario/Entity starts).
++
++* **Scenario of a ScenarioOutline:**
++ Use the file-location of its Examples row.
++
++Now you can select all entities of a **Scenario Container** (Feature, Rule, ScenarioOutline):
++
++* **Feature:**
++ Use file-location before first contained entity/Scenario starts.
++
++* **Rule:**
++ Use file-location from keyword/title line to line before its first Scenario/Background.
++
++* **ScenarioOutline:**
++ Use file-location from keyword/title line to line before its Examples rows.
++
++A file-location into a **Scenario Container** selects all its entities
++(Scenarios, ...).
new file mode 100644
@@ -0,0 +1,50 @@
+From 93f578698844d7276e5b2f287ee7ff3bcfc049bf Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:35:51 +0200
+Subject: [PATCH] tests: Fix warnings for python3.
+
+---
+ tests/issues/test_issue0336.py | 1 +
+ tests/unit/test_parser.py | 11 +++++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/tests/issues/test_issue0336.py b/tests/issues/test_issue0336.py
+index eb4c3fd..09201ae 100644
+--- a/tests/issues/test_issue0336.py
++++ b/tests/issues/test_issue0336.py
+@@ -52,6 +52,7 @@ AssertionError
+ assert file_line_text in text2
+
+ # @require_python2
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test__problem_exists_with_problematic_encoding(self):
+ """Test ensures that problem exists with encoding=unicode-escape"""
+ # -- NOTE: Explicit use of problematic encoding
+diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
+index ecbb1bf..01006f9 100644
+--- a/tests/unit/test_parser.py
++++ b/tests/unit/test_parser.py
+@@ -564,16 +564,19 @@ Feature: Stuff
+ ('then', 'Then', 'stuff is in buckets', None, None),
+ ])
+
++ @pytest.mark.filterwarnings("ignore:invalid escape sequence")
+ def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self):
++ # -- HINT py37: DeprecationWarning: invalid escape sequence '\|'
++ # USE: Double escaped-backslashes.
+ doc = u'''
+ Feature:
+ Scenario:
+ Given we have special cell values:
+ | name | value |
+- | alice | one\|two |
+- | bob |\|one |
+- | charly | one\||
+- | doro | one\|two\|three\|four |
++ | alice | one\\|two |
++ | bob |\\|one |
++ | charly | one\\||
++ | doro | one\\|two\\|three\\|four |
+ '''.lstrip()
+ feature = parser.parse_feature(doc)
+ assert len(feature.scenarios) == 1
new file mode 100644
@@ -0,0 +1,55 @@
+From 21a399bf6e2e2eb09c98d173f1d0975bcecbf1e5 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 13:36:59 +0200
+Subject: [PATCH] Use cucumber-tag-expressions 1.1.2 (to fix warnings).
+
+py.requirements: Add twine, bump2version
+---
+ py.requirements/basic.txt | 2 +-
+ py.requirements/develop.txt | 6 +++++-
+ setup.py | 2 +-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/py.requirements/basic.txt b/py.requirements/basic.txt
+index 3b71bfb..ad5b9a6 100644
+--- a/py.requirements/basic.txt
++++ b/py.requirements/basic.txt
+@@ -8,7 +8,7 @@
+ # * http://www.pip-installer.org/
+ # ============================================================================
+
+-cucumber-tag-expressions >= 1.1.1
++cucumber-tag-expressions >= 1.1.2
+ parse >= 1.8.2
+ parse_type >= 0.4.2
+ six >= 1.12.0
+diff --git a/py.requirements/develop.txt b/py.requirements/develop.txt
+index d823389..a16d7bf 100644
+--- a/py.requirements/develop.txt
++++ b/py.requirements/develop.txt
+@@ -11,7 +11,11 @@ pathlib; python_version <= '3.4'
+ pycmd
+
+ # -- CONFIGURATION MANAGEMENT (helpers):
+-bumpversion >= 0.4.0
++# FORMER: bumpversion >= 0.4.0
++bump2version >= 0.5.6
++
++# -- RELEASE MANAGEMENT: Push package to pypi.
++twine >= 1.13.0
+
+ # -- DEVELOPMENT SUPPORT:
+ tox >= 1.8.1
+diff --git a/setup.py b/setup.py
+index 9c7560d..cea4392 100644
+--- a/setup.py
++++ b/setup.py
+@@ -76,7 +76,7 @@ setup(
+ # SUPPORT: python2.7, python3.3 (or higher)
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
+ install_requires=[
+- "cucumber-tag-expressions >= 1.1.1",
++ "cucumber-tag-expressions >= 1.1.2",
+ "parse >= 1.8.2",
+ "parse_type >= 0.4.2",
+ "six >= 1.12.0",
new file mode 100644
@@ -0,0 +1,91 @@
+From 1d3c288a2eed8caef24356e2a92483425bee0378 Mon Sep 17 00:00:00 2001
+From: jenisys <jenisys@users.noreply.github.com>
+Date: Sat, 6 Jul 2019 14:18:16 +0200
+Subject: [PATCH] MENTION ENHANCEMENT: Support emojis in feature files and
+ steps.
+
+---
+ CHANGES.rst | 3 ++-
+ docs/new_and_noteworthy_v1.2.7.rst | 17 +++++++++++++++++
+ features/i18n_emoji.feature | 7 +++++++
+ features/steps/i18n_emoji_steps.py | 10 ++++++++++
+ 4 files changed, 36 insertions(+), 1 deletion(-)
+ create mode 100644 features/i18n_emoji.feature
+ create mode 100644 features/steps/i18n_emoji_steps.py
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index 01bd1bd..d165275 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -24,8 +24,9 @@ GOALS:
+ ENHANCEMENTS:
+
+ * Add support for Gherkin v6 grammar and syntax in ``*.feature`` files
+-* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
+ * Use `cucumber-tag-expressions`_ with tag-matching extension (superceeds: old-style tag-expressions)
++* Use cucumber "gherkin-languages.json" now (simplify: Gherkin v6 aliases, language usage)
++* Support emojis in ``*.feature`` files and steps
+ * issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
+ * issue #675: Feature files cannot be found within symlink directories (provided by: smadness, pull #680)
+ * Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
+diff --git a/docs/new_and_noteworthy_v1.2.7.rst b/docs/new_and_noteworthy_v1.2.7.rst
+index ff1cd1f..b7242f7 100644
+--- a/docs/new_and_noteworthy_v1.2.7.rst
++++ b/docs/new_and_noteworthy_v1.2.7.rst
+@@ -8,6 +8,7 @@ Summary:
+ * :pypi:`cucumber-tag-expressions` are extended by "tag-matching"
+ to match partial tag names, like: ``@foo.*``
+ * `Select-by-location for Scenario Containers`_ (Feature, Rule, ScenarioOutline)
++* `Support for emojis in feature files and steps`_
+
+ .. _`Example Mapping`: https://cucumber.io/blog/example-mapping-introduction/
+ .. _`Example Mapping Webinar`: https://cucumber.io/blog/example-mapping-webinar/
+@@ -135,3 +136,19 @@ Now you can select all entities of a **Scenario Container** (Feature, Rule, Scen
+
+ A file-location into a **Scenario Container** selects all its entities
+ (Scenarios, ...).
++
++
++Support for Emojis in Feature Files and Steps
++-------------------------------------------------------------------------------
++
++* Emojis can now be used in ``*.feature`` files.
++* Emojis can now be used in step definitions.
++* You can now use ``language=emoji (em)`` in ``*.feature`` files ;-)
++
++.. literalinclude:: ../features/i18n_emoji.feature
++ :prepend: # -- FILE: features/i18n_emoji.feature
++ :language: gherkin
++
++.. literalinclude:: ../features/steps/i18n_emoji_steps.py
++ :prepend: # -- FILE: features/steps/i18n_emoji_steps.py
++ :language: python
+diff --git a/features/i18n_emoji.feature b/features/i18n_emoji.feature
+new file mode 100644
+index 0000000..db23ac2
+--- /dev/null
++++ b/features/i18n_emoji.feature
+@@ -0,0 +1,7 @@
++# language: em
++# SOURCE: https://github.com/cucumber/cucumber/blob/master/gherkin/testdata/good/i18n_emoji.feature
++
++
Hello, this email is a notification from the Auto Upgrade Helper that the automatic attempt to upgrade the recipe *python3-behave* to *6* has Failed(do_compile). Detailed error information: do_compile failed Next steps: - apply the patch: git am 0001-python3-behave-upgrade-1.2.6-git9520119376046aeff738.patch - check the changes to upstream patches and summarize them in the commit message, - compile an image that contains the package - perform some basic sanity tests - amend the patch and sign it off: git commit -s --reset-author --amend - send it to the appropriate mailing list Alternatively, if you believe the recipe should not be upgraded at this time, you can fill RECIPE_NO_UPDATE_REASON in respective recipe file so that automatic upgrades would no longer be attempted. Please review the attached files for further information and build/update failures. Any problem please file a bug at https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Automated%20Update%20Handler Regards, The Upgrade Helper -- >8 -- From 815d53cd9a095b0b67dc6f98ad9a7fa251edefbf Mon Sep 17 00:00:00 2001 From: Upgrade Helper <auh@moto-timo.dev> Date: Tue, 11 Jul 2023 17:12:25 -0500 Subject: [PATCH] python3-behave: upgrade 1.2.6+git9520119376046aeff73804b5f1ea05d87a63f370 -> 6 --- ...ioOutlineBuilder-was-not-copying-des.patch | 22 + .../0002-UPDATE-FIXED-725.patch | 21 + ...ST-to-verify-that-issue-725-is-fixed.patch | 60 + ...ter-counts-computation-when-Rules-ar.patch | 342 + ...print_summary-Simplify-if-Rules-are-.patch | 60 + ...r-Add-basic-support-output-for-Rules.patch | 395 + ...MP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch | 67 + .../0008-Correct-examples-and-docstring.patch | 41 + ...ure.run_items-processing-with-Rule-s.patch | 58 + ...-sphinx-intl-support-for-READTHEDOCS.patch | 58 + ...ent-package-versions-in-requirements.patch | 81 + .../0012-docs-conf.py-tweaks.patch | 30 + ...lled-after_rule-hook-was-after_after.patch | 30 + ...d-hints-on-Gherkin-v6-grammar-issues.patch | 45 + .../0015-README-ReST-tweaks.patch | 18 + ...016-Example-using-Gherkin-v6-grammar.patch | 228 + .../0017-PREPARE-Python-3.8-support.patch | 39 + ...x-logging.Formatter-validate-problem.patch | 22 + ...arily-move-py38-dev-to-front-build-f.patch | 28 + ...Tweaks-for-faster-builds-temporarily.patch | 35 + ...8-logging.Formatter.validate-problem.patch | 47 + ...on-3.8-asyncio.coroutine-is-deprecat.patch | 42 + .../0023-UPDATE-Add-755-info.patch | 24 + ...ted-to-docstring-example-and-weird-b.patch | 25 + ...pe-sequence-warnings-w-regex-pattern.patch | 29 + ...NUP-Move-deprecated-tag-matcher-clas.patch | 1020 +++ .../python3-behave/0027-Comment-tweaks.patch | 30 + ...-related-to-invalid-escapes-in-regex.patch | 79 + ...ould-not-break-configured-rerun-sett.patch | 73 + ...les-and-failing-scenarios-enable-via.patch | 36 + ..._v6-Tweak-ScenarioOutline-Examples-t.patch | 27 + .../0032-Add-info-on-merged-pull-588.patch | 21 + ...3-Tweak-tests-required-by-pytest-5.0.patch | 97 + ...st-instead-of-nose-to-remove-nose.im.patch | 180 + ...Y-nose-to-avoid-nose.importer-warnin.patch | 1815 ++++ ...0036-FIX-Remove-test-from-pytest-run.patch | 22 + ...on-Add-support-for-Scenario-containe.patch | 652 ++ ...tion-for-Select-by-location-for-Scen.patch | 58 + .../0039-tests-Fix-warnings-for-python3.patch | 50 + ...ag-expressions-1.1.2-to-fix-warnings.patch | 55 + ...ENT-Support-emojis-in-feature-files-.patch | 91 + ...valid-escape-char-in-regex-w-python3.patch | 250 + ...3-Example-related-to-question-in-756.patch | 335 + ...X-python3.8-regressions-on-CI-server.patch | 489 + .../0045-UPDATE-Mark-issue-755-as-fixed.patch | 46 + ...DATE-Cucumber-gherkin-languages.json.patch | 57 + ...ule-keyword-translation-in-portugues.patch | 202 + ...-generate-from-gherkin-languages.jso.patch | 141 + ...ming-to-fixture.behave.no_background.patch | 322 + ...50-EXAMPLE-Cleanup-Gherkin-v6-README.patch | 64 + ...for-feature.background-inheritance-f.patch | 1510 +++ ...-Add-support-for-runtime-constraints.patch | 269 + .../0053-Use-runtime-constraints.patch | 196 + ...0054-CLEANUP-Remove-deprecated-parts.patch | 3937 ++++++++ ...0055-CLEANUP-Remove-deprecated-parts.patch | 736 ++ ...rform-more-Gherkin-v6-checks-and-run.patch | 155 + ...-and-python-module-old-was-broken-no.patch | 72 + .../0058-UTIL-Formatting-tweaks.patch | 22 + ...e-use_fixture_by_tag-didn-t-return-t.patch | 23 + .../0060-Added-issue-unit-test.patch | 62 + ...e-pull-request-767-with-minor-tweaks.patch | 60 + ...sue-766-PrettyFormatter-UnicodeError.patch | 83 + ...enarioOutline.Examples-without-table.patch | 74 + ...enarioOutline.Examples-without-table.patch | 21 + .../0065-Nibble-TravisCI-to-wake-up.patch | 21 + .../0066-Tweak-pytest-version-selection.patch | 37 + ...figuration-to-silence-JUnit-XML-dial.patch | 37 + ...e-test-for-wildcard-pattern-matching.patch | 56 + ...ATE-dependencies-path.py-path-pytest.patch | 141 + ...eprecatedWarning-from-distutils-pack.patch | 25 + ...-Add-ContextMode-enum-related-to-797.patch | 216 + .../0072-Cleanup-comments.patch | 22 + ...phinx-build-problem-async_steps3x.py.patch | 29 + ...-parse_expressions-was-parse_builtin.patch | 185 + ...ssion-add-links-to-parse_type-module.patch | 40 + ...MP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch | 65 + ...leanups-related-to-question-in-800-P.patch | 223 + ...y-name-uses-regex-pattern-related-to.patch | 82 + ...nce-problem-copy-paste-in-Rule-class.patch | 34 + ...API-description-for-Runner-Operation.patch | 195 + ...0081-FIX-DOCS-Runner-operations-typo.patch | 22 + ...ement-Context.add_cleanup-with-layer.patch | 295 + ...8.0-parse-versions-1.16.0-.-1.17.x-h.patch | 37 + ...Duplicated-steps-AmbiguousStepErrors.patch | 34 + .../0085-Add-renovate.json.patch | 21 + ...086-PRPEPARE-RENOVATE-With-adaptions.patch | 175 + .../0087-Pin-dependencies.patch | 36 + ...te-Extend-pip-requirements-file-list.patch | 31 + ...IN-REQUIREMENTS-Extend-to-all-places.patch | 92 + ..._cleanup-replaces-_tasklet_cleanup-r.patch | 8116 +++++++++++++++++ ...nge-code-blocks-from-bash-to-console.patch | 36 + .../0092-Fix-typo-in-tutorial.patch | 24 + ...nts-Use-PyHamcrest-2.0-for-python2.7.patch | 80 + .../0094-UPDATE-PR-877-was-merged.patch | 21 + .../0095-capitalizing-steps.patch | 28 + ...develop.update-gherkin-that-aborted-.patch | 56 + ...ainst-PowerPC-CPU-support-Travis-867.patch | 22 + ...098-Add-Context.attach-docs-and-test.patch | 132 + .../0099-Add-Contributing-chapter.patch | 125 + ...apt-Tox-target-for-building-the-docs.patch | 34 + ...tion-HTML-formatter-in-documentation.patch | 83 + ...le-highlighting-for-pip-install-docs.patch | 22 + ...ocs-fix-simple-typo-tuorial-tutorial.patch | 52 + ...t-for-python3.9-by-using-active-tags.patch | 227 + .../0105-PREFER-python3-from-now-on.patch | 19 + .../0106-REMOVE-invoke-scripts.patch | 41 + ...X-Deprecated-warnings-for-Python-3.x.patch | 124 + ...lems-in-virtualenvs-w-behave4cmd0-st.patch | 18 + .../0109-FIX-Active-tag-logic.patch | 875 ++ .../0110-FIX-Tests-w-more.features.patch | 56 + ...FIX-Some-regressions-with-Python-3.9.patch | 741 ++ .../0112-docs-Update-new-and-noteworthy.patch | 84 + ...elper-function-to-print-the-current-.patch | 278 + ...kin-languages.json-from-cucumber-rep.patch | 541 ++ ...TE-CHANGES-Related-to-PR-895-and-827.patch | 22 + ...r-python-2.7-builds-mock-4.0-only-fo.patch | 39 + ...-to-use-a-custom-runner-in-the-behav.patch | 126 + ...llow-forcing-color-with-color-always.patch | 59 + ...lor-with-no-value-followed-by-posarg.patch | 43 + .../0120-Add-BEHAVE_COLOR-env-var.patch | 31 + ...121-fix-malformed-table-rows-warning.patch | 33 + ...-955-setup-Remove-attribute-use_2to3.patch | 42 + .../0123-Add-info-for-fixed-issue-955.patch | 21 + .../python/python3-behave_1.2.6.bb | 129 +- 124 files changed, 29808 insertions(+), 2 deletions(-) create mode 100644 meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0042-FIX-Invalid-escape-char-in-regex-w-python3.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0043-Example-related-to-question-in-756.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0044-FIX-python3.8-regressions-on-CI-server.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0045-UPDATE-Mark-issue-755-as-fixed.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0046-UPDATE-Cucumber-gherkin-languages.json.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0047-gherkin-Adding-Rule-keyword-translation-in-portugues.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0048-Tweaks-to-update-generate-from-gherkin-languages.jso.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0049-EXAMPLE-Tweak-naming-to-fixture.behave.no_background.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0050-EXAMPLE-Cleanup-Gherkin-v6-README.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0051-Improve-support-for-feature.background-inheritance-f.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0052-Add-support-for-runtime-constraints.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0053-Use-runtime-constraints.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0054-CLEANUP-Remove-deprecated-parts.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0055-CLEANUP-Remove-deprecated-parts.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0056-more.features-Perform-more-Gherkin-v6-checks-and-run.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0057-UTIL-Correct-URL-and-python-module-old-was-broken-no.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0058-UTIL-Formatting-tweaks.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0059-Fixed-a-bug-where-use_fixture_by_tag-didn-t-return-t.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0060-Added-issue-unit-test.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0061-Merge-pull-request-767-with-minor-tweaks.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0062-CHECK-Issue-766-PrettyFormatter-UnicodeError.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0063-FIX-issue-772-ScenarioOutline.Examples-without-table.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0064-FIX-issue-772-ScenarioOutline.Examples-without-table.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0065-Nibble-TravisCI-to-wake-up.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0066-Tweak-pytest-version-selection.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0067-Tweak-pytest-configuration-to-silence-JUnit-XML-dial.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0068-Add-basic-feature-test-for-wildcard-pattern-matching.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0069-UPDATE-dependencies-path.py-path-pytest.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0070-pytest-Disable-DeprecatedWarning-from-distutils-pack.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0071-CLEANUP-Add-ContextMode-enum-related-to-797.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0072-Cleanup-comments.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0073-FIX-sphinx-build-problem-async_steps3x.py.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0074-docs-Rename-page-parse_expressions-was-parse_builtin.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0075-docs-parse_expression-add-links-to-parse_type-module.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0076-BUMP-VERSION-1.2.7.dev2-was-1.2.7.dev1.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0077-Gherkin-parser-Cleanups-related-to-question-in-800-P.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0078-Clarify-select-by-name-uses-regex-pattern-related-to.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0079-FIX-Cross-reference-problem-copy-paste-in-Rule-class.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0080-DOCS-Update-API-description-for-Runner-Operation.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0081-FIX-DOCS-Runner-operations-typo.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0082-issue-740-Enhancement-Context.add_cleanup-with-layer.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0083-UPDATE-parse-1.18.0-parse-versions-1.16.0-.-1.17.x-h.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0084-RELATED-TO-Duplicated-steps-AmbiguousStepErrors.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0085-Add-renovate.json.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0086-PRPEPARE-RENOVATE-With-adaptions.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0087-Pin-dependencies.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0088-renovate-Extend-pip-requirements-file-list.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0089-PIN-REQUIREMENTS-Extend-to-all-places.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0090-tasks-Add-invoke_cleanup-replaces-_tasklet_cleanup-r.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0091-Docs-change-code-blocks-from-bash-to-console.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0092-Fix-typo-in-tutorial.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0093-py.requirements-Use-PyHamcrest-2.0-for-python2.7.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0094-UPDATE-PR-877-was-merged.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0095-capitalizing-steps.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0096-FIX-invoke-task-develop.update-gherkin-that-aborted-.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0097-Test-against-PowerPC-CPU-support-Travis-867.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0098-Add-Context.attach-docs-and-test.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0099-Add-Contributing-chapter.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0100-Adapt-Tox-target-for-building-the-docs.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0101-Mention-HTML-formatter-in-documentation.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0102-Use-console-highlighting-for-pip-install-docs.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0103-docs-fix-simple-typo-tuorial-tutorial.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0104-Add-support-for-python3.9-by-using-active-tags.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0105-PREFER-python3-from-now-on.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0106-REMOVE-invoke-scripts.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0107-FIX-Deprecated-warnings-for-Python-3.x.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0108-FIX-Python2-problems-in-virtualenvs-w-behave4cmd0-st.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0109-FIX-Active-tag-logic.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0110-FIX-Tests-w-more.features.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0111-FIX-Some-regressions-with-Python-3.9.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0112-docs-Update-new-and-noteworthy.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0113-Add-diagnostic-helper-function-to-print-the-current-.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0114-UPDATE-i18n-gherkin-languages.json-from-cucumber-rep.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0115-UPDATE-CHANGES-Related-to-PR-895-and-827.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0116-FIX-CI-TRAVIS-For-python-2.7-builds-mock-4.0-only-fo.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0117-Adds-the-ability-to-use-a-custom-runner-in-the-behav.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0118-Allow-forcing-color-with-color-always.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0119-Allow-color-with-no-value-followed-by-posarg.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0120-Add-BEHAVE_COLOR-env-var.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0121-fix-malformed-table-rows-warning.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0122-FIX-955-setup-Remove-attribute-use_2to3.patch create mode 100644 meta-python/recipes-devtools/python/python3-behave/0123-Add-info-for-fixed-issue-955.patch