Skip to content

Commit

Permalink
Merge pull request #13 from sondrelg/pre-commit
Browse files Browse the repository at this point in the history
Pre-commit config, linting, and type hints
  • Loading branch information
jerry-git authored May 31, 2021
2 parents 9307bac + 0153cf4 commit e67d449
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 29 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: test

on:
pull_request_target:
pull_request:
push:
branches:
- master

jobs:
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- uses: actions/cache@v2
with:
path: |
~/.cache/pip
~/.cache/pre-commit
key: ${{ runner.os }}-pip-2
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- run: python -m pip install pre-commit
- run: pre-commit run --all-files
test:
needs: linting
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.6", "3.7", "3.8", "3.9", ] # "3.10.0-beta.1"
pytest-version: [ "4", "5", "6" ]
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Pytest ${{ matrix.pytest-version }}
run: pip install pytest==${{ matrix.pytest-version }}
- name: Install package
run: pip install -e .
- name: Run tests
run: pytest --verbose --assert=plain
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ venv.bak/

.idea/

/src/*/_version.py
/src/*/_version.py
42 changes: 34 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
- repo: https://github.com/ambv/black
rev: stable
repos:
- repo: https://github.com/ambv/black
rev: 20.8b1
hooks:
- id: black
language_version: python3.7
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
- id: black
args: ['--quiet']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: debug-statements
- id: flake8
- id: check-ast
- id: check-added-large-files
- id: check-merge-conflict
- id: check-case-conflict
- id: check-docstring-first
- id: check-json
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
'flake8-comprehensions',
'flake8-deprecated',
'flake8-print',
'flake8-type-checking',
]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.812'
hooks:
- id: mypy
2 changes: 1 addition & 1 deletion LICENCE
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ 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.
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Pytest plugin which splits the test suite to equally sized "sub suites" based on test execution time.

## Motivation
* Splitting the test suite is a prerequisite for parallelization (who does not want faster CI builds?). It's valuable to have sub suites which execution time is around the same.
* Splitting the test suite is a prerequisite for parallelization (who does not want faster CI builds?). It's valuable to have sub suites which execution time is around the same.
* [`pytest-test-groups`](https://pypi.org/project/pytest-test-groups/) is great but it does not take into account the execution time of sub suites which can lead to notably unbalanced execution times between the sub suites.
* [`pytest-xdist`](https://pypi.org/project/pytest-xdist/) is great but it's not suitable for all use cases.
For example, some test suites may be fragile considering the order in which the tests are executed.
Expand All @@ -21,7 +21,7 @@ pip install pytest-split

## Usage
First we have to store test durations from a complete test suite run.
This produces .test_durations file which should be stored in the repo in order to have it available during future test runs.
This produces .test_durations file which should be stored in the repo in order to have it available during future test runs.
The file path is configurable via `--durations-path` CLI option.
```
pytest --store-durations
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.black]
line-length = 88
include = '\.pyi?$'
21 changes: 20 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
[flake8]
max-line-length = 88
max-line-length = 88
select =
# B: flake8-bugbear
B,
# B950: opt-in flake8-bugbear check: line too long
B950,
# C: complexity, comprehensions, etc
C,
# E: pycodestyle errors
E,
# F: pyflakes violations
F,
# W: pycodestyle warnings
W,
# T: flake8-print, prevent prints
T,
# TC: flake8-type-checking
TC,
# TC1: flake8-type-checking, use futures imports for forward reference management(could have used TC2 instead)
TC2
11 changes: 5 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import setuptools


with open("README.md", "r") as readme_file:
long_description = readme_file.read()


tests_require = ["pytest", "pytest-cov", "pre-commit"]


setuptools.setup(
name="pytest-split",
use_scm_version=dict(write_to="src/pytest_split/_version.py"),
use_scm_version={"write_to": "src/pytest_split/_version.py"},
author="Jerry Pussinen",
author_email="[email protected]",
description="Pytest plugin for splitting test suite based on test execution time",
Expand All @@ -25,14 +22,16 @@
install_requires=["pytest"],
extras_require={"testing": tests_require},
classifiers=[
"Development Status :: 1 - Planning",
"Intended Audience :: Developers",
"Development Status :: 4 - Beta" "Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Framework :: Pytest",
"Typing :: Typed",
],
entry_points={"pytest11": ["pytest-split = pytest_split.plugin"]},
package_data={"pytest_split": ["py.typed"]},
)
35 changes: 25 additions & 10 deletions src/pytest_split/plugin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import json
import os
from collections import defaultdict, OrderedDict
from typing import TYPE_CHECKING

from _pytest.config import create_terminal_writer

if TYPE_CHECKING:
from typing import List, Tuple
from _pytest.config.argparsing import Parser
from _pytest.main import Session

from _pytest import nodes
from _pytest.config import Config

# Ugly hacks for freezegun compatibility: https://github.com/spulec/freezegun/issues/286
STORE_DURATIONS_SETUP_AND_TEARDOWN_THRESHOLD = 60 * 10 # seconds


def pytest_addoption(parser):
def pytest_addoption(parser: "Parser") -> None:
group = parser.getgroup(
"Split tests into groups which execution time is about the same. "
"Run first the whole suite with --store-durations to save information "
Expand Down Expand Up @@ -43,20 +52,20 @@ def pytest_addoption(parser):
)


def pytest_collection_modifyitems(session, config, items):
def pytest_collection_modifyitems(config: "Config", items: "List[nodes.Item]") -> None:
splits = config.option.splits
group = config.option.group
store_durations = config.option.store_durations
durations_report_path = config.option.durations_path

if any((splits, group)):
if not all((splits, group)):
return
return None
if not os.path.isfile(durations_report_path):
return
return None
if store_durations:
# Don't split if we are storing durations
return
return None
total_tests_count = len(items)
if splits and group:
with open(durations_report_path) as f:
Expand All @@ -75,19 +84,21 @@ def pytest_collection_modifyitems(session, config, items):
)
)
terminal_reporter.write(message)
return None


def pytest_sessionfinish(session, exitstatus):
def pytest_sessionfinish(session: "Session") -> None:
if session.config.option.store_durations:
report_path = session.config.option.durations_path
terminal_reporter = session.config.pluginmanager.get_plugin("terminalreporter")
durations = defaultdict(float)
durations: dict = defaultdict(float)
for test_reports in terminal_reporter.stats.values():
for test_report in test_reports:
if hasattr(test_report, "duration"):
stage = getattr(test_report, "when", "")
duration = test_report.duration
# These ifs be removed after this is solved: https://github.com/spulec/freezegun/issues/286
# These ifs be removed after this is solved:
# https://github.com/spulec/freezegun/issues/286
if duration < 0:
continue
if (
Expand All @@ -108,9 +119,13 @@ def pytest_sessionfinish(session, exitstatus):
terminal_reporter.write(message)


def _calculate_suite_start_and_end_idx(splits, group, items, stored_durations):
def _calculate_suite_start_and_end_idx(
splits: int, group: int, items: "List[nodes.Item]", stored_durations: OrderedDict
) -> "Tuple[int, int]":
item_node_ids = [item.nodeid for item in items]
stored_durations = {k: v for k, v in stored_durations.items() if k in item_node_ids}
stored_durations = OrderedDict(
{k: v for k, v in stored_durations.items() if k in item_node_ids}
)
avg_duration_per_test = sum(stored_durations.values()) / len(stored_durations)

durations = OrderedDict()
Expand Down
Empty file added src/pytest_split/py.typed
Empty file.

0 comments on commit e67d449

Please sign in to comment.