move to legacy addon
This commit is contained in:
Binary file not shown.
@@ -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
1
tests/e2e/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""End-to-end tests for addon package validation."""
|
||||
BIN
tests/e2e/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
tests/e2e/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
72
tests/e2e/test_bl_info_parsing.py
Normal file
72
tests/e2e/test_bl_info_parsing.py
Normal 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"
|
||||
BIN
tests/fixtures/__pycache__/__init__.cpython-311.pyc
vendored
BIN
tests/fixtures/__pycache__/__init__.cpython-311.pyc
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user