Files
horizon-airdef/tools/interaction_lab.py
T
Ludovic Marie c9ad9869d9 Start the menu click oracle and tweak the interaction lab
Work in progress on the vanilla menu oracle adapter used by the upcoming menu click parity harness.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 03:49:56 +02:00

75 lines
3.1 KiB
Python

"""Shared plumbing for the interaction/inventory differential harnesses
(test_recipe_parity, test_menu_click_parity, test_block_interaction_parity):
compile and run a vanilla Java oracle headlessly, run the matching Rust
example, and report differing lines grouped by a key."""
import json
import os
from pathlib import Path
import subprocess
try:
from . import lab, test_rust_parity as base
except ImportError:
import lab
import test_rust_parity as base
RESOURCES = lab.LOCAL / 'horizon-client' / '26.3'
PARITY = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity'
def work_dir(name):
path = lab.LOCAL / name
path.mkdir(parents=True, exist_ok=True)
return path
class Vanilla:
"""Compiles oracles against the pinned server jar and runs them."""
def __init__(self, work):
self.lock, self.jars, self.libraries = base.verify_artifacts()
self.work = work
self.classes = work / 'classes'
self.classes.mkdir(exist_ok=True)
self.deps = os.pathsep.join(map(str, [self.jars['server'], *self.libraries]))
def compile(self, *oracles):
sources = [PARITY / (name + '.java') for name in oracles]
done = subprocess.run(['javac', '--release', str(self.lock['minecraft']['java_major']), '-proc:none',
'-sourcepath', str(lab.ROOT / 'mods/horizon/src/test/java'), '-nowarn', '-Xmaxerrs', '30',
'-cp', self.deps, '-d', str(self.classes), *map(str, sources)], capture_output=True, text=True)
if done.returncode:
print(done.stderr.replace(str(lab.ROOT) + '/', ''))
raise SystemExit('javac failed')
def run(self, oracle, *args, heap='2G'):
subprocess.run(['java', f'-Xmx{heap}', '-cp', str(self.classes) + os.pathsep + self.deps,
'dev.horizonlab.parity.' + oracle, *map(str, args)], check=True)
def cargo_example(package, example, *args, profile=None):
command = ['cargo', 'run', '-q', '-j3']
if profile:
command += ['--profile', profile]
command += ['-p', package, '--example', example, '--', *map(str, args)]
subprocess.run(command, check=True, cwd=lab.ROOT)
def compare(cases, native_path, rust_path, key, describe=None, limit=3):
"""Print differing cases grouped by key(case, native_line); returns the count."""
native = Path(native_path).read_text().split('\n')
rust = Path(rust_path).read_text().split('\n')
if len(native) != len(rust):
print(f'line counts differ: native {len(native)} rust {len(rust)}')
groups = {}
for case, a, b in zip(cases, native, rust):
if a != b:
groups.setdefault(key(case, a), []).append((case, a, b))
describe = describe or (lambda case: case if isinstance(case, str) else json.dumps(case))
total = sum(len(v) for v in groups.values())
for name, entries in sorted(groups.items(), key=lambda e: -len(e[1])):
print(f'{name}: {len(entries)} differing')
for case, a, b in entries[:limit]:
print(f' case {describe(case)}\n native {a}\n rust {b}')
print(f'{len(cases) - total}/{len(cases)} cases match')
return total