99
1010
1111@pytest .fixture
12- def jobs ():
12+ def kueue_jobs ():
1313 return [
1414 DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}}),
1515 DictToObject ({"model" : {"metadata" : {"name" : "job-job-2" }}}),
1616 ]
1717
1818
1919@pytest .fixture
20- def ignored_jobs ():
21- return [DictToObject ({"model" : {"metadata" : {"name" : "ignored-1" }}})]
20+ def mixed_jobs ():
21+ return [
22+ DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}}),
23+ DictToObject ({"model" : {"metadata" : {"name" : "ignored-1" }}}),
24+ ]
2225
2326
2427@pytest .fixture
25- def failed_jobs ():
26- return [ DictToObject ({ "model" : { "metadata" : { "name" : "job-job-1" }}})]
28+ def args ():
29+ return argparse . Namespace ( job_names = [])
2730
2831
2932@contextmanager
30- def patch_jobs_selector (job_list : list [ DictToObject ] ):
33+ def patch_selector_with (job_list ):
3134 """
3235 Patches openshift_client.selector for BOTH:
33- - the list call: selector("job", labels=... ).objects() -> job_list
34- - the delete calls: selector("job/<name>").delete()
36+ - list call: selector("jobs" ).objects() -> job_list
37+ - delete calls: selector("job/<name>").delete()
3538 """
3639 with mock .patch ("openshift_client.selector" ) as mock_selector :
3740 result = mock .Mock (name = "selector_result" )
@@ -40,43 +43,107 @@ def patch_jobs_selector(job_list: list[DictToObject]):
4043 yield mock_selector
4144
4245
43- def test_no_jobs (args : argparse .Namespace , capsys ):
44- with patch_jobs_selector ([]):
46+ def patch_kueue_managed (* names_that_are_kueue ):
47+ """
48+ Patch batchtools.bd.is_kueue_managed_job so ONLY the provided job names return True.
49+
50+ bd.py calls is_kueue_managed_job with a STRING job name, not an APIObject.
51+ This predicate accepts either a str or an object and normalizes to the name.
52+ """
53+
54+ def _predicate (arg ):
55+ if isinstance (arg , str ):
56+ name = arg
57+ else :
58+ # tolerate APIObject/DictToObject
59+ name = getattr (
60+ getattr (getattr (arg , "model" , None ), "metadata" , None ), "name" , None
61+ ) or getattr (arg , "name" , None )
62+ return name in names_that_are_kueue
63+
64+ return mock .patch ("batchtools.bd.is_kueue_managed_job" , side_effect = _predicate )
65+
66+
67+ def test_no_jobs_found (args , capsys ):
68+ with patch_selector_with ([]):
69+ DeleteJobsCommand .run (args )
70+ out = capsys .readouterr ().out
71+ assert "No jobs found." in out
72+
73+
74+ def test_no_kueue_managed_gpu_jobs (args , kueue_jobs , capsys ):
75+ with patch_selector_with (kueue_jobs ), patch_kueue_managed ():
4576 DeleteJobsCommand .run (args )
4677 out = capsys .readouterr ().out
47- assert "No jobs found " in out
78+ assert "No Kueue-managed GPU jobs to delete. " in out
4879
4980
50- def test_no_gpu_jobs (args : argparse . Namespace , ignored_jobs , capsys ):
51- with patch_jobs_selector ( ignored_jobs ):
81+ def test_ignores_non_gpu_named_jobs (args , mixed_jobs , capsys ):
82+ with patch_selector_with ( mixed_jobs ), patch_kueue_managed ( "job-job-1" ):
5283 DeleteJobsCommand .run (args )
5384 out = capsys .readouterr ().out
54- assert "No GPU workloads to delete" in out
85+ # delete only job-job-1
86+ assert "Deleting job/job-job-1" in out
87+ assert "Deleted job: job-job-1" in out
88+ assert "ignored-1" not in out
5589
5690
57- def test_delete_obj (args : argparse . Namespace , jobs , capsys ):
58- args .job_names = []
59- with patch_jobs_selector ( jobs ) as mock_selector :
91+ def test_delete_all_when_no_names_given (args , kueue_jobs , capsys ):
92+ args .job_names = [] # explicit
93+ with patch_selector_with ( kueue_jobs ), patch_kueue_managed ( "job-job-1" , "job-job-2" ) :
6094 DeleteJobsCommand .run (args )
6195 out = capsys .readouterr ().out
6296
63- for obj in jobs :
97+ assert "No job names provided -> deleting all Kueue-managed GPU jobs:" in out
98+ for obj in kueue_jobs :
6499 name = obj .model .metadata .name
65100 assert f"Deleting job/{ name } " in out
101+ assert f"Deleted job: { name } " in out
66102
67- called_with = [c .args [0 ] for c in mock_selector .call_args_list if c .args ]
68- for obj in jobs :
69- assert f"job/{ obj .model .metadata .name } " in called_with
70103
104+ def test_delete_only_specified_allowed (args , kueue_jobs , capsys ):
105+ args .job_names = ["job-job-1" , "job-job-2" ]
106+ with patch_selector_with (kueue_jobs ), patch_kueue_managed ("job-job-1" ):
107+ DeleteJobsCommand .run (args )
108+ out = capsys .readouterr ().out
109+
110+ assert "Deleting job/job-job-1" in out
111+ assert "Deleted job: job-job-1" in out
112+ assert "job-job-2 is not a Kueue-managed GPU job; skipping." in out
113+ assert "Deleting job/job-job-2" not in out
114+
115+
116+ def test_only_deletes_listed_names_even_if_more_kueue (args , kueue_jobs , capsys ):
117+ args .job_names = ["job-job-2" ]
118+ with patch_selector_with (kueue_jobs ), patch_kueue_managed ("job-job-1" , "job-job-2" ):
119+ DeleteJobsCommand .run (args )
120+ out = capsys .readouterr ().out
71121
72- def test_delete_jobs_fail (args : argparse .Namespace , failed_jobs , capsys ):
73- args .job_names = []
74- with patch_jobs_selector (failed_jobs ) as mock_selector :
122+ assert "Deleting job/job-job-2" in out
123+ assert "Deleted job: job-job-2" in out
124+ # make sure job-job-1 is not deleted implicitly
125+ assert "Deleting job/job-job-1" not in out
126+
127+
128+ def test_delete_jobs_prints_error_when_delete_raises (args , capsys ):
129+ jobs = [DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}})]
130+ with patch_selector_with (jobs ) as mock_selector , patch_kueue_managed ("job-job-1" ):
75131 mock_selector .return_value .delete .side_effect = OpenShiftPythonException (
76132 "test exception"
77133 )
78134
79135 DeleteJobsCommand .run (args )
80136 out = capsys .readouterr ().out
81-
82137 assert "Error occurred while deleting job/job-job-1: test exception" in out
138+
139+
140+ def test_sys_exit_when_list_selector_raises (args ):
141+ with mock .patch (
142+ "openshift_client.selector" ,
143+ side_effect = OpenShiftPythonException ("not successful" ),
144+ ):
145+ with pytest .raises (SystemExit ) as excinfo :
146+ DeleteJobsCommand .run (args )
147+ assert "Error occurred while deleting jobs: not successful" in str (
148+ excinfo .value
149+ )
0 commit comments