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

Issue:
Version 3.1.0 installs on Python 2.7 but fails running
Two issues:

* pip installs pytest-messenger 3.1.0 on Python 2.7 even though that version does not run there (it fails in plugins.py line 13 on the type declarations that require Python 3.6 or higher and that were introduced in version 3.1.0 of the package). Pip does this because the package does not declare its supported Python versions using the `python_requires` argument of `setup()`.
  Solution: Add a `python_requires='>=3.6'` argument to `setup()` in `setup.py`.

* The Python versions stated in the "Programming Language" Trove classifiers still include Python 2.7 which is incorrect (Note that the Trove classifiers are not used by Pip to decide about Python version support).
  Solution: Remove Python 2.7 from the `classifiers` argument of `setup()` in `setup.py`.


Discussion / hints:
Also, in `setup.cfg` `universal` shall be set to 0 to avoid building Python2/3 wheels.

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