Skip to content

Commit 4a0212f

Browse files
committed
fix(ci): resolve pytest import errors and correct pattern validation tests
1 parent b0ebf45 commit 4a0212f

File tree

5 files changed

+144
-41
lines changed

5 files changed

+144
-41
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-doctor"
33
description = "A real-time runtime diagnostics suite for ComfyUI, featuring interactive debugging chat, and 50+ fix patterns. Automatically intercepts terminal output from startup, and delivers prioritized fix suggestions with node-level context extraction. Now supports JSON-based pattern management with hot-reload and full i18n support for 9 languages."
4-
version = "1.3.1"
4+
version = "1.3.2"
55
license = {text = "MIT"}
66
readme = "README.md"
77
requires-python = ">=3.10"

pytest.ini

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1-
# Explicitly exclude root __init__.py fromPytest collection
2-
[tool:pytest]
3-
collect_ignore = ['__init__.py']
1+
# ═══════════════════════════════════════════════════════════════════════════
2+
# Pytest Configuration for ComfyUI-Doctor
3+
# ═══════════════════════════════════════════════════════════════════════════
4+
# Purpose: Prevent pytest from importing __init__.py as a test module
5+
#
6+
# Problem (without this config):
7+
# - pytest auto-discovers all Python files
8+
# - Tries to import __init__.py as a test module
9+
# - __init__.py uses relative imports (from .logger import ...)
10+
# - Relative imports fail with "no known parent package" error
11+
#
12+
# Solution:
13+
# - Explicitly tell pytest to only look in tests/ directory
14+
# - Ignore __init__.py and other non-test files
15+
#
16+
# Last Modified: 2026-01-03 (Fixed CI import errors)
17+
# ═══════════════════════════════════════════════════════════════════════════
18+
[pytest]
19+
# Only collect tests from these directories
20+
testpaths = tests
21+
22+
# Ignore these files during test collection
23+
norecursedirs = .git .github REFERENCE web patterns __pycache__ *.egg-info
24+
25+
# Don't try to import __init__.py as a test module
26+
python_files = test_*.py
27+
python_classes = Test*
28+
python_functions = test_*
29+
30+
# CRITICAL: Use importlib mode to prevent pytest from treating root as package
31+
# This prevents pytest from trying to import the root __init__.py
32+
addopts = --import-mode=importlib

tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Empty __init__.py for tests package
2+
# This prevents pytest from trying to import the root __init__.py

tests/conftest.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,60 @@
11
"""
2-
Pytest configuration for ComfyUI-Doctor tests
2+
Pytest configuration for ComfyUI-Doctor tests.
3+
4+
CRITICAL: This conftest.py prevents pytest from treating the project root as a Python package.
5+
6+
Problem:
7+
- ComfyUI-Doctor/__init__.py uses relative imports (from .logger import ...)
8+
- These only work when the module is imported as part of a package (e.g., custom_nodes.ComfyUI-Doctor)
9+
- Pytest tries to import __init__.py directly, causing "no known parent package" errors
10+
11+
Solution:
12+
- Tell pytest to NOT treat the root directory as a package
13+
- Rename root __init__.py temporarily during test collection
14+
15+
Last Modified: 2026-01-03 (Fixed CI import errors)
316
"""
417

518
import sys
619
from pathlib import Path
720

8-
# Add project root to path
21+
# Add project root to sys.path for absolute imports
922
project_root = Path(__file__).parent.parent
10-
sys.path.insert(0, str(project_root))
23+
if str(project_root) not in sys.path:
24+
sys.path.insert(0, str(project_root))
1125

1226
def pytest_ignore_collect(collection_path, config):
1327
"""
14-
Ignore __init__.py in project root (it's a ComfyUI extension with relative imports)
28+
Prevent pytest from collecting __init__.py in the project root.
29+
30+
This prevents the "attempted relative import with no known parent package" error
31+
that occurs when pytest tries to import __init__.py as a standalone module.
1532
"""
1633
if collection_path.name == "__init__.py" and collection_path.parent == project_root:
1734
return True
1835
return False
1936

37+
def pytest_configure(config):
38+
"""
39+
Rename root __init__.py before test collection to prevent pytest from treating root as package.
40+
"""
41+
root_init = project_root / "__init__.py"
42+
backup_init = project_root / "__init__.py.bak"
43+
44+
if root_init.exists() and not backup_init.exists():
45+
root_init.rename(backup_init)
46+
config._comfyui_doctor_init_renamed = True
47+
else:
48+
config._comfyui_doctor_init_renamed = False
49+
50+
def pytest_unconfigure(config):
51+
"""
52+
Restore root __init__.py after tests complete.
53+
"""
54+
if getattr(config, '_comfyui_doctor_init_renamed', False):
55+
root_init = project_root / "__init__.py"
56+
backup_init = project_root / "__init__.py.bak"
57+
58+
if backup_init.exists():
59+
backup_init.rename(root_init)
60+

tests/test_pattern_validation.py

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
Validates JSON schema, regex syntax, i18n completeness, and metadata.
66
77
Run: pytest tests/test_pattern_validation.py -v
8+
9+
═══════════════════════════════════════════════════════════════════════════
10+
⚠️ CRITICAL: Field Name Consistency Warning
11+
═══════════════════════════════════════════════════════════════════════════
12+
This test suite validates JSON pattern files against the schema.
13+
14+
IMPORTANT: JSON Schema Field Names (DO NOT CHANGE):
15+
- patterns/schema.json uses "regex" (NOT "pattern")
16+
- patterns/schema.json uses "error_key" (NOT "suggestion_key")
17+
18+
Common Mistake:
19+
❌ required_fields = ["id", "pattern", "suggestion_key", ...]
20+
✅ required_fields = ["id", "regex", "error_key", ...]
21+
22+
If tests fail with "missing required field", check that test expectations
23+
match the actual schema definition in patterns/schema.json.
24+
25+
Last Modified: 2026-01-03 (Fixed field name mismatch)
26+
═══════════════════════════════════════════════════════════════════════════
827
"""
928

1029
import json
@@ -51,28 +70,34 @@ def load_pattern_file(file_path: Path) -> Dict:
5170
return json.load(f)
5271

5372

54-
def extract_error_keys_from_i18n() -> Set[str]:
55-
"""Extract all ERROR_KEYS from i18n.py"""
73+
def extract_error_keys_from_i18n() -> Dict[str, str]:
74+
"""
75+
Extract ERROR_KEYS mapping from i18n.py.
76+
77+
Returns:
78+
Dict mapping uppercase keys to lowercase suggestion keys.
79+
Example: {"TYPE_MISMATCH": "type_mismatch", "CUDNN_ERROR": "cudnn_error", ...}
80+
"""
5681
with open(I18N_PATH, "r", encoding="utf-8") as f:
5782
content = f.read()
58-
83+
5984
# Find ERROR_KEYS dictionary
60-
start = content.find("ERROR_KEYS")
85+
start = content.find("ERROR_KEYS = {")
6186
if start == -1:
62-
return set()
63-
64-
# Extract keys (simplified parsing)
65-
keys = set()
87+
return {}
88+
89+
# Extract key-value pairs (simplified parsing)
90+
error_keys_dict = {}
6691
lines = content[start:].split("\n")
6792
for line in lines:
6893
if '"' in line and ":" in line:
69-
# Extract key from line like: "oom": "oom",
70-
match = re.search(r'"([^"]+)":\s*"', line)
94+
# Extract key-value from line like: "TYPE_MISMATCH": "type_mismatch",
95+
match = re.search(r'"([^"]+)":\s*"([^"]+)"', line)
7196
if match:
72-
keys.add(match.group(1))
97+
error_keys_dict[match.group(1)] = match.group(2)
7398
if line.strip() == "}":
7499
break
75-
return keys
100+
return error_keys_dict
76101

77102

78103
def extract_suggestions_from_i18n() -> Dict[str, Set[str]]:
@@ -152,8 +177,11 @@ def test_all_patterns_valid_json(self):
152177

153178
def test_all_patterns_have_required_fields(self, all_patterns):
154179
"""Test 2: All patterns have required fields"""
155-
required_fields = ["id", "pattern", "priority", "category", "suggestion_key"]
156-
180+
# ⚠️ CRITICAL: These field names MUST match patterns/schema.json
181+
# DO NOT change "regex" to "pattern" or "error_key" to "suggestion_key"
182+
# See file docstring for detailed explanation
183+
required_fields = ["id", "regex", "error_key", "priority", "category"]
184+
157185
for pattern in all_patterns:
158186
for field in required_fields:
159187
assert field in pattern, (
@@ -165,43 +193,46 @@ def test_all_regex_compile(self, all_patterns):
165193
"""Test 3: All regex patterns compile successfully"""
166194
for pattern in all_patterns:
167195
pattern_id = pattern.get("id", "UNKNOWN")
168-
regex_str = pattern.get("pattern", "")
169-
196+
# ⚠️ CRITICAL: Use "regex" (schema field name), NOT "pattern"
197+
regex_str = pattern.get("regex", "")
198+
170199
try:
171200
re.compile(regex_str)
172201
except re.error as e:
173202
pytest.fail(
174203
f"Pattern {pattern_id} in {pattern['_source_file']} "
175204
f"has invalid regex: {e}"
176205
)
177-
206+
178207
def test_all_suggestions_exist(self, all_patterns, error_keys, suggestions):
179-
"""Test 4: All suggestion_key values exist in i18n.py"""
208+
"""Test 4: All error_key values exist in i18n.py ERROR_KEYS and SUGGESTIONS"""
180209
# Check ERROR_KEYS mapping
181210
for pattern in all_patterns:
182211
pattern_id = pattern.get("id", "UNKNOWN")
183-
suggestion_key = pattern.get("suggestion_key", "")
184-
185-
assert suggestion_key in error_keys, (
212+
# ⚠️ CRITICAL: Use "error_key" (schema field name), NOT "suggestion_key"
213+
error_key = pattern.get("error_key", "")
214+
215+
assert error_key in error_keys, (
186216
f"Pattern {pattern_id} in {pattern['_source_file']} "
187-
f"has suggestion_key '{suggestion_key}' not found in ERROR_KEYS"
217+
f"has error_key '{error_key}' not found in ERROR_KEYS"
188218
)
189-
219+
190220
# Check all languages have translations
191221
for lang in SUPPORTED_LANGUAGES:
192222
assert lang in suggestions, f"Language '{lang}' not found in SUGGESTIONS"
193-
223+
194224
for pattern in all_patterns:
195225
pattern_id = pattern.get("id", "UNKNOWN")
196-
suggestion_key = pattern.get("suggestion_key", "")
197-
198-
# Map through ERROR_KEYS to get the actual key
199-
if suggestion_key in error_keys:
200-
# For simplicity, assume suggestion_key IS the final key
201-
# (in reality, ERROR_KEYS maps pattern keys to suggestion keys)
202-
assert suggestion_key in suggestions[lang], (
203-
f"Pattern {pattern_id} missing {lang} translation for '{suggestion_key}'"
204-
)
226+
error_key = pattern.get("error_key", "")
227+
228+
# Get the suggestion key from ERROR_KEYS mapping
229+
# Example: error_key="CUDNN_ERROR" -> suggestion_key="cudnn_error"
230+
suggestion_key = error_keys.get(error_key, "")
231+
232+
assert suggestion_key in suggestions[lang], (
233+
f"Pattern {pattern_id} (error_key: {error_key}) missing {lang} translation. "
234+
f"Expected suggestion key '{suggestion_key}' in SUGGESTIONS['{lang}']"
235+
)
205236

206237
def test_priority_ranges(self, all_patterns):
207238
"""Test 5: All priorities are within valid range 50-95"""

0 commit comments

Comments
 (0)