Normalize VLM detection output to int 0/1 so easy anythingIndexSwitch never receives None, while keeping raw VLM results observable via ShowText. Also wrap Doubao HTTP request/parse/status handling in a single error boundary that falls back to defaultPrompt on any failure.
165 lines
6.6 KiB
Python
165 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""FD_BinaryDecisionNormalizer 与 normalize_binary_decision 离线测试。
|
|
|
|
任何输入下输出都必须是 int 且值属于 {0, 1},保证下游
|
|
easy anythingIndexSwitch 的 index 永远不会是 None 或浮点。
|
|
"""
|
|
|
|
import os
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
# 环境变量须在 config 导入前就位;强制赋值以免其他测试先导入
|
|
os.environ["FD_DOUBAO_KEY"] = os.environ.get("FD_DOUBAO_KEY") or "test-key"
|
|
os.environ["FD_DOUBAO_URL"] = os.environ.get("FD_DOUBAO_URL") or "https://example.com/v1/chat"
|
|
|
|
from src.Comfyui_Fd_Nodes.old_fd_nodes import ( # noqa: E402
|
|
FD_BinaryDecisionNormalizer,
|
|
FD_imgToText_Doubao,
|
|
normalize_binary_decision,
|
|
)
|
|
|
|
|
|
class NormalizeBinaryDecisionTest(unittest.TestCase):
|
|
def assert_binary(self, value, expected=None):
|
|
result = normalize_binary_decision(value)
|
|
self.assertIs(type(result), int, f"value={value!r} result={result!r}")
|
|
self.assertIn(result, (0, 1), f"value={value!r} result={result!r}")
|
|
if expected is not None:
|
|
self.assertEqual(result, expected, f"value={value!r}")
|
|
|
|
def test_plain_binary(self):
|
|
self.assert_binary("0", 0)
|
|
self.assert_binary("1", 1)
|
|
|
|
def test_int_and_float_inputs(self):
|
|
self.assert_binary(0, 0)
|
|
self.assert_binary(1, 1)
|
|
self.assert_binary(0.0, 0)
|
|
self.assert_binary(1.0, 1)
|
|
self.assert_binary(True, 1)
|
|
self.assert_binary(False, 0)
|
|
|
|
def test_whitespace_and_newlines(self):
|
|
self.assert_binary(" 0 ", 0)
|
|
self.assert_binary("\n1\n", 1)
|
|
self.assert_binary("\t0\t", 0)
|
|
|
|
def test_markdown_code_fence(self):
|
|
self.assert_binary("```\n0\n```", 0)
|
|
self.assert_binary("```text\n1\n```", 1)
|
|
self.assert_binary("```\n0", 0)
|
|
|
|
def test_single_token_explanation(self):
|
|
self.assert_binary("无 logo 叠加,结果:0", 0)
|
|
self.assert_binary("结论是 1(有水印)", 1)
|
|
self.assert_binary("图中无任何后期叠加标识,返回 0。", 0)
|
|
|
|
def test_empty_and_none(self):
|
|
for value in (None, "", " ", "\n\t\n", "```\n```", "```\n \n```"):
|
|
result = normalize_binary_decision(value)
|
|
self.assertIs(type(result), int)
|
|
self.assertIn(result, (0, 1))
|
|
|
|
def test_default_value_used_on_empty(self):
|
|
self.assertEqual(normalize_binary_decision(None, 1), 1)
|
|
self.assertEqual(normalize_binary_decision("", 1), 1)
|
|
self.assertEqual(normalize_binary_decision(" ", 0), 0)
|
|
|
|
def test_ambiguous_text_falls_back(self):
|
|
for value in ("0 与 1 均存在", "10", "01", "1.0", "0.0", "101"):
|
|
result = normalize_binary_decision(value)
|
|
self.assertIs(type(result), int)
|
|
self.assertIn(result, (0, 1))
|
|
self.assertEqual(result, 0)
|
|
|
|
def test_ambiguous_text_uses_given_default(self):
|
|
self.assertEqual(normalize_binary_decision("0 与 1 均存在", 1), 1)
|
|
self.assertEqual(normalize_binary_decision("10", 1), 1)
|
|
self.assertEqual(normalize_binary_decision("1.0", 1), 1)
|
|
self.assertEqual(normalize_binary_decision("2", 1), 1)
|
|
|
|
def test_non_binary_value_falls_back(self):
|
|
for value in ("2", "-1", "0.5", "abc", "yes", "watermark", ["0"]):
|
|
result = normalize_binary_decision(value)
|
|
self.assertIs(type(result), int)
|
|
self.assertIn(result, (0, 1))
|
|
|
|
def test_default_value_out_of_range_coerced(self):
|
|
self.assertEqual(normalize_binary_decision("", 7), 0)
|
|
self.assertEqual(normalize_binary_decision(None, -1), 0)
|
|
|
|
|
|
class BinaryDecisionNormalizerNodeTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.node = FD_BinaryDecisionNormalizer()
|
|
|
|
def test_return_type_declared_int(self):
|
|
self.assertEqual(FD_BinaryDecisionNormalizer.RETURN_TYPES, ("INT",))
|
|
|
|
def test_plain_inputs(self):
|
|
self.assertEqual(self.node.normalize("0"), (0,))
|
|
self.assertEqual(self.node.normalize("1"), (1,))
|
|
|
|
def test_explanation_and_fence(self):
|
|
self.assertEqual(self.node.normalize("检测无任何水印,结论 0"), (0,))
|
|
self.assertEqual(self.node.normalize("```\n1\n```"), (1,))
|
|
self.assertEqual(self.node.normalize("\n0\n"), (0,))
|
|
|
|
def test_garbage_uses_default_value(self):
|
|
self.assertEqual(self.node.normalize("", 0), (0,))
|
|
self.assertEqual(self.node.normalize("10", 0), (0,))
|
|
self.assertEqual(self.node.normalize("1.0", 0), (0,))
|
|
self.assertEqual(self.node.normalize(None, 1), (1,))
|
|
self.assertEqual(self.node.normalize("10", 1), (1,))
|
|
self.assertEqual(self.node.normalize("2", 1), (1,))
|
|
|
|
def test_node_output_always_binary_int(self):
|
|
for value in (None, "", "0", "1", "10", "1.0", "```\n0\n```", "结论:0", "abc"):
|
|
result = self.node.normalize(value, 0)[0]
|
|
self.assertIs(type(result), int)
|
|
self.assertIn(result, (0, 1))
|
|
|
|
|
|
class DoubaoNodeErrorBoundaryTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.node = FD_imgToText_Doubao()
|
|
|
|
def _patch_post(self, response):
|
|
return patch(
|
|
"src.Comfyui_Fd_Nodes.old_fd_nodes.requests.post",
|
|
return_value=response,
|
|
)
|
|
|
|
def test_transport_exception_returns_default_prompt(self):
|
|
with patch("src.Comfyui_Fd_Nodes.old_fd_nodes.requests.post", side_effect=RuntimeError("connection refused")):
|
|
self.assertEqual(self.node.gen("http://img", "p", "fallback"), ("fallback",))
|
|
|
|
def test_non_json_response_returns_default_prompt(self):
|
|
response = Mock()
|
|
response.content = b"<html>gateway error</html>"
|
|
with self._patch_post(response):
|
|
self.assertEqual(self.node.gen("http://img", "p", "fallback"), ("fallback",))
|
|
|
|
def test_failed_status_returns_default_prompt(self):
|
|
response = Mock()
|
|
response.content = b'{"status": false, "response": {"Result": "oops"}}'
|
|
with self._patch_post(response):
|
|
self.assertEqual(self.node.gen("http://img", "p", "fallback"), ("fallback",))
|
|
|
|
def test_missing_field_returns_default_prompt(self):
|
|
response = Mock()
|
|
response.content = b'{"status": true}'
|
|
with self._patch_post(response):
|
|
self.assertEqual(self.node.gen("http://img", "p", "fallback"), ("fallback",))
|
|
|
|
def test_success_returns_vlm_result(self):
|
|
response = Mock()
|
|
response.content = b'{"status": true, "response": {"Result": "1"}}'
|
|
with self._patch_post(response):
|
|
self.assertEqual(self.node.gen("http://img", "p", "fallback"), ("1",))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|