Skip to content

Commit 9efcb8c

Browse files
committed
feat: optimizations, tests
1 parent 78c552e commit 9efcb8c

11 files changed

Lines changed: 1119 additions & 147 deletions

‎pyproject.toml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "wooper-dev"
3-
version = "0.1.9"
3+
version = "0.1.10"
44
description = "A nix thing"
55
readme = "README.md"
66
requires-python = ">=3.14"
@@ -13,3 +13,11 @@ dependencies = [
1313
"requests>=2.32.3",
1414
"uvicorn>=0.34.2",
1515
]
16+
17+
[dependency-groups]
18+
dev = [
19+
"httpx>=0.28.1",
20+
"pytest>=9.0.2",
21+
"pytest-asyncio>=1.3.0",
22+
"ty>=0.0.7",
23+
]

‎tests/__init__.py‎

Whitespace-only changes.

‎tests/conftest.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Pytest configuration."""
2+
3+
import pytest
4+
5+
6+
@pytest.fixture(scope="session")
7+
def anyio_backend():
8+
return "asyncio"

‎tests/test_actual_logic.py‎

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
"""Tests for actual_logic.py"""
2+
3+
import pytest
4+
from packaging.requirements import Requirement
5+
from packaging.version import Version
6+
7+
from wooper_dev.actual_logic import (
8+
NixpkgsRev,
9+
Package,
10+
get_flake_lock,
11+
get_flake_nix,
12+
select_optimal_packages,
13+
)
14+
15+
16+
class TestNixpkgsRev:
17+
def test_comparison(self):
18+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
19+
rev2 = NixpkgsRev(rev="def456", hash="sha256-yyy", date=2000)
20+
21+
assert rev2 > rev1
22+
assert not rev1 > rev2
23+
24+
def test_hash(self):
25+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
26+
rev2 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
27+
28+
assert hash(rev1) == hash(rev2)
29+
assert rev1 == rev2
30+
31+
def test_can_be_used_in_set(self):
32+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
33+
rev2 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
34+
rev3 = NixpkgsRev(rev="def456", hash="sha256-yyy", date=2000)
35+
36+
s = {rev1, rev2, rev3}
37+
assert len(s) == 2
38+
39+
40+
class TestPackage:
41+
def test_input_name_default(self):
42+
rev = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
43+
pkg = Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev)
44+
45+
assert pkg.input_name == "n-python3"
46+
47+
def test_input_name_custom(self):
48+
rev = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
49+
pkg = Package(
50+
name="python3",
51+
version=Version("3.11.0"),
52+
nixpkgs_rev=rev,
53+
_input_name="nixpkgs-0",
54+
)
55+
56+
assert pkg.input_name == "nixpkgs-0"
57+
58+
def test_comparison_by_version(self):
59+
rev = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
60+
pkg1 = Package(name="python3", version=Version("3.10.0"), nixpkgs_rev=rev)
61+
pkg2 = Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev)
62+
63+
assert pkg2 > pkg1
64+
65+
def test_comparison_by_rev_when_same_version(self):
66+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
67+
rev2 = NixpkgsRev(rev="def456", hash="sha256-yyy", date=2000)
68+
pkg1 = Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev1)
69+
pkg2 = Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev2)
70+
71+
assert pkg2 > pkg1
72+
73+
74+
class TestSelectOptimalPackages:
75+
def test_single_package(self):
76+
rev = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
77+
candidates = {
78+
"python3": [
79+
Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev),
80+
]
81+
}
82+
requirements = [Requirement("python3")]
83+
84+
result = select_optimal_packages(requirements, candidates)
85+
86+
assert len(result) == 1
87+
assert result[0].name == "python3"
88+
assert result[0].version == Version("3.11.0")
89+
90+
def test_selects_max_version(self):
91+
rev = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
92+
candidates = {
93+
"python3": [
94+
Package(name="python3", version=Version("3.10.0"), nixpkgs_rev=rev),
95+
Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev),
96+
Package(name="python3", version=Version("3.9.0"), nixpkgs_rev=rev),
97+
]
98+
}
99+
requirements = [Requirement("python3")]
100+
101+
result = select_optimal_packages(requirements, candidates)
102+
103+
assert result[0].version == Version("3.11.0")
104+
105+
def test_consolidates_revisions(self):
106+
"""When multiple packages can use the same revision, they should share it."""
107+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
108+
rev2 = NixpkgsRev(rev="def456", hash="sha256-yyy", date=2000)
109+
110+
candidates = {
111+
"python3": [
112+
Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev1),
113+
Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev2),
114+
],
115+
"nodejs": [
116+
Package(name="nodejs", version=Version("20.0.0"), nixpkgs_rev=rev1),
117+
Package(name="nodejs", version=Version("20.0.0"), nixpkgs_rev=rev2),
118+
],
119+
}
120+
requirements = [Requirement("python3"), Requirement("nodejs")]
121+
122+
result = select_optimal_packages(requirements, candidates)
123+
124+
# Both packages should share the same input_name (same revision)
125+
assert result[0].input_name == result[1].input_name
126+
127+
def test_uses_minimum_revisions(self):
128+
"""Should use minimum number of revisions to cover all packages."""
129+
rev1 = NixpkgsRev(rev="abc123", hash="sha256-xxx", date=1000)
130+
rev2 = NixpkgsRev(rev="def456", hash="sha256-yyy", date=2000)
131+
rev3 = NixpkgsRev(rev="ghi789", hash="sha256-zzz", date=3000)
132+
133+
# python3 v3.11 only in rev1, nodejs v20 only in rev2, git v2.40 in both
134+
candidates = {
135+
"python3": [
136+
Package(name="python3", version=Version("3.11.0"), nixpkgs_rev=rev1),
137+
],
138+
"nodejs": [
139+
Package(name="nodejs", version=Version("20.0.0"), nixpkgs_rev=rev2),
140+
],
141+
"git": [
142+
Package(name="git", version=Version("2.40.0"), nixpkgs_rev=rev1),
143+
Package(name="git", version=Version("2.40.0"), nixpkgs_rev=rev2),
144+
Package(name="git", version=Version("2.40.0"), nixpkgs_rev=rev3),
145+
],
146+
}
147+
requirements = [
148+
Requirement("python3"),
149+
Requirement("nodejs"),
150+
Requirement("git"),
151+
]
152+
153+
result = select_optimal_packages(requirements, candidates)
154+
155+
# Should use only 2 revisions (rev1 for python3+git, rev2 for nodejs)
156+
# or (rev1 for python3, rev2 for nodejs+git)
157+
input_names = {pkg.input_name for pkg in result}
158+
assert len(input_names) == 2
159+
160+
def test_raises_on_missing_package(self):
161+
candidates = {"python3": []}
162+
requirements = [Requirement("python3")]
163+
164+
with pytest.raises(ValueError, match="Version not found"):
165+
select_optimal_packages(requirements, candidates)
166+
167+
168+
class TestGetFlakeNix:
169+
@pytest.mark.asyncio
170+
async def test_generates_valid_flake(self):
171+
rev = NixpkgsRev(rev="abc123def456", hash="sha256-xxx", date=1000)
172+
packages = [
173+
Package(
174+
name="python3",
175+
version=Version("3.11.0"),
176+
nixpkgs_rev=rev,
177+
_input_name="nixpkgs-0",
178+
),
179+
]
180+
181+
flake_nix = await get_flake_nix(packages)
182+
183+
assert "nixpkgs-0" in flake_nix
184+
assert "abc123def456" in flake_nix
185+
assert "python3" in flake_nix
186+
187+
@pytest.mark.asyncio
188+
async def test_deduplicates_inputs(self):
189+
rev = NixpkgsRev(rev="abc123def456", hash="sha256-xxx", date=1000)
190+
packages = [
191+
Package(
192+
name="python3",
193+
version=Version("3.11.0"),
194+
nixpkgs_rev=rev,
195+
_input_name="nixpkgs-0",
196+
),
197+
Package(
198+
name="nodejs",
199+
version=Version("20.0.0"),
200+
nixpkgs_rev=rev,
201+
_input_name="nixpkgs-0",
202+
),
203+
]
204+
205+
flake_nix = await get_flake_nix(packages)
206+
207+
# Input should appear only once
208+
assert flake_nix.count('"nixpkgs-0".url') == 1
209+
# But both packages should be in outputs
210+
assert "python3" in flake_nix
211+
assert "nodejs" in flake_nix
212+
213+
214+
class TestGetFlakeLock:
215+
@pytest.mark.asyncio
216+
async def test_generates_valid_lock(self):
217+
rev = NixpkgsRev(rev="abc123def456", hash="sha256-xxx", date=1000)
218+
packages = [
219+
Package(
220+
name="python3",
221+
version=Version("3.11.0"),
222+
nixpkgs_rev=rev,
223+
_input_name="nixpkgs-0",
224+
),
225+
]
226+
227+
lock = await get_flake_lock(packages)
228+
229+
import json
230+
231+
lock_data = json.loads(lock)
232+
assert "nixpkgs-0" in lock_data["nodes"]
233+
assert lock_data["nodes"]["nixpkgs-0"]["locked"]["rev"] == "abc123def456"
234+
235+
@pytest.mark.asyncio
236+
async def test_deduplicates_inputs(self):
237+
rev = NixpkgsRev(rev="abc123def456", hash="sha256-xxx", date=1000)
238+
packages = [
239+
Package(
240+
name="python3",
241+
version=Version("3.11.0"),
242+
nixpkgs_rev=rev,
243+
_input_name="nixpkgs-0",
244+
),
245+
Package(
246+
name="nodejs",
247+
version=Version("20.0.0"),
248+
nixpkgs_rev=rev,
249+
_input_name="nixpkgs-0",
250+
),
251+
]
252+
253+
lock = await get_flake_lock(packages)
254+
255+
import json
256+
257+
lock_data = json.loads(lock)
258+
# Should only have root, nixpkgs, and nixpkgs-0 (not duplicated)
259+
assert len(lock_data["nodes"]) == 3

0 commit comments

Comments
 (0)