refactor: migrate from FL Studio to REAPER with rpp library
Replace FL Studio binary .flp output with REAPER text-based .rpp output using the rpp Python library (Perlence/rpp). - Add core/schema.py: DAW-agnostic data types (SongDefinition, TrackDef, ClipDef, MidiNote, PluginDef) - Add reaper_builder/: RPP file generation via rpp.Element + headless render via reaper.exe CLI - Add composer/converters.py: bridge rhythm.py/melodic.py note dicts to core.schema MidiNote objects - Rewrite scripts/compose.py: real generator pipeline with --render flag - Delete src/flp_builder/, src/scanner/, mcp/, flstudio-mcp/, old scripts - Add 40 passing tests (schema, builder, converters, compose, render)
This commit is contained in:
105
tests/test_render.py
Normal file
105
tests/test_render.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Tests for src/reaper_builder/render.py — render_project."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parents[1]))
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.reaper_builder.render import render_project
|
||||
|
||||
|
||||
class TestRenderProjectFileNotFound:
|
||||
"""Test render_project raises FileNotFoundError when reaper.exe path doesn't exist."""
|
||||
|
||||
def test_render_project_raises_file_not_found_for_nonexistent_reaper_exe(self):
|
||||
"""FileNotFoundError is raised when reaper.exe does not exist."""
|
||||
nonexistent = Path("C:/Program Files/NONEXISTENT_REAPER/reaper.exe")
|
||||
if nonexistent.exists():
|
||||
pytest.skip("Nonexistent path actually exists — cannot test this")
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
render_project(
|
||||
rpp_path="input.rpp",
|
||||
output_wav="output.wav",
|
||||
reaper_exe=nonexistent,
|
||||
)
|
||||
|
||||
def test_render_project_raises_file_not_found_default_path(self):
|
||||
"""FileNotFoundError raised when default reaper.exe doesn't exist and none provided."""
|
||||
# Patch DEFAULT_REAPER_EXE to a definitely-nonexistent path
|
||||
with patch("src.reaper_builder.render.DEFAULT_REAPER_EXE", Path("/no/such/reaper.exe")):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
render_project(
|
||||
rpp_path="input.rpp",
|
||||
output_wav="output.wav",
|
||||
reaper_exe=None,
|
||||
)
|
||||
|
||||
|
||||
class TestRenderProjectSubprocessError:
|
||||
"""Test render_project raises RuntimeError when subprocess returns non-zero exit code."""
|
||||
|
||||
def test_render_project_raises_runtime_error_on_nonzero_exit(self):
|
||||
"""RuntimeError is raised when subprocess.run returns non-zero returncode."""
|
||||
from src.reaper_builder.render import DEFAULT_REAPER_EXE
|
||||
|
||||
# Check if reaper exists — if not, skip
|
||||
if not DEFAULT_REAPER_EXE.exists():
|
||||
pytest.skip(f"REAPER not installed at {DEFAULT_REAPER_EXE}")
|
||||
|
||||
# Create a minimal valid .rpp for this test
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".rpp", delete=False, encoding="utf-8") as f:
|
||||
rpp_path = f.name
|
||||
f.write('<REAPER_PROJECT 0.1 "6.0" 0\n>\n')
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
||||
wav_path = f.name
|
||||
|
||||
try:
|
||||
# Mock subprocess.run to return non-zero
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 1
|
||||
mock_result.stdout = ""
|
||||
mock_result.stderr = "Test error"
|
||||
|
||||
with patch("subprocess.run", return_value=mock_result):
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
render_project(rpp_path=rpp_path, output_wav=wav_path)
|
||||
assert "1" in str(exc_info.value) or "failed" in str(exc_info.value).lower()
|
||||
finally:
|
||||
Path(rpp_path).unlink(missing_ok=True)
|
||||
Path(wav_path).unlink(missing_ok=True)
|
||||
|
||||
def test_render_project_raises_runtime_error_with_error_message(self):
|
||||
"""RuntimeError output includes stdout/stderr from REAPER failure."""
|
||||
from src.reaper_builder.render import DEFAULT_REAPER_EXE
|
||||
|
||||
if not DEFAULT_REAPER_EXE.exists():
|
||||
pytest.skip(f"REAPER not installed at {DEFAULT_REAPER_EXE}")
|
||||
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".rpp", delete=False, encoding="utf-8") as f:
|
||||
rpp_path = f.name
|
||||
f.write('<REAPER_PROJECT 0.1 "6.0" 0\n>\n')
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
||||
wav_path = f.name
|
||||
|
||||
try:
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 2
|
||||
mock_result.stdout = "standard output text"
|
||||
mock_result.stderr = "error output text"
|
||||
|
||||
with patch("subprocess.run", return_value=mock_result):
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
render_project(rpp_path=rpp_path, output_wav=wav_path)
|
||||
# Error message should include the exit code or stderr
|
||||
err_str = str(exc_info.value)
|
||||
assert "2" in err_str or "error output text" in err_str
|
||||
finally:
|
||||
Path(rpp_path).unlink(missing_ok=True)
|
||||
Path(wav_path).unlink(missing_ok=True)
|
||||
Reference in New Issue
Block a user