Repository: pytest-dev/pytest-messenger (language: python)

Issue:
wrong test count when using xdist
running 10 tests with xdist.. seems to multiply test count by worker count in output


Discussion / hints:

Fix patch:
diff --git a/docs/conf.py b/docs/conf.py
index 736bf35..281d084 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -20,6 +20,7 @@
 #
 import os
 import sys
+
 sys.path.insert(0, os.path.abspath('..'))
 
 import pytest_messenger
@@ -78,7 +79,6 @@
 # If true, `todo` and `todoList` produce output, else they produce nothing.
 todo_include_todos = False
 
-
 # -- Options for HTML output -------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
@@ -97,13 +97,11 @@
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ['_static']
 
-
 # -- Options for HTMLHelp output ---------------------------------------
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'pytest_messengerdoc'
 
-
 # -- Options for LaTeX output ------------------------------------------
 
 latex_elements = {
@@ -133,7 +131,6 @@
      u'LaserPhaser', 'manual'),
 ]
 
-
 # -- Options for manual page output ------------------------------------
 
 # One entry per manual page. List of tuples
@@ -144,7 +141,6 @@
      [author], 1)
 ]
 
-
 # -- Options for Texinfo output ----------------------------------------
 
 # Grouping the document tree into Texinfo files. List of tuples
@@ -158,6 +154,3 @@
      'One line description of project.',
      'Miscellaneous'),
 ]
-
-
-
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 0abd642..36b4c48 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,2 +1,3 @@
 mock
+pytest-xdist
 requests==2.24.0
diff --git a/requirements_pub.txt b/requirements_pub.txt
index 72887e4..e648f06 100644
--- a/requirements_pub.txt
+++ b/requirements_pub.txt
@@ -7,6 +7,7 @@ tox==3.19.0
 coverage==5.2
 Sphinx==3.3.0
 twine==3.1.1
-pytest==6.2.1
+pytest
+pytest-xdist
 mock==4.0.1
 pytest-runner==5.2
diff --git a/setup.py b/setup.py
index 7bdcb1a..401004a 100644
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@
 
 setup_requirements = ['pytest-runner', ]
 
-test_requirements = ['pytest', ]
+test_requirements = ['pytest', 'pytest-xdist']
 
 setup(
     author="LaserPhaser",
@@ -29,7 +29,6 @@
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
         'Natural Language :: English',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
@@ -50,6 +49,7 @@
     url='https://github.com/pytest-dev/pytest-messenger',
     version='3.1.0',
     zip_safe=False,
+    python_requires='>=3.6',
     entry_points={
         'pytest11': [
             'pytest-messenger = pytest_messenger.plugin',


Test patch:
diff --git a/pytest_messenger/dingtalk.py b/pytest_messenger/dingtalk.py
index 8db252e..ee5eb0d 100644
--- a/pytest_messenger/dingtalk.py
+++ b/pytest_messenger/dingtalk.py
@@ -3,7 +3,7 @@
 import hmac
 import time
 import urllib.parse
-import json
+
 import requests
 
 
diff --git a/pytest_messenger/slack.py b/pytest_messenger/slack.py
index de3fd14..c95858f 100644
--- a/pytest_messenger/slack.py
+++ b/pytest_messenger/slack.py
@@ -1,6 +1,7 @@
-import requests
 import json
 
+import requests
+
 
 def add_slack_options(parser):
     group = parser.getgroup('pytest-messenger[slack]')
@@ -90,10 +91,19 @@ def add_slack_options(parser):
         help='Set icon (a url) for a failed run. Overrides slack_failed_icon'
     )
 
+    group.addoption(
+        '--only_failed',
+        action='store',
+        dest='only_failed',
+        default=False,
+        help='Send only failed results to the messenger'
+    )
+
 
 def slack_send_message(test_result, config, exitstatus):
     timeout = config.option.slack_timeout
     report_link = config.option.slack_report_link
+    only_failed = config.option.only_failed
     slack_hook = config.option.slack_hook
     channel = config.option.slack_channel
     ssl_verify = config.option.ssl_verify
@@ -107,13 +117,22 @@ def slack_send_message(test_result, config, exitstatus):
         color = '#ff0000'
         emoji = config.option.slack_failed_emoji
         icon = config.option.slack_failed_icon
-    final_results = 'Passed=%s Failed=%s Skipped=%s Error=%s XFailed=%s XPassed=%s' % (
-        test_result.passed,
-        test_result.failed,
-        test_result.skipped,
-        test_result.error,
-        test_result.xfailed,
-        test_result.xpassed)
+    if only_failed:
+        if test_result.failed == 0 and test_result.error == 0:
+            return  # Do not send anything when all passed and no errors.
+        final_results = 'Failed=%s Error=%s' % (
+            test_result.failed,
+            test_result.error,
+        )
+
+    else:
+        final_results = 'Passed=%s Failed=%s Skipped=%s Error=%s XFailed=%s XPassed=%s' % (
+            test_result.passed,
+            test_result.failed,
+            test_result.skipped,
+            test_result.error,
+            test_result.xfailed,
+            test_result.xpassed)
     if report_link:
         final_results = '<%s|%s>' % (report_link, final_results)
     if message_prefix:
diff --git a/tests/test_ding.py b/tests/test_ding.py
index 9235b33..d600ea1 100644
--- a/tests/test_ding.py
+++ b/tests/test_ding.py
@@ -1,8 +1,5 @@
-import json
 import mock
 
-import pytest
-
 
 def test_pytest_messenger_ding_failed(testdir):
     """Make sure that our pytest-messenger works."""
diff --git a/tests/test_slack.py b/tests/test_slack.py
index 15233f8..f4de4cd 100644
--- a/tests/test_slack.py
+++ b/tests/test_slack.py
@@ -1,6 +1,6 @@
 import json
-import mock
 
+import mock
 import pytest
 
 
@@ -234,3 +234,141 @@ def test_icon_url():
 
         assert url == expected_url
         assert emoji is None
+
+
+def test_only_failed(testdir):
+    """Make sure that our pytest-messenger works."""
+
+    testdir.makepyfile(
+        """
+        import pytest
+        def test_pass():
+            assert 1 == 1
+
+
+        def test_fail():
+            assert 1 == 2
+
+        def test_error(test):
+            assert 1 == ""
+        """
+    )
+
+    slack_hook_host = 'http://test.com/any_hash'
+    slack_hook_username = 'regression Testing'
+    slack_hook_report_host = 'http://report_link.com'
+    slack_hook_channel = 'test'
+    slack_hook_icon_emoji = ':thumbsdown:'
+    expected_text = '<http://report_link.com|Failed=1 Error=1>'
+    with mock.patch('requests.post') as mock_post:
+        testdir.runpytest('--slack_channel', slack_hook_channel,
+                          '--slack_hook', slack_hook_host,
+                          '--slack_report_link', slack_hook_report_host,
+                          '--slack_username', slack_hook_username,
+                          '--slack_failed_emoji', slack_hook_icon_emoji,
+                          '--only_failed', True)
+
+        called_data = json.loads(mock_post.call_args[1]['data'])
+        called_host = mock_post.call_args[0][0]
+        called_channel = called_data['channel']
+        called_username = called_data['username']
+        text = called_data['attachments'][0]['text']
+        color = called_data['attachments'][0]['color']
+        emoji = called_data['icon_emoji']
+
+        assert called_host == slack_hook_host
+        assert text == expected_text
+        assert called_channel == slack_hook_channel
+        assert called_username == slack_hook_username
+        assert color == '#ff0000'
+        assert emoji == slack_hook_icon_emoji
+
+
+def test_only_failed_no_fails(testdir):
+    """Make sure that our pytest-messenger works."""
+
+    testdir.makepyfile(
+        """
+        import pytest
+        def test_pass():
+            assert 1 == 1
+
+        """
+    )
+
+    slack_hook_host = 'http://test.com/any_hash'
+    slack_hook_username = 'regression Testing'
+    slack_hook_report_host = 'http://report_link.com'
+    slack_hook_channel = 'test'
+    slack_hook_icon_emoji = ':thumbsdown:'
+
+    with mock.patch('requests.post') as mock_post:
+        testdir.runpytest('--slack_channel', slack_hook_channel,
+                          '--slack_hook', slack_hook_host,
+                          '--slack_report_link', slack_hook_report_host,
+                          '--slack_username', slack_hook_username,
+                          '--slack_failed_emoji', slack_hook_icon_emoji,
+                          '--only_failed', True)
+        assert mock_post.call_args is None
+
+
+def test_xdist_count(testdir):
+    """Make sure that our pytest-messenger works."""
+
+    testdir.makepyfile(
+        """
+        import pytest
+        def test_pass_1():
+            assert 1 == 1
+
+        def test_pass_2():
+            assert 1 == 1
+
+        def test_pass_3():
+            assert 1 == 1
+
+        def test_pass_4():
+            assert 1 == 1
+
+        def test_pass_5():
+            assert 1 == 1
+
+        def test_pass_6():
+            assert 1 == 1
+
+        def test_pass_7():
+            assert 1 == 1
+
+        def test_fail_8():
+            assert 1 == 0
+        """
+    )
+
+    slack_hook_host = 'http://test.com/any_hash'
+    slack_hook_username = 'regression Testing'
+    slack_hook_report_host = 'http://report_link.com'
+    slack_hook_channel = 'test'
+    slack_hook_icon_emoji = ':thumbsdown:'
+    expected_text = '<http://report_link.com|Passed=7 Failed=1 Skipped=0 Error=0 XFailed=0 XPassed=0>'
+    with mock.patch('requests.post') as mock_post:
+        testdir.runpytest('-n', '2',
+                          '--slack_channel', slack_hook_channel,
+                          '--slack_hook', slack_hook_host,
+                          '--slack_report_link', slack_hook_report_host,
+                          '--slack_username', slack_hook_username,
+                          '--slack_failed_emoji', slack_hook_icon_emoji)
+
+        called_data = json.loads(mock_post.call_args[1]['data'])
+        called_host = mock_post.call_args[0][0]
+        called_channel = called_data['channel']
+        called_username = called_data['username']
+        text = called_data['attachments'][0]['text']
+        color = called_data['attachments'][0]['color']
+        emoji = called_data['icon_emoji']
+
+        assert called_host == slack_hook_host
+        assert text == expected_text
+        assert called_channel == slack_hook_channel
+        assert called_username == slack_hook_username
+        assert color == '#ff0000'
+        assert emoji == slack_hook_icon_emoji