Files
RustNotMC/tools/test_forced_end_parity.py
T
SashegDev 874b6f20c2 Клиент: начальный импорт кодовой базы
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
2026-10-29 22:04:28 +03:00

92 lines
4.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""The End of a dedicated server nobody has entered, force-loaded from the console, against vanilla's.
A vanilla dedicated server and Horizon's each open a copy of one world vanilla generated (its End not made yet).
With no player online, the console force-loads the End's main island and its arena. A FORCED ticket keeps its level active
(`ServerChunkCache.hasActiveTickets`, `TicketType.FORCED`), so the chunks are generated, loaded and ticked
without a player in the dimension. With nobody in the fight, `EnderDragonFight.tick` neither scans the state nor
places the exit portal: no bedrock and no torches around 0, 0. Both servers must answer the same.
"""
import os
import re
import subprocess
import sys
import time
try:
from . import lab, test_rust_parity as base
from .test_dedicated_server_parity import Server, wait_ticks
from .test_locator_bar_parity import generate_world, server_dir
except ImportError:
import lab
import test_rust_parity as base
from test_dedicated_server_parity import Server, wait_ticks
from test_locator_bar_parity import generate_world, server_dir
NAME = 'forced-end-parity'
END = 'execute in minecraft:the_end'
QUERIES = [
f'{END} run fill -3 0 -3 3 255 3 minecraft:air replace minecraft:bedrock',
f'{END} run fill -3 0 -3 3 255 3 minecraft:air replace minecraft:wall_torch',
]
def answer(server, command, n):
start = len(server.lines)
server.console(command)
server.console(f'say done {n}')
server.wait_for(rf'\[Server\] done {n}$', timeout=120)
replies = [l.split(']: ', 1)[-1] for l in server.lines[start:] if 'done ' not in l]
return ' / '.join(r for r in replies if r) or '(no reply)'
def run_side(label, command, build, world):
directory = build / label
server_dir(directory, world)
server = Server(command, directory, label)
try:
server.wait_for(r'Done \(')
# The main island's arena (EnderDragonFight.isArenaLoaded: chunks -8..8), in pieces of at most 256 chunks.
for area in ('-128 -128 143 -1', '-128 0 143 143'):
server.console(f'{END} run forceload add {area}')
deadline = time.time() + 600
for poll in range(1_000_000):
reply = answer(server, f'{END} if loaded 0 0 0', f'loaded {poll}')
if 'passed' in reply:
break
if time.time() > deadline:
raise AssertionError(f'{label}: the End centre never loaded')
time.sleep(2)
wait_ticks(server, 200)
return {q: answer(server, q, i) for i, q in enumerate(QUERIES)}
finally:
server.stop()
def main():
_, jars, libraries = base.verify_artifacts()
build = lab.LOCAL / NAME
build.mkdir(parents=True, exist_ok=True)
dependencies = os.pathsep.join(map(str, [jars['server'], *libraries]))
subprocess.run(['cargo', 'build', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
check=True, capture_output=True)
binary = lab.cargo_target() / 'release' / ('horizon-client.exe' if os.name == 'nt' else 'horizon-client')
vanilla_server = ['java', '-Xmx2g', '-Duser.timezone=UTC', '-cp', dependencies, 'net.minecraft.server.Main', 'nogui']
horizon_server = [binary, '--resources', lab.LOCAL / 'horizon-client/26.3', '--dedicated', '--nogui']
world = generate_world(vanilla_server, build)
vanilla = run_side('vanilla', vanilla_server, build, world)
horizon = run_side('horizon', horizon_server, build, world)
normal = lambda s: re.sub(r'\s+', ' ', s).strip()
problems = [f'{q}\n vanilla: {vanilla[q]}\n horizon: {horizon[q]}'
for q in QUERIES if normal(vanilla[q]) != normal(horizon[q])]
if problems:
raise AssertionError('the forced End differs:\n' + '\n'.join(problems))
print(f'forced End: loaded and ticked with nobody in it, no exit portal, as vanilla ({vanilla[QUERIES[0]]})')
if __name__ == '__main__':
try:
main()
except AssertionError as error:
sys.exit(f'{NAME}: {error}')