874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
101 lines
4.8 KiB
Python
101 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the player's recipe book with vanilla's ServerRecipeBook.
|
|
|
|
Each case runs a sequence of operations on a fresh book: awarding recipes
|
|
(advancement rewards, /recipe give, crafting; special recipes are never
|
|
learnt, and the count is the displays added), taking them back, the client
|
|
having seen a highlighted recipe, the open and filter settings of the four
|
|
book types (through the serverbound settings packet's codec), and saving and
|
|
loading the player's recipeBook compound (with unknown ids dropped, and a
|
|
compound missing a list ignored). The Java oracle
|
|
(VanillaRecipeBookOracle) drives ServerRecipeBook and its codec;
|
|
examples/probe/recipe_book_fixture drives horizon_client::recipe_book. After each
|
|
operation the count, the known and highlighted recipes, the settings and
|
|
the saved compound are compared.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import random
|
|
try:
|
|
from . import interaction_lab as lib
|
|
except ImportError:
|
|
import interaction_lab as lib
|
|
|
|
TYPES = ['CRAFTING', 'FURNACE', 'BLAST_FURNACE', 'SMOKER']
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--seed', type=int, default=5)
|
|
parser.add_argument('--cases', type=int, default=1500)
|
|
parser.add_argument('--limit', type=int, default=3)
|
|
args = parser.parse_args()
|
|
work = lib.work_dir('recipe-book-parity')
|
|
vanilla = lib.Vanilla(work)
|
|
vanilla.compile('ParityData', 'VanillaMenuOracle', 'VanillaRecipeBookOracle')
|
|
ids = sorted('minecraft:' + p.stem for p in (lib.RESOURCES / 'data/minecraft/recipe').glob('*.json'))
|
|
rng = random.Random(args.seed)
|
|
cases = []
|
|
# Every book and flag combination crosses the wire, then survives a save/load.
|
|
for book_type in TYPES:
|
|
for opened in (False, True):
|
|
for filtering in (False, True):
|
|
cases.append({'ops': [{'op': 'set', 'type': book_type, 'open': opened,
|
|
'filtering': filtering}, {'op': 'save'}, {'op': 'load'}]})
|
|
for _ in range(args.cases):
|
|
ops, known = [], set()
|
|
for _ in range(rng.randrange(1, 12)):
|
|
kind = rng.choices(['give', 'take', 'seen', 'set', 'save', 'load'], [10, 4, 4, 3, 3, 2])[0]
|
|
if kind in ('give', 'take'):
|
|
pool = list(known) if kind == 'take' and known and rng.random() < 0.7 else ids
|
|
chosen = ids if rng.random() < 0.03 else rng.sample(pool, min(len(pool), rng.choice([1, 1, 2, 5, 20])))
|
|
if rng.random() < 0.2:
|
|
chosen = chosen + chosen[:1]
|
|
ops.append({'op': kind, 'ids': chosen})
|
|
if kind == 'give':
|
|
known.update(chosen)
|
|
elif kind == 'seen':
|
|
ops.append({'op': kind, 'id': rng.choice(list(known) or ids)})
|
|
elif kind == 'set':
|
|
ops.append({'op': kind, 'type': rng.choice(TYPES), 'open': rng.random() < 0.5, 'filtering': rng.random() < 0.5})
|
|
elif kind == 'save':
|
|
ops.append({'op': kind})
|
|
else:
|
|
op = {'op': kind}
|
|
if rng.random() < 0.2:
|
|
op['drop'] = rng.choice(['recipes', 'toBeDisplayed'])
|
|
ops.append(op)
|
|
cases.append({'ops': ops})
|
|
(work / 'cases.jsonl').write_text(''.join(json.dumps(c) + '\n' for c in cases))
|
|
vanilla.run('VanillaRecipeBookOracle', work / 'cases.jsonl', work / 'native.txt')
|
|
lib.cargo_example('horizon-client', 'recipe_book_fixture', lib.RESOURCES, work / 'cases.jsonl', work / 'rust.txt')
|
|
|
|
def by_case(path):
|
|
out, cur = [], []
|
|
for line in path.read_text().splitlines():
|
|
cur.append(line)
|
|
if line == 'end' or line.startswith('error-case'):
|
|
out.append(cur)
|
|
cur = []
|
|
return out
|
|
native, rust = by_case(work / 'native.txt'), by_case(work / 'rust.txt')
|
|
bad = {}
|
|
for case, a, b in zip(cases, native, rust):
|
|
if a != b:
|
|
step = next((i for i, (x, y) in enumerate(zip(a, b)) if x != y), min(len(a), len(b)))
|
|
op = case['ops'][step]['op'] if step < len(case['ops']) else 'end'
|
|
bad.setdefault(op, []).append((case, step, a, b))
|
|
total = sum(len(v) for v in bad.values())
|
|
for key, entries in sorted(bad.items(), key=lambda e: -len(e[1])):
|
|
print(f'{key}: {len(entries)} differing')
|
|
for case, step, a, b in entries[:args.limit]:
|
|
print(f' ops {json.dumps(case["ops"][:step + 1])[:600]}')
|
|
print(f' native {a[step] if step < len(a) else "-"}\n rust {b[step] if step < len(b) else "-"}')
|
|
errors = sum(1 for c in native if c and c[-1].startswith('error-case'))
|
|
print(f'{len(cases) - total}/{len(cases)} cases match ({errors} oracle errors)')
|
|
raise SystemExit(1 if total or errors or len(native) != len(cases) else 0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|