Skip to content

Commit f24f6fb

Browse files
cursoragentendolith
andcommitted
Add unit tests for partisan primaries and coverage gaps (codecov)
- Exercise nominee_restricted_plurality, pairwise_majority, open/closed primaries, top_two_runoff_reduced_turnout (including sntv edge cases) - Cover three_two_one pairwise branches and 2D validation - Cover honest_321_ratings and honest_normed_scores invalid min/max range Co-authored-by: endolith <endolith@gmail.com>
1 parent c4b9e56 commit f24f6fb

3 files changed

Lines changed: 169 additions & 1 deletion

File tree

tests/test_partisan_primaries.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import numpy as np
2+
import pytest
3+
4+
from elsim.methods.partisan_primaries import (
5+
closed_partisan_primary_runoff,
6+
nominee_restricted_plurality,
7+
open_partisan_primary,
8+
pairwise_majority_from_rankings,
9+
top_two_runoff_reduced_turnout,
10+
)
11+
12+
13+
def test_nominee_restricted_plurality_basic():
14+
rankings = np.array([
15+
[0, 1, 2, 3],
16+
[0, 2, 1, 3],
17+
[2, 3, 0, 1],
18+
[2, 0, 1, 3],
19+
], dtype=np.uint8)
20+
assert nominee_restricted_plurality(
21+
rankings, [0, 1], np.array([0, 1]), 'order') == 0
22+
assert nominee_restricted_plurality(
23+
rankings, [2, 3], np.array([2, 3]), 'order') == 2
24+
25+
26+
def test_nominee_restricted_plurality_tie_order():
27+
rankings = np.array([[0, 1], [1, 0]], dtype=np.uint8)
28+
assert nominee_restricted_plurality(
29+
rankings, [0, 1], np.array([0, 1]), 'order') == 0
30+
31+
32+
def test_pairwise_majority_prefers_a():
33+
rankings = np.array([
34+
[0, 1],
35+
[0, 1],
36+
[1, 0],
37+
], dtype=np.uint8)
38+
assert pairwise_majority_from_rankings(
39+
rankings, [0, 1, 2], 0, 1, 'order') == 0
40+
41+
42+
def test_pairwise_majority_prefers_b():
43+
rankings = np.array([
44+
[1, 0],
45+
[1, 0],
46+
[0, 1],
47+
], dtype=np.uint8)
48+
assert pairwise_majority_from_rankings(
49+
rankings, [0, 1, 2], 0, 1, 'order') == 1
50+
51+
52+
def test_pairwise_majority_tie_order():
53+
rankings = np.array([[0, 1], [1, 0]], dtype=np.uint8)
54+
assert pairwise_majority_from_rankings(
55+
rankings, [0, 1], 0, 1, 'order') == 0
56+
57+
58+
def test_open_partisan_primary():
59+
rankings = np.array([
60+
[0, 1, 2, 3],
61+
[0, 1, 2, 3],
62+
[2, 3, 0, 1],
63+
[2, 3, 0, 1],
64+
], dtype=np.uint8)
65+
w = open_partisan_primary(
66+
rankings, 2, [0, 1], [2, 3], [0, 1, 2, 3], 'order')
67+
assert w == 0
68+
69+
70+
def test_closed_partisan_primary_runoff_subset():
71+
rankings = np.array([
72+
[0, 1, 2, 3],
73+
[0, 1, 2, 3],
74+
[2, 3, 0, 1],
75+
[2, 3, 0, 1],
76+
], dtype=np.uint8)
77+
w = closed_partisan_primary_runoff(
78+
rankings, 2, [0, 1], [2, 3], [0, 2], 'order')
79+
assert w == 0
80+
81+
82+
def test_top_two_runoff_unanimous_first_place():
83+
rankings = np.array([[0, 1, 2], [0, 2, 1], [0, 1, 2]], dtype=np.uint8)
84+
w = top_two_runoff_reduced_turnout(
85+
rankings, [0, 1, 2], [0, 1, 2], 'order')
86+
assert w == 0
87+
88+
89+
def test_top_two_runoff_two_finalists_pairwise():
90+
rankings = np.array([
91+
[0, 1, 2],
92+
[0, 2, 1],
93+
[1, 0, 2],
94+
[1, 2, 0],
95+
], dtype=np.uint8)
96+
w = top_two_runoff_reduced_turnout(
97+
rankings, np.arange(4), np.arange(4), 'order')
98+
assert w == 0
99+
100+
101+
def test_top_two_runoff_sntv_returns_none():
102+
rankings = np.array([
103+
[0, 1, 2, 3],
104+
[1, 0, 2, 3],
105+
[2, 1, 0, 3],
106+
[3, 1, 2, 0],
107+
], dtype=np.uint8)
108+
assert top_two_runoff_reduced_turnout(
109+
rankings, [0, 1, 2, 3], [0, 1, 2, 3], tiebreaker=None) is None
110+
111+
112+
def test_top_two_runoff_too_many_sntv_winners_returns_none():
113+
from unittest.mock import patch
114+
115+
rankings = np.array([[0, 1], [1, 0]], dtype=np.uint8)
116+
with patch('elsim.methods.partisan_primaries.sntv',
117+
return_value={0, 1, 2}):
118+
assert top_two_runoff_reduced_turnout(
119+
rankings, [0, 1], [0, 1], 'order') is None

tests/test_strategies.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from hypothesis.strategies import floats, integers, tuples
66
from numpy.testing import assert_array_equal
77

8-
from elsim.strategies import approval_optimal, honest_normed_scores, vote_for_k
8+
from elsim.strategies import (approval_optimal, honest_321_ratings,
9+
honest_normed_scores, vote_for_k)
910

1011

1112
def test_approval_optimal():
@@ -38,6 +39,28 @@ def test_honest_normed_scores():
3839
])
3940

4041

42+
def test_honest_normed_scores_invalid_range():
43+
with pytest.raises(ValueError):
44+
honest_normed_scores([[0.0, 1.0]], max_score=1, min_score=5)
45+
46+
47+
def test_honest_321_ratings_shapes():
48+
assert_array_equal(honest_321_ratings([[1.0]]), [[2]])
49+
assert_array_equal(
50+
honest_321_ratings([[0.0, 1.0], [1.0, 0.0]]),
51+
[[0, 2], [2, 0]])
52+
u = np.ones((1, 9))
53+
r = honest_321_ratings(u)
54+
assert r.shape == (1, 9)
55+
assert set(np.unique(r)) <= {0, 1, 2}
56+
57+
58+
def test_honest_321_ratings_too_many_candidates():
59+
utilities = np.ones((1, 256))
60+
with pytest.raises(ValueError):
61+
honest_321_ratings(utilities)
62+
63+
4164
def test_vote_for_k():
4265
utilities = np.array([[0.0, 0.4, 1.0],
4366
[1.0, 0.5, 0.9],

tests/test_three_two_one.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from elsim.methods import three_two_one
5+
from elsim.methods.three_two_one import _pairwise_rating_preference
56

67

78
def test_three_two_one_invalid_ballots():
@@ -24,6 +25,31 @@ def test_three_two_one_two_candidates():
2425
assert three_two_one(election, 'order') == 0
2526

2627

28+
def test_three_two_one_two_candidates_second_wins():
29+
election = np.array([[0, 2], [0, 2], [2, 0]], dtype=np.uint8)
30+
assert three_two_one(election, 'order') == 1
31+
32+
33+
def test_three_two_one_requires_2d():
34+
with pytest.raises(ValueError, match='2D'):
35+
three_two_one(np.array([0, 1, 2]))
36+
37+
38+
def test_pairwise_rating_preference_total_score_favors_a():
39+
election = np.array([[0, 1], [2, 0]], dtype=np.uint8)
40+
assert _pairwise_rating_preference(election, 0, 1, 'order') == 0
41+
42+
43+
def test_pairwise_rating_preference_total_score_favors_b():
44+
election = np.array([[0, 2], [1, 0]], dtype=np.uint8)
45+
assert _pairwise_rating_preference(election, 0, 1, 'order') == 1
46+
47+
48+
def test_pairwise_rating_preference_full_tie_none_tiebreaker():
49+
election = np.array([[1, 1], [1, 1]], dtype=np.uint8)
50+
assert _pairwise_rating_preference(election, 0, 1, None) is None
51+
52+
2753
def test_three_two_one_eliminates_most_bad():
2854
# Semifinalists 0,1,2 by Good count; among them candidate 1 has most Bad.
2955
election = np.array([

0 commit comments

Comments
 (0)