Skip to content

Commit 5a619ed

Browse files
committed
feat(tests): Testing of utils and add coverage
1 parent 9e533f8 commit 5a619ed

File tree

8 files changed

+374
-4
lines changed

8 files changed

+374
-4
lines changed

.github/workflows/pr.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,10 @@ jobs:
2929
3030
- name: Run tests
3131
run: |
32-
uv run pytest
32+
uv run pytest --cov --cov-branch --cov-report=xml
3333
uv run ruff check
34+
- name: Upload coverage reports to Codecov
35+
uses: codecov/codecov-action@v5
36+
with:
37+
token: ${{ secrets.CODECOV_TOKEN }}
38+
slug: CalvoM/my-chess-style

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__/
33
.env
44
uploads/
55
.ipynb_checkpoints/
6+
.coverage

pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ data_training = [
4343
dev = [
4444
"celery-types>=0.23.0",
4545
"chess-com>=3.11.1",
46+
"coverage>=7.9.2",
4647
"django-stubs>=5.1.3",
4748
"django-types>=0.20.0",
4849
"flower>=2.0.1",
4950
"ipython>=9.1.0",
5051
"pytest>=8.3.5",
52+
"pytest-cov>=6.2.1",
5153
"ruff>=0.11.3",
5254
"types-requests>=2.32.0.20250328",
5355
"watchdog>=6.0.0",
@@ -65,3 +67,11 @@ venv = ".venv"
6567

6668
[tool.uv.sources]
6769
stockfish = { git = "https://github.com/py-stockfish/stockfish", rev = "master" }
70+
71+
[tool.coverage.run]
72+
source = ["style_predictor"]
73+
branch = true
74+
75+
[tool.coverage.report]
76+
show_missing = true
77+
skip_covered = false

requirements.txt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,32 @@ contourpy==1.3.1 \
142142
--hash=sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699 \
143143
--hash=sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81
144144
# via matplotlib
145+
coverage==7.9.2 \
146+
--hash=sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a \
147+
--hash=sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b \
148+
--hash=sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926 \
149+
--hash=sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6 \
150+
--hash=sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e \
151+
--hash=sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694 \
152+
--hash=sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5 \
153+
--hash=sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868 \
154+
--hash=sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584 \
155+
--hash=sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d \
156+
--hash=sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d \
157+
--hash=sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8 \
158+
--hash=sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038 \
159+
--hash=sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b \
160+
--hash=sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3 \
161+
--hash=sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46 \
162+
--hash=sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3 \
163+
--hash=sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14 \
164+
--hash=sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac \
165+
--hash=sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb \
166+
--hash=sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b \
167+
--hash=sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4 \
168+
--hash=sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b \
169+
--hash=sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd
170+
# via pytest-cov
145171
cramjam==2.10.0 \
146172
--hash=sha256:27b2625c0840b9a5522eba30b165940084391762492e03b9d640fca5074016ae \
147173
--hash=sha256:44c15f6117031a84497433b5f55d30ee72d438fdcba9778fec0c5ca5d416aa96 \
@@ -475,7 +501,9 @@ pillow==11.1.0 \
475501
pluggy==1.5.0 \
476502
--hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
477503
--hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
478-
# via pytest
504+
# via
505+
# pytest
506+
# pytest-cov
479507
prometheus-client==0.21.1 \
480508
--hash=sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb \
481509
--hash=sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301
@@ -598,6 +626,10 @@ pyparsing==3.2.3 \
598626
pytest==8.3.5 \
599627
--hash=sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820 \
600628
--hash=sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845
629+
# via pytest-cov
630+
pytest-cov==6.2.1 \
631+
--hash=sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2 \
632+
--hash=sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5
601633
python-dateutil==2.9.0.post0 \
602634
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
603635
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427

style_predictor/tests/test_tasks.py

Whitespace-only changes.
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import pytest
2+
3+
from style_predictor.utils import (
4+
average_rating,
5+
group_openings_with_eco,
6+
merge_game_objects,
7+
sort_openings,
8+
)
9+
10+
11+
class TestUtils:
12+
@pytest.mark.parametrize(
13+
"opening_data,expected",
14+
[
15+
(
16+
(
17+
("E1", "Opening_1", 1),
18+
("E2", "Opening_2", 2),
19+
("E3", "Opening_3", 3),
20+
("E1", "Opening_1", 4),
21+
),
22+
{
23+
"Opening_1": {"total": 5, "eco_codes": ["E1"]},
24+
"Opening_2": {"total": 2, "eco_codes": ["E2"]},
25+
"Opening_3": {"total": 3, "eco_codes": ["E3"]},
26+
},
27+
),
28+
(
29+
(
30+
("E1", "Opening_1", 1),
31+
("E2", "Opening_2", 2),
32+
("E3", "Opening_3", 3),
33+
("E5", "Opening_1", 4),
34+
),
35+
{
36+
"Opening_1": {"total": 5, "eco_codes": ["E1", "E5"]},
37+
"Opening_2": {"total": 2, "eco_codes": ["E2"]},
38+
"Opening_3": {"total": 3, "eco_codes": ["E3"]},
39+
},
40+
),
41+
],
42+
)
43+
def test_group_openings_with_eco(self, opening_data, expected):
44+
assert group_openings_with_eco(opening_data) == expected # nosec
45+
46+
@pytest.mark.parametrize(
47+
"openings,expected",
48+
[
49+
(
50+
{
51+
"Opening_1": {"total": 5, "eco_codes": ["E1", "E5"]},
52+
"Opening_2": {"total": 2, "eco_codes": ["E2"]},
53+
"Opening_3": {"total": 3, "eco_codes": ["E3"]},
54+
},
55+
[
56+
("Opening_1", {"total": 5, "eco_codes": ["E1", "E5"]}),
57+
("Opening_3", {"total": 3, "eco_codes": ["E3"]}),
58+
("Opening_2", {"total": 2, "eco_codes": ["E2"]}),
59+
],
60+
),
61+
(
62+
{
63+
"Opening_1": {"total": 5, "eco_codes": ["E1", "E5"]},
64+
"Opening_2": {"total": 2, "eco_codes": ["E2"]},
65+
"Opening_3": {"total": 3, "eco_codes": ["E3"]},
66+
},
67+
[
68+
("Opening_1", {"total": 5, "eco_codes": ["E1", "E5"]}),
69+
("Opening_3", {"total": 3, "eco_codes": ["E3"]}),
70+
("Opening_2", {"total": 2, "eco_codes": ["E2"]}),
71+
],
72+
),
73+
],
74+
)
75+
def test_sort_openings(self, openings, expected):
76+
assert sort_openings(openings) == expected # nosec
77+
78+
@pytest.mark.parametrize(
79+
"game_rating,expected",
80+
[
81+
(
82+
{"hyper": (10, 2), "bullet": (15, 2), "classical": (20, 2)},
83+
{"hyper": 5.0, "bullet": 7.5, "classical": 10.0},
84+
),
85+
(
86+
{"hyper": (10, 0), "bullet": (0, 0), "classical": (20, 2)},
87+
{"hyper": 0, "bullet": 0, "classical": 10.0},
88+
),
89+
],
90+
)
91+
def test_average_rating(self, game_rating, expected):
92+
assert average_rating(game_rating) == expected # nosec
93+
94+
@pytest.mark.parametrize(
95+
"game_objects,expected",
96+
[
97+
(
98+
[
99+
{"count": 1, "wins": 2, "losses": 3},
100+
{"count": 1, "wins": 2, "losses": 3},
101+
{"count": 1, "wins": 2, "losses": 3},
102+
],
103+
{"count": 3, "wins": 6, "losses": 9},
104+
),
105+
(
106+
[
107+
{"count": 1, "wins": 2, "losses": 3},
108+
{"count": 1, "wins": 2, "losses": 3},
109+
{"count": 1, "wins": 2, "losses": 3},
110+
],
111+
{"count": 3, "wins": 6, "losses": 9},
112+
),
113+
],
114+
)
115+
def test_merge_game_objects(self, game_objects, expected):
116+
res = {}
117+
for obj in game_objects:
118+
merge_game_objects(res, obj)
119+
assert res == expected # nosec
120+
121+
@pytest.mark.parametrize(
122+
"game_objects,expected",
123+
[
124+
(
125+
[
126+
{
127+
"opponents_avg_rating": {
128+
"bullet": [941.0, 1],
129+
"blitz": [201.0, 1],
130+
"rapid": [401.0, 98],
131+
"classical": [0.0, 0],
132+
},
133+
"openings": [
134+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
135+
["B01", "Scandinavian Defense", 8],
136+
[
137+
"C20",
138+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
139+
6,
140+
],
141+
["B20", "Sicilian Defense", 5],
142+
["C20", "King's Pawn Game: Napoleon Attack", 4],
143+
],
144+
},
145+
{
146+
"opponents_avg_rating": {
147+
"bullet": [500.0, 4],
148+
"blitz": [201.0, 1],
149+
"rapid": [401.0, 98],
150+
"classical": [0.0, 0],
151+
},
152+
"openings": [
153+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
154+
["B01", "Scandinavian Defense", 8],
155+
[
156+
"C20",
157+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
158+
6,
159+
],
160+
["B20", "Sicilian Defense", 5],
161+
["C20", "King's Pawn Game: Napoleon Attack", 4],
162+
],
163+
},
164+
],
165+
{
166+
"opponents_avg_rating": {
167+
"bullet": (2941.0, 5),
168+
"blitz": (402.0, 2),
169+
"rapid": (78596.0, 196),
170+
"classical": (0.0, 0),
171+
},
172+
"openings": [
173+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
174+
["B01", "Scandinavian Defense", 8],
175+
[
176+
"C20",
177+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
178+
6,
179+
],
180+
["B20", "Sicilian Defense", 5],
181+
["C20", "King's Pawn Game: Napoleon Attack", 4],
182+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
183+
["B01", "Scandinavian Defense", 8],
184+
[
185+
"C20",
186+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
187+
6,
188+
],
189+
["B20", "Sicilian Defense", 5],
190+
["C20", "King's Pawn Game: Napoleon Attack", 4],
191+
],
192+
},
193+
),
194+
(
195+
[
196+
{
197+
"opponents_avg_rating": {
198+
"bullet": [941.0, 1],
199+
"blitz": [201.0, 1],
200+
"rapid": [401.1, 98],
201+
"classical": [0.0, 0],
202+
},
203+
"openings": [
204+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
205+
["B01", "Scandinavian Defense", 8],
206+
[
207+
"C20",
208+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
209+
6,
210+
],
211+
["B20", "Sicilian Defense", 5],
212+
["C20", "King's Pawn Game: Napoleon Attack", 4],
213+
],
214+
},
215+
{
216+
"opponents_avg_rating": {
217+
"bullet": [941.0, 1],
218+
"blitz": [201.0, 1],
219+
"rapid": [401.1, 98],
220+
"classical": [0.0, 0],
221+
},
222+
"openings": [
223+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
224+
["B01", "Scandinavian Defense", 8],
225+
[
226+
"C20",
227+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
228+
6,
229+
],
230+
["B20", "Sicilian Defense", 5],
231+
["C20", "King's Pawn Game: Napoleon Attack", 4],
232+
],
233+
},
234+
],
235+
{
236+
"opponents_avg_rating": {
237+
"bullet": (1882.0, 2),
238+
"blitz": (402.0, 2),
239+
"rapid": (78615.6, 196),
240+
"classical": (0.0, 0),
241+
},
242+
"openings": [
243+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
244+
["B01", "Scandinavian Defense", 8],
245+
[
246+
"C20",
247+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
248+
6,
249+
],
250+
["B20", "Sicilian Defense", 5],
251+
["C20", "King's Pawn Game: Napoleon Attack", 4],
252+
["C20", "King's Pawn Game: Wayward Queen Attack", 23],
253+
["B01", "Scandinavian Defense", 8],
254+
[
255+
"C20",
256+
"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit",
257+
6,
258+
],
259+
["B20", "Sicilian Defense", 5],
260+
["C20", "King's Pawn Game: Napoleon Attack", 4],
261+
],
262+
},
263+
),
264+
],
265+
)
266+
def test_nested_merge_game_objects(self, game_objects, expected):
267+
res = {}
268+
for obj in game_objects:
269+
res = merge_game_objects(res, obj)
270+
assert res == expected # nosec

style_predictor/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
import logging
12
from collections import defaultdict
23
from typing import Any
34

5+
LOG = logging.getLogger(__name__)
6+
47

58
def group_openings_with_eco(
69
data: list[tuple[str, str, int]],
710
) -> dict[str, dict[str, int | list[str]]]:
811
"""Group the openings and tally the count of each opening.
912
1013
Args:
11-
data: list of (chess echo code, chess full name, count of chess openings)
14+
data: list of (chess eco code, chess full name, count of chess openings)
1215
1316
Returns:
1417
Map of chess full name to eco code and total count of openings.
@@ -22,7 +25,7 @@ def group_openings_with_eco(
2225
return grouped
2326

2427

25-
def sort_openings(openings):
28+
def sort_openings(openings: dict[str, dict[str, dict[str, int | list[str]]]]):
2629
return sorted(openings.items(), key=lambda item: item[1]["total"], reverse=True)[:5]
2730

2831

0 commit comments

Comments
 (0)