test: add safety guards and unit tests for video loop calculators

Added input validations (`source_frame_count <= 0` or `target_frames_per_loop <= 0`) to `AutoLoopCalculatorLTX` (in `loop.py`), and `AutoLoopCalculator`, `AutoLoopCalculatorWan` (in `video.py`). When conditions are met, a safe default is returned to prevent math domain errors. Also included a new comprehensive unit test suite in `tests/test_loop_calculator.py`.
This commit is contained in:
Meisoftcoltd
2026-05-14 20:24:55 +00:00
parent 9df7a16940
commit 3afcee2bef
3 changed files with 158 additions and 0 deletions
+6
View File
@@ -95,6 +95,12 @@ class AutoLoopCalculatorLTX:
CATEGORY = "🔁 Sequential Batcher/Loop"
def calculate(self, source_frame_count, target_frames_per_loop, select_every_nth, current_loop_index, safe_faces_list=None, scene_cuts_list=None):
if source_frame_count <= 0 or target_frames_per_loop <= 0:
return (1, 0, select_every_nth)
source_frame_count = min(source_frame_count, 10000)
select_every_nth = max(1, select_every_nth)
# --- Ajuste Proporcional LTX (Regla DiT 8n + 1) ---
import math
from . import loop
+136
View File
@@ -0,0 +1,136 @@
import sys
import types
import unittest
from unittest.mock import MagicMock
import os
# Mock required modules (as per memory)
mock_torch = types.ModuleType("torch")
mock_torch.cuda = MagicMock()
mock_torch.cuda.is_available.return_value = False
sys.modules["torch"] = mock_torch
sys.modules["comfy"] = types.ModuleType("comfy")
sys.modules["comfy.model_management"] = types.ModuleType("comfy.model_management")
sys.modules["comfy.utils"] = types.ModuleType("comfy.utils")
sys.modules["torchaudio"] = types.ModuleType("torchaudio")
sys.modules["folder_paths"] = types.ModuleType("folder_paths")
# Mock the loop module since it will be imported in video.py
mock_loop = types.ModuleType("loop")
mock_loop.global_step_by_chunk = False
mock_loop.global_source_frame_count = 0
mock_loop.global_select_every_nth = 1
mock_loop.global_is_final_chunk = False
mock_loop.global_accumulated_frames = 0
sys.modules["loop"] = mock_loop
import sys
sys.modules["tqdm"] = types.ModuleType("tqdm")
sys.modules["tqdm"].tqdm = lambda x, **kwargs: x
sys.modules["cv2"] = types.ModuleType("cv2")
def get_node_class(file_name, class_name):
# Read the file
path = os.path.join(os.path.dirname(__file__), "..", file_name)
with open(path, 'r') as f:
code = f.read()
# Mock 'from . import register_node'
code = code.replace("from . import register_node", "def register_node(c, *args, **kwargs): return c")
# In video.py, there are more relative imports
code = code.replace("from . import loop", "import loop")
# We create a new module
module = types.ModuleType(file_name.replace(".py", ""))
# We must patch sys.modules inside so it can import loop
sys.modules[module.__name__] = module
try:
exec(code, module.__dict__)
except Exception as e:
print(f"Failed to execute {file_name}: {e}")
pass
return getattr(module, class_name, None)
class TestAutoLoopCalculators(unittest.TestCase):
def setUp(self):
LTXClass = get_node_class("loop.py", "AutoLoopCalculatorLTX")
self.calc_ltx = LTXClass() if LTXClass else None
BaseClass = get_node_class("video.py", "AutoLoopCalculator")
self.calc_base = BaseClass() if BaseClass else None
WanClass = get_node_class("video.py", "AutoLoopCalculatorWan")
self.calc_wan = WanClass() if WanClass else None
# LTX Calculator
def test_ltx_guard_zero_source(self):
res = self.calc_ltx.calculate(0, 100, 1, 0)
self.assertEqual(res, (1, 0, 1))
def test_ltx_guard_negative_source(self):
res = self.calc_ltx.calculate(-5, 100, 1, 0)
self.assertEqual(res, (1, 0, 1))
def test_ltx_guard_zero_target(self):
res = self.calc_ltx.calculate(100, 0, 1, 0)
self.assertEqual(res, (1, 0, 1))
def test_ltx_valid_input(self):
res = self.calc_ltx.calculate(80, 81, 1, 0)
self.assertEqual(res[0], 81)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
# Base Calculator
def test_base_guard_zero_source(self):
res = self.calc_base.calculate(0, 100, 1, 0)
self.assertEqual(res[0], 1)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
self.assertIn("Invalid frame count", res[3])
def test_base_guard_zero_target(self):
res = self.calc_base.calculate(100, 0, 1, 0)
self.assertEqual(res[0], 1)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
self.assertIn("Invalid frame count", res[3])
def test_base_valid_input(self):
res = self.calc_base.calculate(100, 50, 1, 0)
self.assertEqual(res[0], 50)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
# Wan Calculator
def test_wan_guard_zero_source(self):
res = self.calc_wan.calculate(0, 100, 1, 0)
self.assertEqual(res[0], 1)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
self.assertIn("Invalid frame count", res[3])
def test_wan_guard_zero_target(self):
res = self.calc_wan.calculate(100, 0, 1, 0)
self.assertEqual(res[0], 1)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
self.assertIn("Invalid frame count", res[3])
def test_wan_valid_input(self):
res = self.calc_wan.calculate(80, 81, 1, 0)
self.assertEqual(res[0], 81)
self.assertEqual(res[1], 0)
self.assertEqual(res[2], 1)
if __name__ == "__main__":
unittest.main()
+16
View File
@@ -484,6 +484,14 @@ class AutoLoopCalculator:
def _log(msg):
print(msg)
log_output.append(str(msg))
if source_frame_count <= 0 or target_frames_per_loop <= 0:
_log("⚠️ [WARNING] Invalid frame count or target frames per loop.")
return (1, 0, select_every_nth, "\n".join(log_output))
source_frame_count = min(source_frame_count, 10000)
select_every_nth = max(1, select_every_nth)
# --- Ajuste Proporcional Base (Sin restricciones VAE) ---
import math
from . import loop
@@ -807,6 +815,14 @@ class AutoLoopCalculatorWan:
def _log(msg):
print(msg)
log_output.append(str(msg))
if source_frame_count <= 0 or target_frames_per_loop <= 0:
_log("⚠️ [WARNING] Invalid frame count or target frames per loop.")
return (1, 0, select_every_nth, "\n".join(log_output))
source_frame_count = min(source_frame_count, 10000)
select_every_nth = max(1, select_every_nth)
import math
from . import loop