Files
Text-Texture-Generator-for-…/tests/e2e/test_pil_dependency_regression.py
2025-10-12 10:45:29 +02:00

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