pax_global_header00006660000000000000000000000064137472417570014533gustar00rootroot0000000000000052 comment=e987effed02b37d30f6cbc1e8c7f51a4b3b9e62f pytest-repeat-0.9.1/000077500000000000000000000000001374724175700143505ustar00rootroot00000000000000pytest-repeat-0.9.1/.gitignore000066400000000000000000000001121374724175700163320ustar00rootroot00000000000000__pycache__ .cache .eggs .DS_Store .tox build dist *.egg-info *.pyc *.pyo pytest-repeat-0.9.1/.travis.yml000066400000000000000000000035731374724175700164710ustar00rootroot00000000000000language: python matrix: include: # On Python 2 we test pytest 3 (minimum supported) and 4 (latest to support Python 2) - python: 2.7 env: TOXENV=py27-pytest3 - python: pypy env: TOXENV=pypy-pytest3 - python: 2.7 env: TOXENV=py27-pytest4 - python: pypy env: TOXENV=pypy-pytest4 # On Python 3.5+ we test pytest 3 (minimum supported) and 6 (latest) - python: 3.5 env: TOXENV=py35-pytest3 - python: 3.5 env: TOXENV=py35-pytest6 - python: 3.6 env: TOXENV=py36-pytest3 - python: 3.6 env: TOXENV=py36-pytest6 - python: 3.7 env: TOXENV=py37-pytest3 - python: 3.7 env: TOXENV=py37-pytest6 - python: 3.8 env: TOXENV=py38-pytest3 - python: 3.8 env: TOXENV=py38-pytest6 - python: 3.9 env: TOXENV=py39-pytest3 - python: 3.9 env: TOXENV=py39-pytest6 - python: pypy3 env: TOXENV=pypy3-pytest3 - python: pypy3 env: TOXENV=pypy3-pytest6 - python: 3.9 env: TOXENV=flake8 install: - pip install tox-travis script: - tox deploy: provider: pypi user: davehunt password: secure: IuoBL03YKh7rZw78aQqRrlD6YcvwSPK6nKTpoxFc3uiljOufyfV3s4rGffSxEl4gAkMlBz3SXnfPKJPqNizfp3TEJXrc84TBO/Iq+U7j43rWyUluzJ0okPgU4892aalQiEvVyrrjEaMWA5ndhX7tkyTICN0BfHlKPmMrN+1mq73y4eUQl49CJWsbhF8Qnc5tcDh2athtgFA3fG0kSWDZHdw02i2JC1qlm+WvqBfw813BeUnmR55HKRgPQMJ2whUzab7m2QBg0HkjniNXaqS+aYY8fMGrntNtEDRrWO130ioeUqpFQvg7FoigbE5eQBX07Z50lpJn5CuCgnMzOrDpkXMy7Y+k+MyxKFUWaaqZjZiKolaPSs2qW5zggmCD4ECa5g8BpfpBkUj+D6iFkYyZIJUdq2jX0JFA536L9H2E1KOATxE8u0utQvkKcJhHgWVa0iw0q14MWSVLcZ8OdzP0Bo5nVVICcZAyIj7FQn0pIRnoXQ7cIpGbmMeJUBT9n08hyUYWoxfXKNt8KNkKDww81nAVphuz6ic1KwEKIDt3MJlHHbazjt59/+SBU+L0bDhS5ZHZkHAVHyl3EP18DCAYZWJjR4TZxlLRb2o3BUTcSwqFcIlTGw1O1DbZ/LTaGRRUxwh41AY98DKxrujxerALgX3MIlaS6WkM1FbOdO+ZB1Q= distributions: sdist bdist_wheel on: tags: true repo: pytest-dev/pytest-repeat python: 3.8 condition: "$TOXENV = py38-pytest6" pytest-repeat-0.9.1/CHANGES.rst000066400000000000000000000026261374724175700161600ustar00rootroot00000000000000Release Notes ------------- **0.9.1 (2020-10-31)** * Using ``@pytest.mark.repeat(1)`` can now be used to disable repeating a test regardless of the ``--count`` parameter given in the command-line. * Python 3.4 is no longer officially supported. **0.9.0 (-)** * Not released do PyPI due to a deploy problem. **0.8.0 (2019-02-26)** * Fix mark deprecation warnings in new pytest versions. * ``pytest-repeat`` now requires pytest>=3.6. **0.7.0 (2018-08-23)** * Move step number to the end of the parametrisation ID * Thanks to `@gdyuldin `_ for suggesting this enhancement and providing a patch **0.6.0 (2018-08-01)** * Add option for controlling the scope of the repeat parameterisation * Thanks to `@gdyuldin `_ for suggesting this enhancement and providing a patch **0.5.0 (2018-07-19)** * Allow repeating a test using a decorator (`#16 `_) * Thanks to `@Peque `_ for suggesting this enhancement and providing a patch **0.4.0 (2016-08-25)** * No changes - testing deploy to PyPI from Travis CI **0.4.0 (2016-08-09)** * Fix deprecation warning present in pytest 3.0 for type argument in addoption **0.3.0 (2016-06-30)** * Added support for repeating parameterised tests **0.2 (2015-10-27)** * README updates **0.1 (2015-10-19)** * Initial release pytest-repeat-0.9.1/LICENSE000066400000000000000000000003141374724175700153530ustar00rootroot00000000000000This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://www.mozilla.org/en-US/MPL/2.0/. pytest-repeat-0.9.1/MANIFEST.in000066400000000000000000000000271374724175700161050ustar00rootroot00000000000000include pyproject.toml pytest-repeat-0.9.1/README.rst000066400000000000000000000067071374724175700160510ustar00rootroot00000000000000pytest-repeat =================== pytest-repeat is a plugin for `pytest `_ that makes it easy to repeat a single test, or multiple tests, a specific number of times. .. image:: https://img.shields.io/badge/license-MPL%202.0-blue.svg :target: https://github.com/pytest-dev/pytest-repeat/blob/master/LICENSE :alt: License .. image:: https://img.shields.io/pypi/v/pytest-repeat.svg :target: https://pypi.python.org/pypi/pytest-repeat/ :alt: PyPI .. image:: https://img.shields.io/pypi/pyversions/pytest-repeat.svg :target: https://pypi.org/project/pytest-repeat/ :alt: Python versions .. image:: https://img.shields.io/travis/pytest-dev/pytest-repeat.svg :target: https://travis-ci.org/pytest-dev/pytest-repeat/ :alt: Travis .. image:: https://img.shields.io/github/issues-raw/pytest-dev/pytest-repeat.svg :target: https://github.com/pytest-dev/pytest-repeat/issues :alt: Issues .. image:: https://img.shields.io/requires/github/pytest-dev/pytest-repeat.svg :target: https://requires.io/github/pytest-dev/pytest-repeat/requirements/?branch=master :alt: Requirements Requirements ------------ You will need the following prerequisites in order to use pytest-repeat: - Python 2.7, 3.5+ or PyPy - pytest 3.6 or newer Installation ------------ To install pytest-repeat: .. code-block:: bash $ pip install pytest-repeat Repeating a test ---------------- Use the :code:`--count` command line option to specify how many times you want your test, or tests, to be run: .. code-block:: bash $ pytest --count=10 test_file.py Each test collected by pytest will be run :code:`count` times. If you want to mark a test in your code to be repeated a number of times, you can use the :code:`@pytest.mark.repeat(count)` decorator: .. code-block:: python import pytest @pytest.mark.repeat(3) def test_repeat_decorator(): pass If you want to override default tests executions order, you can use :code:`--repeat-scope` command line option with one of the next values: :code:`session`, :code:`module`, :code:`class` or :code:`function` (default). It behaves like a scope of the pytest fixture. :code:`function` (default) scope repeats each test :code:`count` or :code:`repeat` times before executing next test. :code:`session` scope repeats whole tests session, i.e. all collected tests executed once, then all such tests executed again and etc. :code:`class` and :code:`module` behaves similar :code:`session` , but repeating set of tests is a tests from class or module, not all collected tests. Repeating a test until failure ------------------------------ If you are trying to diagnose an intermittent failure, it can be useful to run the same test over and over again until it fails. You can use pytest's :code:`-x` option in conjunction with pytest-repeat to force the test runner to stop at the first failure. For example: .. code-block:: bash $ pytest --count=1000 -x test_file.py This will attempt to run test_file.py 1000 times, but will stop as soon as a failure occurs. UnitTest Style Tests -------------------- Unfortunately pytest-repeat is not able to work with unittest.TestCase test classes. These tests will simply always run once, regardless of :code:`--count`, and show a warning. Resources --------- - `Release Notes `_ - `Issue Tracker `_ - `Code `_ pytest-repeat-0.9.1/pyproject.toml000066400000000000000000000001641374724175700172650ustar00rootroot00000000000000[build-system] requires = ["setuptools>=40.8.0", "wheel", "setuptools_scm"] build-backend = 'setuptools.build_meta' pytest-repeat-0.9.1/pytest_repeat.py000066400000000000000000000041241374724175700176130ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://www.mozilla.org/en-US/MPL/2.0/. import warnings from unittest import TestCase import pytest def pytest_addoption(parser): parser.addoption( '--count', action='store', default=1, type=int, help='Number of times to repeat each test') parser.addoption( '--repeat-scope', action='store', default='function', type=str, choices=('function', 'class', 'module', 'session'), help='Scope for repeating tests') def pytest_configure(config): config.addinivalue_line( 'markers', 'repeat(n): run the given test function `n` times.') class UnexpectedError(Exception): pass @pytest.fixture def __pytest_repeat_step_number(request): marker = request.node.get_closest_marker("repeat") count = marker and marker.args[0] or request.config.option.count if count > 1: try: return request.param except AttributeError: if issubclass(request.cls, TestCase): warnings.warn( "Repeating unittest class tests not supported") else: raise UnexpectedError( "This call couldn't work with pytest-repeat. " "Please consider raising an issue with your usage.") @pytest.hookimpl(trylast=True) def pytest_generate_tests(metafunc): count = metafunc.config.option.count m = metafunc.definition.get_closest_marker('repeat') if m is not None: count = int(m.args[0]) if count > 1: metafunc.fixturenames.append("__pytest_repeat_step_number") def make_progress_id(i, n=count): return '{0}-{1}'.format(i + 1, n) scope = metafunc.config.option.repeat_scope metafunc.parametrize( '__pytest_repeat_step_number', range(count), indirect=True, ids=make_progress_id, scope=scope ) pytest-repeat-0.9.1/setup.cfg000066400000000000000000000000341374724175700161660ustar00rootroot00000000000000[bdist_wheel] universal = 1 pytest-repeat-0.9.1/setup.py000066400000000000000000000030241374724175700160610ustar00rootroot00000000000000from setuptools import setup setup(name='pytest-repeat', use_scm_version=True, description='pytest plugin for repeating tests', long_description=open('README.rst').read(), author='Bob Silverberg', author_email='bsilverberg@mozilla.com', url='https://github.com/pytest-dev/pytest-repeat', py_modules=['pytest_repeat'], entry_points={'pytest11': ['repeat = pytest_repeat']}, setup_requires=['setuptools_scm'], install_requires=['pytest>=3.6'], license='Mozilla Public License 2.0 (MPL 2.0)', keywords='pytest pytest repeat', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: Pytest', 'Intended Audience :: Developers', 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', 'Topic :: Utilities', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ]) pytest-repeat-0.9.1/test_repeat.py000066400000000000000000000253721374724175700172520ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://www.mozilla.org/en-US/MPL/2.0/. import pytest pytest_plugins = "pytester", class TestRepeat: def test_no_repeat(self, testdir): testdir.makepyfile(""" def test_no_repeat(request): fixtures = request.fixturenames assert "__pytest_repeat_step_number" not in fixtures """) result = testdir.runpytest('-v', '--count', '1') result.stdout.fnmatch_lines([ '*test_no_repeat.py::test_no_repeat PASSED*', '*1 passed*', ]) assert result.ret == 0 def test_can_repeat(self, testdir): testdir.makepyfile(""" def test_repeat(): pass """) result = testdir.runpytest('--count', '2') result.stdout.fnmatch_lines(['*2 passed*']) assert result.ret == 0 def test_mark_repeat_decorator_is_registered(self, testdir): result = testdir.runpytest('--markers') result.stdout.fnmatch_lines([ '@pytest.mark.repeat(n): run the given test function `n` times.']) assert result.ret == 0 def test_mark_repeat_decorator(self, testdir): testdir.makepyfile(""" import pytest @pytest.mark.repeat(3) def test_mark_repeat_decorator(): pass """) result = testdir.runpytest() result.stdout.fnmatch_lines(['*3 passed*']) assert result.ret == 0 def test_mark_repeat_decorator_repeat_once(self, testdir): testdir.makepyfile(""" import pytest @pytest.mark.repeat(1) def test_mark_repeat_decorator_repeat_once(): pass """) result = testdir.runpytest('--count', '10') result.stdout.fnmatch_lines(['*1 passed*']) assert result.ret == 0 def test_parametrize(self, testdir): testdir.makepyfile(""" import pytest @pytest.mark.parametrize('x', ['a', 'b', 'c']) def test_repeat(x): pass """) result = testdir.runpytest('-v', '--count', '2') result.stdout.fnmatch_lines([ '*test_parametrize.py::test_repeat[[]a-1-2[]] PASSED*', '*test_parametrize.py::test_repeat[[]a-2-2[]] PASSED*', '*test_parametrize.py::test_repeat[[]b-1-2[]] PASSED*', '*test_parametrize.py::test_repeat[[]b-2-2[]] PASSED*', '*test_parametrize.py::test_repeat[[]c-1-2[]] PASSED*', '*test_parametrize.py::test_repeat[[]c-2-2[]] PASSED*', '*6 passed*', ]) assert result.ret == 0 def test_parametrized_fixture(self, testdir): testdir.makepyfile(""" import pytest @pytest.fixture(params=['a', 'b', 'c']) def parametrized_fixture(request): return request.param def test_repeat(parametrized_fixture): pass """) result = testdir.runpytest('--count', '2') result.stdout.fnmatch_lines(['*6 passed*']) assert result.ret == 0 def test_step_number(self, testdir): testdir.makepyfile(""" import pytest expected_steps = iter(range(5)) def test_repeat(__pytest_repeat_step_number): assert next(expected_steps) == __pytest_repeat_step_number if __pytest_repeat_step_number == 4: assert not list(expected_steps) """) result = testdir.runpytest('-v', '--count', '5') result.stdout.fnmatch_lines([ '*test_step_number.py::test_repeat[[]1-5[]] PASSED*', '*test_step_number.py::test_repeat[[]2-5[]] PASSED*', '*test_step_number.py::test_repeat[[]3-5[]] PASSED*', '*test_step_number.py::test_repeat[[]4-5[]] PASSED*', '*test_step_number.py::test_repeat[[]5-5[]] PASSED*', '*5 passed*', ]) assert result.ret == 0 def test_invalid_option(self, testdir): testdir.makepyfile(""" def test_repeat(): pass """) result = testdir.runpytest('--count', 'a') assert result.ret == 4 def test_unittest_test(self, testdir): testdir.makepyfile(""" from unittest import TestCase class ClassStyleTest(TestCase): def test_this(self): assert 1 """) result = testdir.runpytest('-v', '--count', '2') result.stdout.fnmatch_lines([ '*test_unittest_test.py::ClassStyleTest::test_this PASSED*', '*1 passed*', ]) @pytest.mark.parametrize(['scope', 'lines'], [ ('session', [ '*test_1.py::test_repeat1[[]1-2[]] PASSED*', '*test_1.py::test_repeat2[[]1-2[]] PASSED*', '*test_2.py::test_repeat3[[]1-2[]] PASSED*', '*test_2.py::test_repeat4[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]1-2[]] PASSED*', '*test_1.py::test_repeat1[[]2-2[]] PASSED*', '*test_1.py::test_repeat2[[]2-2[]] PASSED*', '*test_2.py::test_repeat3[[]2-2[]] PASSED*', '*test_2.py::test_repeat4[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]2-2[]] PASSED*', '*16 passed*', ]), ('module', [ '*test_1.py::test_repeat1[[]1-2[]] PASSED*', '*test_1.py::test_repeat2[[]1-2[]] PASSED*', '*test_1.py::test_repeat1[[]2-2[]] PASSED*', '*test_1.py::test_repeat2[[]2-2[]] PASSED*', '*test_2.py::test_repeat3[[]1-2[]] PASSED*', '*test_2.py::test_repeat4[[]1-2[]] PASSED*', '*test_2.py::test_repeat3[[]2-2[]] PASSED*', '*test_2.py::test_repeat4[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]2-2[]] PASSED*', '*16 passed*', ]), ('class', [ '*test_1.py::test_repeat1[[]1-2[]] PASSED*', '*test_1.py::test_repeat2[[]1-2[]] PASSED*', '*test_1.py::test_repeat1[[]2-2[]] PASSED*', '*test_1.py::test_repeat2[[]2-2[]] PASSED*', '*test_2.py::test_repeat3[[]1-2[]] PASSED*', '*test_2.py::test_repeat4[[]1-2[]] PASSED*', '*test_2.py::test_repeat3[[]2-2[]] PASSED*', '*test_2.py::test_repeat4[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]2-2[]] PASSED*', '*16 passed*', ]), ('function', [ '*test_1.py::test_repeat1[[]1-2[]] PASSED*', '*test_1.py::test_repeat1[[]2-2[]] PASSED*', '*test_1.py::test_repeat2[[]1-2[]] PASSED*', '*test_1.py::test_repeat2[[]2-2[]] PASSED*', '*test_2.py::test_repeat3[[]1-2[]] PASSED*', '*test_2.py::test_repeat3[[]2-2[]] PASSED*', '*test_2.py::test_repeat4[[]1-2[]] PASSED*', '*test_2.py::test_repeat4[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat5[[]2-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]1-2[]] PASSED*', '*test_3.py::TestRepeat1::test_repeat6[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat7[[]2-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]1-2[]] PASSED*', '*test_3.py::TestRepeat2::test_repeat8[[]2-2[]] PASSED*', '*16 passed*', ]), ]) def test_scope(self, testdir, scope, lines): testdir.makepyfile(test_1=""" def test_repeat1(): pass def test_repeat2(): pass """) testdir.makepyfile(test_2=""" def test_repeat3(): pass def test_repeat4(): pass """) testdir.makepyfile(test_3=""" class TestRepeat1(object): def test_repeat5(self): pass def test_repeat6(self): pass class TestRepeat2(object): def test_repeat7(self): pass def test_repeat8(self): pass """) result = testdir.runpytest('-v', '--count', '2', '--repeat-scope', scope) result.stdout.fnmatch_lines(lines) assert result.ret == 0 def test_omitted_scope(self, testdir): testdir.makepyfile(""" def test_repeat1(): pass def test_repeat2(): pass """) result = testdir.runpytest('-v', '--count', '2') result.stdout.fnmatch_lines([ '*test_omitted_scope.py::test_repeat1[[]1-2[]] PASSED*', '*test_omitted_scope.py::test_repeat1[[]2-2[]] PASSED*', '*test_omitted_scope.py::test_repeat2[[]1-2[]] PASSED*', '*test_omitted_scope.py::test_repeat2[[]2-2[]] PASSED*', '*4 passed*', ]) assert result.ret == 0 def test_invalid_scope(self, testdir): testdir.makepyfile(""" def test_repeat(): pass """) result = testdir.runpytest('--count', '2', '--repeat-scope', 'a') assert result.ret == 4 pytest-repeat-0.9.1/tox.ini000066400000000000000000000011151374724175700156610ustar00rootroot00000000000000# Tox (https://tox.readthedocs.io/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] minversion = 3.4.0 isolated_build = true envlist = py{27,35,36,37,38,39,py,py3}-pytest{3,4,6}, flake8 [testenv] commands = pytest {posargs} deps = pytest3: pytest~=3.6 pytest4: pytest~=4.6 pytest6: pytest>=6,<7 [testenv:flake8] basepython = python deps = flake8 commands = flake8 {posargs:pytest_repeat.py test_repeat.py}