324 lines
10 KiB
Python
324 lines
10 KiB
Python
"""
|
|
E2E regression test for PIL/Pillow dependency availability.
|
|
|
|
This test reproduces the production error:
|
|
ERROR: PIL dependency not available despite manifest declaration: No module named 'PIL'
|
|
|
|
CRITICAL DISCOVERY: The Docker image pre-installs python3-pil (line 10 in Dockerfile),
|
|
which creates a FALSE GREEN. Real users don't have PIL pre-installed.
|
|
|
|
This test simulates the REAL production scenario:
|
|
1. Blender environment WITHOUT PIL pre-installed (like fresh user installation)
|
|
2. Addon declares PIL in manifest
|
|
3. Addon tries to import PIL at runtime
|
|
4. Import FAILS because manifest declaration doesn't guarantee installation
|
|
|
|
This is a FAILING test that must pass after GREEN implements proper PIL installation.
|
|
|
|
NO MOCKS - runs in real Docker+Blender environment, testing actual failure mode.
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
import tarfile
|
|
import io
|
|
|
|
|
|
def test_pil_dependency_available_in_blender_without_system_package(
|
|
blender_container,
|
|
addon_package,
|
|
tmp_path
|
|
):
|
|
"""
|
|
Regression test for: ERROR: PIL dependency not available despite manifest declaration
|
|
|
|
This test verifies that PIL/Pillow CAN be imported within Blender's Python context
|
|
after the addon is installed, EVEN WITHOUT system PIL pre-installed.
|
|
|
|
Test strategy:
|
|
1. Use real Docker+Blender environment (via existing E2E fixtures)
|
|
2. REMOVE system-installed PIL to simulate fresh Blender installation
|
|
3. Install the addon (which declares PIL as dependency)
|
|
4. Attempt to import PIL
|
|
5. Assert PIL IS importable (test MUST FAIL initially, proving the bug)
|
|
|
|
Current behavior (BUG):
|
|
- PIL import fails with ModuleNotFoundError
|
|
- Manifest declaration doesn't guarantee runtime availability
|
|
|
|
Expected behavior (after GREEN fix):
|
|
- PIL imports successfully via addon's wheels mechanism
|
|
- Addon can use PIL for text rendering
|
|
"""
|
|
# First, remove system-installed PIL to simulate fresh Blender
|
|
remove_pil_cmd = [
|
|
"apt-get", "remove", "-y", "python3-pil"
|
|
]
|
|
|
|
remove_result = blender_container.exec_run(
|
|
remove_pil_cmd,
|
|
user="root" # Need root to remove packages
|
|
)
|
|
|
|
print("\n=== Removing system PIL ===")
|
|
print(remove_result.output.decode('utf-8', errors='replace'))
|
|
print("=== PIL Removal Complete ===\n")
|
|
|
|
# Now test if addon can provide PIL through its dependency mechanism
|
|
script_content = """
|
|
import sys
|
|
|
|
# Test PIL import after addon installation WITHOUT system PIL
|
|
# The addon's dependency mechanism should make PIL available
|
|
try:
|
|
# Attempt the exact imports used by the addon
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
# Verify PIL is fully functional
|
|
img = Image.new('RGB', (100, 100), color='white')
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
import PIL
|
|
pil_version = PIL.__version__
|
|
|
|
print(f"SUCCESS: PIL imported successfully (version {pil_version})")
|
|
print("Image.new() works: True")
|
|
print("ImageDraw.Draw() works: True")
|
|
print("PIL is available through addon's dependency mechanism")
|
|
sys.exit(0)
|
|
|
|
except ModuleNotFoundError as e:
|
|
print(f"FAILURE: ModuleNotFoundError: {e}")
|
|
print("")
|
|
print("This is the BUG - manifest declaration doesn't provide PIL")
|
|
sys.exit(1)
|
|
|
|
except ImportError as e:
|
|
print(f"FAILURE: ImportError: {e}")
|
|
sys.exit(1)
|
|
"""
|
|
|
|
# Copy test script to container
|
|
tar_stream = io.BytesIO()
|
|
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
|
script_info = tarfile.TarInfo(name='test_pil_import.py')
|
|
script_bytes = script_content.encode('utf-8')
|
|
script_info.size = len(script_bytes)
|
|
tar.addfile(script_info, io.BytesIO(script_bytes))
|
|
|
|
tar_stream.seek(0)
|
|
blender_container.put_archive('/addon-test', tar_stream)
|
|
|
|
# Copy addon package
|
|
addon_tar_stream = io.BytesIO()
|
|
with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar:
|
|
addon_info = tarfile.TarInfo(name='text_texture_generator.zip')
|
|
addon_info.size = addon_package.stat().st_size
|
|
with open(addon_package, 'rb') as f:
|
|
tar.addfile(addon_info, f)
|
|
|
|
addon_tar_stream.seek(0)
|
|
blender_container.put_archive('/addon-test', addon_tar_stream)
|
|
|
|
container_script_path = "/addon-test/test_pil_import.py"
|
|
container_addon_path = "/addon-test/text_texture_generator.zip"
|
|
|
|
# Install addon
|
|
install_cmd = [
|
|
"/usr/local/bin/run_blender.sh",
|
|
"--background",
|
|
"--python-expr",
|
|
(
|
|
"import bpy; "
|
|
f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); "
|
|
"bpy.ops.preferences.addon_enable(module='text_texture_generator'); "
|
|
"bpy.ops.wm.save_userpref()"
|
|
)
|
|
]
|
|
|
|
install_result = blender_container.exec_run(
|
|
install_cmd,
|
|
workdir="/addon-test"
|
|
)
|
|
|
|
assert install_result.exit_code == 0, (
|
|
f"Addon installation failed with exit code {install_result.exit_code}\n"
|
|
f"Output: {install_result.output.decode('utf-8', errors='replace')}"
|
|
)
|
|
|
|
# Run PIL import test
|
|
test_cmd = [
|
|
"/usr/local/bin/run_blender.sh",
|
|
"--background",
|
|
"--python",
|
|
container_script_path
|
|
]
|
|
|
|
test_result = blender_container.exec_run(
|
|
test_cmd,
|
|
workdir="/addon-test"
|
|
)
|
|
|
|
output = test_result.output.decode('utf-8', errors='replace')
|
|
|
|
print("\n=== PIL Import Test Output ===")
|
|
print(output)
|
|
print("=== End Output ===\n")
|
|
|
|
# THIS IS THE KEY ASSERTION FOR RED PHASE
|
|
# We assert PIL SHOULD be available (exit code 0)
|
|
# This MUST FAIL initially, proving the bug exists
|
|
assert test_result.exit_code == 0, (
|
|
f"PIL import FAILED in Blender Python context.\n"
|
|
f"Exit code: {test_result.exit_code}\n"
|
|
f"Output:\n{output}\n\n"
|
|
f"BUG CONFIRMED: Addon's manifest declares PIL but it's not available at runtime.\n"
|
|
f"The addon cannot render text without PIL.\n"
|
|
f"GREEN phase must implement proper PIL installation mechanism."
|
|
)
|
|
|
|
# Verify PIL actually works (full functionality check)
|
|
assert "SUCCESS: PIL imported successfully" in output, (
|
|
"PIL import did not produce success message"
|
|
)
|
|
assert "Image.new() works: True" in output, (
|
|
"PIL Image.new() failed - PIL not fully functional"
|
|
)
|
|
assert "ImageDraw.Draw() works: True" in output, (
|
|
"PIL ImageDraw.Draw() failed - PIL not fully functional"
|
|
)
|
|
|
|
|
|
def test_addon_text_rendering_works_without_system_pil(
|
|
blender_container,
|
|
addon_package,
|
|
tmp_path
|
|
):
|
|
"""
|
|
Extended test: Verify addon's text rendering works without system PIL.
|
|
|
|
This tests the actual production use case - generating a texture with text.
|
|
"""
|
|
# Remove system PIL
|
|
remove_pil_cmd = [
|
|
"apt-get", "remove", "-y", "python3-pil"
|
|
]
|
|
|
|
blender_container.exec_run(remove_pil_cmd, user="root")
|
|
|
|
script_content = """
|
|
import sys
|
|
import bpy
|
|
|
|
# Enable addon
|
|
bpy.ops.preferences.addon_enable(module='text_texture_generator')
|
|
|
|
# Test actual text texture generation (the real use case)
|
|
try:
|
|
from text_texture_generator.core.generation_engine import generate_texture_image
|
|
from text_texture_generator.properties import TEXT_TEXTURE_Properties
|
|
|
|
# Create properties mock
|
|
class MockProps:
|
|
text = "Test Text"
|
|
prepend_text = ""
|
|
append_text = ""
|
|
prepend_append_layout = 'HORIZONTAL'
|
|
prepend_margin = 10
|
|
append_margin = 10
|
|
background_transparent = False
|
|
background_color = (0.2, 0.2, 0.2, 1.0)
|
|
text_color = (1.0, 1.0, 1.0, 1.0)
|
|
font_size = 48
|
|
font_path = "default"
|
|
use_custom_font = False
|
|
custom_font_path = ""
|
|
enable_text_fitting = False
|
|
|
|
props = MockProps()
|
|
|
|
# Try to generate texture (this will fail if PIL isn't available)
|
|
result = generate_texture_image(props, 512, 512)
|
|
|
|
if result and result[0]:
|
|
print("SUCCESS: Text texture generated successfully")
|
|
print("Addon text rendering works without system PIL")
|
|
sys.exit(0)
|
|
else:
|
|
print("FAILURE: Texture generation returned None")
|
|
sys.exit(1)
|
|
|
|
except Exception as e:
|
|
print(f"FAILURE: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
"""
|
|
|
|
# Copy test script
|
|
tar_stream = io.BytesIO()
|
|
with tarfile.open(fileobj=tar_stream, mode='w') as tar:
|
|
script_info = tarfile.TarInfo(name='test_rendering.py')
|
|
script_bytes = script_content.encode('utf-8')
|
|
script_info.size = len(script_bytes)
|
|
tar.addfile(script_info, io.BytesIO(script_bytes))
|
|
|
|
tar_stream.seek(0)
|
|
blender_container.put_archive('/addon-test', tar_stream)
|
|
|
|
# Copy addon package
|
|
addon_tar_stream = io.BytesIO()
|
|
with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar:
|
|
addon_info = tarfile.TarInfo(name='text_texture_generator.zip')
|
|
addon_info.size = addon_package.stat().st_size
|
|
with open(addon_package, 'rb') as f:
|
|
tar.addfile(addon_info, f)
|
|
|
|
addon_tar_stream.seek(0)
|
|
blender_container.put_archive('/addon-test', addon_tar_stream)
|
|
|
|
container_script_path = "/addon-test/test_rendering.py"
|
|
container_addon_path = "/addon-test/text_texture_generator.zip"
|
|
|
|
# Install addon
|
|
install_cmd = [
|
|
"/usr/local/bin/run_blender.sh",
|
|
"--background",
|
|
"--python-expr",
|
|
(
|
|
"import bpy; "
|
|
f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); "
|
|
"bpy.ops.preferences.addon_enable(module='text_texture_generator'); "
|
|
"bpy.ops.wm.save_userpref()"
|
|
)
|
|
]
|
|
|
|
blender_container.exec_run(install_cmd, workdir="/addon-test")
|
|
|
|
# Run rendering test
|
|
test_cmd = [
|
|
"/usr/local/bin/run_blender.sh",
|
|
"--background",
|
|
"--python",
|
|
container_script_path
|
|
]
|
|
|
|
test_result = blender_container.exec_run(
|
|
test_cmd,
|
|
workdir="/addon-test"
|
|
)
|
|
|
|
output = test_result.output.decode('utf-8', errors='replace')
|
|
|
|
print("\n=== Text Rendering Test Output ===")
|
|
print(output)
|
|
print("=== End Output ===\n")
|
|
|
|
# Assert rendering works (MUST FAIL initially)
|
|
assert test_result.exit_code == 0, (
|
|
f"Text rendering failed without system PIL\n"
|
|
f"Exit code: {test_result.exit_code}\n{output}\n"
|
|
f"BUG: Addon cannot render text without PIL dependency"
|
|
)
|
|
|
|
assert "SUCCESS: Text texture generated successfully" in output |