Files
PozzettiAndrea-ComfyUI-Geom…/tests/test_scripts/conftest.py
T
2025-11-14 22:00:06 +01:00

193 lines
5.8 KiB
Python

"""Pytest configuration and fixtures for ComfyUI-GeometryPack tests."""
import sys
from pathlib import Path
from unittest.mock import MagicMock
import pytest
import numpy as np
import trimesh
# Add parent directory to path so we can import nodes
PROJECT_ROOT = Path(__file__).parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
# Set up paths
TEST_DIR = Path(__file__).parent.parent # Go up to tests/ directory
ASSETS_DIR = TEST_DIR / "assets"
OUTPUT_DIR = TEST_DIR / "outputs"
MESHES_DIR = OUTPUT_DIR / "meshes"
RENDERS_DIR = OUTPUT_DIR / "renders"
# Mock ComfyUI modules before importing nodes
sys.modules["folder_paths"] = MagicMock()
sys.modules["folder_paths"].get_input_directory = MagicMock(
return_value=str(ASSETS_DIR)
)
sys.modules["folder_paths"].get_output_directory = MagicMock(
return_value=str(MESHES_DIR)
)
def pytest_configure(config):
"""Register custom markers."""
config.addinivalue_line("markers", "unit: Unit tests (fast, no optional dependencies)")
config.addinivalue_line("markers", "optional: Tests requiring optional dependencies")
config.addinivalue_line("markers", "blender: Tests requiring Blender")
config.addinivalue_line("markers", "slow: Slow tests (heavy processing)")
config.addinivalue_line("markers", "browser: Browser automation tests (require Selenium + browser + ComfyUI running)")
@pytest.fixture(scope="session", autouse=True)
def setup_output_dirs():
"""Create output directories for test artifacts."""
MESHES_DIR.mkdir(parents=True, exist_ok=True)
RENDERS_DIR.mkdir(parents=True, exist_ok=True)
@pytest.fixture
def test_assets_dir():
"""Return path to test assets directory."""
return ASSETS_DIR
@pytest.fixture
def test_output_dir(request):
"""Return path to test-specific output directory."""
# Create a directory for this specific test
test_name = request.node.name
test_dir = OUTPUT_DIR / test_name
test_dir.mkdir(parents=True, exist_ok=True)
return test_dir
@pytest.fixture
def meshes_output_dir(test_output_dir):
"""Return path to test-specific meshes directory.
For I/O tests, this returns the mocked ComfyUI output directory
where SaveMesh actually saves files.
"""
# Return the mocked output directory that SaveMesh uses
# This is where files are actually saved via folder_paths.get_output_directory()
return Path(sys.modules["folder_paths"].get_output_directory())
@pytest.fixture
def renders_output_dir(test_output_dir):
"""Return path to test-specific renders directory."""
render_dir = test_output_dir / "renders"
render_dir.mkdir(exist_ok=True)
return render_dir
@pytest.fixture
def bunny_mesh():
"""Load Stanford Bunny mesh."""
bunny_path = ASSETS_DIR / "Stanford_Bunny.stl"
if bunny_path.exists():
return trimesh.load(str(bunny_path))
return None
@pytest.fixture
def cube_mesh():
"""Create a simple cube mesh."""
return trimesh.creation.box(extents=[1.0, 1.0, 1.0])
@pytest.fixture
def sphere_mesh():
"""Create a simple sphere mesh."""
return trimesh.creation.icosphere(subdivisions=2, radius=1.0)
@pytest.fixture
def open_mesh():
"""Create an open mesh (cylinder without caps) for UV unwrapping tests."""
# Create a cylinder and remove the caps to make it open
cylinder = trimesh.creation.cylinder(radius=1.0, height=2.0, sections=16)
# Identify and remove cap faces (faces at top/bottom with normals pointing up/down)
face_normals = cylinder.face_normals
# Cap faces have normals nearly vertical (pointing up or down)
is_cap = np.abs(face_normals[:, 2]) > 0.9 # Z component dominates
# Keep only side faces
side_faces = cylinder.faces[~is_cap]
# Create open mesh with only side faces and clean it up
open_cylinder = trimesh.Trimesh(vertices=cylinder.vertices, faces=side_faces, process=True)
# Remove unreferenced vertices
open_cylinder.remove_unreferenced_vertices()
return open_cylinder
def render_mesh_to_png(mesh, output_path, resolution=(800, 800)):
"""Render mesh to PNG file for visual verification."""
try:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
# Create mesh collection
collection = Poly3DCollection(
mesh.vertices[mesh.faces],
alpha=0.7,
facecolor='cyan',
edgecolor='black',
linewidths=0.1
)
ax.add_collection3d(collection)
# Set axis limits
scale = mesh.vertices.flatten()
ax.auto_scale_xyz(scale, scale, scale)
# Labels
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.savefig(output_path, dpi=100, bbox_inches='tight')
plt.close()
return True
except Exception as e:
print(f"Warning: Could not render mesh: {e}")
return False
@pytest.fixture
def render_helper(renders_output_dir):
"""Helper fixture for rendering meshes."""
def _render(mesh, name, **kwargs):
output_path = renders_output_dir / f"{name}.png"
render_mesh_to_png(mesh, output_path, **kwargs)
return output_path
return _render
@pytest.fixture
def save_mesh_helper(meshes_output_dir):
"""Helper fixture for saving meshes."""
def _save(mesh, name, format="obj"):
output_path = meshes_output_dir / f"{name}.{format}"
mesh.export(str(output_path))
return output_path
return _save
@pytest.fixture
def save_and_render(save_mesh_helper, render_helper):
"""Combined helper to save mesh and render it."""
def _save_and_render(mesh, name, format="obj"):
save_mesh_helper(mesh, name, format)
render_helper(mesh, name)
return True
return _save_and_render