- compose-test-sync: fix 3 failing tests (NOTE_TO_MIDI, DrumLoopAnalyzer mock, section name) - generate-song: CLI wrapper + RPP validator (6 structural checks) + 4 e2e tests - reascript-hybrid: ReaScriptGenerator + command protocol + CLI + 16 unit tests - 110/110 tests passing - Full SDD cycle (propose→spec→design→tasks→apply→verify) for all 3 changes
97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""Tests for scripts/generate.py — E2E song generation and RPP validator."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).parents[1]))
|
|
|
|
|
|
class TestGenerateCLI:
|
|
"""Smoke and integration tests for the generate.py CLI."""
|
|
|
|
def test_generate_cli_smoke(self, tmp_path):
|
|
"""CLI produces a non-empty .rpp file at the expected path."""
|
|
output = tmp_path / "song.rpp"
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"scripts/generate.py",
|
|
"--bpm", "95",
|
|
"--key", "Am",
|
|
"--output", str(output),
|
|
"--seed", "42",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, f"stderr: {result.stderr}"
|
|
assert output.exists(), f"File not created: {output}"
|
|
assert output.stat().st_size > 0, "File is empty"
|
|
|
|
def test_validate_passes_for_valid_output(self, tmp_path):
|
|
"""With --validate, CLI returns 0 when validator sees no errors."""
|
|
output = tmp_path / "song.rpp"
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"scripts/generate.py",
|
|
"--bpm", "95",
|
|
"--key", "Am",
|
|
"--output", str(output),
|
|
"--seed", "42",
|
|
"--validate",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, f"stderr: {result.stderr}\nstdout: {result.stdout}"
|
|
|
|
def test_validate_detects_track_count_violation(self, tmp_path):
|
|
"""Validator flags a project with fewer than 9 tracks."""
|
|
from src.validator.rpp_validator import validate_rpp_output
|
|
|
|
rpp_path = tmp_path / "bad.rpp"
|
|
# Write a minimal .rpp with only 5 <TRACK> blocks
|
|
content = (
|
|
"<REAPER_PROJECT 0.1 \"7.65/win64\" 0 0\n"
|
|
+ " <TRACK\n NAME \"t1\"\n>\n"
|
|
+ " <TRACK\n NAME \"t2\"\n>\n"
|
|
+ " <TRACK\n NAME \"t3\"\n>\n"
|
|
+ " <TRACK\n NAME \"t4\"\n>\n"
|
|
+ " <TRACK\n NAME \"t5\"\n>\n"
|
|
+ ">"
|
|
)
|
|
rpp_path.write_text(content, encoding="utf-8")
|
|
errors = validate_rpp_output(str(rpp_path))
|
|
assert any("Expected 9" in e for e in errors), f"Got: {errors}"
|
|
|
|
def test_reproducibility_same_seed(self, tmp_path):
|
|
"""Two runs with the same seed produce byte-identical output."""
|
|
output_a = tmp_path / "song_a.rpp"
|
|
output_b = tmp_path / "song_b.rpp"
|
|
for out_path in (output_a, output_b):
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"scripts/generate.py",
|
|
"--bpm", "95",
|
|
"--key", "Am",
|
|
"--output", str(out_path),
|
|
"--seed", "42",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, f"stderr: {result.stderr}"
|
|
|
|
assert output_a.read_bytes() == output_b.read_bytes(), (
|
|
"Outputs differ — seed does not guarantee reproducibility"
|
|
) |