Skip to content

Commit 49d08e3

Browse files
Replace placeholder test file with comparison-sort coverage
Part of #15234
1 parent 68f4b06 commit 49d08e3

1 file changed

Lines changed: 243 additions & 1 deletion

File tree

‎tests/test_sorts.py‎

Lines changed: 243 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,243 @@
1-
PLACEHOLDER
1+
"""
2+
Tests for the general-purpose comparison sorts in ``sorts/``.
3+
4+
Every algorithm exercised here implements the same contract: given a list of
5+
mutually comparable items it returns a new list with the same items in
6+
non-decreasing order (i.e. it agrees with the built-in ``sorted``). Rather than
7+
repeat a hand-written test per file we run each sort against a shared battery of
8+
inputs with :func:`pytest.mark.parametrize`.
9+
10+
Specialised sorts that only accept a restricted domain are intentionally left
11+
out (e.g. ``counting_sort``/``radix_sort``/``pigeon_sort`` are integer-only,
12+
``bead_sort`` needs non-negative integers, ``dutch_national_flag_sort`` expects
13+
0/1/2, ``bitonic_sort`` needs a power-of-two length, ``topological_sort`` works
14+
on a graph, and ``stalin_sort``/``wiggle_sort`` deliberately do not fully sort).
15+
``rec_insertion_sort`` is also left out of the battery: it sorts in place and
16+
returns ``None`` rather than the sorted collection, so it is exercised
17+
separately below.
18+
"""
19+
20+
from dataclasses import dataclass
21+
from typing import NamedTuple
22+
23+
import pytest
24+
25+
from sorts.binary_insertion_sort import binary_insertion_sort
26+
from sorts.bogo_sort import bogo_sort
27+
from sorts.bubble_sort import bubble_sort_iterative, bubble_sort_recursive
28+
from sorts.circle_sort import circle_sort
29+
from sorts.cocktail_shaker_sort import cocktail_shaker_sort
30+
from sorts.comb_sort import comb_sort
31+
from sorts.cycle_sort import cycle_sort
32+
from sorts.double_sort import double_sort
33+
from sorts.exchange_sort import exchange_sort
34+
from sorts.gnome_sort import gnome_sort
35+
from sorts.heap_sort import heap_sort
36+
from sorts.insertion_sort import insertion_sort
37+
from sorts.intro_sort import heap_sort as intro_heap_sort
38+
from sorts.intro_sort import intro_sort as intro_sort_range
39+
from sorts.intro_sort import sort as intro_sort
40+
from sorts.iterative_merge_sort import iter_merge_sort
41+
from sorts.merge_insertion_sort import merge_insertion_sort
42+
from sorts.merge_sort import merge_sort
43+
from sorts.odd_even_sort import odd_even_sort
44+
from sorts.odd_even_transposition_single_threaded import odd_even_transposition
45+
from sorts.pancake_sort import pancake_sort
46+
from sorts.patience_sort import patience_sort
47+
from sorts.quick_sort import quick_sort
48+
from sorts.quick_sort_3_partition import three_way_radix_quicksort
49+
from sorts.recursive_insertion_sort import rec_insertion_sort
50+
from sorts.recursive_mergesort_array import merge
51+
from sorts.recursive_quick_sort import quick_sort as recursive_quick_sort
52+
from sorts.reverse_selection import reverse_selection_sort
53+
from sorts.reversort import reversort
54+
from sorts.selection_sort import selection_sort
55+
from sorts.shell_sort import shell_sort
56+
from sorts.shrink_shell_sort import shell_sort as shrink_shell_sort
57+
from sorts.smoothsort import smoothsort
58+
from sorts.stooge_sort import stooge_sort
59+
from sorts.strand_sort import strand_sort
60+
from sorts.tim_sort import tim_sort
61+
from sorts.unknown_sort import merge_sort as unknown_sort
62+
63+
64+
def test_heap_sort() -> None:
65+
assert heap_sort([]) == []
66+
assert heap_sort([1]) == [1]
67+
assert heap_sort([5, 2, 5, 1]) == [1, 2, 5, 5]
68+
assert heap_sort([1, 2, 3, 4]) == [1, 2, 3, 4]
69+
assert heap_sort([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5]
70+
71+
72+
@pytest.mark.parametrize(
73+
("start", "end"),
74+
[(start, end) for start in range(7) for end in [None, *range(start, 7)]],
75+
)
76+
def test_intro_heap_sort_range(start: int, end: int | None) -> None:
77+
collection = [100, 4, 1, 3, 1, -100]
78+
expected = collection[:start] + sorted(collection[start:end])
79+
if end is not None:
80+
expected += collection[end:]
81+
82+
result = intro_heap_sort(collection, start, end)
83+
84+
assert result is collection
85+
assert collection == expected
86+
87+
88+
@pytest.mark.parametrize("max_depth", [0, 1])
89+
def test_intro_sort_heap_fallback_preserves_surrounding_items(max_depth: int) -> None:
90+
collection = [100, *range(40, 0, -1), -100]
91+
expected = [100, *range(1, 41), -100]
92+
93+
result = intro_sort_range(collection, 1, 41, 16, max_depth)
94+
95+
assert result is collection
96+
assert collection == expected
97+
98+
99+
SORTS = (
100+
binary_insertion_sort,
101+
bubble_sort_iterative,
102+
circle_sort,
103+
cocktail_shaker_sort,
104+
comb_sort,
105+
cycle_sort,
106+
double_sort,
107+
exchange_sort,
108+
gnome_sort,
109+
heap_sort,
110+
insertion_sort,
111+
intro_sort,
112+
iter_merge_sort,
113+
merge,
114+
merge_insertion_sort,
115+
merge_sort,
116+
odd_even_sort,
117+
odd_even_transposition,
118+
pancake_sort,
119+
patience_sort,
120+
quick_sort,
121+
recursive_quick_sort,
122+
reverse_selection_sort,
123+
reversort,
124+
selection_sort,
125+
shell_sort,
126+
shrink_shell_sort,
127+
smoothsort,
128+
stooge_sort,
129+
strand_sort,
130+
three_way_radix_quicksort,
131+
tim_sort,
132+
unknown_sort,
133+
)
134+
135+
136+
@dataclass(order=True)
137+
class Person:
138+
name: str = "Bob"
139+
age: int = 37
140+
cost: float = 0.0
141+
142+
143+
class Dog(NamedTuple):
144+
name: str = "Fido"
145+
age: int = 5
146+
weight: float = 15.5
147+
148+
149+
CASES = (
150+
[],
151+
[1],
152+
[10, -10, -1, 1, 0],
153+
[1.1, -1.1, -1, 1, 0],
154+
list("Python!"),
155+
[3, 3, 1, 2, 2, 1],
156+
[5, 4, 3, 2, 1],
157+
[1, 2, 3, 4, 5],
158+
[-2, -2, 0, 0, 7, 7],
159+
[Person(cost=100.0), Person(cost=-100.0), Person(name="Al")],
160+
[Dog(weight=15.5), Dog(weight=15.1), Dog(name="Buddy")],
161+
)
162+
163+
164+
@pytest.mark.parametrize("sort", SORTS, ids=lambda f: f.__name__)
165+
@pytest.mark.parametrize("case", CASES, ids=repr)
166+
def test_sort_matches_builtin(sort, case) -> None:
167+
"""Each sort must reproduce the ordering of the built-in ``sorted``."""
168+
assert list(sort(list(case))) == sorted(case)
169+
170+
171+
@pytest.mark.parametrize("case", CASES, ids=repr)
172+
def test_rec_insertion_sort(case) -> None:
173+
"""``rec_insertion_sort`` sorts in place and returns ``None``."""
174+
collection = list(case)
175+
assert rec_insertion_sort(collection, len(collection)) is None
176+
assert collection == sorted(case)
177+
178+
179+
@pytest.mark.parametrize(
180+
"sort",
181+
[
182+
binary_insertion_sort,
183+
bubble_sort_iterative,
184+
bubble_sort_recursive,
185+
circle_sort,
186+
cocktail_shaker_sort,
187+
comb_sort,
188+
cycle_sort,
189+
exchange_sort,
190+
gnome_sort,
191+
insertion_sort,
192+
intro_sort,
193+
iter_merge_sort,
194+
merge,
195+
merge_insertion_sort,
196+
merge_sort,
197+
odd_even_sort,
198+
odd_even_transposition,
199+
pancake_sort,
200+
patience_sort,
201+
recursive_quick_sort,
202+
reverse_selection_sort,
203+
reversort,
204+
selection_sort,
205+
shrink_shell_sort,
206+
strand_sort,
207+
three_way_radix_quicksort,
208+
tim_sort,
209+
unknown_sort,
210+
],
211+
ids=lambda f: f.__name__,
212+
)
213+
def test_sort_rejects_non_comparable_items(sort) -> None:
214+
with pytest.raises(TypeError):
215+
sort([1, "a"])
216+
217+
218+
def test_rec_insertion_sort_rejects_non_comparable_items() -> None:
219+
with pytest.raises(TypeError):
220+
rec_insertion_sort([1, "a"], 2)
221+
222+
223+
def test_bogo_sort_comparable_items() -> None:
224+
assert bogo_sort(["c", "a", "b"]) == ["a", "b", "c"]
225+
assert bogo_sort([2.5, -1.0, 0.0]) == [-1.0, 0.0, 2.5]
226+
227+
with pytest.raises(TypeError):
228+
bogo_sort([1, "a"])
229+
230+
231+
def test_bitonic_sort_comparable_items() -> None:
232+
from sorts.bitonic_sort import bitonic_sort
233+
234+
strings = ["banana", "apple", "cherry", "date"]
235+
bitonic_sort(strings, 0, len(strings), 1)
236+
assert strings == ["apple", "banana", "cherry", "date"]
237+
238+
numbers = [3, 1.5, 2, 4.5]
239+
bitonic_sort(numbers, 0, len(numbers), 1)
240+
assert numbers == [1.5, 2, 3, 4.5]
241+
242+
with pytest.raises(TypeError):
243+
bitonic_sort([1, "two", 3, "four"], 0, 4, 1)

0 commit comments

Comments
 (0)