Skip to content

Commit 5b3e801

Browse files
committed
unit tests for bq
1 parent 7948cad commit 5b3e801

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

tests/test_bq.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import argparse
2+
from contextlib import contextmanager
3+
from unittest import mock
4+
5+
import openshift_client as oc
6+
import pytest
7+
8+
from batchtools.bq import GpuQueuesCommand
9+
10+
11+
def _cq_dict(
12+
name="a-cq",
13+
*,
14+
admitted=1,
15+
pending=2,
16+
reserving=0,
17+
queueing="StrictFIFO",
18+
gpu_quotas=(2, 3),
19+
include_nongpu=True,
20+
):
21+
"""
22+
Build a ClusterQueue dict with spec.resourceGroups[].flavors[].resources[]
23+
and status.{admittedWorkloads,pendingWorkloads,reservingWorkloads}.
24+
"""
25+
resources = []
26+
for q in gpu_quotas:
27+
# GPU entry
28+
resources.append({"name": "nvidia.com/gpu", "nominalQuota": q})
29+
if include_nongpu:
30+
resources.append({"name": "cpu", "nominalQuota": 1000})
31+
32+
spec = {
33+
"queueingStrategy": queueing,
34+
"resourceGroups": [
35+
{
36+
"flavors": [
37+
{"resources": resources[: len(resources) // 2]},
38+
{"resources": resources[len(resources) // 2 :]},
39+
]
40+
}
41+
],
42+
}
43+
return {
44+
"apiVersion": "kueue.x-k8s.io/v1beta1",
45+
"kind": "ClusterQueue",
46+
"metadata": {"name": name},
47+
"spec": spec,
48+
"status": {
49+
"admittedWorkloads": admitted,
50+
"pendingWorkloads": pending,
51+
"reservingWorkloads": reserving,
52+
},
53+
}
54+
55+
56+
def _obj_with_as_dict(d):
57+
class Obj:
58+
def as_dict(self):
59+
return d
60+
61+
return Obj()
62+
63+
64+
def _obj_with_model_to_dict(d):
65+
class Model:
66+
def to_dict(self):
67+
return d
68+
69+
class Obj:
70+
model = Model()
71+
72+
return Obj()
73+
74+
75+
@pytest.fixture
76+
def args() -> argparse.Namespace:
77+
return argparse.Namespace()
78+
79+
80+
@contextmanager
81+
def patch_selector(clusterqueues=None, *, raise_on: str | None = None):
82+
"""
83+
Patch openshift_client.selector to feed GpuQueuesCommand:
84+
- selector("clusterqueue").objects() -> clusterqueues (list)
85+
- if raise_on == "clusterqueue", raise OpenShiftPythonException
86+
"""
87+
with mock.patch("openshift_client.selector") as sel:
88+
89+
def _sel(kind: str, *a, **k):
90+
if raise_on == kind:
91+
raise oc.OpenShiftPythonException("test exception")
92+
m = mock.Mock(name=f"selector<{kind}>")
93+
if kind == "clusterqueue":
94+
m.objects.return_value = clusterqueues or []
95+
else:
96+
m.objects.return_value = []
97+
return m
98+
99+
sel.side_effect = _sel
100+
yield sel
101+
102+
103+
def test_no_clusterqueues_prints_message(args, capsys):
104+
with patch_selector([]):
105+
GpuQueuesCommand.run(args)
106+
out = capsys.readouterr().out
107+
assert "No ClusterQueues found." in out
108+
109+
110+
def test_sums_gpu_across_groups_and_flavors(args, capsys):
111+
cq = _obj_with_as_dict(_cq_dict(name="gpu-cq", gpu_quotas=(2, 3)))
112+
with patch_selector([cq]):
113+
GpuQueuesCommand.run(args)
114+
out = capsys.readouterr().out
115+
116+
# 2 + 3 = 5 GPUs total
117+
assert "gpu-cq" in out
118+
assert "admitted: 1" in out
119+
assert "pending: 2" in out
120+
assert "reserved: 0" in out
121+
assert "GPUs: 5" in out
122+
assert "StrictFIFO" in out
123+
124+
125+
def test_handles_as_dict_and_model_to_dict(args, capsys):
126+
cq1 = _obj_with_as_dict(_cq_dict(name="cq-as-dict", gpu_quotas=(1,)))
127+
cq2 = _obj_with_model_to_dict(_cq_dict(name="cq-model", gpu_quotas=(4, 4)))
128+
with patch_selector([cq1, cq2]):
129+
GpuQueuesCommand.run(args)
130+
out = capsys.readouterr().out
131+
132+
assert "cq-as-dict" in out and "GPUs: 1" in out
133+
assert "cq-model" in out and "GPUs: 8" in out
134+
135+
136+
def test_ignores_bad_nominal_quota(args, capsys):
137+
# One valid GPU quota (1) and one invalid quota ("abc") -> total should be 1
138+
bad = _cq_dict(name="cq-bad", gpu_quotas=(1, "abc"))
139+
cq = _obj_with_as_dict(bad)
140+
with patch_selector([cq]):
141+
GpuQueuesCommand.run(args)
142+
out = capsys.readouterr().out
143+
144+
assert "cq-bad" in out
145+
assert "GPUs: 1" in out
146+
147+
148+
def test_selector_exception_exits_cleanly(args):
149+
with patch_selector(raise_on="clusterqueue"):
150+
with pytest.raises(SystemExit) as e:
151+
GpuQueuesCommand.run(args)
152+
assert "Error occurred while retrieving ClusterQueues: test exception" in str(
153+
e.value
154+
)

0 commit comments

Comments
 (0)