""" E2E test fixtures for Docker-based Blender testing. Manages Docker container lifecycle for real Blender tests. """ import pytest import docker import shutil import zipfile from pathlib import Path import time @pytest.fixture(scope="module") def docker_client(): """Provide Docker client for container management.""" try: client = docker.from_env() # Verify Docker is running client.ping() return client except docker.errors.DockerException as e: pytest.skip(f"Docker not available: {e}") @pytest.fixture(scope="module") def blender_image(docker_client): """Build Blender Docker image if not exists.""" image_tag = "text-texture-generator-e2e:latest" dockerfile_path = Path(__file__).parent / "docker" try: # Try to get existing image docker_client.images.get(image_tag) print(f"Using existing Docker image: {image_tag}") except docker.errors.ImageNotFound: print(f"Building Docker image: {image_tag}") # Build image docker_client.images.build( path=str(dockerfile_path), tag=image_tag, rm=True ) return image_tag @pytest.fixture(scope="function") def blender_container(docker_client, blender_image, tmp_path): """ Spin up Blender container for test execution. Yields container instance, cleans up after test. """ container = None try: # Start container with addon mounted container = docker_client.containers.run( blender_image, command="/bin/sleep 3600", # Keep alive for test duration detach=True, remove=False, # We'll remove manually after cleanup volumes={ str(tmp_path): {"bind": "/addon-test", "mode": "rw"} } ) # Wait for container to be ready timeout = 10 start = time.time() while time.time() - start < timeout: container.reload() if container.status == "running": break time.sleep(0.5) if container.status != "running": pytest.fail(f"Container failed to start: {container.status}") yield container finally: # Cleanup if container: try: container.stop(timeout=5) container.remove() except Exception as e: print(f"Warning: Container cleanup failed: {e}") @pytest.fixture(scope="function") def addon_package(tmp_path, request): """ Package the addon as a zip file for installation in Blender. Detects version type from test file name and patches VERSION_TYPE accordingly. Returns path to the packaged addon zip. """ src_dir = Path(__file__).parent.parent.parent / "src" # Determine version type from test module name test_file = request.node.fspath.basename version_type = "free" if "free_version" in test_file else "full" # Copy src to temp and patch constants.py temp_src = tmp_path / "src_temp" shutil.copytree(src_dir, temp_src) constants_file = temp_src / "utils" / "constants.py" content = constants_file.read_text() content = content.replace('VERSION_TYPE = "free"', f'VERSION_TYPE = "{version_type}"') constants_file.write_text(content) # Create zip from patched source addon_zip = tmp_path / "text_texture_generator.zip" with zipfile.ZipFile(addon_zip, 'w', zipfile.ZIP_DEFLATED) as zf: for file_path in temp_src.rglob("*"): if file_path.is_file(): arc_name = f"text_texture_generator/{file_path.relative_to(temp_src)}" zf.write(file_path, arc_name) return addon_zip