├── setup.cfg ├── tox.ini ├── pytest_notifier ├── pytest_logo.png ├── test_notifier.py ├── utils.py ├── test_utils.py ├── notifier.py └── __init__.py ├── .gitignore ├── CHANGELOG.rst ├── setup.py └── README.rst /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27,py34,py35,py36 3 | 4 | [testenv] 5 | commands=python setup.py test 6 | -------------------------------------------------------------------------------- /pytest_notifier/pytest_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratson/pytest-notifier/HEAD/pytest_notifier/pytest_logo.png -------------------------------------------------------------------------------- /pytest_notifier/test_notifier.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from .notifier import notify_via_terminal_notifier 4 | 5 | 6 | def test_notify_via_terminal_notifier(mocker): 7 | mocker.patch('subprocess.check_call') 8 | notify_via_terminal_notifier('title', 'body') 9 | subprocess.check_call.assert_called_once() 10 | -------------------------------------------------------------------------------- /pytest_notifier/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | from collections import namedtuple 3 | 4 | 5 | Info = namedtuple('Info', ['total', 'passed', 'failed']) 6 | 7 | 8 | def terminal_reporter_info(tr): 9 | passed = len(tr.stats.get('passed', [])) 10 | failed = sum([len(tr.stats.get('failed', [])), 11 | len(tr.stats.get('error', []))]) 12 | return Info( 13 | total=passed + failed, 14 | passed=passed, 15 | failed=failed, 16 | ) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /pytest_notifier/test_utils.py: -------------------------------------------------------------------------------- 1 | from .utils import Info, terminal_reporter_info 2 | 3 | 4 | def test_terminal_reporter_info_test_counts(mocker): 5 | m = mocker.MagicMock( 6 | stats={ 7 | 'passed': [ 8 | mocker.MagicMock(when='call'), 9 | mocker.MagicMock(when='call'), 10 | ], 11 | 'failed': [ 12 | mocker.MagicMock(when='call'), 13 | ], 14 | 'error': [ 15 | mocker.MagicMock(when='setup', outcome='failed'), 16 | ], 17 | }, 18 | ) 19 | info = terminal_reporter_info(m) 20 | assert info.total == 4 21 | assert info.passed == 2 22 | assert info.failed == 2 23 | -------------------------------------------------------------------------------- /pytest_notifier/notifier.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | 5 | def notify_via_libnotify(title, body, icon=None): 6 | args = ['notify-send', title, body] 7 | subprocess.check_call(args) 8 | 9 | 10 | def notify_via_terminal_notifier(title, body, icon=None): 11 | args = ['terminal-notifier', '-title', title, '-message', body] 12 | if icon: 13 | args.extend(['-appIcon', icon]) 14 | subprocess.check_call(args) 15 | 16 | 17 | def notify_via_apple_script(title, body, **kwargs): 18 | command = [ 19 | 'osascript', '-e', 20 | 'display notification "{body}" with title "{title}"' 21 | ''.format(title=title, body=body), 22 | ] 23 | p = subprocess.Popen(command) 24 | p.wait() 25 | 26 | 27 | def notify(**kwargs): 28 | notify_functions = { 29 | 'darwin': (notify_via_terminal_notifier, 30 | notify_via_apple_script, 31 | notify_via_libnotify), 32 | 'linux': (notify_via_libnotify,), 33 | }.get(sys.platform, (notify_via_libnotify, 34 | notify_via_terminal_notifier, 35 | notify_via_apple_script,)) 36 | for f in notify_functions: 37 | try: 38 | f(**kwargs) 39 | except OSError: 40 | pass 41 | else: 42 | break 43 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Change Log 3 | ========== 4 | 5 | 1.0.4 (2020-6-12) 6 | ================== 7 | 8 | - Compatibility with pytest-xdist 9 | 10 | 11 | 1.0.3 (2019-9-23) 12 | ================== 13 | 14 | - Compatibility with pytest 5 15 | 16 | 17 | 1.0.2 (2017-12-24) 18 | ================== 19 | 20 | - Fix failed message typo (`#8 `_) 21 | 22 | 23 | 1.0.1 (2017-07-01) 24 | ================== 25 | 26 | - Fix ``terminal-notifier`` crash (`#6 `_) 27 | 28 | 29 | 1.0.0 (2017-03-31) 30 | ================== 31 | 32 | **New Features** 33 | 34 | - Add ``--notifier-off`` option 35 | 36 | **Behavioural Changes** 37 | 38 | - Remove all old options 39 | - Count setup errors as failed 40 | - Update notificaiton message with emoji and pytest icon 41 | 42 | 43 | 0.3.1 (2017-01-21) 44 | ================== 45 | 46 | - Avoid subprocess.CalledProcessError when interrupted 47 | 48 | 49 | 0.3 (2016-06-15) 50 | ================ 51 | 52 | **New Features** 53 | 54 | - Allow override notifier titles based on zero/pass/fail (`#1 `_) 55 | 56 | **Behavioural Changes** 57 | 58 | - Exclude setup/teardown from test counts (`#2 `_) 59 | 60 | 61 | 0.2 (2016-01-09) 62 | ================ 63 | 64 | - Add libnotify support 65 | 66 | 67 | 0.1 (2016-01-07) 68 | ================ 69 | 70 | - Initial public release 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | tests_require = [ 4 | 'pytest', 5 | 'pytest-mock', 6 | ] 7 | 8 | setuptools.setup( 9 | name="pytest-notifier", 10 | version="1.0.4", 11 | url="https://github.com/ratson/pytest-notifier", 12 | 13 | author="Ratson", 14 | author_email="contact@ratson.name", 15 | 16 | description="A pytest plugin to notify test result", 17 | long_description=open('README.rst').read(), 18 | keywords=[ 19 | 'pytest', 'pytest-', 'osx', 'linux', 'notifications', 'notifier', 20 | 'notificationcenter', 'py.test', 'terminal-notifier', 'libnotify'], 21 | 22 | packages=setuptools.find_packages(), 23 | 24 | install_requires=['pytest'], 25 | setup_requires=['pytest-runner'], 26 | tests_require=tests_require, 27 | 28 | classifiers=[ 29 | 'Development Status :: 3 - Alpha', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Programming Language :: Python', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'Programming Language :: Python :: 3.5', 38 | 'Programming Language :: Python :: 3.6', 39 | 'Programming Language :: Python :: 3.7', 40 | ], 41 | 42 | entry_points={ 43 | 'pytest11': [ 44 | 'notifier = pytest_notifier', 45 | ], 46 | }, 47 | ) 48 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-notifier 2 | =============== 3 | 4 | .. image:: https://img.shields.io/pypi/v/pytest-notifier.svg 5 | :target: https://pypi.python.org/pypi/pytest-notifier 6 | :alt: Latest PyPI version 7 | 8 | A `pytest`_ plugin to notify test result on Linux and OS X. 9 | 10 | 11 | Requirements 12 | ------------ 13 | 14 | Linux 15 | ^^^^^ 16 | 17 | Make sure ``notify-send`` is exists in ``$PATH``, 18 | which comes from ``libnotify``. 19 | 20 | OS X 21 | ^^^^ 22 | 23 | OS X 10.8 and higher is required. 24 | 25 | Optionally, you could install `terminal-notifier`_ 26 | to avoid Script Editor being opened when notification icon is clicked. 27 | 28 | 29 | Installation 30 | ------------ 31 | 32 | You can install ``pytest-notifier`` via `pip`_ from `PyPI`_:: 33 | 34 | $ pip install pytest-notifier 35 | 36 | 37 | 38 | Usage 39 | ----- 40 | 41 | Once installed, notification will be shown after each test run. 42 | 43 | By using with `pytest-watch `_, 44 | you can get continous feedback during development:: 45 | 46 | $ ptw 47 | 48 | You can use ``--notifier-off`` flag to disable notification:: 49 | 50 | $ pytest --notifier-off 51 | 52 | 53 | Licence 54 | ------- 55 | 56 | Distributed under the terms of the `MIT`_ license, ``pytest-notifier`` is free and open source software 57 | 58 | 59 | Issues 60 | ------ 61 | 62 | If you encounter any problems, please `file an issue`_ along with a detailed description. 63 | 64 | .. _`file an issue`: https://github.com/ratson/pytest-notifier/issues 65 | .. _`MIT`: http://opensource.org/licenses/MIT 66 | .. _`pip`: https://pypi.python.org/pypi/pip/ 67 | .. _`PyPI`: https://pypi.python.org/pypi 68 | .. _`pytest`: https://github.com/pytest-dev/pytest 69 | .. _`terminal-notifier`: https://github.com/julienXX/terminal-notifier 70 | -------------------------------------------------------------------------------- /pytest_notifier/__init__.py: -------------------------------------------------------------------------------- 1 | """pytest-notifier - A pytest plugin to notify test result""" 2 | from __future__ import unicode_literals 3 | import os 4 | 5 | try: 6 | # pytest < 5 7 | from _pytest.main import EXIT_INTERRUPTED 8 | except ImportError: 9 | # pytest >= 5 10 | from _pytest.main import ExitCode 11 | EXIT_INTERRUPTED = ExitCode.INTERRUPTED 12 | 13 | from .notifier import notify 14 | from .utils import terminal_reporter_info 15 | 16 | 17 | def pytest_addoption(parser): 18 | """ 19 | Adds options to control notifications. 20 | """ 21 | group = parser.getgroup('terminal reporting') 22 | group.addoption( 23 | '--notifier-off', 24 | action='store_true', 25 | dest='notifier_off', 26 | help='Turn off test result notifications.', 27 | ) 28 | 29 | 30 | def pytest_terminal_summary(terminalreporter, exitstatus): 31 | 32 | # If run together with pytest-xdist 33 | # We don't want to send notifications from workers 34 | if os.getenv('PYTEST_XDIST_WORKER'): 35 | return 36 | 37 | if terminalreporter.config.option.notifier_off: 38 | return 39 | 40 | if exitstatus == EXIT_INTERRUPTED: 41 | return 42 | 43 | info = terminal_reporter_info(terminalreporter) 44 | 45 | if info.total == 0: 46 | return 47 | elif info.passed == info.total: 48 | title = '100% Passed' 49 | msg = '\u2705 {} tests passed'.format(info.passed) 50 | elif info.failed > 0: 51 | title = '{}% Failed'.format(int(info.failed * 100 / info.total)) 52 | msg = '\u26D4\uFE0F {0.failed} of {0.total} tests failed'.format(info) 53 | else: 54 | title = 'Unexpected Result' 55 | msg = 'Please report an issue how to trigger this' 56 | 57 | icon = os.path.join(os.path.dirname(__file__), 'pytest_logo.png') 58 | notify(title=title, body=msg, icon=icon) 59 | --------------------------------------------------------------------------------