d68efbabfc
The oracle runs vanilla SoundManager/SoundEngine on OpenAL Soft null device (inline executor, silent buffers) and replays scripts of plays, stops, category gains, option volumes, channel ends, pauses, queued, delayed and looping sounds and tickable ambient instances, printing every AL source parameter, isActive, subtitle notification and pool count per step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
4.5 KiB
Python
92 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Shared runner for the audio differential harnesses.
|
|
|
|
Each harness writes an input file, runs a Java oracle against the pinned
|
|
client (the real SoundManager / SoundEngine / MusicManager / ambient handlers;
|
|
OpenAL runs on OpenAL Soft's "No Output" device so channel parameters can be
|
|
read back from real AL sources) and a Rust example replaying the same inputs
|
|
through Horizon's audio code, then requires byte-identical output.
|
|
"""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import subprocess
|
|
|
|
try:
|
|
from . import lab, test_rust_parity as base, test_model_baking_parity as baking
|
|
except ImportError:
|
|
import lab, test_rust_parity as base, test_model_baking_parity as baking
|
|
|
|
RESOURCES = lab.LOCAL / 'horizon-client/26.3'
|
|
COMMON_SOURCES = [
|
|
lab.ROOT / 'tools/audio_harness.py',
|
|
lab.ROOT / 'tools/test_rust_parity.py',
|
|
lab.ROOT / 'tools/lab.py',
|
|
lab.ROOT / 'Cargo.toml',
|
|
lab.ROOT / 'Cargo.lock',
|
|
lab.ROOT / 'crates/horizon-client/Cargo.toml',
|
|
lab.ROOT / 'crates/horizon-client/src/sound_events.rs',
|
|
lab.ROOT / 'crates/horizon-client/src/sound_engine.rs',
|
|
]
|
|
|
|
|
|
def require_sounds():
|
|
if not (RESOURCES / 'assets/minecraft/sounds.json').is_file():
|
|
raise SystemExit('Sound assets missing: run tools/fetch_sounds.py first')
|
|
|
|
|
|
def run_harness(name, oracle, example, inputs_text, extra_sources=(), java_args=(), rust_args=(), scope='', timeout=1800):
|
|
"""Run oracle and example on inputs; returns the number of matched lines."""
|
|
require_sounds()
|
|
lock, jars, _ = base.verify_artifacts()
|
|
libraries = baking.client_libraries()
|
|
build = lab.LOCAL / f'rust-{name}'
|
|
build.mkdir(parents=True, exist_ok=True)
|
|
report = build / 'report.json'
|
|
report.unlink(missing_ok=True)
|
|
oracle_path = lab.ROOT / f'mods/horizon/src/test/java/dev/horizonlab/parity/{oracle}.java'
|
|
example_path = lab.ROOT / f'crates/horizon-client/examples/{example}.rs'
|
|
sources = [*COMMON_SOURCES, oracle_path, example_path, *[Path(s) for s in extra_sources]]
|
|
tested = base.source_hashes(sources)
|
|
inputs = build / 'inputs.txt'
|
|
inputs.write_text(inputs_text)
|
|
input_hash = lab.digest(inputs, 'sha256')
|
|
|
|
def run(command, log, env=None):
|
|
with (build / (log + ".log")).open("w") as out:
|
|
try:
|
|
subprocess.run(list(map(str, command)), cwd=lab.ROOT, check=True, timeout=timeout, stdout=out, stderr=subprocess.STDOUT, env=env)
|
|
except subprocess.CalledProcessError:
|
|
raise SystemExit(f"{log} failed: see {build / (log + '.log')}") from None
|
|
|
|
classes = build / 'classes'
|
|
classes.mkdir(exist_ok=True)
|
|
dependencies = os.pathsep.join(map(str, [jars['client'], *libraries]))
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-sourcepath', lab.ROOT / 'mods/horizon/src/test/java',
|
|
'-cp', dependencies, '-d', classes, oracle_path], 'compile-java')
|
|
run(['cargo', 'build', '--locked', '-p', 'horizon-client', '--example', example], 'compile-rust')
|
|
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--no-deps', '--format-version', '1'], cwd=lab.ROOT))
|
|
binary = Path(metadata['target_directory']) / 'debug/examples' / (example + ('.exe' if os.name == 'nt' else ''))
|
|
run([binary, *rust_args, inputs, build / 'rust.txt'], 'rust')
|
|
env = dict(os.environ, ALSOFT_DRIVERS='null')
|
|
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies, f'dev.horizonlab.parity.{oracle}', *java_args, inputs, build / 'client.txt'],
|
|
'client', env=env)
|
|
matched = base.compare(build / 'client.txt', build / 'rust.txt')
|
|
base.ensure_sources_unchanged(sources, tested)
|
|
if input_hash != lab.digest(inputs, 'sha256'):
|
|
raise AssertionError('Input fixtures changed')
|
|
lab.write_json(report, dict(
|
|
schema=1, minecraft=lock['minecraft']['version'], manifest_sha1=lock['minecraft']['manifest_sha1'], platform=platform.platform(),
|
|
rustc=subprocess.check_output(['rustc', '--version'], text=True).strip(),
|
|
java=subprocess.check_output(['java', '--version'], text=True).splitlines()[0],
|
|
sources_sha256=tested, input_sha256=input_hash, client_jar_sha256=lab.digest(jars['client'], 'sha256'),
|
|
sounds_json_sha256=lab.digest(RESOURCES / 'assets/minecraft/sounds.json', 'sha256'),
|
|
matched_output_lines=matched, output_sha256=lab.digest(build / 'rust.txt', 'sha256'), scope=scope))
|
|
return matched, report
|
|
|
|
|
|
def bits(value):
|
|
import struct
|
|
return struct.pack('>f', value).hex()
|