mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-07 21:00:49 +08:00
Merge d23a9633d9 into 4f3f9e72a9
This commit is contained in:
commit
068129997f
14
nodes.py
14
nodes.py
@ -2109,6 +2109,10 @@ EXTENSION_WEB_DIRS = {}
|
||||
# Dictionary of successfully loaded module names and associated directories.
|
||||
LOADED_MODULE_DIRS = {}
|
||||
|
||||
# Dictionary of import failure reasons keyed by module path.
|
||||
# Used to provide diagnostic information in the import summary.
|
||||
IMPORT_FAILED_REASONS: dict[str, str] = {}
|
||||
|
||||
|
||||
def get_module_name(module_path: str) -> str:
|
||||
"""
|
||||
@ -2223,6 +2227,9 @@ async def load_custom_node(module_path: str, ignore=set(), module_parent="custom
|
||||
logging.warning(f"Skip {module_path} module for custom nodes due to the lack of NODE_CLASS_MAPPINGS or NODES_LIST (need one).")
|
||||
return False
|
||||
except Exception as e:
|
||||
# Capture one-line failure reason for the import summary
|
||||
error_msg = str(e).split('\n')[0][:100] # First line, max 100 chars
|
||||
IMPORT_FAILED_REASONS[module_path] = f"{type(e).__name__}: {error_msg}"
|
||||
logging.warning(traceback.format_exc())
|
||||
logging.warning(f"Cannot import {module_path} module for custom nodes: {e}")
|
||||
return False
|
||||
@ -2270,7 +2277,12 @@ async def init_external_custom_nodes():
|
||||
if n[2]:
|
||||
import_message = ""
|
||||
else:
|
||||
import_message = " (IMPORT FAILED)"
|
||||
# Include failure reason if available
|
||||
reason = IMPORT_FAILED_REASONS.get(n[1], "")
|
||||
if reason:
|
||||
import_message = f" (IMPORT FAILED: {reason})"
|
||||
else:
|
||||
import_message = " (IMPORT FAILED)"
|
||||
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
||||
logging.info("")
|
||||
|
||||
|
||||
0
tests-unit/nodes_test/__init__.py
Normal file
0
tests-unit/nodes_test/__init__.py
Normal file
89
tests-unit/nodes_test/test_import_failure_reasons.py
Normal file
89
tests-unit/nodes_test/test_import_failure_reasons.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""Tests for custom node import failure reason reporting."""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
from unittest.mock import patch, MagicMock
|
||||
import asyncio
|
||||
|
||||
|
||||
class TestImportFailureReasons:
|
||||
"""Test that import failures include diagnostic information."""
|
||||
|
||||
def test_import_failure_reason_format(self):
|
||||
"""Test that failure reason is formatted correctly."""
|
||||
# Simulate the formatting logic
|
||||
exception = ImportError("No module named 'missing_dep'")
|
||||
error_msg = str(exception).split('\n')[0][:100]
|
||||
reason = f"{type(exception).__name__}: {error_msg}"
|
||||
|
||||
assert reason == "ImportError: No module named 'missing_dep'"
|
||||
|
||||
def test_import_failure_reason_truncation(self):
|
||||
"""Test that long error messages are truncated."""
|
||||
long_msg = "a" * 200
|
||||
exception = ValueError(long_msg)
|
||||
error_msg = str(exception).split('\n')[0][:100]
|
||||
reason = f"{type(exception).__name__}: {error_msg}"
|
||||
|
||||
# Should be truncated to 100 chars for the message part
|
||||
assert len(error_msg) == 100
|
||||
assert reason.startswith("ValueError: ")
|
||||
|
||||
def test_import_failure_reason_multiline(self):
|
||||
"""Test that only first line of error is used."""
|
||||
multi_line_msg = "First line\nSecond line\nThird line"
|
||||
exception = RuntimeError(multi_line_msg)
|
||||
error_msg = str(exception).split('\n')[0][:100]
|
||||
reason = f"{type(exception).__name__}: {error_msg}"
|
||||
|
||||
assert reason == "RuntimeError: First line"
|
||||
assert "Second line" not in reason
|
||||
|
||||
def test_import_failure_reason_various_exceptions(self):
|
||||
"""Test formatting for various exception types."""
|
||||
test_cases = [
|
||||
(ModuleNotFoundError("No module named 'foo'"), "ModuleNotFoundError: No module named 'foo'"),
|
||||
(SyntaxError("invalid syntax"), "SyntaxError: invalid syntax"),
|
||||
(AttributeError("'NoneType' object has no attribute 'bar'"), "AttributeError: 'NoneType' object has no attribute 'bar'"),
|
||||
(FileNotFoundError("[Errno 2] No such file"), "FileNotFoundError: [Errno 2] No such file"),
|
||||
]
|
||||
|
||||
for exception, expected in test_cases:
|
||||
error_msg = str(exception).split('\n')[0][:100]
|
||||
reason = f"{type(exception).__name__}: {error_msg}"
|
||||
assert reason == expected, f"Failed for {type(exception).__name__}"
|
||||
|
||||
|
||||
class TestImportSummaryOutput:
|
||||
"""Test the import summary output format."""
|
||||
|
||||
def test_summary_message_with_reason(self):
|
||||
"""Test that summary includes reason when available."""
|
||||
reason = "ImportError: No module named 'xyz'"
|
||||
import_message = f" (IMPORT FAILED: {reason})"
|
||||
|
||||
assert import_message == " (IMPORT FAILED: ImportError: No module named 'xyz')"
|
||||
|
||||
def test_summary_message_without_reason(self):
|
||||
"""Test fallback when no reason is available."""
|
||||
reason = ""
|
||||
if reason:
|
||||
import_message = f" (IMPORT FAILED: {reason})"
|
||||
else:
|
||||
import_message = " (IMPORT FAILED)"
|
||||
|
||||
assert import_message == " (IMPORT FAILED)"
|
||||
|
||||
def test_summary_format_string(self):
|
||||
"""Test the full summary line format."""
|
||||
time_taken = 0.05
|
||||
import_message = " (IMPORT FAILED: ImportError: missing module)"
|
||||
module_path = "/path/to/custom_nodes/my_node"
|
||||
|
||||
summary_line = "{:6.1f} seconds{}: {}".format(time_taken, import_message, module_path)
|
||||
|
||||
assert "0.1 seconds" in summary_line
|
||||
assert "(IMPORT FAILED: ImportError: missing module)" in summary_line
|
||||
assert module_path in summary_line
|
||||
Loading…
Reference in New Issue
Block a user