Files
AnyImage/tests/anyimage/operators/test_ai_input_failures.py
nan 489003c2cc Add new tests and functionality for image processing and geometry handling
- Introduced color_image.py for preparing standalone color images for tests.
- Added hdr_image.py to create HDR textures for testing.
- Implemented test_image_depth_panorama.py to evaluate panorama topology, UVs, and depth discontinuities.
- Created test_shadeless.py to validate the opacity and HDR color endpoints of the Shadeless group.
- Developed moge3_export.py for exporting dynamic MoGe-3 backbone and ONNX graphs.
- Added material.py to inherit material slots onto generated geometry.
- Built image_depth_panorama.py to construct a quad panorama with depth separation and sampling.
2026-09-10 14:42:17 +08:00

97 lines
5.6 KiB
Python

import tempfile
from contextlib import ExitStack
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock, patch
from tests.support.blender import BlenderTestCase
class AiInputFailuresTest(BlenderTestCase):
def test_image_edits_reject_same_input_before_submission(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "input.tiff"
path.touch()
messages = []
for module, operator_type in (
(self.upscale, self.upscale.UpscaleImage),
(self.remove_background, self.remove_background.RunBackgroundRemoval),
):
operator = operator_type()
operator.report = Mock()
target = Mock(image=SimpleNamespace(size=(16, 16)), start_frame=1)
target.prepare.return_value = (path, False)
context = SimpleNamespace(scene=SimpleNamespace(
anyimage_settings=SimpleNamespace(video_frames=1)))
with ExitStack() as stack:
stack.enter_context(patch.object(module.ImageEditTarget, "capture", return_value=target))
stack.enter_context(patch.object(module, "require_environment"))
stack.enter_context(patch.object(module, "require_model"))
stack.enter_context(patch.object(self.runtime.runtime, "active_job", None))
submit = stack.enter_context(patch.object(self.runtime.runtime, "begin_job"))
if module == self.upscale:
stack.enter_context(patch.object(module, "configured_upscale_model", return_value="SAFMN_REAL_X4"))
stack.enter_context(patch.object(module, "configured_max_ai_input_size", return_value=2048))
self.assertEqual(operator.execute(context), {"CANCELLED"})
submit.assert_not_called()
messages.append(operator.report.call_args.args[1])
self.assertIsNone(operator._image_target)
self.assertTrue(path.exists())
self.assertEqual(messages[0], messages[1])
self.assertIn("Unsupported input format: .tiff", messages[0])
def test_remove_background_catches_nested_error(self):
operator = self.remove_background.RemoveImageBackground()
operator.report = Mock()
ops = SimpleNamespace(anyimage=SimpleNamespace(
run_background_removal=Mock(side_effect=RuntimeError("Error: invalid input\n"))))
with patch.object(self.remove_background.bpy, "ops", ops, create=True):
self.assertEqual(operator.execute(None), {"CANCELLED"})
operator.report.assert_called_once_with({"ERROR"}, "invalid input")
def test_setup_failures_cancel_at_each_boundary(self):
for ready in (True, False):
operator = self.ai_setup.SetupAIEnvironment()
operator.use_mirror = False
operator.report = Mock()
failing = Mock(side_effect=RuntimeError("Error: setup failed\n"))
ops = SimpleNamespace(anyimage=SimpleNamespace(
download_required_models=failing, install_environment=failing))
with (
patch.object(self.ai_setup, "ai_status", return_value={"ready": False, "environment_ready": ready}),
patch.object(self.ai_setup.bpy, "ops", ops, create=True),
):
self.assertEqual(operator.execute(None), {"CANCELLED"})
operator.report.assert_called_once_with({"ERROR"}, "setup failed")
owner = SimpleNamespace(report=Mock())
with (
patch.object(self.ai, "ai_ready", return_value=False),
patch.object(self.ai.bpy, "ops", SimpleNamespace(anyimage=SimpleNamespace(
setup_ai_environment=Mock(side_effect=RuntimeError("Error: setup failed\n")))), create=True),
):
self.assertEqual(self.ai.invoke_ai_setup_if_needed(owner), {"CANCELLED"})
owner.report.assert_called_once_with({"ERROR"}, "setup failed")
def test_depth_and_relief_cleanup_on_nested_failure(self):
module = self.convert_to_plane
for plane_type in ("DEPTH", "RELIEF"):
for failure in (RuntimeError("Error: invalid input\n"), None):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "prepared" / "input.png"
path.parent.mkdir()
path.touch()
operator = SimpleNamespace(mesh_detail=4, report=Mock())
context = SimpleNamespace(object=SimpleNamespace(name="source", as_pointer=lambda: 1,
data=SimpleNamespace(as_pointer=lambda: 2)), scene=object())
generate = Mock(side_effect=failure, return_value={"CANCELLED"})
with (
patch.object(module, "invoke_ai_setup_if_needed", return_value=None),
patch.object(module, "prepare_material_color_input", return_value=path),
patch.object(module, "material_analysis_input", return_value=path),
patch.object(module, "depth_plane_settings", return_value=("model", 5)),
patch.object(module, "configured_max_ai_input_size", return_value=2048),
patch.object(module.bpy, "ops", SimpleNamespace(anyimage=SimpleNamespace(generate_depth_plane=generate)), create=True),
):
self.assertEqual(module.start_depth_plane_job(operator, context, plane_type), {"CANCELLED"})
self.assertFalse(path.parent.exists())