874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
99 lines
4.9 KiB
Python
99 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the resource pack screen's selection with the vanilla client's.
|
|
|
|
Each case is a pack repository (packs required or not, added at the top or
|
|
the bottom, fixed in place or not, compatible, too old or too new), an
|
|
initial selection as options.txt stores it, and the actions the screen's
|
|
entries take on a click or key press: select (an incompatible pack only when
|
|
its confirmation is accepted), unselect, move up, move down. The Java oracle
|
|
(VanillaPackSelectionOracle) runs vanilla's PackRepository and
|
|
PackSelectionModel; examples/probe/pack_selection_fixture runs
|
|
horizon_client::packs::SelectionModel, which the game's pack screen uses.
|
|
After every action both lists, in order, with each entry's canSelect,
|
|
canUnselect, canMoveUp and canMoveDown, and the selection committed at the
|
|
end must agree.
|
|
|
|
test_pack_selection_parity.py [--count N] [--detail N]
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
|
|
try:
|
|
from . import lab, test_rust_parity as base, test_model_baking_parity as baking
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
import test_model_baking_parity as baking
|
|
|
|
PARITY = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity'
|
|
IDS = ['vanilla', 'programmer_art', 'file/Faithful.zip', 'file/alpha', 'file/Zeta pack', 'file/zeta', 'file/é',
|
|
'file/0', 'server', 'world', 'fabric', 'file/a.zip', 'file/A.zip', 'file/b']
|
|
|
|
|
|
def cases(count):
|
|
rng = random.Random(0x5041434B)
|
|
out = []
|
|
for _ in range(count):
|
|
ids = rng.sample(IDS, rng.randint(1, 9))
|
|
packs = [{'id': i, 'required': rng.random() < 0.2, 'position': rng.choice(['top', 'top', 'bottom']),
|
|
'fixed': rng.random() < 0.2, 'compat': rng.choice(['compatible'] * 4 + ['old', 'new'])} for i in ids]
|
|
selected = rng.sample(ids + ['file/missing'], rng.randint(0, len(ids)))
|
|
ops = [{'list': rng.choice(['selected', 'unselected']), 'index': rng.randint(-2, 8),
|
|
'op': rng.choice(['activate', 'activate', 'up', 'down']), 'confirm': rng.random() < 0.5}
|
|
for _ in range(rng.randint(0, 16))]
|
|
out.append({'packs': packs, 'selected': selected, 'ops': ops})
|
|
return out
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--count', type=int, default=4000)
|
|
parser.add_argument('--detail', type=int, default=3)
|
|
args = parser.parse_args()
|
|
lock, jars, _ = base.verify_artifacts()
|
|
libraries = baking.client_libraries()
|
|
build = lab.LOCAL / 'rust-pack-selection-parity'; build.mkdir(parents=True, exist_ok=True)
|
|
chosen = cases(args.count)
|
|
inputs = build / 'inputs.jsonl'; inputs.write_text(''.join(json.dumps(c) + '\n' for c in chosen))
|
|
classes = build / 'classes'; classes.mkdir(exist_ok=True)
|
|
dependencies = os.pathsep.join(map(str, [jars['client'], *libraries]))
|
|
|
|
def run(command, name):
|
|
with (build / (name + '.log')).open('w') as log:
|
|
result = subprocess.run(list(map(str, command)), cwd=lab.ROOT, timeout=3600, stdout=log, stderr=subprocess.STDOUT)
|
|
if result.returncode:
|
|
print((build / (name + '.log')).read_text()[-4000:]); sys.exit(f'{name} failed')
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-nowarn', '-cp', dependencies, '-d', classes,
|
|
*(PARITY / f'{n}.java' for n in ('ParityData', 'VanillaPackSelectionOracle'))], 'compile-java')
|
|
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaPackSelectionOracle',
|
|
inputs, build / 'vanilla.jsonl'], 'vanilla')
|
|
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
|
'pack_selection_fixture', inputs, build / 'rust.jsonl'], 'rust')
|
|
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
|
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
|
assert len(vanilla) == len(rust) == len(chosen)
|
|
errors = [v['error'] for v in vanilla if 'error' in v]
|
|
bad = [(c, v, r) for c, v, r in zip(chosen, vanilla, rust) if 'error' not in v and v != r]
|
|
for e in sorted(set(errors))[:5]:
|
|
print('ORACLE ERROR', e)
|
|
for c, v, r in bad[:args.detail]:
|
|
print('MISMATCH', json.dumps(c))
|
|
step = next((i for i, (a, b) in enumerate(zip(v['steps'], r['steps'])) if a != b), None)
|
|
if step is None:
|
|
print(f' committed: vanilla {v["committed"]} | horizon {r["committed"]}')
|
|
else:
|
|
print(f' after action {step}: vanilla {v["steps"][step]}\n horizon {r["steps"][step]}')
|
|
steps = sum(len(v.get('steps', [])) for v in vanilla)
|
|
print(f'{len(chosen) - len(bad) - len(errors)}/{len(chosen)} pack selections match ({steps} states); {len(errors)} oracle errors')
|
|
if bad or errors:
|
|
sys.exit(1)
|
|
print('pack selection parity ok')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|