Blob Blame History Raw
From 72257e2db209f60626e43a1335e656f868b72935 Mon Sep 17 00:00:00 2001
From: Petr Schindler <pschindl@redhat.com>
Date: Fri, 20 Sep 2019 08:38:18 +0200
Subject: [PATCH] Backport for py38 fixes

Cherry picked from commits:
83906ba779956af9437defcb8975debb18440e0d
ce8f2eddd832b34fb4f628d898383db16f5c92ed
e69d28ea8ce3bf361b278bfe5e10cafa6bdf6760
c000c88eb5239b87f299c85e83b349b0ef387ae7

---
 behave.ini                            |  3 +-
 features/environment.py               | 14 ++++++
 features/step.duplicated_step.feature | 20 ++++----
 issue.features/environment.py         | 38 ++++++++++++---
 issue.features/issue0330.feature      | 64 ++++++++++++++++++++++++
 issue.features/issue0446.feature      | 70 +++++++++++++++++++++++++++
 issue.features/issue0457.feature      | 49 +++++++++++++++++++
 test/test_runner.py                   |  6 +++
 tests/api/_test_async_step34.py       |  9 ++--
 tests/unit/test_capture.py            |  2 +
 tox.ini                               |  2 +-
 11 files changed, 255 insertions(+), 22 deletions(-)

diff --git a/behave.ini b/behave.ini
index 431956d..1a18843 100644
--- a/behave.ini
+++ b/behave.ini
@@ -15,8 +15,9 @@ show_skipped = false
 format   = rerun
     progress3
 outfiles = rerun.txt
-    reports/report_progress3.txt
+    build/behave.reports/report_progress3.txt
 junit = true
+junit_directory = build/behave.reports
 logging_level = INFO
 # logging_format = LOG.%(levelname)-8s  %(name)-10s: %(message)s
 # logging_format = LOG.%(levelname)-8s  %(asctime)s  %(name)-10s: %(message)s
diff --git a/features/environment.py b/features/environment.py
index 4744e89..3769ee4 100644
--- a/features/environment.py
+++ b/features/environment.py
@@ -1,5 +1,7 @@
 # -*- coding: UTF-8 -*-
+# FILE: features/environemnt.py
 
+from __future__ import absolute_import, print_function
 from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
 from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
 import platform
@@ -20,6 +22,15 @@ active_tag_value_provider = {
 }
 active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
 
+
+def print_active_tags_summary():
+    active_tag_data = active_tag_value_provider
+    print("ACTIVE-TAG SUMMARY:")
+    print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+    # print("use.with_os=%s" % active_tag_data.get("os"))
+    print()
+
+
 # -----------------------------------------------------------------------------
 # HOOKS:
 # -----------------------------------------------------------------------------
@@ -30,11 +41,14 @@ def before_all(context):
     setup_python_path()
     setup_context_with_global_params_test(context)
     setup_command_shell_processors4behave()
+    print_active_tags_summary()
+
 
 def before_feature(context, feature):
     if active_tag_matcher.should_exclude_with(feature.tags):
         feature.skip(reason=active_tag_matcher.exclude_reason)
 
+
 def before_scenario(context, scenario):
     if active_tag_matcher.should_exclude_with(scenario.effective_tags):
         scenario.skip(reason=active_tag_matcher.exclude_reason)
diff --git a/features/step.duplicated_step.feature b/features/step.duplicated_step.feature
index 59888b0..396cca2 100644
--- a/features/step.duplicated_step.feature
+++ b/features/step.duplicated_step.feature
@@ -32,11 +32,11 @@ Feature: Duplicated Step Definitions
         AmbiguousStep: @given('I call Alice') has already been defined in
         existing step @given('I call Alice') at features/steps/alice_steps.py:3
         """
-    And the command output should contain:
-        """
-        File "features/steps/alice_steps.py", line 7, in <module>
-        @given(u'I call Alice')
-        """
+    # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
+    # And the command output should contain:
+    #    """
+    #    File "features/steps/alice_steps.py", line 7, in <module>
+    #    """
 
 
   Scenario: Duplicated Step Definition in another File
@@ -70,11 +70,11 @@ Feature: Duplicated Step Definitions
         AmbiguousStep: @given('I call Bob') has already been defined in
         existing step @given('I call Bob') at features/steps/bob1_steps.py:3
         """
-    And the command output should contain:
-        """
-        File "features/steps/bob2_steps.py", line 3, in <module>
-        @given('I call Bob')
-        """
+    # -- DISABLED: Python 3.8 traceback line numbers differ w/ decorators (+1).
+    # And the command output should contain:
+    #    """
+    #    File "features/steps/bob2_steps.py", line 3, in <module>
+    #    """
 
   @xfail
   Scenario: Duplicated Same Step Definition via import from another File
diff --git a/issue.features/environment.py b/issue.features/environment.py
index 3737155..dc8a7fb 100644
--- a/issue.features/environment.py
+++ b/issue.features/environment.py
@@ -1,5 +1,5 @@
 # -*- coding: UTF-8 -*-
-# FILE: features/environment.py
+# FILE: issue.features/environemnt.py
 # pylint: disable=unused-argument
 """
 Functionality:
@@ -7,17 +7,20 @@ Functionality:
   * active tags
 """
 
-from __future__ import print_function
+
+from __future__ import absolute_import, print_function
 import sys
 import platform
 import os.path
 import six
 from behave.tag_matcher import ActiveTagMatcher
 from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
-# PREPARED:
-# from behave.tag_matcher import setup_active_tag_values
+# PREPARED: from behave.tag_matcher import setup_active_tag_values
 
 
+# ---------------------------------------------------------------------------
+# TEST SUPPORT: For Active Tags
+# ---------------------------------------------------------------------------
 def require_tool(tool_name):
     """Check if a tool (an executable program) is provided on this platform.
 
@@ -45,12 +48,14 @@ def require_tool(tool_name):
     # print("TOOL-NOT-FOUND: %s" % tool_name)
     return False
 
+
 def as_bool_string(value):
     if bool(value):
         return "yes"
     else:
         return "no"
 
+
 def discover_ci_server():
     # pylint: disable=invalid-name
     ci_server = "none"
@@ -67,11 +72,16 @@ def discover_ci_server():
     return ci_server
 
 
+# ---------------------------------------------------------------------------
+# BEHAVE SUPPORT: Active Tags
+# ---------------------------------------------------------------------------
 # -- MATCHES ANY TAGS: @use.with_{category}={value}
 # NOTE: active_tag_value_provider provides category values for active tags.
+python_version = "%s.%s" % sys.version_info[:2]
 active_tag_value_provider = {
     "python2": str(six.PY2).lower(),
     "python3": str(six.PY3).lower(),
+    "python.version": python_version,
     # -- python.implementation: cpython, pypy, jython, ironpython
     "python.implementation": platform.python_implementation().lower(),
     "pypy":    str("__pypy__" in sys.modules).lower(),
@@ -81,17 +91,33 @@ active_tag_value_provider = {
 }
 active_tag_matcher = ActiveTagMatcher(active_tag_value_provider)
 
+
+def print_active_tags_summary():
+    active_tag_data = active_tag_value_provider
+    print("ACTIVE-TAG SUMMARY:")
+    print("use.with_python.version=%s" % active_tag_data.get("python.version"))
+    # print("use.with_platform=%s" % active_tag_data.get("platform"))
+    # print("use.with_os=%s" % active_tag_data.get("os"))
+    print()
+
+
+# ---------------------------------------------------------------------------
+# BEHAVE HOOKS:
+# ---------------------------------------------------------------------------
 def before_all(context):
     # -- SETUP ACTIVE-TAG MATCHER (with userdata):
     # USE: behave -D browser=safari ...
-    # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
-    #                                     context.config.userdata)
+    # NOT-NEEDED:
+    # setup_active_tag_values(active_tag_value_provider, context.config.userdata)
     setup_command_shell_processors4behave()
+    print_active_tags_summary()
+
 
 def before_feature(context, feature):
     if active_tag_matcher.should_exclude_with(feature.tags):
         feature.skip(reason=active_tag_matcher.exclude_reason)
 
+
 def before_scenario(context, scenario):
     if active_tag_matcher.should_exclude_with(scenario.effective_tags):
         scenario.skip(reason=active_tag_matcher.exclude_reason)
diff --git a/issue.features/issue0330.feature b/issue.features/issue0330.feature
index dc1ebe7..81cb6e2 100644
--- a/issue.features/issue0330.feature
+++ b/issue.features/issue0330.feature
@@ -70,6 +70,7 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
     And note that "bob.feature is skipped"
 
 
+  @not.with_python.version=3.8
   Scenario: Junit report for skipped feature is created with --show-skipped
     When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
     Then it should pass with:
@@ -83,6 +84,23 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
       <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
       """
 
+  @use.with_python.version=3.8
+  Scenario: Junit report for skipped feature is created with --show-skipped
+    When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset"
+    Then it should pass with:
+      """
+      1 feature passed, 0 failed, 1 skipped
+      """
+    And a file named "test_results/TESTS-alice.xml" exists
+    And a file named "test_results/TESTS-bob.xml" exists
+    And the file "test_results/TESTS-bob.xml" should contain:
+      """
+      <testsuite name="bob.Bob" tests="1" errors="0" failures="0" skipped="1" time="0.0">
+      """
+      # -- HINT FOR: Python < 3.8
+      # <testsuite errors="0" failures="0" name="bob.Bob" skipped="1" tests="1" time="0.0">
+
+  @not.with_python.version=3.8
   Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
     When I run "behave --junit -t @tag1 --no-skipped"
     Then it should pass with:
@@ -102,7 +120,30 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
       """
     And note that "Charly2 is the skipped scenarion in charly.feature"
 
+  @use.with_python.version=3.8
+  Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped
+    When I run "behave --junit -t @tag1 --no-skipped"
+    Then it should pass with:
+      """
+      2 features passed, 0 failed, 1 skipped
+      2 scenarios passed, 0 failed, 2 skipped
+      """
+    And a file named "test_results/TESTS-alice.xml" exists
+    And a file named "test_results/TESTS-charly.xml" exists
+    And the file "test_results/TESTS-charly.xml" should contain:
+      """
+      <testsuite name="charly.Charly" tests="1" errors="0" failures="0" skipped="0"
+      """
+      # -- HINT FOR: Python < 3.8
+      # <testsuite errors="0" failures="0" name="charly.Charly" skipped="0" tests="1"
+    And the file "test_results/TESTS-charly.xml" should not contain:
+      """
+      <testcase classname="charly.Charly" name="Charly2"
+      """
+    And note that "Charly2 is the skipped scenarion in charly.feature"
+
 
+  @not.with_python.version=3.8
   Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
     When I run "behave --junit -t @tag1 --show-skipped"
     Then it should pass with:
@@ -122,3 +163,26 @@ Feature: Issue #330: Skipped scenarios are included in junit reports when --no-s
       """
     And note that "Charly2 is the skipped scenarion in charly.feature"
 
+
+  @use.with_python.version=3.8
+  Scenario: Junit report for skipped scenario is shown and counted with --show-skipped
+    When I run "behave --junit -t @tag1 --show-skipped"
+    Then it should pass with:
+      """
+      2 features passed, 0 failed, 1 skipped
+      2 scenarios passed, 0 failed, 2 skipped
+      """
+    And a file named "test_results/TESTS-alice.xml" exists
+    And a file named "test_results/TESTS-charly.xml" exists
+    And the file "test_results/TESTS-charly.xml" should contain:
+      """
+      <testsuite name="charly.Charly" tests="2" errors="0" failures="0" skipped="1"
+      """
+      # HINT: Python < 3.8
+      # <testsuite errors="0" failures="0" name="charly.Charly" skipped="1" tests="2"
+    And the file "test_results/TESTS-charly.xml" should contain:
+      """
+      <testcase classname="charly.Charly" name="Charly2" status="skipped"
+      """
+    And note that "Charly2 is the skipped scenarion in charly.feature"
+
diff --git a/issue.features/issue0446.feature b/issue.features/issue0446.feature
index a2ed892..901bdec 100644
--- a/issue.features/issue0446.feature
+++ b/issue.features/issue0446.feature
@@ -58,6 +58,7 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
         behave.reporter.junit.show_hostname = False
         """
 
+    @not.with_python.version=3.8
     Scenario: Hook error in before_scenario()
       When I run "behave -f plain --junit features/before_scenario_failure.feature"
       Then it should fail with:
@@ -86,6 +87,40 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
       And note that "the traceback is contained in the XML element <error/>"
 
 
+    @use.with_python.version=3.8
+    Scenario: Hook error in before_scenario()
+      When I run "behave -f plain --junit features/before_scenario_failure.feature"
+      Then it should fail with:
+        """
+        0 scenarios passed, 1 failed, 0 skipped
+        """
+      And the command output should contain:
+        """
+        HOOK-ERROR in before_scenario: RuntimeError: OOPS
+        """
+      And the file "reports/TESTS-before_scenario_failure.xml" should contain:
+        """
+        <testsuite name="before_scenario_failure.Alice" tests="1" errors="1" failures="0" skipped="0"
+        """
+        # -- HINT FOR: Python < 3.8
+        # <testsuite errors="1" failures="0" name="before_scenario_failure.Alice" skipped="0" tests="1"
+      And the file "reports/TESTS-before_scenario_failure.xml" should contain:
+        """
+        <error type="RuntimeError" message="HOOK-ERROR in before_scenario: RuntimeError: OOPS">
+        """
+        # -- HINT FOR: Python < 3.8
+        # <error message="HOOK-ERROR in before_scenario: RuntimeError: OOPS" type="RuntimeError">
+      And the file "reports/TESTS-before_scenario_failure.xml" should contain:
+        """
+        File "features/environment.py", line 6, in before_scenario
+          cause_hook_failure()
+        File "features/environment.py", line 2, in cause_hook_failure
+          raise RuntimeError("OOPS")
+        """
+      And note that "the traceback is contained in the XML element <error/>"
+
+
+    @not.with_python.version=3.8
     Scenario: Hook error in after_scenario()
       When I run "behave -f plain --junit features/after_scenario_failure.feature"
       Then it should fail with:
@@ -114,3 +149,38 @@ Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter
           raise RuntimeError("OOPS")
         """
       And note that "the traceback is contained in the XML element <error/>"
+
+
+    @use.with_python.version=3.8
+    Scenario: Hook error in after_scenario()
+      When I run "behave -f plain --junit features/after_scenario_failure.feature"
+      Then it should fail with:
+        """
+        0 scenarios passed, 1 failed, 0 skipped
+        """
+      And the command output should contain:
+        """
+          Scenario: B1
+            Given another step passes ... passed
+        HOOK-ERROR in after_scenario: RuntimeError: OOPS
+        """
+      And the file "reports/TESTS-after_scenario_failure.xml" should contain:
+        """
+        <testsuite name="after_scenario_failure.Bob" tests="1" errors="1" failures="0" skipped="0"
+        """
+        # -- HINT FOR: Python < 3.8
+        # <testsuite errors="1" failures="0" name="after_scenario_failure.Bob" skipped="0" tests="1"
+      And the file "reports/TESTS-after_scenario_failure.xml" should contain:
+        """
+        <error type="RuntimeError" message="HOOK-ERROR in after_scenario: RuntimeError: OOPS">
+        """
+        # -- HINT FOR: Python < 3.8
+        # <error message="HOOK-ERROR in after_scenario: RuntimeError: OOPS" type="RuntimeError">
+      And the file "reports/TESTS-after_scenario_failure.xml" should contain:
+        """
+        File "features/environment.py", line 10, in after_scenario
+          cause_hook_failure()
+        File "features/environment.py", line 2, in cause_hook_failure
+          raise RuntimeError("OOPS")
+        """
+      And note that "the traceback is contained in the XML element <error/>"
diff --git a/issue.features/issue0457.feature b/issue.features/issue0457.feature
index f80640e..46f96e9 100644
--- a/issue.features/issue0457.feature
+++ b/issue.features/issue0457.feature
@@ -24,6 +24,7 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
         """
 
 
+    @not.with_python.version=3.8
     Scenario: Use failing assertation in a JUnit XML report
       Given a file named "features/fails1.feature" with:
         """
@@ -44,6 +45,31 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
         <failure message="FAILED: My name is &quot;Alice&quot;"
         """
 
+    @use.with_python.version=3.8
+    Scenario: Use failing assertation in a JUnit XML report
+      Given a file named "features/fails1.feature" with:
+        """
+        Feature:
+          Scenario: Alice
+            Given a step fails with message:
+              '''
+              My name is "Alice"
+              '''
+        """
+      When I run "behave --junit features/fails1.feature"
+      Then it should fail with:
+        """
+        0 scenarios passed, 1 failed, 0 skipped
+        """
+      And the file "reports/TESTS-fails1.xml" should contain:
+        """
+        <failure type="AssertionError" message="FAILED: My name is &quot;Alice&quot;">
+        """
+        # -- HINT FOR: Python < 3.8
+        # <failure message="FAILED: My name is &quot;Alice&quot;"
+
+
+    @not.with_python.version=3.8
     Scenario: Use exception in a JUnit XML report
       Given a file named "features/fails2.feature" with:
         """
@@ -63,3 +89,26 @@ Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports
         """
         <error message="My name is &quot;Bob&quot; and &lt;here&gt; I am"
         """
+
+    @use.with_python.version=3.8
+    Scenario: Use exception in a JUnit XML report
+      Given a file named "features/fails2.feature" with:
+        """
+        Feature:
+          Scenario: Bob
+            Given a step fails with error and message:
+              '''
+              My name is "Bob" and <here> I am
+              '''
+        """
+      When I run "behave --junit features/fails2.feature"
+      Then it should fail with:
+        """
+        0 scenarios passed, 1 failed, 0 skipped
+        """
+      And the file "reports/TESTS-fails2.xml" should contain:
+        """
+        <error type="RuntimeError" message="My name is &quot;Bob&quot; and &lt;here&gt; I am">
+        """
+        # -- HINT FOR: Python < 3.8
+        # <error message="My name is &quot;Bob&quot; and &lt;here&gt; I am"
diff --git a/test/test_runner.py b/test/test_runner.py
index 70a7002..1b5afa2 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/tests/api/_test_async_step34.py b/tests/api/_test_async_step34.py
index c93fb74..4e4edf4 100644
--- a/tests/api/_test_async_step34.py
+++ b/tests/api/_test_async_step34.py
@@ -37,15 +37,16 @@ from .testing_support_async import AsyncStepTheory
 # -----------------------------------------------------------------------------
 # TEST MARKERS:
 # -----------------------------------------------------------------------------
-python_version = float("%s.%s" % sys.version_info[:2])
+# DEPRECATED: @asyncio.coroutine decorator (since: Python >= 3.8)
+_python_version = float("%s.%s" % sys.version_info[:2])
+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)")
 
-# xfail = pytest.mark.xfail
-py34_or_newer = pytest.mark.skipif(python_version < 3.4, reason="Needs Python >= 3.4")
 
 # -----------------------------------------------------------------------------
 # TESTSUITE:
 # -----------------------------------------------------------------------------
-@py34_or_newer
+@requires_py34_to_py37
 class TestAsyncStepDecorator34(object):
 
     def test_step_decorator_async_run_until_complete2(self):
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/tox.ini b/tox.ini
index 92f6679..16a392c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,7 @@
 
 [tox]
 minversion   = 2.3
-envlist      = py26, py27, py33, py34, py35, py36, pypy, docs
+envlist      = py26, py27, py33, py34, py35, py36, py37, py38, pypy, docs
 skip_missing_interpreters = True
 sitepackages = False
 indexserver =
-- 
2.23.0