99
1010
1111@pytest .fixture
12- def jobs ():
12+ def kueue_jobs ():
1313 return [
14- DictToObject ({"model" : {"metadata" : {"name" : "job-1" }}}),
15- DictToObject ({"model" : {"metadata" : {"name" : "job-2" }}}),
16- DictToObject ({"model" : {"metadata" : {"name" : "other-1" }}}),
14+ DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}}),
15+ DictToObject ({"model" : {"metadata" : {"name" : "job-job-2" }}}),
16+ ]
17+
18+
19+ @pytest .fixture
20+ def mixed_jobs ():
21+ return [
22+ DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}}),
23+ DictToObject ({"model" : {"metadata" : {"name" : "ignored-1" }}}),
1724 ]
1825
1926
@@ -36,85 +43,107 @@ def patch_selector_with(job_list):
3643 yield mock_selector
3744
3845
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+
3967def test_no_jobs_found (args , capsys ):
4068 with patch_selector_with ([]):
4169 DeleteJobsCommand .run (args )
4270 out = capsys .readouterr ().out
4371 assert "No jobs found." in out
4472
4573
46- def test_delete_all_when_no_names_given (args , jobs , capsys ):
74+ def test_no_kueue_managed_gpu_jobs (args , kueue_jobs , capsys ):
75+ with patch_selector_with (kueue_jobs ), patch_kueue_managed ():
76+ DeleteJobsCommand .run (args )
77+ out = capsys .readouterr ().out
78+ assert "No Kueue-managed GPU jobs to delete." in out
79+
80+
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" ):
83+ DeleteJobsCommand .run (args )
84+ out = capsys .readouterr ().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
89+
90+
91+ def test_delete_all_when_no_names_given (args , kueue_jobs , capsys ):
4792 args .job_names = [] # explicit
48- with patch_selector_with (jobs ):
93+ with patch_selector_with (kueue_jobs ), patch_kueue_managed ( "job-job-1" , "job-job-2" ):
4994 DeleteJobsCommand .run (args )
5095 out = capsys .readouterr ().out
5196
52- assert "No job names provided -> deleting ALL jobs:" in out
53- # oc_delete should print "Deleting job/<name>" and "Deleted job: <name>"
54- for obj in jobs :
97+ assert "No job names provided -> deleting all Kueue-managed GPU jobs:" in out
98+ for obj in kueue_jobs :
5599 name = obj .model .metadata .name
56100 assert f"Deleting job/{ name } " in out
57101 assert f"Deleted job: { name } " in out
58102
59103
60- def test_delete_only_specified_existing (args , jobs , capsys ):
61- args .job_names = ["job-1" , "job-2" ]
62- with patch_selector_with (jobs ):
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" ):
63107 DeleteJobsCommand .run (args )
64108 out = capsys .readouterr ().out
65109
66- # Only the two specified jobs should be deleted
67- assert "Deleting job/job-1" in out
68- assert "Deleted job: job-1" in out
69- assert "Deleting job/job-2" in out
70- assert "Deleted job: job-2" in out
71-
72- # other-1 exists but is not listed; should not be deleted
73- assert "Deleting job/other-1" not in out
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
74114
75115
76- def test_skips_nonexistent_names (args , jobs , capsys ):
77- args .job_names = ["job-1" , "does-not-exist " ]
78- with patch_selector_with (jobs ):
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" ):
79119 DeleteJobsCommand .run (args )
80120 out = capsys .readouterr ().out
81121
82- # Existing job gets deleted
83- assert "Deleting job/job-1" in out
84- assert "Deleted job: job-1" in out
85-
86- # Missing job is skipped with a message
87- assert "does-not-exist does not exist; skipping." in out
88- assert "Deleting job/does-not-exist" not in out
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
89126
90127
91128def test_delete_jobs_prints_error_when_delete_raises (args , capsys ):
92- # We rely on oc_delete's behavior here: it should catch the exception and
93- # print "Error occurred while deleting job/<name>: <msg>"
94- jobs = [DictToObject ({"model" : {"metadata" : {"name" : "job-1" }}})]
95-
96- with patch_selector_with (jobs ) as mock_selector :
129+ jobs = [DictToObject ({"model" : {"metadata" : {"name" : "job-job-1" }}})]
130+ with patch_selector_with (jobs ) as mock_selector , patch_kueue_managed ("job-job-1" ):
97131 mock_selector .return_value .delete .side_effect = OpenShiftPythonException (
98132 "test exception"
99133 )
100134
101135 DeleteJobsCommand .run (args )
102136 out = capsys .readouterr ().out
103-
104- # From oc_delete
105- assert "Deleting job/job-1" in out
106- assert "Error occurred while deleting job/job-1: test exception" in out
137+ assert "Error occurred while deleting job/job-job-1: test exception" in out
107138
108139
109140def test_sys_exit_when_list_selector_raises (args ):
110- # If listing jobs fails, DeleteJobsCommand should exit with an error
111141 with mock .patch (
112142 "openshift_client.selector" ,
113143 side_effect = OpenShiftPythonException ("not successful" ),
114144 ):
115145 with pytest .raises (SystemExit ) as excinfo :
116146 DeleteJobsCommand .run (args )
117-
118147 assert "Error occurred while deleting jobs: not successful" in str (
119148 excinfo .value
120149 )
0 commit comments