From 3eca51e4d92e9a43d3a236f081a39d99ee949c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Tue, 22 Jun 2021 23:57:26 +0200 Subject: [PATCH] Update tests to reflect changes in the algorithm --- src/pytest_split/algorithms.py | 19 +++++++++++++------ tests/test_algorithms.py | 30 +++++++++++++++--------------- tests/test_plugin.py | 33 +++++++++++++++++---------------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/pytest_split/algorithms.py b/src/pytest_split/algorithms.py index 0821a98..d452254 100644 --- a/src/pytest_split/algorithms.py +++ b/src/pytest_split/algorithms.py @@ -81,14 +81,21 @@ def duration_based_chunks(splits: int, items: "List[nodes.Item]", durations: "Di duration: "List[float]" = [0 for i in range(splits)] group_idx = 0 - for item in items: + test_count = len(items) + for index, item in enumerate(items): item_duration = tests_and_durations.pop(item) - if duration[group_idx] + item_duration > time_per_group: - if not selected[group_idx]: - # Add test to current group if group has no tests - pass - elif group_idx + 1 >= splits: + tests_left = test_count - index + groups_left = splits - group_idx + + if not selected[group_idx]: + # Add test to current group if group has no tests + pass + elif tests_left < groups_left: + # Make sure that we assign at least one test to each group + group_idx += 1 + elif duration[group_idx] + item_duration > time_per_group: + if group_idx + 1 >= splits: # Stay with group index if it's the final group in the split pass else: diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 82714ca..6142022 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -2,7 +2,7 @@ import pytest -from pytest_split.algorithms import Algorithms +from src.pytest_split.algorithms import Algorithms, duration_based_chunks item = namedtuple("item", "nodeid") @@ -11,7 +11,7 @@ class TestAlgorithms: @pytest.mark.parametrize("algo_name", Algorithms.names()) def test__split_test(self, algo_name): durations = {"a": 1, "b": 1, "c": 1} - items = [item(x) for x in durations.keys()] + items = [item(x) for x in durations] algo = Algorithms[algo_name].value first, second, third = algo(splits=3, items=items, durations=durations) @@ -84,26 +84,26 @@ def test__split_tests_calculates_avg_test_duration_only_on_present_tests(self, a assert first.selected == expected_first assert second.selected == expected_second - def test_each_group_is_assigned_a_test(self): - from collections import namedtuple + def test_each_group_is_assigned_a_test_front_loaded(self): + item = namedtuple("item", "nodeid") + + durations = {"a": 100, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1, "g": 1, "h": 1} + + items = [item(x) for x in ["a", "b", "c", "d", "e", "f", "g", "h"]] - from pytest_split import algorithms + groups = duration_based_chunks(8, items, durations) + + for i in range(7): + assert groups[i].selected != [] + def test_each_group_is_assigned_a_test_back_loaded(self): item = namedtuple("item", "nodeid") - durations = {} - durations["a"] = 2313.7016449670773 - durations["b"] = 46.880724348986405 - durations["c"] = 2196.7077018650016 - durations["d"] = 379.9717799640057 - durations["e"] = 1476.3481151770247 - durations["f"] = 979.7326026459923 - durations["g"] = 1876.5443489580794 - durations["h"] = 1.3951316330058035 + durations = {"a": 1, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1, "g": 1, "h": 100} items = [item(x) for x in ["a", "b", "c", "d", "e", "f", "g", "h"]] - groups = algorithms.duration_based_chunks(7, items, durations) + groups = duration_based_chunks(8, items, durations) for i in range(7): assert groups[i].selected != [] diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 4123005..42c3729 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -61,20 +61,20 @@ class TestSplitToSuites: "least_duration", ["test_1", "test_2", "test_3", "test_4", "test_5", "test_6", "test_7", "test_8", "test_9", "test_10"], ), - (2, 1, "duration_based_chunks", ["test_1", "test_2", "test_3", "test_4", "test_5", "test_6", "test_7"]), - (2, 2, "duration_based_chunks", ["test_8", "test_9", "test_10"]), + (2, 1, "duration_based_chunks", ["test_1", "test_2", "test_3", "test_4", "test_5", "test_6"]), + (2, 2, "duration_based_chunks", ["test_7", "test_8", "test_9", "test_10"]), (2, 1, "least_duration", ["test_1", "test_3", "test_5", "test_7", "test_9"]), (2, 2, "least_duration", ["test_2", "test_4", "test_6", "test_8", "test_10"]), (3, 1, "duration_based_chunks", ["test_1", "test_2", "test_3", "test_4", "test_5"]), - (3, 2, "duration_based_chunks", ["test_6", "test_7", "test_8"]), - (3, 3, "duration_based_chunks", ["test_9", "test_10"]), + (3, 2, "duration_based_chunks", ["test_6", "test_7"]), + (3, 3, "duration_based_chunks", ["test_8", "test_9", "test_10"]), (3, 1, "least_duration", ["test_1", "test_4", "test_7", "test_10"]), (3, 2, "least_duration", ["test_2", "test_5", "test_8"]), (3, 3, "least_duration", ["test_3", "test_6", "test_9"]), - (4, 1, "duration_based_chunks", ["test_1", "test_2", "test_3", "test_4"]), - (4, 2, "duration_based_chunks", ["test_5", "test_6", "test_7"]), - (4, 3, "duration_based_chunks", ["test_8", "test_9"]), - (4, 4, "duration_based_chunks", ["test_10"]), + (4, 1, "duration_based_chunks", ["test_1", "test_2", "test_3"]), + (4, 2, "duration_based_chunks", ["test_4", "test_5"]), + (4, 3, "duration_based_chunks", ["test_6"]), + (4, 4, "duration_based_chunks", ["test_7", "test_8", "test_9", "test_10"]), (4, 1, "least_duration", ["test_1", "test_5", "test_9"]), (4, 2, "least_duration", ["test_2", "test_6", "test_10"]), (4, 3, "least_duration", ["test_3", "test_7"]), @@ -107,6 +107,7 @@ def test_it_splits(self, test_idx, splits, group, algo, expected, legacy_flag, e "--splitting-algorithm", algo, ) + result.assertoutcome(passed=len(expected)) assert _passed_test_names(result) == expected @@ -128,16 +129,16 @@ def test_it_adapts_splits_based_on_new_and_deleted_tests(self, example_suite, du json.dump(durations, f) result = example_suite.inline_run("--splits", "3", "--group", "1", "--durations-path", durations_path) - result.assertoutcome(passed=4) - assert _passed_test_names(result) == ["test_1", "test_2", "test_3", "test_4"] + result.assertoutcome(passed=3) + assert _passed_test_names(result) == ["test_1", "test_2", "test_3"] # 3 sec result = example_suite.inline_run("--splits", "3", "--group", "2", "--durations-path", durations_path) - result.assertoutcome(passed=3) - assert _passed_test_names(result) == ["test_5", "test_6", "test_7"] + result.assertoutcome(passed=1) + assert _passed_test_names(result) == ["test_4"] # 1 sec result = example_suite.inline_run("--splits", "3", "--group", "3", "--durations-path", durations_path) - result.assertoutcome(passed=3) - assert _passed_test_names(result) == ["test_8", "test_9", "test_10"] + result.assertoutcome(passed=6) + assert _passed_test_names(result) == ["test_5", "test_6", "test_7", "test_8", "test_9", "test_10"] # 3 sec def test_handles_case_of_no_durations_for_group(self, example_suite, durations_path): with open(durations_path, "w") as f: @@ -149,8 +150,8 @@ def test_handles_case_of_no_durations_for_group(self, example_suite, durations_p def test_it_splits_with_other_collect_hooks(self, testdir, durations_path): expected_tests_per_group = [ - ["test_1", "test_2", "test_3"], - ["test_4", "test_5"], + ["test_1", "test_2"], + ["test_3", "test_4", "test_5"], ] tests_to_run = "".join(f"@pytest.mark.mark_one\ndef test_{num}(): pass\n" for num in range(1, 6))