Merges full history from art-dag/mono.git into the monorepo under the artdag/ directory. Contains: core (DAG engine), l1 (Celery rendering server), l2 (ActivityPub registry), common (shared templates/middleware), client (CLI), test (e2e). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> git-subtree-dir: artdag git-subtree-mainline:1a179de547git-subtree-split:4c2e716558
186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
"""
|
|
Integration tests for GPU/CPU frame compatibility.
|
|
|
|
These tests verify that all primitives work correctly with both:
|
|
- numpy arrays (CPU frames)
|
|
- CuPy arrays (GPU frames)
|
|
- GPUFrame wrapper objects
|
|
|
|
Run with: pytest tests/test_frame_compatibility.py -v
|
|
"""
|
|
|
|
import pytest
|
|
import numpy as np
|
|
import sys
|
|
import os
|
|
|
|
# Add parent to path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
# Try to import CuPy
|
|
try:
|
|
import cupy as cp
|
|
HAS_GPU = True
|
|
except ImportError:
|
|
cp = None
|
|
HAS_GPU = False
|
|
|
|
|
|
def create_test_frame(on_gpu=False):
|
|
"""Create a test frame (100x100 RGB)."""
|
|
frame = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
|
|
if on_gpu and HAS_GPU:
|
|
return cp.asarray(frame)
|
|
return frame
|
|
|
|
|
|
class MockGPUFrame:
|
|
"""Mock GPUFrame for testing without full GPU stack."""
|
|
def __init__(self, data):
|
|
self._data = data
|
|
|
|
@property
|
|
def cpu(self):
|
|
if HAS_GPU and hasattr(self._data, 'get'):
|
|
return self._data.get()
|
|
return self._data
|
|
|
|
@property
|
|
def gpu(self):
|
|
if HAS_GPU:
|
|
if hasattr(self._data, 'get'):
|
|
return self._data
|
|
return cp.asarray(self._data)
|
|
raise RuntimeError("No GPU available")
|
|
|
|
|
|
class TestColorOps:
|
|
"""Test color_ops primitives with different frame types."""
|
|
|
|
def test_shift_hsv_numpy(self):
|
|
"""shift-hsv should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.color_ops import prim_shift_hsv
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = prim_shift_hsv(frame, h=30, s=1.2, v=0.9)
|
|
assert isinstance(result, np.ndarray)
|
|
assert result.shape == frame.shape
|
|
|
|
@pytest.mark.skipif(not HAS_GPU, reason="No GPU")
|
|
def test_shift_hsv_cupy(self):
|
|
"""shift-hsv should work with CuPy arrays."""
|
|
from sexp_effects.primitive_libs.color_ops import prim_shift_hsv
|
|
frame = create_test_frame(on_gpu=True)
|
|
result = prim_shift_hsv(frame, h=30, s=1.2, v=0.9)
|
|
assert isinstance(result, np.ndarray) # Should return numpy
|
|
|
|
def test_shift_hsv_gpuframe(self):
|
|
"""shift-hsv should work with GPUFrame objects."""
|
|
from sexp_effects.primitive_libs.color_ops import prim_shift_hsv
|
|
frame = MockGPUFrame(create_test_frame(on_gpu=False))
|
|
result = prim_shift_hsv(frame, h=30, s=1.2, v=0.9)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
def test_invert_numpy(self):
|
|
"""invert-img should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.color_ops import prim_invert_img
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = prim_invert_img(frame)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
def test_adjust_numpy(self):
|
|
"""adjust should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.color_ops import prim_adjust
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = prim_adjust(frame, brightness=10, contrast=1.2)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
|
|
class TestGeometry:
|
|
"""Test geometry primitives with different frame types."""
|
|
|
|
def test_rotate_numpy(self):
|
|
"""rotate should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.geometry import prim_rotate
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = prim_rotate(frame, 45)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
def test_scale_numpy(self):
|
|
"""scale should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.geometry import prim_scale
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = prim_scale(frame, 1.5)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
|
|
class TestBlending:
|
|
"""Test blending primitives with different frame types."""
|
|
|
|
def test_blend_numpy(self):
|
|
"""blend should work with numpy arrays."""
|
|
from sexp_effects.primitive_libs.blending import prim_blend
|
|
frame_a = create_test_frame(on_gpu=False)
|
|
frame_b = create_test_frame(on_gpu=False)
|
|
result = prim_blend(frame_a, frame_b, 0.5)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
|
|
class TestInterpreterConversion:
|
|
"""Test the interpreter's frame conversion."""
|
|
|
|
def test_maybe_to_numpy_none(self):
|
|
"""_maybe_to_numpy should handle None."""
|
|
from streaming.stream_sexp_generic import StreamInterpreter
|
|
# Create minimal interpreter
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.sexp', delete=False) as f:
|
|
f.write('(stream "test" :fps 30 :width 100 :height 100 (frame frame))')
|
|
f.flush()
|
|
interp = StreamInterpreter(f.name)
|
|
|
|
assert interp._maybe_to_numpy(None) is None
|
|
|
|
def test_maybe_to_numpy_numpy(self):
|
|
"""_maybe_to_numpy should pass through numpy arrays."""
|
|
from streaming.stream_sexp_generic import StreamInterpreter
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.sexp', delete=False) as f:
|
|
f.write('(stream "test" :fps 30 :width 100 :height 100 (frame frame))')
|
|
f.flush()
|
|
interp = StreamInterpreter(f.name)
|
|
|
|
frame = create_test_frame(on_gpu=False)
|
|
result = interp._maybe_to_numpy(frame)
|
|
assert result is frame # Should be same object
|
|
|
|
@pytest.mark.skipif(not HAS_GPU, reason="No GPU")
|
|
def test_maybe_to_numpy_cupy(self):
|
|
"""_maybe_to_numpy should convert CuPy to numpy."""
|
|
from streaming.stream_sexp_generic import StreamInterpreter
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.sexp', delete=False) as f:
|
|
f.write('(stream "test" :fps 30 :width 100 :height 100 (frame frame))')
|
|
f.flush()
|
|
interp = StreamInterpreter(f.name)
|
|
|
|
frame = create_test_frame(on_gpu=True)
|
|
result = interp._maybe_to_numpy(frame)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
def test_maybe_to_numpy_gpuframe(self):
|
|
"""_maybe_to_numpy should convert GPUFrame to numpy."""
|
|
from streaming.stream_sexp_generic import StreamInterpreter
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.sexp', delete=False) as f:
|
|
f.write('(stream "test" :fps 30 :width 100 :height 100 (frame frame))')
|
|
f.flush()
|
|
interp = StreamInterpreter(f.name)
|
|
|
|
frame = MockGPUFrame(create_test_frame(on_gpu=False))
|
|
result = interp._maybe_to_numpy(frame)
|
|
assert isinstance(result, np.ndarray)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v'])
|