wip
This commit is contained in:
324
tests/e2e/test_pil_dependency_regression.py
Normal file
324
tests/e2e/test_pil_dependency_regression.py
Normal file
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user