Skip to content

Commit ca9b210

Browse files
[cueman] Add Unit Tests for Query and Listing Commands (AcademySoftwareFoundation#1988)
**Link the Issue(s) this Pull Request is related to.** - AcademySoftwareFoundation#1930 **Summarize your change.** Implement comprehensive unit tests for all query commands that list jobs, frames, processes, and layers with filtering and pagination. Create `cueman/tests/test_query_commands.py` testing: - Frame listing (`-lf`) with layer and state filters - Process listing (`-lp`) with memory and duration filters - Layer listing (`-ll`) with formatting and statistics - Job info display (`-info`) with detailed output - Pagination with page and limit parameters - Filter combination validation - Empty result handling - Large dataset performance considerations Mock `job.getFrames()`, `job.getLayers()`, `opencue.api.getProcs()` to avoid network calls. Test various data sizes and filter combinations. **Bug fix:** The `-lf` command was not applying search filters. Updated `handleArgs()` to call `buildFrameSearch()` and pass parameters to `job.getFrames()`, making it consistent with other frame management commands (`-eat`, `-kill`, `-retry`, `-done`). Enhanced test assertions to verify `buildFrameSearch` is called and getFrames receives correct search parameters. Co-authored-by: Aniket Singh Yadav <singhyadavaniket43@gmail.com> Co-authored-by: Ramon Figueiredo <rfigueiredo@imageworks.com>
1 parent 700497d commit ca9b210

File tree

3 files changed

+196
-2
lines changed

3 files changed

+196
-2
lines changed

cueman/cueman/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,8 @@ def handleArgs(args):
356356
if args.lf:
357357
try:
358358
job = opencue.api.findJob(args.lf)
359-
frames = job.getFrames()
359+
search = buildFrameSearch(args)
360+
frames = job.getFrames(**search)
360361
cueadmin.output.displayFrames(frames)
361362
except Exception as e:
362363
if (

cueman/tests/test_main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,17 +405,22 @@ def test_handleArgs_no_command(self):
405405

406406
@mock.patch("opencue.api.findJob")
407407
@mock.patch("cueadmin.output.displayFrames")
408-
def test_handleArgs_list_frames(self, mock_display, mock_findJob):
408+
@mock.patch("cueman.main.buildFrameSearch")
409+
def test_handleArgs_list_frames(
410+
self, mock_buildFrameSearch, mock_display, mock_findJob
411+
):
409412
"""Test handleArgs with -lf flag."""
410413
self.args.lf = "test_job"
411414
mock_job = mock.Mock()
412415
mock_frames = ["frame1", "frame2"]
413416
mock_job.getFrames.return_value = mock_frames
414417
mock_findJob.return_value = mock_job
418+
mock_buildFrameSearch.return_value = {}
415419

416420
main.handleArgs(self.args)
417421

418422
mock_findJob.assert_called_once_with("test_job")
423+
mock_buildFrameSearch.assert_called_once_with(self.args)
419424
mock_job.getFrames.assert_called_once()
420425
mock_display.assert_called_once_with(mock_frames)
421426

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Copyright Contributors to the OpenCue Project
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
"""Unit tests for cueman query and listing commands (-lf, -lp, -ll, -info)."""
17+
18+
import unittest
19+
from unittest import mock
20+
import argparse
21+
22+
from cueman import main as cueman_main
23+
24+
25+
def build_args(**kwargs):
26+
# Set all possible attributes to None unless specified
27+
fields = [
28+
"lf", "lp", "ll", "info",
29+
"pause", "resume", "term", "eat", "kill", "retry", "done",
30+
"stagger", "reorder", "retries", "autoeaton", "autoeatoff",
31+
"force",
32+
# frame/proc filter options
33+
"layer", "state", "range", "memory", "duration", "page", "limit",
34+
# general options cueman accepts
35+
"server", "facility", "verbose",
36+
]
37+
args_dict = {f: kwargs.get(f, None) for f in fields}
38+
# Defaults
39+
if "force" not in kwargs:
40+
args_dict["force"] = False
41+
return argparse.Namespace(**args_dict)
42+
43+
44+
class TestCuemanQueryCommands(unittest.TestCase):
45+
@mock.patch("opencue.api.findJob")
46+
@mock.patch("cueadmin.output.displayFrames")
47+
@mock.patch("cueman.main.buildFrameSearch")
48+
def test_list_frames_with_layer_and_state_filters(
49+
self, mock_buildFrameSearch, mock_display, mock_findJob
50+
):
51+
args = build_args(lf="job1", layer=["layerA"], state=["RUNNING"], limit=1000)
52+
job = mock.Mock()
53+
job.getFrames.return_value = ["frame1", "frame2"]
54+
mock_findJob.return_value = job
55+
mock_buildFrameSearch.return_value = {
56+
"layer": ["layerA"],
57+
"state": ["RUNNING"],
58+
"limit": 1000,
59+
}
60+
61+
cueman_main.handleArgs(args)
62+
63+
mock_buildFrameSearch.assert_called_once_with(args)
64+
job.getFrames.assert_called_once_with(
65+
layer=["layerA"], state=["RUNNING"], limit=1000
66+
)
67+
mock_display.assert_called_once_with(["frame1", "frame2"])
68+
69+
@mock.patch("opencue.api.getProcs")
70+
@mock.patch("cueadmin.output.displayProcs")
71+
def test_list_processes_with_memory_and_duration_filters(self, mock_display, mock_getProcs):
72+
# cueman_main._get_proc_filters will compute duration_range.
73+
args = build_args(lp="job1", memory="2-4", duration="1-2", limit=1000)
74+
mock_getProcs.return_value = ["proc1", "proc2"]
75+
76+
cueman_main.handleArgs(args)
77+
78+
mock_display.assert_called_once_with(["proc1", "proc2"])
79+
80+
@mock.patch("opencue.api.findJob")
81+
@mock.patch("cueadmin.output.displayJobInfo")
82+
def test_job_info_display(self, mock_display, mock_findJob):
83+
args = build_args(info="job1")
84+
job = mock.Mock()
85+
mock_findJob.return_value = job
86+
87+
cueman_main.handleArgs(args)
88+
89+
mock_display.assert_called_once_with(job)
90+
91+
@mock.patch("opencue.api.findJob")
92+
@mock.patch("cueadmin.output.displayFrames")
93+
@mock.patch("cueman.main.buildFrameSearch")
94+
def test_pagination_and_limit(
95+
self, mock_buildFrameSearch, mock_display, mock_findJob
96+
):
97+
args = build_args(lf="job1", page=2, limit=500)
98+
job = mock.Mock()
99+
job.getFrames.return_value = ["frameA", "frameB"]
100+
mock_findJob.return_value = job
101+
mock_buildFrameSearch.return_value = {"page": 2, "limit": 500}
102+
103+
cueman_main.handleArgs(args)
104+
105+
mock_buildFrameSearch.assert_called_once_with(args)
106+
job.getFrames.assert_called_once_with(page=2, limit=500)
107+
mock_display.assert_called_once_with(["frameA", "frameB"])
108+
109+
@mock.patch("opencue.api.findJob")
110+
@mock.patch("cueadmin.output.displayFrames")
111+
@mock.patch("cueman.main.buildFrameSearch")
112+
def test_empty_result_handling(
113+
self, mock_buildFrameSearch, mock_display, mock_findJob
114+
):
115+
args = build_args(lf="job1", limit=1000)
116+
job = mock.Mock()
117+
job.getFrames.return_value = []
118+
mock_findJob.return_value = job
119+
mock_buildFrameSearch.return_value = {"limit": 1000}
120+
121+
cueman_main.handleArgs(args)
122+
123+
job.getFrames.assert_called_once_with(limit=1000)
124+
mock_display.assert_called_once_with([])
125+
126+
@mock.patch("opencue.api.findJob")
127+
@mock.patch("cueadmin.output.displayFrames")
128+
@mock.patch("cueman.main.buildFrameSearch")
129+
def test_large_dataset_performance(
130+
self, mock_buildFrameSearch, mock_display, mock_findJob
131+
):
132+
args = build_args(lf="job1", limit=1000)
133+
job = mock.Mock()
134+
job.getFrames.return_value = [f"frame{i}" for i in range(2000)]
135+
mock_findJob.return_value = job
136+
mock_buildFrameSearch.return_value = {"limit": 1000}
137+
138+
cueman_main.handleArgs(args)
139+
140+
job.getFrames.assert_called_once_with(limit=1000)
141+
mock_display.assert_called_once_with([f"frame{i}" for i in range(2000)])
142+
143+
@mock.patch("opencue.api.findJob")
144+
@mock.patch("cueadmin.output.displayFrames")
145+
@mock.patch("cueman.main.buildFrameSearch")
146+
def test_filter_combination(
147+
self, mock_buildFrameSearch, mock_display, mock_findJob
148+
):
149+
args = build_args(
150+
lf="job1",
151+
layer=["layerA", "layerB"],
152+
state=["RUNNING", "WAITING"],
153+
range="1-100",
154+
memory="gt2",
155+
duration="lt1",
156+
page=1,
157+
limit=50,
158+
)
159+
job = mock.Mock()
160+
job.getFrames.return_value = ["frameX", "frameY"]
161+
mock_findJob.return_value = job
162+
mock_buildFrameSearch.return_value = {
163+
"layer": ["layerA", "layerB"],
164+
"state": ["RUNNING", "WAITING"],
165+
"range": "1-100",
166+
"page": 1,
167+
"limit": 50,
168+
}
169+
170+
cueman_main.handleArgs(args)
171+
172+
mock_buildFrameSearch.assert_called_once_with(args)
173+
job.getFrames.assert_called_once()
174+
mock_display.assert_called_once_with(["frameX", "frameY"])
175+
176+
@mock.patch("opencue.api.findJob")
177+
@mock.patch("cueman.main.displayLayers")
178+
def test_list_layers_with_formatting_and_statistics(self, mock_displayLayers, mock_findJob):
179+
args = build_args(ll="job1")
180+
job = mock.Mock()
181+
mock_findJob.return_value = job
182+
183+
cueman_main.handleArgs(args)
184+
185+
mock_displayLayers.assert_called_once_with(job)
186+
187+
if __name__ == "__main__":
188+
unittest.main()

0 commit comments

Comments
 (0)