pytest-portion 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_portion-0.2.0/.github/FUNDING.yml +1 -0
- pytest_portion-0.2.0/.github/workflows/cd.yml +45 -0
- pytest_portion-0.2.0/.github/workflows/ci.yml +40 -0
- pytest_portion-0.2.0/.gitignore +76 -0
- pytest_portion-0.2.0/.travis.yml +24 -0
- pytest_portion-0.2.0/LICENSE +28 -0
- pytest_portion-0.2.0/MANIFEST.in +5 -0
- pytest_portion-0.2.0/Makefile +58 -0
- pytest_portion-0.2.0/PKG-INFO +148 -0
- pytest_portion-0.2.0/README.md +119 -0
- pytest_portion-0.2.0/docs/Makefile +192 -0
- pytest_portion-0.2.0/docs/_static/.gitkeep +0 -0
- pytest_portion-0.2.0/docs/conf.py +309 -0
- pytest_portion-0.2.0/docs/index.rst +75 -0
- pytest_portion-0.2.0/docs/make.bat +263 -0
- pytest_portion-0.2.0/pyproject.toml +56 -0
- pytest_portion-0.2.0/pytest_portion.py +158 -0
- pytest_portion-0.2.0/tests/conftest.py +1 -0
- pytest_portion-0.2.0/tests/test_portion.py +125 -0
- pytest_portion-0.2.0/uv.lock +927 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: mgaitan
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
pypi-publish:
|
|
15
|
+
name: upload release to PyPI
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v7
|
|
22
|
+
- run: uv build
|
|
23
|
+
- if: github.event_name == 'release'
|
|
24
|
+
run: uv publish
|
|
25
|
+
|
|
26
|
+
docs-publish:
|
|
27
|
+
name: Publish documentation to GitHub Pages
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
needs: pypi-publish
|
|
30
|
+
environment:
|
|
31
|
+
name: github-pages
|
|
32
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: actions/configure-pages@v5
|
|
36
|
+
- uses: astral-sh/setup-uv@v7
|
|
37
|
+
- run: make docs
|
|
38
|
+
- uses: actions/upload-pages-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
path: docs/_build/html
|
|
41
|
+
- id: deployment
|
|
42
|
+
uses: actions/deploy-pages@v4
|
|
43
|
+
- name: Summarize documentation URL
|
|
44
|
+
run: |
|
|
45
|
+
echo "Documentation published at: ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
qa:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
env:
|
|
14
|
+
UV_PYTHON: "3.13"
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v7
|
|
18
|
+
|
|
19
|
+
- name: Ruff check
|
|
20
|
+
run: uv run ruff check --output-format=github
|
|
21
|
+
|
|
22
|
+
- name: Ruff format
|
|
23
|
+
run: uv run ruff format --check
|
|
24
|
+
|
|
25
|
+
- name: Ty check
|
|
26
|
+
run: uv run ty check --output-format=github
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
test:
|
|
30
|
+
needs: qa
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
strategy:
|
|
33
|
+
matrix:
|
|
34
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: astral-sh/setup-uv@v7
|
|
39
|
+
- name: Run pytest
|
|
40
|
+
run: uv run -p ${{ matrix.python-version }} pytest
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
# Usually these files are written by a python script from a template
|
|
29
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
*,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Django stuff:
|
|
54
|
+
*.log
|
|
55
|
+
local_settings.py
|
|
56
|
+
|
|
57
|
+
# Flask instance folder
|
|
58
|
+
instance/
|
|
59
|
+
|
|
60
|
+
# Sphinx documentation
|
|
61
|
+
docs/_build/
|
|
62
|
+
|
|
63
|
+
# MkDocs documentation
|
|
64
|
+
/site/
|
|
65
|
+
|
|
66
|
+
# PyBuilder
|
|
67
|
+
target/
|
|
68
|
+
|
|
69
|
+
# IPython Notebook
|
|
70
|
+
.ipynb_checkpoints
|
|
71
|
+
|
|
72
|
+
# pyenv
|
|
73
|
+
.python-version
|
|
74
|
+
|
|
75
|
+
# IDE
|
|
76
|
+
.idea/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Config file for automatic testing at travis-ci.org
|
|
2
|
+
|
|
3
|
+
language: python
|
|
4
|
+
python:
|
|
5
|
+
- "3.6"
|
|
6
|
+
- "3.7"
|
|
7
|
+
- "3.8"
|
|
8
|
+
- "3.9"
|
|
9
|
+
- "3.9-dev" # 3.9 development branch
|
|
10
|
+
|
|
11
|
+
# command to install dependencies
|
|
12
|
+
install:
|
|
13
|
+
- pip install -e .
|
|
14
|
+
|
|
15
|
+
# command to run tests
|
|
16
|
+
script:
|
|
17
|
+
- pytest
|
|
18
|
+
|
|
19
|
+
before_cache:
|
|
20
|
+
- rm -rf $HOME/.cache/pip/log
|
|
21
|
+
|
|
22
|
+
cache:
|
|
23
|
+
directories:
|
|
24
|
+
- $HOME/.cache/pip
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
Copyright (c) 2021, Martín Gaitán
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
* Neither the name of pytest-portion nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.PHONY: install
|
|
2
|
+
install: ## Install the virtual environment and install the pre-commit hooks
|
|
3
|
+
@echo "🚀 Creating virtual environment using uv"
|
|
4
|
+
@uv sync
|
|
5
|
+
|
|
6
|
+
.PHONY: test qa
|
|
7
|
+
test: ## Run tests with coverage
|
|
8
|
+
@echo "🧪 Running tests with coverage"
|
|
9
|
+
@uv run pytest
|
|
10
|
+
|
|
11
|
+
qa: ## Run local QA checks (Ruff + Ty)
|
|
12
|
+
@echo "🔍 Running Ruff lint checks"
|
|
13
|
+
@uv run ruff check --fix
|
|
14
|
+
@echo "✨ Verifying Ruff formatting"
|
|
15
|
+
@uv run ruff format --check
|
|
16
|
+
@echo "🧹 Running Ty checks"
|
|
17
|
+
@uv run ty check
|
|
18
|
+
|
|
19
|
+
.PHONY: bump
|
|
20
|
+
bump:
|
|
21
|
+
uv version --bump minor
|
|
22
|
+
|
|
23
|
+
.PHONY: release
|
|
24
|
+
release: ## Create a GitHub release for the current version
|
|
25
|
+
@version=$$(uv version --short); \
|
|
26
|
+
git commit -m "Bump $$version"; \
|
|
27
|
+
git push origin main; \
|
|
28
|
+
gh release create "$$version" --generate-notes
|
|
29
|
+
|
|
30
|
+
.PHONY: docs docs-html docs-epub docs-open html epub open
|
|
31
|
+
|
|
32
|
+
DOCS_SOURCE := docs
|
|
33
|
+
DOCS_BUILD := $(DOCS_SOURCE)/_build
|
|
34
|
+
|
|
35
|
+
docs: ## Build HTML documentation (default).
|
|
36
|
+
@$(MAKE) docs-html
|
|
37
|
+
|
|
38
|
+
html: docs-html
|
|
39
|
+
|
|
40
|
+
docs-html: ## Build documentation as static HTML.
|
|
41
|
+
@echo "📖 Building HTML documentation"
|
|
42
|
+
@uv run --group docs sphinx-build $(DOCS_SOURCE) $(DOCS_BUILD)/html -b html -W
|
|
43
|
+
|
|
44
|
+
epub: docs-epub
|
|
45
|
+
|
|
46
|
+
docs-epub: ## Build documentation as EPUB.
|
|
47
|
+
@echo "📖 Building EPUB documentation"
|
|
48
|
+
@uv run --group docs sphinx-build $(DOCS_SOURCE) $(DOCS_BUILD)/epub -b epub -W
|
|
49
|
+
|
|
50
|
+
docs-open open: docs-html ## Build docs and open them in the browser.
|
|
51
|
+
@uv run -m webbrowser docs/_build/html/index.html
|
|
52
|
+
|
|
53
|
+
.PHONY: help
|
|
54
|
+
help:
|
|
55
|
+
@uv run python -c "import re; \
|
|
56
|
+
[[print(f'\033[36m{m[0]:<20}\033[0m {m[1]}') for m in re.findall(r'^([a-zA-Z_.-]+):.*?## (.*)$$', open(makefile).read(), re.M)] for makefile in ('$(MAKEFILE_LIST)').strip().split()]"
|
|
57
|
+
|
|
58
|
+
.DEFAULT_GOAL := help
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-portion
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Select a portion of the collected tests
|
|
5
|
+
Project-URL: homepage, https://github.com/mgaitan/pytest-portion
|
|
6
|
+
Author-email: Martín Gaitán <gaitan@gmail.com>
|
|
7
|
+
License: BSD-3-Clause
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: pytest,testing
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: Pytest
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: pytest>=3.5.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# pytest-portion
|
|
31
|
+
|
|
32
|
+
[](https://github.com/mgaitan/pytest-portion/actions/workflows/ci.yml)
|
|
33
|
+
[](https://pypi.org/project/pytest-portion)
|
|
34
|
+
[](https://pypi.org/project/pytest-portion)
|
|
35
|
+
[](https://github.com/mgaitan/pytest-portion/releases)
|
|
36
|
+
[](https://mgaitan.github.io/pytest-portion/)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Select a portion of the collected tests, so you can run different parts of your test suite
|
|
40
|
+
in different instances to scale horizontally.
|
|
41
|
+
|
|
42
|
+
## Use case
|
|
43
|
+
|
|
44
|
+
Suppose you have a big, slow test suite, but you can trigger several CI workers
|
|
45
|
+
to run different portions of it, in a sake lazy/simple way to parallelize it.
|
|
46
|
+
|
|
47
|
+
A basic, obvious way to do that is to explictily
|
|
48
|
+
collect from different directories/modules:
|
|
49
|
+
|
|
50
|
+
- worker1: `pytest tests/a` (100 tests, ~4 minutes to finish)
|
|
51
|
+
- worker2: `pytest tests/b` (20 tests, ~1 minute to finish)
|
|
52
|
+
- worker3: `pytest tests/c tests/d` (30 tests, ~1 minute to finish)
|
|
53
|
+
|
|
54
|
+
The problem is that directory `tests/a` may have a lot more tests that `tests/c` plus `test/d`,
|
|
55
|
+
so `worker1` takes a lot more to finish.
|
|
56
|
+
|
|
57
|
+
With `pytest-portion` you can still split the tests in different instances, but letting
|
|
58
|
+
the extension makes the selection in a more balanced way.
|
|
59
|
+
|
|
60
|
+
- worker1: `pytest --portion 1/3 tests` (first 50 tests, ~2 minutes)
|
|
61
|
+
- worker2: `pytest --portion 2/3 tests` (next 50 tests, ~2 minutes)
|
|
62
|
+
- worker3: `pytest --portion 3/3 tests` (last 50 tests, ~2 minutes)
|
|
63
|
+
|
|
64
|
+
In this case, the tests of all the directories are collected, but only a third (a different one!) of them will
|
|
65
|
+
be actually executed on each worker.
|
|
66
|
+
|
|
67
|
+
Note this balance is **by number of tests**, so if there is very slow tests in a particular portion,
|
|
68
|
+
the duration may not be expected.
|
|
69
|
+
|
|
70
|
+
For a fine tuning, you could pass the portion in a more explicit way:
|
|
71
|
+
|
|
72
|
+
- worker1: `pytest --portion 0:0.5 tests` (first half, 1st to 75th test)
|
|
73
|
+
- worker2: `pytest --portion 0.5:0.8 tests` (next 30%, from 76th to 125th)
|
|
74
|
+
- worker3: `pytest --portion 0.8:1 tests` (last 20%)
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
You can add "pytest-portion" to your project from [PyPI](https://pypi.org/project/pytest-portion/) with [uv](https://docs.astral.sh/uv/).
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv add --dev pytetest-portion
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or via [pip]
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install pytest-portion
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
There are two modes of operation: **Test-level** (default) and **File-level**.
|
|
93
|
+
|
|
94
|
+
### 1. Test-level Slicing (Default)
|
|
95
|
+
|
|
96
|
+
Pytest collects all tests first, then `pytest-portion` filters them.
|
|
97
|
+
|
|
98
|
+
Pass `--portion <i/n>` where:
|
|
99
|
+
|
|
100
|
+
- `n` is the total number of portions.
|
|
101
|
+
- `i` is the i-th portion to select (`1 <= i <= n`).
|
|
102
|
+
|
|
103
|
+
> **Note:**
|
|
104
|
+
> If the number of tests collected is not divisible by `n`, the last portion will contain the rest.
|
|
105
|
+
> For instance, if you have `test_1`, `test_2` and `test_3`, `--portion 1/2` will run the first one,
|
|
106
|
+
> and `--portion 2/2` the last 2.
|
|
107
|
+
|
|
108
|
+
Alternatively, use `--portion start:end` where `start` and `end` are coefficients between 0 and 1.
|
|
109
|
+
|
|
110
|
+
### 2. File-level Slicing
|
|
111
|
+
|
|
112
|
+
For very large projects, collection itself can be slow. Use `--portion-files` to slice the list of
|
|
113
|
+
discovered files **before** pytest starts collecting tests from within them. This can significantly
|
|
114
|
+
reduce collection time in large repositories.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Collect and run only the files belonging to the first half of the suite
|
|
118
|
+
pytest --portion 1/2 --portion-files tests/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
Contributions are very welcome. Please ensure the coverage at least stays
|
|
124
|
+
the same before you submit a pull request.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
Distributed under the terms of the [BSD-3] license, "pytest-portion" is free and open source software.
|
|
129
|
+
|
|
130
|
+
## Issues
|
|
131
|
+
|
|
132
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
133
|
+
|
|
134
|
+
## Acknowledgements
|
|
135
|
+
|
|
136
|
+
I used [cookiecutter] along with [@hackebrot]'s [cookiecutter-pytest-plugin] template for the boilerplate code of this package. Thanks!
|
|
137
|
+
|
|
138
|
+
[cookiecutter]: https://github.com/audreyr/cookiecutter
|
|
139
|
+
[@hackebrot]: https://github.com/hackebrot
|
|
140
|
+
[MIT]: http://opensource.org/licenses/MIT
|
|
141
|
+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
|
|
142
|
+
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
|
|
143
|
+
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
|
|
144
|
+
[cookiecutter-pytest-plugin]: https://github.com/pytest-dev/cookiecutter-pytest-plugin
|
|
145
|
+
[file an issue]: https://github.com/mgaitan/pytest-portion/issues
|
|
146
|
+
[pytest]: https://github.com/pytest-dev/pytest
|
|
147
|
+
[pip]: https://pypi.org/project/pip/
|
|
148
|
+
[PyPI]: https://pypi.org/project
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# pytest-portion
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mgaitan/pytest-portion/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/pytest-portion)
|
|
5
|
+
[](https://pypi.org/project/pytest-portion)
|
|
6
|
+
[](https://github.com/mgaitan/pytest-portion/releases)
|
|
7
|
+
[](https://mgaitan.github.io/pytest-portion/)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Select a portion of the collected tests, so you can run different parts of your test suite
|
|
11
|
+
in different instances to scale horizontally.
|
|
12
|
+
|
|
13
|
+
## Use case
|
|
14
|
+
|
|
15
|
+
Suppose you have a big, slow test suite, but you can trigger several CI workers
|
|
16
|
+
to run different portions of it, in a sake lazy/simple way to parallelize it.
|
|
17
|
+
|
|
18
|
+
A basic, obvious way to do that is to explictily
|
|
19
|
+
collect from different directories/modules:
|
|
20
|
+
|
|
21
|
+
- worker1: `pytest tests/a` (100 tests, ~4 minutes to finish)
|
|
22
|
+
- worker2: `pytest tests/b` (20 tests, ~1 minute to finish)
|
|
23
|
+
- worker3: `pytest tests/c tests/d` (30 tests, ~1 minute to finish)
|
|
24
|
+
|
|
25
|
+
The problem is that directory `tests/a` may have a lot more tests that `tests/c` plus `test/d`,
|
|
26
|
+
so `worker1` takes a lot more to finish.
|
|
27
|
+
|
|
28
|
+
With `pytest-portion` you can still split the tests in different instances, but letting
|
|
29
|
+
the extension makes the selection in a more balanced way.
|
|
30
|
+
|
|
31
|
+
- worker1: `pytest --portion 1/3 tests` (first 50 tests, ~2 minutes)
|
|
32
|
+
- worker2: `pytest --portion 2/3 tests` (next 50 tests, ~2 minutes)
|
|
33
|
+
- worker3: `pytest --portion 3/3 tests` (last 50 tests, ~2 minutes)
|
|
34
|
+
|
|
35
|
+
In this case, the tests of all the directories are collected, but only a third (a different one!) of them will
|
|
36
|
+
be actually executed on each worker.
|
|
37
|
+
|
|
38
|
+
Note this balance is **by number of tests**, so if there is very slow tests in a particular portion,
|
|
39
|
+
the duration may not be expected.
|
|
40
|
+
|
|
41
|
+
For a fine tuning, you could pass the portion in a more explicit way:
|
|
42
|
+
|
|
43
|
+
- worker1: `pytest --portion 0:0.5 tests` (first half, 1st to 75th test)
|
|
44
|
+
- worker2: `pytest --portion 0.5:0.8 tests` (next 30%, from 76th to 125th)
|
|
45
|
+
- worker3: `pytest --portion 0.8:1 tests` (last 20%)
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
You can add "pytest-portion" to your project from [PyPI](https://pypi.org/project/pytest-portion/) with [uv](https://docs.astral.sh/uv/).
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv add --dev pytetest-portion
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or via [pip]
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install pytest-portion
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
There are two modes of operation: **Test-level** (default) and **File-level**.
|
|
64
|
+
|
|
65
|
+
### 1. Test-level Slicing (Default)
|
|
66
|
+
|
|
67
|
+
Pytest collects all tests first, then `pytest-portion` filters them.
|
|
68
|
+
|
|
69
|
+
Pass `--portion <i/n>` where:
|
|
70
|
+
|
|
71
|
+
- `n` is the total number of portions.
|
|
72
|
+
- `i` is the i-th portion to select (`1 <= i <= n`).
|
|
73
|
+
|
|
74
|
+
> **Note:**
|
|
75
|
+
> If the number of tests collected is not divisible by `n`, the last portion will contain the rest.
|
|
76
|
+
> For instance, if you have `test_1`, `test_2` and `test_3`, `--portion 1/2` will run the first one,
|
|
77
|
+
> and `--portion 2/2` the last 2.
|
|
78
|
+
|
|
79
|
+
Alternatively, use `--portion start:end` where `start` and `end` are coefficients between 0 and 1.
|
|
80
|
+
|
|
81
|
+
### 2. File-level Slicing
|
|
82
|
+
|
|
83
|
+
For very large projects, collection itself can be slow. Use `--portion-files` to slice the list of
|
|
84
|
+
discovered files **before** pytest starts collecting tests from within them. This can significantly
|
|
85
|
+
reduce collection time in large repositories.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Collect and run only the files belonging to the first half of the suite
|
|
89
|
+
pytest --portion 1/2 --portion-files tests/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Contributions are very welcome. Please ensure the coverage at least stays
|
|
95
|
+
the same before you submit a pull request.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Distributed under the terms of the [BSD-3] license, "pytest-portion" is free and open source software.
|
|
100
|
+
|
|
101
|
+
## Issues
|
|
102
|
+
|
|
103
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
104
|
+
|
|
105
|
+
## Acknowledgements
|
|
106
|
+
|
|
107
|
+
I used [cookiecutter] along with [@hackebrot]'s [cookiecutter-pytest-plugin] template for the boilerplate code of this package. Thanks!
|
|
108
|
+
|
|
109
|
+
[cookiecutter]: https://github.com/audreyr/cookiecutter
|
|
110
|
+
[@hackebrot]: https://github.com/hackebrot
|
|
111
|
+
[MIT]: http://opensource.org/licenses/MIT
|
|
112
|
+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
|
|
113
|
+
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
|
|
114
|
+
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
|
|
115
|
+
[cookiecutter-pytest-plugin]: https://github.com/pytest-dev/cookiecutter-pytest-plugin
|
|
116
|
+
[file an issue]: https://github.com/mgaitan/pytest-portion/issues
|
|
117
|
+
[pytest]: https://github.com/pytest-dev/pytest
|
|
118
|
+
[pip]: https://pypi.org/project/pip/
|
|
119
|
+
[PyPI]: https://pypi.org/project
|