diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98378f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.egg-info +.cache +*.pyc +*.tox +build/ +dist/ +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b94e060 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +sudo: false +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" +install: + - "pip install flake8" + - "pip install -e ." +before_script: + - "flake8" +script: + - py.test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..781af49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Mark Adams + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..127ea1c --- /dev/null +++ b/README.rst @@ -0,0 +1,31 @@ +.. image:: https://secure.travis-ci.org/mark-adams/pytest-test-groups.png?branch=master + :alt: Build Status + :target: https://travis-ci.org/mark-adams/pytest-test-groups + +Welcome to pytest-test-groups! +============================== + +pytest-test-groups allows you to split your test runs into groups of a specific +size to make it easier to split up your test runs. + + +Usage +--------------------- + +:: + + # Install pytest-test-groups + pip install pytest-test-groups + + # Split the tests into 10 groups and run the second group + py.test --test-group-count 10 --test-group=2 + + +Why would I use this? +------------------------------------------------------------------ + +Sometimes you may have some long running test jobs that take a +while to complete. This can be a major headache when trying to +run tests quickly. pytest-test-groups allows you to easily say +"split my tests into groups of 10 tests and run the second group". +This is primarily useful in the context of CI builds. diff --git a/pytest_test_groups/__init__.py b/pytest_test_groups/__init__.py new file mode 100644 index 0000000..999602a --- /dev/null +++ b/pytest_test_groups/__init__.py @@ -0,0 +1,40 @@ +import math + + +def get_group_size(total_items, total_groups): + return int(math.ceil(float(total_items) / total_groups)) + + +def get_group(items, group_size, group_id): + start = group_size * (group_id - 1) + end = start + group_size + + if start >= len(items) or start < 0: + raise ValueError("Invalid test-group argument") + + return items[start:end] + + +def pytest_addoption(parser): + group = parser.getgroup('split your tests into evenly sized groups and run them') + group.addoption('--test-group-count', dest='test-group-count', type=int, + help='The number of groups to split the tests into') + group.addoption('--test-group', dest='test-group', type=int, + help='The group of tests that should be executed') + + +def pytest_collection_modifyitems(session, config, items): + group_count = config.getoption('test-group-count') + group_id = config.getoption('test-group') + + if not group_count or not group_id: + return + + total_items = len(items) + + group_size = get_group_size(total_items, group_count) + tests_in_group = get_group(items, group_size, group_id) + del items[:] + items.extend(tests_in_group) + + print('Running test group #{0} ({1} tests)'.format(group_id, len(items))) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..791f075 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 119 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..70345b2 --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +import codecs +import os + +from setuptools import setup + + +def read(fname): + file_path = os.path.join(os.path.dirname(__file__), fname) + return codecs.open(file_path, encoding='utf-8').read() + +setup( + name="pytest-test-groups", + description=('A Pytest plugin for running a subset of your tests by ' + 'splitting them in to equally sized groups.'), + url='https://github.com/mark-adams/pytest-test-groups', + author='Mark Adams', + author_email='mark@markadams.me', + packages=['pytest_test_groups'], + version='0.9', + long_description=read('README.rst'), + install_requires=['pytest>=2.5'], + classifiers=['Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Testing', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5' + ], + entry_points={ + 'pytest11': [ + 'test-groups = pytest_test_groups', + ] + }, +) diff --git a/tests/test_groups.py b/tests/test_groups.py new file mode 100644 index 0000000..f2b18df --- /dev/null +++ b/tests/test_groups.py @@ -0,0 +1,50 @@ +import pytest + +from pytest_test_groups import get_group, get_group_size + + +def test_group_size_computed_correctly_for_even_group(): + expected = 8 + actual = get_group_size(32, 4) # 32 total tests; 4 groups + + assert expected == actual + + +def test_group_size_computed_correctly_for_odd_group(): + expected = 8 + actual = get_group_size(31, 4) # 31 total tests; 4 groups + + assert expected == actual + + +def test_group_is_the_proper_size(): + items = [str(i) for i in range(32)] + group = get_group(items, 8, 1) + + assert len(group) == 8 + + +def test_all_groups_together_form_original_set_of_tests(): + items = [str(i) for i in range(32)] + + groups = [get_group(items, 8, i) for i in range(1, 5)] + + combined = [] + for group in groups: + combined.extend(group) + + assert combined == items + + +def test_group_that_is_too_high_raises_value_error(): + items = [str(i) for i in range(32)] + + with pytest.raises(ValueError): + get_group(items, 8, 5) + + +def test_group_that_is_too_low_raises_value_error(): + items = [str(i) for i in range(32)] + + with pytest.raises(ValueError): + get_group(items, 8, 0) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..3dbf756 --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +envlist = py{26,27,33,34,35}, flake8 + +[testenv] +commands = + py.test +deps = + pytest==2.8.7 + + +[testenv:flake8] +commands = + flake8 +deps = + flake8 + flake8-import-order + pep8-naming