diff mbox series

[AUH] python3-behave: upgrading to 6 FAILED

Message ID 0101018b97fb16a4-ad2e623e-6578-47f0-b6ca-90d6758abcde-000000@us-west-2.amazonses.com
State New
Headers show
Series [AUH] python3-behave: upgrading to 6 FAILED | expand

Commit Message

auh@yoctoproject.org Nov. 4, 2023, 1:38 a.m. UTC
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-6.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 d9ceb27176c0404fd6c577dbebd3ad671b58af1c Mon Sep 17 00:00:00 2001
From: Upgrade Helper <auh@yoctoproject.org>
Date: Fri, 3 Nov 2023 20:10:50 +0000
Subject: [PATCH] python3-behave: upgrade 1.2.6 -> 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
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
new file mode 100644
index 000000000..e94b2f25c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0001-FIXES-725-ScenarioOutlineBuilder-was-not-copying-des.patch
@@ -0,0 +1,22 @@ 
+From 0ae5e867f73ba50744531024e3bad79979f1dbaa 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
new file mode 100644
index 000000000..8a113a20f
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0002-UPDATE-FIXED-725.patch
@@ -0,0 +1,21 @@ 
+From 34a1d26a1683d4d952da7f32c02316d67cb13925 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)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
new file mode 100644
index 000000000..e0f34b532
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0003-ADD-TEST-to-verify-that-issue-725-is-fixed.patch
@@ -0,0 +1,60 @@ 
+From 92baa7df57b5db861a03c80034a52e6e7681c8d2 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
new file mode 100644
index 000000000..6ed826b97
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0004-FIX-SummaryReporter-counts-computation-when-Rules-ar.patch
@@ -0,0 +1,342 @@ 
+From a0678cdece593697082973abbb59648f27530c99 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,
diff --git a/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
new file mode 100644
index 000000000..6c5915c4d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0005-SummaryReporter.print_summary-Simplify-if-Rules-are-.patch
@@ -0,0 +1,60 @@ 
+From bd74dcc8af6b48d15a1f2e8158aa53565a4bb45c 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
new file mode 100644
index 000000000..8fa8b59fa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0006-Formatter-Add-basic-support-output-for-Rules.patch
@@ -0,0 +1,395 @@ 
+From 1cc411a82767fd63b4eff5bc9cff6d7867d45d17 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*.
+ 
diff --git a/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
new file mode 100644
index 000000000..ffd433857
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0007-BUMP-VERSION-1.2.7.dev1-was-1.2.7.dev0.patch
@@ -0,0 +1,67 @@ 
+From 466337566f163bc7339fe20d12d8fb8561eabbdf 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",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
new file mode 100644
index 000000000..c0e9a67bf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0008-Correct-examples-and-docstring.patch
@@ -0,0 +1,41 @@ 
+From 6b698d73616c3eefbf67c0bb184755af5334f37f 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)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
new file mode 100644
index 000000000..a3d12df88
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0009-FIX-feature.run_items-processing-with-Rule-s.patch
@@ -0,0 +1,58 @@ 
+From c27f43ef8270c7e08a20fbb3066c8780f26ecccd 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:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
new file mode 100644
index 000000000..71bcb8d37
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0010-docs-Disable-sphinx-intl-support-for-READTHEDOCS.patch
@@ -0,0 +1,58 @@ 
+From 9b5204ebaa619f2776f2c6350c619927327e5aca 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":
diff --git a/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
new file mode 100644
index 000000000..94e7d6352
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0011-Cleanup-Dependent-package-versions-in-requirements.patch
@@ -0,0 +1,81 @@ 
+From f7313c14ee5b1038447d104a885bd0f99875b3e8 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",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
new file mode 100644
index 000000000..b8f2527a0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0012-docs-conf.py-tweaks.patch
@@ -0,0 +1,30 @@ 
+From 1c8b368ed9016f9db7f17851144250712717f1c5 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"
+ 
diff --git a/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
new file mode 100644
index 000000000..dcf234771
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0013-FIX-Misspelled-after_rule-hook-was-after_after.patch
@@ -0,0 +1,30 @@ 
+From 60438ebea003efae558ea344fbeb633cf21a8267 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
new file mode 100644
index 000000000..5eedb5875
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0014-Add-hints-on-Gherkin-v6-grammar-issues.patch
@@ -0,0 +1,45 @@ 
+From 7a7984717e1d5278bac29283e0870f0a638b22fe 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
new file mode 100644
index 000000000..584931bd9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0015-README-ReST-tweaks.patch
@@ -0,0 +1,18 @@ 
+From 603bae920105bb97b4dfe2979d08cfea61e72dd8 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
new file mode 100644
index 000000000..dec47bbbf
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0016-Example-using-Gherkin-v6-grammar.patch
@@ -0,0 +1,228 @@ 
+From 4a27977f21a0179e8d0c01581e93ee689f0b992e 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
new file mode 100644
index 000000000..d1dd355ab
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0017-PREPARE-Python-3.8-support.patch
@@ -0,0 +1,39 @@ 
+From 2bdb1cd02f10b307e11df7f33a410401ae8a49b1 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:
diff --git a/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
new file mode 100644
index 000000000..83c01dc49
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0018-py3.8-Try-to-fix-logging.Formatter-validate-problem.patch
@@ -0,0 +1,22 @@ 
+From 4c62d7eaf7efd930b6d4d681c5ebe0cd901e529e 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):
diff --git a/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
new file mode 100644
index 000000000..d5535afe9
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0019-travis.ci-Temporarily-move-py38-dev-to-front-build-f.patch
@@ -0,0 +1,28 @@ 
+From 89da35f7b165d74cce802aff5ba9b4833bb2171d 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"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
new file mode 100644
index 000000000..b72886009
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0020-travis.ci-Tweaks-for-faster-builds-temporarily.patch
@@ -0,0 +1,35 @@ 
+From 756f9f7e6254e576315880cee3feb32e9dc7c452 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
new file mode 100644
index 000000000..5cd5ca8de
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0021-FIX-py3.8-logging.Formatter.validate-problem.patch
@@ -0,0 +1,47 @@ 
+From 34e0fc3657577c00931663501087788188b5bfdf 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()
diff --git a/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
new file mode 100644
index 000000000..462fbb6d4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0022-PREPARE-FOR-Python-3.8-asyncio.coroutine-is-deprecat.patch
@@ -0,0 +1,42 @@ 
+From ebdd9ce8253ec22f1e31dbb5cc42702c949af301 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."""
+ 
diff --git a/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
new file mode 100644
index 000000000..3c6eda380
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0023-UPDATE-Add-755-info.patch
@@ -0,0 +1,24 @@ 
+From 67eb237284f89d694bb09ebf7a3166c6c08cb025 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)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
new file mode 100644
index 000000000..f39386642
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0024-FIX-WARNING-Related-to-docstring-example-and-weird-b.patch
@@ -0,0 +1,25 @@ 
+From 958e8ddce6dd80b211c336e7b809b10e92ef1fa5 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).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
new file mode 100644
index 000000000..7082dbc25
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0025-FIX-invalid-escape-sequence-warnings-w-regex-pattern.patch
@@ -0,0 +1,29 @@ 
+From 5a039c49d0e158013bf308e824212decd3085fbf 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],
+ ]
diff --git a/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
new file mode 100644
index 000000000..d8f68c3bd
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0026-DEPRECATING-CLEANUP-Move-deprecated-tag-matcher-clas.patch
@@ -0,0 +1,1020 @@ 
+From 5619b3375f9a4bbf760dafd6e77945658d5e06e6 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))
+-
diff --git a/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
new file mode 100644
index 000000000..b4f54a2d2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0027-Comment-tweaks.patch
@@ -0,0 +1,30 @@ 
+From 2fdad16ca8bbca127b6b181da2ccf4eebb58a8a0 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
new file mode 100644
index 000000000..d40c78afa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0028-FIX-warnings-related-to-invalid-escapes-in-regex.patch
@@ -0,0 +1,79 @@ 
+From 2d151afe0191a8c227cb873e577575eb9d78057f 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"
diff --git a/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
new file mode 100644
index 000000000..f85f73e83
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0029-Steps-catalog-should-not-break-configured-rerun-sett.patch
@@ -0,0 +1,73 @@ 
+From 18784841314072f089f47ef208851ae475271acd 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).
++          """
++
diff --git a/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
new file mode 100644
index 000000000..3839c8d55
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0030-Add-feature-w-rules-and-failing-scenarios-enable-via.patch
@@ -0,0 +1,36 @@ 
+From 1b9fa431aa92cfee62c1efcfd74f72774707e6b2 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
new file mode 100644
index 000000000..01b30c7ef
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0031-examples-gherkin_v6-Tweak-ScenarioOutline-Examples-t.patch
@@ -0,0 +1,27 @@ 
+From 17fd92cd7fd764757651097e9bd947ce35e39196 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   |
diff --git a/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
new file mode 100644
index 000000000..a9e619c58
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0032-Add-info-on-merged-pull-588.patch
@@ -0,0 +1,21 @@ 
+From f6f22d97662e74fd728648f6d42a7f07bfc60dee 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)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
new file mode 100644
index 000000000..4f520396e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0033-Tweak-tests-required-by-pytest-5.0.patch
@@ -0,0 +1,97 @@ 
+From 18bf6e97784b4d1b4c91833826af8963e0daf612 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)
diff --git a/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
new file mode 100644
index 000000000..f99a1c226
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0034-CLEANUP-Use-pytest-instead-of-nose-to-remove-nose.im.patch
@@ -0,0 +1,180 @@ 
+From 029ed12454b56682849ccd0cec00c9cb4611e23e 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")
diff --git a/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
new file mode 100644
index 000000000..714e3a530
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0035-REMOVE-DEPENDENCY-nose-to-avoid-nose.importer-warnin.patch
@@ -0,0 +1,1815 @@ 
+From 878af8e5a05b138ecbb63bcfcae522669cabb034 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}
diff --git a/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
new file mode 100644
index 000000000..3b0f4823a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0036-FIX-Remove-test-from-pytest-run.patch
@@ -0,0 +1,22 @@ 
+From e706a7720cc8e75b4fd8e43e43139901a701b16b 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/
diff --git a/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
new file mode 100644
index 000000000..8021f2088
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0037-Select-by-location-Add-support-for-Scenario-containe.patch
@@ -0,0 +1,652 @@ 
+From 6b9f9eb25283c2bbdc742aa2f3fcd5bcb33ea618 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
new file mode 100644
index 000000000..d64c466f5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0038-docs-Add-description-for-Select-by-location-for-Scen.patch
@@ -0,0 +1,58 @@ 
+From 97f18f67e7ede362b5817700521df790479d8061 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, ...).
diff --git a/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
new file mode 100644
index 000000000..9041195d5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0039-tests-Fix-warnings-for-python3.patch
@@ -0,0 +1,50 @@ 
+From 9b451c67a09acafa4c82af278535368cb1ee9b4a 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
diff --git a/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
new file mode 100644
index 000000000..44611cc84
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0040-Use-cucumber-tag-expressions-1.1.2-to-fix-warnings.patch
@@ -0,0 +1,55 @@ 
+From 6fcb1210f8d2790c41475e2eac6de70095da7865 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",
diff --git a/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
new file mode 100644
index 000000000..94aaee98d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-behave/0041-MENTION-ENHANCEMENT-Support-emojis-in-feature-files-.patch
@@ -0,0 +1,91 @@ 
+From 89181e0c0edef7b723f4d314c5bb55b32368df49 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
++
++