mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Keep specialized parity harnesses compatible with normal CI invocation
This commit is contained in:
1 parent
b0a8cbf6fb
commit
ccb85ce454
5 files changed
+199
-10
No files matched your search
@@ -0,0 +1,178 @@
|
||||
"""CLI admission/routing contracts; every Java, Cargo and native launch is stubbed."""
|
||||
from contextlib import ExitStack, redirect_stdout
|
||||
import copy
|
||||
import importlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
TOOLS = Path(__file__).resolve().parents[1] / 'tools'
|
||||
|
||||
|
||||
class RunnerAdmissionTests(unittest.TestCase):
|
||||
def run_fixture(self, *, reject=False, reference_only=False, binary=False):
|
||||
with tempfile.TemporaryDirectory() as directory, ExitStack() as stack:
|
||||
folder = Path(directory)
|
||||
stack.enter_context(mock.patch.object(sys, 'path', [str(TOOLS), *sys.path]))
|
||||
runner = importlib.import_module('test_command_exec_parity')
|
||||
events = []
|
||||
row = {'cmd': '/say cli', 'messages': [], 'results': [[True, 1]]}
|
||||
argv = ['parity.py', '--work-dir', str(folder)]
|
||||
if reference_only:
|
||||
argv.append('--reference-only')
|
||||
if binary:
|
||||
executable = folder / 'archived-game'
|
||||
executable.touch()
|
||||
argv += ['--binary', str(executable)]
|
||||
stack.enter_context(mock.patch.object(sys, 'argv', argv))
|
||||
stack.enter_context(mock.patch.object(runner.base, 'verify_artifacts', return_value=(
|
||||
{'minecraft': {'java_major': '25', 'version': '26.3'}},
|
||||
{'server': folder / 'server.jar'}, [])))
|
||||
stack.enter_context(mock.patch.object(runner.corpus_module, 'groups', return_value=[
|
||||
('cli admission', [{'op': 'cmd', 'cmd': '/say cli', 'expect_result': 1}])]))
|
||||
stack.enter_context(mock.patch.object(runner, 'install_datapack_fixtures'))
|
||||
stack.enter_context(mock.patch.object(runner.platform, 'platform', return_value='unit'))
|
||||
|
||||
def subprocess_run(command, **kwargs):
|
||||
if command[0] == 'java':
|
||||
events.append('java')
|
||||
(folder / 'vanilla.jsonl').write_text(json.dumps(row) + '\n')
|
||||
elif command[0] == 'cargo':
|
||||
events.append('native-build')
|
||||
else:
|
||||
self.assertEqual(command[0], 'javac')
|
||||
return SimpleNamespace(returncode=0)
|
||||
|
||||
def native_run(command, **kwargs):
|
||||
events.append('native-run')
|
||||
Path(kwargs['env']['HORIZON_COMMAND_OUTPUT']).write_text(json.dumps(row) + '\n')
|
||||
return SimpleNamespace(returncode=0)
|
||||
|
||||
def admit(rows):
|
||||
events.append('admit')
|
||||
self.assertEqual(rows, [row])
|
||||
if reject:
|
||||
raise ValueError('reference rejected')
|
||||
|
||||
stack.enter_context(mock.patch.object(runner.subprocess, 'run', side_effect=subprocess_run))
|
||||
stack.enter_context(mock.patch.object(runner.subprocess, 'check_output', return_value=json.dumps(
|
||||
{'target_directory': str(folder / 'target')})))
|
||||
stack.enter_context(mock.patch.object(runner, 'run_game', side_effect=native_run))
|
||||
with redirect_stdout(io.StringIO()):
|
||||
if reject:
|
||||
with self.assertRaisesRegex(ValueError, 'reference rejected'):
|
||||
runner.main(reference_admission=admit)
|
||||
else:
|
||||
runner.main(reference_admission=admit)
|
||||
return events
|
||||
|
||||
def test_normal_run_admits_before_native_build_and_execution(self):
|
||||
self.assertEqual(self.run_fixture(), ['java', 'admit', 'native-build', 'native-run'])
|
||||
|
||||
def test_rejected_reference_never_builds_or_runs_native(self):
|
||||
self.assertEqual(self.run_fixture(reject=True), ['java', 'admit'])
|
||||
|
||||
def test_reference_only_still_admits_without_native(self):
|
||||
self.assertEqual(self.run_fixture(reference_only=True), ['java', 'admit'])
|
||||
|
||||
def test_archived_binary_keeps_admission_and_skips_build(self):
|
||||
self.assertEqual(self.run_fixture(binary=True), ['java', 'admit', 'native-run'])
|
||||
|
||||
|
||||
class SpecializedHarnessRoutingTests(unittest.TestCase):
|
||||
def run_fixture(self, name, mode, invalid=False):
|
||||
with tempfile.TemporaryDirectory() as directory, ExitStack() as stack:
|
||||
folder = Path(directory)
|
||||
stack.enter_context(mock.patch.object(sys, 'path', [str(TOOLS), *sys.path]))
|
||||
module = importlib.import_module('test_' + name + '_parity')
|
||||
runner = module.runner
|
||||
# Pot admission independently requires two unchanged before/after sources.
|
||||
program = [dict(op='cmd', cmd='/source ' + source, expect_result=1,
|
||||
source_snapshot=source, snapshot_phase=phase)
|
||||
for source in ['a', 'b'] for phase in ['before', 'after']]
|
||||
rows = [dict(cmd=op['cmd'], messages=[], results=[[True, 1]]) for op in program]
|
||||
if invalid:
|
||||
rows[0]['results'] = [[True, True]] # bool is not an integer callback.
|
||||
original = copy.deepcopy(rows)
|
||||
argv = ['harness.py', '--work-dir', str(folder)]
|
||||
if mode == 'reference-only':
|
||||
argv.append('--reference-only')
|
||||
if mode == 'reuse':
|
||||
argv += ['--reference-dir', str(folder), '--binary', 'archived-game']
|
||||
stack.enter_context(mock.patch.object(sys, 'argv', argv))
|
||||
stack.enter_context(mock.patch.object(module, 'cases', return_value=program))
|
||||
stack.enter_context(mock.patch.object(runner.corpus_module, 'group_setup', return_value=[]))
|
||||
stack.enter_context(mock.patch.object(runner.corpus_module, 'groups'))
|
||||
events = []
|
||||
|
||||
def outputs():
|
||||
text = ''.join(json.dumps(row) + '\n' for row in rows)
|
||||
(folder / 'vanilla.jsonl').write_text(text)
|
||||
if mode != 'reference-only':
|
||||
(folder / 'rust.jsonl').write_text(text)
|
||||
|
||||
def fresh(*, reference_admission):
|
||||
events.append('fresh')
|
||||
reference_admission(rows)
|
||||
events.append('admitted')
|
||||
outputs()
|
||||
|
||||
def reuse(reference, output, actual_program):
|
||||
self.assertEqual(reference, folder.resolve())
|
||||
self.assertEqual(output, folder.resolve())
|
||||
self.assertEqual(actual_program, program)
|
||||
events.append('reuse')
|
||||
outputs()
|
||||
|
||||
stack.enter_context(mock.patch.object(runner, 'main', side_effect=fresh))
|
||||
stack.enter_context(mock.patch.object(module, 'native_from_reference', side_effect=reuse))
|
||||
with redirect_stdout(io.StringIO()):
|
||||
if invalid:
|
||||
with self.assertRaises(AssertionError):
|
||||
module.main()
|
||||
else:
|
||||
self.assertFalse(module.main())
|
||||
self.assertEqual(rows, original, 'admission must not normalize or alter raw reference rows')
|
||||
return events
|
||||
|
||||
def test_both_harnesses_support_normal_reference_only_and_frozen_routes(self):
|
||||
for name in ['loaded_pig_tags', 'loot_mine_pot_components']:
|
||||
for mode in ['normal', 'reference-only', 'reuse']:
|
||||
with self.subTest(harness=name, mode=mode):
|
||||
self.assertEqual(self.run_fixture(name, mode),
|
||||
['reuse'] if mode == 'reuse' else ['fresh', 'admitted'])
|
||||
|
||||
def test_both_normal_routes_reject_typed_admission_before_native(self):
|
||||
for name in ['loaded_pig_tags', 'loot_mine_pot_components']:
|
||||
with self.subTest(harness=name):
|
||||
self.assertEqual(self.run_fixture(name, 'normal', invalid=True), ['fresh'])
|
||||
|
||||
|
||||
class MobDiscardArgumentsTests(unittest.TestCase):
|
||||
def parse(self, argv, environment):
|
||||
with mock.patch.object(sys, 'path', [str(TOOLS), *sys.path]), mock.patch.dict(os.environ, environment, clear=True):
|
||||
module = importlib.import_module('test_mob_discard_parity')
|
||||
return module, module.parse_args(argv)
|
||||
|
||||
def test_normal_cli_uses_prepared_resources_without_environment(self):
|
||||
module, args = self.parse([], {})
|
||||
self.assertEqual(args.resources, module.lab.RESOURCES)
|
||||
|
||||
def test_environment_selects_explicit_resource_directory(self):
|
||||
_, args = self.parse([], {'HORIZON_TEST_RESOURCES': '/fixture/environment'})
|
||||
self.assertEqual(args.resources, Path('/fixture/environment'))
|
||||
|
||||
def test_empty_environment_uses_prepared_resources(self):
|
||||
module, args = self.parse([], {'HORIZON_TEST_RESOURCES': ''})
|
||||
self.assertEqual(args.resources, module.lab.RESOURCES)
|
||||
|
||||
def test_cli_resource_directory_overrides_environment(self):
|
||||
_, args = self.parse(['--resources', '/fixture/argument'], {'HORIZON_TEST_RESOURCES': '/fixture/environment'})
|
||||
self.assertEqual(args.resources, Path('/fixture/argument'))
|
||||
@@ -80,7 +80,7 @@ def install_datapack_fixtures(world, predicates, functions, jar, advancements=No
|
||||
target.write_text(json.dumps(recipe), encoding='utf-8')
|
||||
|
||||
|
||||
def main():
|
||||
def main(*, reference_admission=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--prepare', action='store_true')
|
||||
parser.add_argument('--raid-village-fixture', action='store_true', help='match the existing development command trees in the isolated Java/native fixture processes')
|
||||
@@ -376,6 +376,11 @@ def main():
|
||||
any(len(r.get('bits', [])) != 3 or
|
||||
any(re.fullmatch(r'[0-9a-f]{1,16}', b) is None for b in r['bits']) for r in direct)):
|
||||
sys.exit('Java direct underwater reference prerequisite failed')
|
||||
# Specialized harnesses qualify the fresh reference before any native build
|
||||
# or execution. Their strict raw-record checks also apply to normal CI runs.
|
||||
if reference_admission is not None:
|
||||
reference_admission([json.loads(line) for line in
|
||||
(build / 'vanilla.jsonl').read_text(encoding='utf-8').splitlines()])
|
||||
if args.reference_only:
|
||||
reference = [json.loads(line) for line in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
recorded = [op for op in script if op['op'] in ('player_state', 'player_loop', 'map_state', 'frame_map_state', 'mob_anger', 'raid_state', 'menu_state') or
|
||||
@@ -447,8 +452,8 @@ def main():
|
||||
native = json.loads((build / 'rust-client-config.json').read_text())
|
||||
if native.get('render_setting') != 2 or native.get('simulation_setting') != 2 or not native.get('joined'):
|
||||
sys.exit(f'native settings/join prerequisite failed: {native}')
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').open(encoding='utf-8')]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').open(encoding='utf-8')]
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text(encoding='utf-8').splitlines()]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text(encoding='utf-8').splitlines()]
|
||||
if len(vanilla) != len(rust):
|
||||
sys.exit(f'record count differs: vanilla {len(vanilla)} rust {len(rust)}')
|
||||
recorded_ops = [op for op in script if op['op'] in ('falling', 'destroy_progress', 'weather_state', 'chunk_ready', 'server_distances', 'map_state', 'frame_map_state', 'light', 'player_state', 'player_loop', 'mob_anger', 'raid_state', 'menu_state') or
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Actual pig Tags load/save/mutation proof; complete raw list order is diagnostic.
|
||||
|
||||
The default mode validates Java before building or running native.
|
||||
Admit fresh Java with --reference-only --work-dir REF. Reuse exact REF bytes for
|
||||
both normal binaries with --reference-dir REF --binary GAME --work-dir NEW_OUT.
|
||||
No output masks, ordering normalization or synthetic tag observer are applied.
|
||||
@@ -89,8 +90,7 @@ def main():
|
||||
sys.argv.append("--allow-gaps")
|
||||
folder = Path(sys.argv[sys.argv.index("--work-dir") + 1]).resolve()
|
||||
if reference is None:
|
||||
assert "--reference-only" in sys.argv, "admit fresh Java, then reuse its frozen reference"
|
||||
runner.main()
|
||||
runner.main(reference_admission=lambda rows: admit(program, rows))
|
||||
else:
|
||||
assert "--reference-only" not in sys.argv
|
||||
native_from_reference(reference, folder, program)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Actual loot-mine pot component boundary; raw snapshots and clocks retained.
|
||||
|
||||
The default mode validates Java before building or running native.
|
||||
Admit fresh Java with --reference-only --work-dir REF; compare each archived
|
||||
normal Game against --reference-dir REF with a fresh --work-dir OUT.
|
||||
"""
|
||||
@@ -92,8 +93,7 @@ def main():
|
||||
sys.argv.append("--allow-gaps")
|
||||
folder = Path(sys.argv[sys.argv.index("--work-dir") + 1]).resolve()
|
||||
if reference is None:
|
||||
assert "--reference-only" in sys.argv, "admit Java before the native pair"
|
||||
runner.main()
|
||||
runner.main(reference_admission=lambda rows: admit(program, rows))
|
||||
else:
|
||||
assert "--reference-only" not in sys.argv
|
||||
native_from_reference(reference, folder, program)
|
||||
|
||||
@@ -3,18 +3,24 @@
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import zipfile
|
||||
import lab
|
||||
|
||||
|
||||
def main():
|
||||
def parse_args(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--game-dir', type=Path, default=lab.GAME)
|
||||
parser.add_argument('--resources', type=Path, required=True)
|
||||
parser.add_argument('--resources', type=Path,
|
||||
default=Path(os.environ.get('HORIZON_TEST_RESOURCES') or lab.RESOURCES))
|
||||
parser.add_argument('--java-home', type=Path)
|
||||
args = parser.parse_args()
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
build = lab.LOCAL / 'rust-mob-discard-parity'
|
||||
build.mkdir(parents=True, exist_ok=True)
|
||||
report = build / 'report.json'
|
||||
|
||||
Reference in new issue
Block a user