Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ jobs:
- uses: ./.github/actions/python-poetry-env
with:
python-version: ${{ matrix.python-version }}
- run: poetry run pytest
- run: poetry run coverage run -m pytest
- run: poetry run coverage report

docs:
runs-on: ubuntu-latest
Expand Down
12 changes: 4 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,12 @@ pytest-split = "pytest_split.plugin"
target-version = ["py310", "py311", "py312", "py313", "py314"]
include = '\.pyi?$'

[tool.pytest.ini_options]
addopts = """\
--cov pytest_split \
--cov tests \
--cov-report term-missing \
--no-cov-on-fail \
"""
[tool.coverage.run]
source = ["pytest_split"]

[tool.coverage.report]
fail_under = 90
fail_under = 95
show_missing = true
exclude_lines = [
'if TYPE_CHECKING:',
'pragma: no cover'
Expand Down
142 changes: 105 additions & 37 deletions tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pytest_split.algorithms import (
AlgorithmBase,
Algorithms,
TestGroup,
)

item = namedtuple("item", "nodeid") # noqa: PYI024
Expand All @@ -21,20 +22,25 @@ def test__split_test(self, algo_name):
durations = {"a": 1, "b": 1, "c": 1}
items = [item(x) for x in durations]
algo = Algorithms[algo_name].value

first, second, third = algo(splits=3, items=items, durations=durations)

# each split should have one test
assert first.selected == [item("a")]
assert first.deselected == [item("b"), item("c")]
assert first.duration == 1

assert second.selected == [item("b")]
assert second.deselected == [item("a"), item("c")]
assert second.duration == 1

assert third.selected == [item("c")]
assert third.deselected == [item("a"), item("b")]
assert third.duration == 1
assert first == TestGroup(
selected=[item("a")],
deselected=[item("b"), item("c")],
duration=1,
)
assert second == TestGroup(
selected=[item("b")],
deselected=[item("a"), item("c")],
duration=1,
)
assert third == TestGroup(
selected=[item("c")],
deselected=[item("a"), item("b")],
duration=1,
)

@pytest.mark.parametrize("algo_name", Algorithms.names())
def test__split_tests_handles_tests_in_durations_but_missing_from_items(
Expand All @@ -43,39 +49,83 @@ def test__split_tests_handles_tests_in_durations_but_missing_from_items(
durations = {"a": 1, "b": 1}
items = [item(x) for x in ["a"]]
algo = Algorithms[algo_name].value
splits = algo(splits=2, items=items, durations=durations)

first, second = splits
assert first.selected == [item("a")]
assert second.selected == []
first, second = algo(splits=2, items=items, durations=durations)

assert first == TestGroup(
selected=[item("a")], deselected=[], duration=1
)
assert second == TestGroup(
selected=[], deselected=[item("a")], duration=0
)

@pytest.mark.parametrize("algo_name", Algorithms.names())
def test__split_tests_handles_tests_with_missing_durations(self, algo_name):
durations = {"a": 1}
items = [item(x) for x in ["a", "b"]]
algo = Algorithms[algo_name].value
splits = algo(splits=2, items=items, durations=durations)

first, second = splits
assert first.selected == [item("a")]
assert second.selected == [item("b")]
first, second = algo(splits=2, items=items, durations=durations)

assert first == TestGroup(
selected=[item("a")], deselected=[item("b")], duration=1
)
assert second == TestGroup(
selected=[item("b")], deselected=[item("a")], duration=1
)

def test__split_test_handles_large_duration_at_end(self):
"""NOTE: only least_duration does this correctly"""
durations = {"a": 1, "b": 1, "c": 1, "d": 3}
items = [item(x) for x in ["a", "b", "c", "d"]]
algo = Algorithms["least_duration"].value
splits = algo(splits=2, items=items, durations=durations)

first, second = splits
assert first.selected == [item("d")]
assert second.selected == [item(x) for x in ["a", "b", "c"]]
first, second = algo(splits=2, items=items, durations=durations)

assert first == TestGroup(
selected=[item("d")],
deselected=[item("a"), item("b"), item("c")],
duration=3,
)
assert second == TestGroup(
selected=[item("a"), item("b"), item("c")],
deselected=[item("d")],
duration=3,
)

@pytest.mark.parametrize(
("algo_name", "expected"),
[
("duration_based_chunks", [[item("a"), item("b")], [item("c"), item("d")]]),
("least_duration", [[item("a"), item("c")], [item("b"), item("d")]]),
(
"duration_based_chunks",
[
TestGroup(
selected=[item("a"), item("b")],
deselected=[item("c"), item("d")],
duration=2,
),
TestGroup(
selected=[item("c"), item("d")],
deselected=[item("a"), item("b")],
duration=2,
),
],
),
(
"least_duration",
[
TestGroup(
selected=[item("a"), item("c")],
deselected=[item("b"), item("d")],
duration=2,
),
TestGroup(
selected=[item("b"), item("d")],
deselected=[item("a"), item("c")],
duration=2,
),
],
),
],
)
def test__split_tests_calculates_avg_test_duration_only_on_present_tests(
Expand All @@ -88,36 +138,54 @@ def test__split_tests_calculates_avg_test_duration_only_on_present_tests(
durations = {"b": 1, "c": 1, "d": 1, "e": 10000}
items = [item(x) for x in ["a", "b", "c", "d"]]
algo = Algorithms[algo_name].value
splits = algo(splits=2, items=items, durations=durations)

first, second = splits
expected_first, expected_second = expected
assert first.selected == expected_first
assert second.selected == expected_second
groups = algo(splits=2, items=items, durations=durations)

assert groups == expected

@pytest.mark.parametrize(
("algo_name", "expected"),
[
(
"duration_based_chunks",
[[item("a"), item("b"), item("c"), item("d"), item("e")], []],
[
TestGroup(
selected=[item(x) for x in "abcde"],
deselected=[],
duration=10014,
),
TestGroup(
selected=[],
deselected=[item(x) for x in "abcde"],
duration=0,
),
],
),
(
"least_duration",
[[item("e")], [item("a"), item("b"), item("c"), item("d")]],
[
TestGroup(
selected=[item("e")],
deselected=[item(x) for x in "dcba"],
duration=10000,
),
TestGroup(
selected=[item(x) for x in "abcd"],
deselected=[item("e")],
duration=14,
),
],
),
],
)
def test__split_tests_maintains_relative_order_of_tests(self, algo_name, expected):
durations = {"a": 2, "b": 3, "c": 4, "d": 5, "e": 10000}
items = [item(x) for x in ["a", "b", "c", "d", "e"]]
algo = Algorithms[algo_name].value
splits = algo(splits=2, items=items, durations=durations)

first, second = splits
expected_first, expected_second = expected
assert first.selected == expected_first
assert second.selected == expected_second
groups = algo(splits=2, items=items, durations=durations)

assert groups == expected

def test__split_tests_same_set_regardless_of_order(self):
"""NOTE: only least_duration does this correctly"""
Expand Down