move to legacy addon

This commit is contained in:
2025-10-09 23:36:10 +02:00
parent 3618699504
commit 205aa7c421
187 changed files with 33643 additions and 168 deletions

View File

@@ -1,11 +1,65 @@
# Mock bpy BEFORE test collection can import src modules
import sys
from unittest.mock import Mock, MagicMock
# Create comprehensive bpy mock
bpy_mock = MagicMock()
bpy_mock.types = MagicMock()
bpy_mock.props = MagicMock()
bpy_mock.utils = MagicMock()
bpy_mock.ops = MagicMock()
bpy_mock.context = MagicMock()
bpy_mock.data = MagicMock()
bpy_mock.app = MagicMock()
# Set up mock attributes that src code needs
bpy_mock.types.Operator = Mock
bpy_mock.types.Panel = Mock
bpy_mock.types.PropertyGroup = Mock
bpy_mock.types.UIList = Mock
bpy_mock.types.Scene = Mock()
bpy_mock.types.NODE_MT_add = Mock()
bpy_mock.types.VIEW3D_MT_add = Mock()
bpy_mock.types.VIEW3D_MT_mesh_add = Mock()
bpy_mock.types.NODE_MT_node = Mock()
bpy_mock.types.IMAGE_MT_image = Mock()
bpy_mock.props.StringProperty = Mock
bpy_mock.props.IntProperty = Mock
bpy_mock.props.FloatVectorProperty = Mock
bpy_mock.props.FloatProperty = Mock
bpy_mock.props.BoolProperty = Mock
bpy_mock.props.EnumProperty = Mock
bpy_mock.props.CollectionProperty = Mock
bpy_mock.props.PointerProperty = Mock
bpy_mock.utils.register_class = Mock()
bpy_mock.utils.unregister_class = Mock()
bpy_mock.app.handlers = Mock()
bpy_mock.app.handlers.load_post = []
bpy_mock.app.handlers.persistent = lambda f: f
bpy_mock.context.window_manager = Mock()
bpy_mock.context.scene = Mock()
# Mock ALL Blender modules at import time
sys.modules['bpy'] = bpy_mock
sys.modules['bmesh'] = MagicMock()
sys.modules['mathutils'] = MagicMock()
sys.modules['bpy.types'] = bpy_mock.types
sys.modules['bpy.props'] = bpy_mock.props
sys.modules['bpy.utils'] = bpy_mock.utils
sys.modules['bpy.ops'] = bpy_mock.ops
sys.modules['bpy.app'] = bpy_mock.app
sys.modules['bpy.context'] = bpy_mock.context
"""
Pytest configuration and fixtures for Text Texture Generator tests.
Provides Blender mocking and common test utilities.
"""
import pytest
import sys
from unittest.mock import Mock, MagicMock, patch
from PIL import Image, ImageDraw, ImageFont
@@ -53,68 +107,14 @@ class MockBlenderImage:
self.packed_file = Mock()
class MockBpy:
"""Mock for the bpy module"""
def __init__(self):
self.data = MockBlenderData()
# Attach mock data to bpy_mock
bpy_mock.data = MockBlenderData()
@pytest.fixture(scope="session", autouse=True)
def mock_bpy():
"""Mock the bpy module for all tests"""
# Mock bpy and related Blender modules BEFORE any imports
mock_bpy_module = MockBpy()
mock_bmesh = Mock()
mock_bpy_types = Mock()
mock_bpy_props = Mock()
mock_bpy_utils = Mock()
mock_bpy_app = Mock()
mock_bpy_context = Mock()
# Set up mock attributes
mock_bpy_types.Operator = Mock
mock_bpy_types.Panel = Mock
mock_bpy_types.PropertyGroup = Mock
mock_bpy_types.UIList = Mock
mock_bpy_types.Scene = Mock()
mock_bpy_props.StringProperty = Mock
mock_bpy_props.IntProperty = Mock
mock_bpy_props.FloatVectorProperty = Mock
mock_bpy_props.FloatProperty = Mock
mock_bpy_props.BoolProperty = Mock
mock_bpy_props.EnumProperty = Mock
mock_bpy_props.CollectionProperty = Mock
mock_bpy_props.PointerProperty = Mock
mock_bpy_utils.register_class = Mock()
mock_bpy_utils.unregister_class = Mock()
mock_bpy_app.handlers = Mock()
mock_bpy_app.handlers.load_post = []
mock_bpy_app.handlers.persistent = lambda f: f
mock_bpy_context.window_manager = Mock()
mock_bpy_context.scene = Mock()
mock_bpy_module.types = mock_bpy_types
mock_bpy_module.props = mock_bpy_props
mock_bpy_module.utils = mock_bpy_utils
mock_bpy_module.app = mock_bpy_app
mock_bpy_module.context = mock_bpy_context
mocked_modules = {
'bpy': mock_bpy_module,
'bmesh': mock_bmesh,
'bpy.types': mock_bpy_types,
'bpy.props': mock_bpy_props,
'bpy.utils': mock_bpy_utils,
'bpy.app': mock_bpy_app,
'bpy.context': mock_bpy_context
}
with patch.dict('sys.modules', mocked_modules):
yield mock_bpy_module
@pytest.fixture(autouse=True)
def mock_bpy_fixture():
"""Provide bpy mock to tests (already mocked at import time)."""
yield bpy_mock
@pytest.fixture
@@ -157,6 +157,7 @@ def standard_dimensions():
@pytest.fixture
def mock_pil_image():
"""Mock PIL Image for testing"""
from unittest.mock import patch
with patch('PIL.Image.new') as mock_new:
mock_img = Mock()
mock_img.size = (512, 512)
@@ -171,6 +172,7 @@ def mock_pil_image():
@pytest.fixture
def mock_image_draw():
"""Mock PIL ImageDraw for testing"""
from unittest.mock import patch
with patch('PIL.ImageDraw.Draw') as mock_draw_class:
mock_draw = Mock()
mock_draw.textbbox.return_value = (0, 0, 100, 20) # x1, y1, x2, y2

1
tests/e2e/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""End-to-end tests for addon package validation."""

Binary file not shown.

View File

@@ -0,0 +1,72 @@
"""E2E test for bl_info parsing in built addon package."""
import ast
import zipfile
import subprocess
import tempfile
from pathlib import Path
def test_built_package_bl_info_is_parseable():
"""
CRITICAL E2E TEST: Validates that the built addon package has a parseable bl_info.
This test rebuilds the package, extracts it, and parses bl_info exactly as Blender does.
"""
project_root = Path(__file__).parent.parent.parent
# Step 1: Rebuild the package to ensure latest code
result = subprocess.run(
["python", "build/build_addon.py", "--type", "full"],
cwd=project_root,
capture_output=True,
text=True
)
assert result.returncode == 0, f"Build failed: {result.stderr}"
# Step 2: Extract the built package
dist_zip = project_root / "dist" / "text_texture_generator_v1.0.0.zip"
assert dist_zip.exists(), f"Built package not found: {dist_zip}"
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(dist_zip, 'r') as z:
z.extractall(tmpdir)
init_file = Path(tmpdir) / "text_texture_generator" / "__init__.py"
assert init_file.exists(), f"__init__.py not found in package"
# Step 3: Read and parse exactly as Blender does
content = init_file.read_text()
tree = ast.parse(content)
bl_info_found = False
for node in tree.body:
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == 'bl_info':
bl_info_found = True
# This is THE critical test - ast.literal_eval() exactly as Blender uses
try:
parsed_bl_info = ast.literal_eval(node.value)
# Validate required keys
assert "name" in parsed_bl_info, "bl_info missing 'name'"
assert "author" in parsed_bl_info, "bl_info missing 'author'"
assert "version" in parsed_bl_info, "bl_info missing 'version'"
assert "blender" in parsed_bl_info, "bl_info missing 'blender'"
# Success!
print(f"\n✅ bl_info parsed successfully:")
print(f" name: {parsed_bl_info['name']}")
print(f" version: {parsed_bl_info['version']}")
except ValueError as e:
# Print diagnostic info before failing
print(f"\n❌ FAILED at line {node.lineno}")
print(f"Error: {e}")
print(f"\nProblematic AST node:")
print(ast.dump(node.value, indent=2))
print(f"\nFirst 30 lines of __init__.py:")
print('\n'.join(content.split('\n')[:30]))
raise AssertionError(f"bl_info failed ast.literal_eval() at line {node.lineno}: {e}")
assert bl_info_found, "bl_info not found in __init__.py"