Skip to content

Commit 8a51a6b

Browse files
[cueadmin] Add Unit Tests for DependUtil Class (AcademySoftwareFoundation#1985)
**Link the Issue(s) this Pull Request is related to.** - AcademySoftwareFoundation#1924 **Summarize your change.** Implement tests for the DependUtil class that manages job dependencies including creation, modification, and removal. Dependency management is complex but completely untested. Additional contextCreate `cueadmin/tests/test_depend_util.py` testing: - Dependency type parsing and validation - Creating job-on-job dependencies - Creating layer-on-layer dependencies - Creating frame-by-frame dependencies - Dependency satisfaction checking - Circular dependency prevention - -drop-depends functionality - Dependency status formatting Mock dependency objects and test various dependency chains. Verify proper error handling for invalid dependency specifications. Co-authored-by: Aniket Singh Yadav <singhyadavaniket43@gmail.com> Co-authored-by: Ramon Figueiredo <rfigueiredo@imageworks.com>
1 parent 92e383e commit 8a51a6b

File tree

2 files changed

+595
-0
lines changed

2 files changed

+595
-0
lines changed

cueadmin/cueadmin/common.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,172 @@ def dropAllDepends(job, layer=None, frame=None):
620620
)
621621
depend.satisfy()
622622

623+
@staticmethod
624+
def parseDependType(depend_type_str):
625+
"""Parse and validate dependency type string.
626+
627+
:type depend_type_str: str
628+
:param depend_type_str: String representation of dependency type
629+
:rtype: int
630+
:return: Dependency type enum value
631+
:raises: ValueError if type is invalid
632+
"""
633+
depend_type_map = {
634+
'JOB_ON_JOB': opencue.api.depend_pb2.JOB_ON_JOB,
635+
'JOB_ON_LAYER': opencue.api.depend_pb2.JOB_ON_LAYER,
636+
'JOB_ON_FRAME': opencue.api.depend_pb2.JOB_ON_FRAME,
637+
'LAYER_ON_JOB': opencue.api.depend_pb2.LAYER_ON_JOB,
638+
'LAYER_ON_LAYER': opencue.api.depend_pb2.LAYER_ON_LAYER,
639+
'LAYER_ON_FRAME': opencue.api.depend_pb2.LAYER_ON_FRAME,
640+
'FRAME_ON_JOB': opencue.api.depend_pb2.FRAME_ON_JOB,
641+
'FRAME_ON_LAYER': opencue.api.depend_pb2.FRAME_ON_LAYER,
642+
'FRAME_ON_FRAME': opencue.api.depend_pb2.FRAME_ON_FRAME,
643+
'FRAME_BY_FRAME': opencue.api.depend_pb2.FRAME_BY_FRAME,
644+
'LAYER_ON_SIM_FRAME': opencue.api.depend_pb2.LAYER_ON_SIM_FRAME,
645+
}
646+
647+
depend_type_upper = depend_type_str.upper()
648+
if depend_type_upper not in depend_type_map:
649+
raise ValueError("Invalid dependency type: %s" % depend_type_str)
650+
651+
return depend_type_map[depend_type_upper]
652+
653+
@staticmethod
654+
def createJobOnJobDepend(job_name, depend_on_job_name):
655+
"""Create a job-on-job dependency.
656+
657+
:type job_name: str
658+
:param job_name: Name of the job that will depend on another
659+
:type depend_on_job_name: str
660+
:param depend_on_job_name: Name of the job to depend on
661+
:rtype: opencue.wrappers.depend.Depend
662+
:return: The created dependency
663+
"""
664+
job = opencue.api.findJob(job_name)
665+
depend_on_job = opencue.api.findJob(depend_on_job_name)
666+
logger.debug("creating job-on-job depend: %s depends on %s", job_name, depend_on_job_name)
667+
return job.createDependencyOnJob(depend_on_job)
668+
669+
@staticmethod
670+
def createLayerOnLayerDepend(job_name, layer_name, depend_on_job_name, depend_on_layer_name):
671+
"""Create a layer-on-layer dependency.
672+
673+
:type job_name: str
674+
:param job_name: Name of the job containing the dependent layer
675+
:type layer_name: str
676+
:param layer_name: Name of the layer that will depend on another
677+
:type depend_on_job_name: str
678+
:param depend_on_job_name: Name of the job containing the layer to depend on
679+
:type depend_on_layer_name: str
680+
:param depend_on_layer_name: Name of the layer to depend on
681+
:rtype: opencue.wrappers.depend.Depend
682+
:return: The created dependency
683+
"""
684+
layer = opencue.api.findLayer(job_name, layer_name)
685+
depend_on_layer = opencue.api.findLayer(depend_on_job_name, depend_on_layer_name)
686+
logger.debug("creating layer-on-layer depend: %s/%s depends on %s/%s",
687+
job_name, layer_name, depend_on_job_name, depend_on_layer_name)
688+
return layer.createDependencyOnLayer(depend_on_layer)
689+
690+
@staticmethod
691+
def createFrameByFrameDepend(job_name, layer_name, depend_on_job_name, depend_on_layer_name):
692+
"""Create a frame-by-frame dependency.
693+
694+
:type job_name: str
695+
:param job_name: Name of the job containing the dependent layer
696+
:type layer_name: str
697+
:param layer_name: Name of the layer that will depend frame-by-frame
698+
:type depend_on_job_name: str
699+
:param depend_on_job_name: Name of the job containing the layer to depend on
700+
:type depend_on_layer_name: str
701+
:param depend_on_layer_name: Name of the layer to depend on
702+
:rtype: opencue.wrappers.depend.Depend
703+
:return: The created dependency
704+
"""
705+
layer = opencue.api.findLayer(job_name, layer_name)
706+
depend_on_layer = opencue.api.findLayer(depend_on_job_name, depend_on_layer_name)
707+
logger.debug("creating frame-by-frame depend: %s/%s depends on %s/%s",
708+
job_name, layer_name, depend_on_job_name, depend_on_layer_name)
709+
return layer.createFrameByFrameDependency(depend_on_layer)
710+
711+
@staticmethod
712+
def checkDependSatisfaction(job_name, layer_name=None, frame_num=None):
713+
"""Check if all dependencies on the given object are satisfied.
714+
715+
:type job_name: str
716+
:param job_name: Name of the job
717+
:type layer_name: str
718+
:param layer_name: Optional name of the layer
719+
:type frame_num: int
720+
:param frame_num: Optional frame number
721+
:rtype: bool
722+
:return: True if all dependencies are satisfied, False otherwise
723+
"""
724+
if frame_num is not None:
725+
obj = opencue.api.findFrame(job_name, layer_name, frame_num)
726+
elif layer_name:
727+
obj = opencue.api.findLayer(job_name, layer_name)
728+
else:
729+
obj = opencue.api.findJob(job_name)
730+
731+
depends = obj.getWhatThisDependsOn()
732+
for depend in depends:
733+
if not depend.data.active:
734+
continue
735+
# A dependency is not satisfied if it's still active
736+
return False
737+
return True
738+
739+
@staticmethod
740+
def detectCircularDepend(job_name, depend_on_job_name):
741+
"""Detect if creating a dependency would create a circular dependency.
742+
743+
:type job_name: str
744+
:param job_name: Name of the job that will depend on another
745+
:type depend_on_job_name: str
746+
:param depend_on_job_name: Name of the job to depend on
747+
:rtype: bool
748+
:return: True if circular dependency detected, False otherwise
749+
"""
750+
if job_name == depend_on_job_name:
751+
return True
752+
753+
try:
754+
job = opencue.api.findJob(job_name)
755+
depend_on_job = opencue.api.findJob(depend_on_job_name)
756+
757+
# Check if depend_on_job already depends on job (would create a cycle)
758+
depends = depend_on_job.getWhatThisDependsOn()
759+
for depend in depends:
760+
# Check if any dependency points back to our job
761+
if (hasattr(depend.data, 'depend_on_job') and
762+
depend.data.depend_on_job == job.data.id):
763+
return True
764+
# For deeper circular dependency detection, we'd need to traverse the full graph
765+
# This is a simplified check for direct circular dependencies
766+
767+
return False
768+
except Exception:
769+
# If we can't find the jobs, we can't detect circularity
770+
return False
771+
772+
@staticmethod
773+
def formatDependStatus(depend):
774+
"""Format dependency status for display.
775+
776+
:type depend: opencue.wrappers.depend.Depend
777+
:param depend: The dependency to format
778+
:rtype: str
779+
:return: Formatted string representing the dependency status
780+
"""
781+
depend_type = str(depend.data.type)
782+
active_status = "ACTIVE" if depend.data.active else "SATISFIED"
783+
784+
# Extract the dependency type name from the enum
785+
type_name = depend_type.rsplit('.', maxsplit=1)[-1] if '.' in depend_type else depend_type
786+
787+
return "%s [%s]" % (type_name, active_status)
788+
623789

624790
class Convert(object):
625791
"""Utility class for converting between units."""

0 commit comments

Comments
 (0)