Tools and harnesses start the game in scratch folders, not a player's
The game logs into HORIZON_HOME and falls back to the player's own game folder when it is unset. Harnesses that set only HORIZON_NO_AUDIO (and test_native_client, which set nothing) wrote logs, and on a world run advancements, into ~/Library/Application Support/horizon. lab.game_env gives every game a fresh HORIZON_HOME, HORIZON_LAUNCHER_HOME, XDG_DATA_HOME and XDG_CONFIG_HOME, no audio and no launcher self-update; a folder the caller passes is kept. Every launcher of the game uses it, parity.py gives each harness its own HORIZON_HOME, and tests/test_game_home.py fails for a script in tools/ that starts the game without it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,10 @@ Passes are shared between worktrees. A harness's key depends only on the content
|
||||
|
||||
Every runner on the machine shares `PARITY_MACHINE_JOBS` slots (default: half the CPU threads), so several agents' runs together can't overload it. Parallel harnesses are also admitted by memory: each harness's recorded peak is reserved, within three quarters of the memory available at the start, and a harness starts only if the memory free at that moment still leaves 2 GB. `-j` caps the count here (default: `PARITY_LOCAL_JOBS`, else 3 while other machines help or a third of the CPU threads alone, within `$FLEET_LOAD_JOBS`).
|
||||
|
||||
### Harnesses that start the game
|
||||
|
||||
The game logs into `HORIZON_HOME` and falls back to the player's own game folder when it is unset, so a harness that starts the game, `horizon-ctl`, the launcher or the dedicated server builds the environment with `lab.game_env(**extra)`: no audio, and fresh scratch folders for `HORIZON_HOME`, `HORIZON_LAUNCHER_HOME`, `XDG_DATA_HOME` and `XDG_CONFIG_HOME`, removed when the harness exits (a folder you pass, or that the environment already has, is kept). The runner also gives each harness its own `HORIZON_HOME` under `.local/parity-runner/game-home/`. `tests/test_game_home.py` fails for a script in `tools/` that starts the game without it.
|
||||
|
||||
### Other machines
|
||||
|
||||
Harnesses can also run on the Mac mini and the ThinkPad (`tools/parity_hosts.json` describes them; its `default` list is empty, so pass `--hosts macmini,thinkpad` or set `PARITY_HOSTS`, after copying `shared-local/` to `~/code/horizon-parity/shared-local` on each host). The runner mirrors the worktree there with rsync (not `.git`, `.local`, `target` or ignored files) into `~/code/horizon-parity/remote/<host><path>`, whose `.local` links to the vanilla data in `~/code/horizon-parity/shared-local`, and starts `tools/parity.py --worker` over ssh. The worker builds the probes (about 80 s the first time) and runs what it is sent under the same tracer, admitting harnesses against its own free memory. One queue, longest first, feeds every machine, remote ones first: the Mac takes up to 14 harnesses, the desktop 3 and only those under 800 MB. Each pass comes back with the content hash of every file it depended on, so it is keyed on exactly what ran, and logs come back to `.local/parity-runner/logs/`; state, sharing and `gate.py` work as before. If a host can't be reached, or drops out, its harnesses run here.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""A tool or harness that starts the game must not write into a real player's folders.
|
||||
|
||||
The game logs into HORIZON_HOME and falls back to the player's own game folder when it is unset, so every
|
||||
spawn goes through lab.game_env, which sets fresh scratch folders. This test fails for a script in tools/
|
||||
that starts the game (or horizon-ctl, the launcher or the dedicated server) without it."""
|
||||
|
||||
import ast
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from tools import lab
|
||||
|
||||
TOOLS = Path(lab.ROOT) / "tools"
|
||||
|
||||
# Strings that mean "this script starts the game": a binary a harness runs, or the audio switch every game run sets.
|
||||
SPAWN_MARKERS = {
|
||||
"HORIZON_NO_AUDIO", "horizon-ctl", "horizon-ctl.exe", "horizon-server", "horizon-server.exe",
|
||||
"horizon-client.exe", "release/horizon-client", "fast/horizon-client", "debug/horizon-client",
|
||||
}
|
||||
|
||||
# Scripts that mention those strings without starting a game themselves, and why.
|
||||
EXEMPT = {
|
||||
"lab.py": "defines game_env",
|
||||
"parity.py": "runs the harnesses and gives each its own HORIZON_HOME",
|
||||
"package_native_client.py": "packs the binaries into an archive",
|
||||
"build_release.py": "builds and packs the release",
|
||||
"release_train.py": "release bookkeeping",
|
||||
"pr_ready.py": "runs cargo checks and parity.py",
|
||||
"port_harness.py": "ports Java code, starts no game",
|
||||
}
|
||||
|
||||
|
||||
def constants(tree):
|
||||
"""Every string in a script that is a whole literal or a keyword name (docstrings are longer text)."""
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
||||
yield node.value
|
||||
elif isinstance(node, ast.keyword) and node.arg:
|
||||
yield node.arg
|
||||
|
||||
|
||||
def uses_game_env(tree):
|
||||
"""The script calls lab.game_env, or starts its servers through test_dedicated_server_parity.Server, which does."""
|
||||
return any(isinstance(node, ast.Attribute) and node.attr == "game_env"
|
||||
or isinstance(node, ast.Name) and node.id == "game_env"
|
||||
or isinstance(node, ast.ImportFrom) and (node.module or "").endswith("test_dedicated_server_parity")
|
||||
for node in ast.walk(tree))
|
||||
|
||||
|
||||
def sets_audio_by_hand(tree):
|
||||
return "HORIZON_NO_AUDIO" in set(constants(tree))
|
||||
|
||||
|
||||
class GameEnvTests(unittest.TestCase):
|
||||
def test_gives_every_game_scratch_folders(self):
|
||||
with mock.patch.dict(os.environ, {}, clear=False):
|
||||
for key in ("HORIZON_HOME", "HORIZON_LAUNCHER_HOME", "XDG_DATA_HOME", "XDG_CONFIG_HOME"):
|
||||
os.environ.pop(key, None)
|
||||
first, second = lab.game_env(), lab.game_env()
|
||||
self.assertEqual(first["HORIZON_NO_AUDIO"], "1")
|
||||
self.assertEqual(first["HORIZON_LAUNCHER_NO_SELF_UPDATE"], "1")
|
||||
for key in ("HORIZON_HOME", "HORIZON_LAUNCHER_HOME", "XDG_DATA_HOME", "XDG_CONFIG_HOME"):
|
||||
self.assertTrue(Path(first[key]).is_dir(), key)
|
||||
self.assertNotEqual(first[key], second[key], key)
|
||||
|
||||
def test_keeps_the_callers_folders(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
env = lab.game_env({"PATH": "x", "HORIZON_HOME": "/from/base"}, HORIZON_LAUNCHER_HOME=Path(temp), A=1)
|
||||
self.assertEqual(env["HORIZON_HOME"], "/from/base")
|
||||
self.assertEqual(env["HORIZON_LAUNCHER_HOME"], temp)
|
||||
self.assertEqual((env["PATH"], env["A"]), ("x", "1"))
|
||||
|
||||
def test_does_not_touch_the_process_environment(self):
|
||||
before = dict(os.environ)
|
||||
lab.game_env()
|
||||
self.assertEqual(dict(os.environ), before)
|
||||
|
||||
|
||||
class SpawnerTests(unittest.TestCase):
|
||||
def test_every_script_that_starts_the_game_uses_game_env(self):
|
||||
offenders = []
|
||||
for path in sorted(TOOLS.rglob("*.py")):
|
||||
if path.name in EXEMPT:
|
||||
continue
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
if SPAWN_MARKERS & set(constants(tree)) and not uses_game_env(tree):
|
||||
offenders.append(str(path.relative_to(lab.ROOT)))
|
||||
self.assertEqual(offenders, [], "these start the game without lab.game_env (HORIZON_HOME falls back to a "
|
||||
"real player's folder); use it, or list the script in EXEMPT")
|
||||
|
||||
def test_nobody_sets_the_audio_switch_by_hand(self):
|
||||
offenders = [str(path.relative_to(lab.ROOT)) for path in sorted(TOOLS.rglob("*.py"))
|
||||
if path.name not in EXEMPT
|
||||
and sets_audio_by_hand(ast.parse(path.read_text(encoding="utf-8"), filename=str(path)))]
|
||||
self.assertEqual(offenders, [], "set it through lab.game_env, which also gives the game scratch folders")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -67,17 +67,20 @@ class Failed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def game_env(screen):
|
||||
def game_env(screen, home=None):
|
||||
"""The environment of a game: lab.game_env with HORIZON_HOME `home` (a scratch folder when None), no live-count
|
||||
ping, and on Linux the virtual screen `screen`."""
|
||||
extra = {'HORIZON_TELEMETRY_URL': 'off'} # no live-count ping from test games
|
||||
if home is not None:
|
||||
extra['HORIZON_HOME'] = home
|
||||
if sys.platform == 'darwin':
|
||||
return dict(os.environ, HORIZON_NO_AUDIO='1', HORIZON_TELEMETRY_URL='off')
|
||||
env = dict(os.environ, VSCREEN=screen)
|
||||
return lab.game_env(**extra)
|
||||
env = lab.game_env(VSCREEN=screen, **extra)
|
||||
subprocess.run(['vscreen', 'up'], env=env, check=False, capture_output=True)
|
||||
text = subprocess.run(['vscreen', 'env'], env=env, capture_output=True, text=True, check=True).stdout
|
||||
env.pop('DISPLAY', None)
|
||||
for match in re.finditer(r'(\w+)=(\S+)', text.split('unset')[0]):
|
||||
env[match.group(1)] = match.group(2)
|
||||
env['HORIZON_NO_AUDIO'] = '1'
|
||||
env['HORIZON_TELEMETRY_URL'] = 'off' # no live-count ping from test games
|
||||
if LAVAPIPE.exists():
|
||||
env['VK_ICD_FILENAMES'] = str(LAVAPIPE)
|
||||
return env
|
||||
@@ -632,7 +635,7 @@ def scratch(mode, resources):
|
||||
(work / '.local/horizon-client').mkdir()
|
||||
os.symlink(resources, work / '.local/horizon-client' / lab.VERSION)
|
||||
# horizon-ctl reads No AI in the game home: this one, not the player's.
|
||||
env = dict(game_env(os.environ.get('VSCREEN', 'agent-control')), HORIZON_HOME=str(work))
|
||||
env = game_env(os.environ.get('VSCREEN', 'agent-control'), home=work)
|
||||
return work, env
|
||||
|
||||
|
||||
|
||||
@@ -53,15 +53,14 @@ guiScale:0
|
||||
|
||||
def vscreen_env():
|
||||
out = subprocess.run(["vscreen", "env"], cwd=ROOT, capture_output=True, text=True, check=True)
|
||||
env = dict(os.environ)
|
||||
env = lab.game_env()
|
||||
for part in out.stdout.replace(";", " ").split():
|
||||
if "=" in part and not part.startswith("export"):
|
||||
k, v = part.split("=", 1)
|
||||
env[k] = v
|
||||
env.pop("DISPLAY", None)
|
||||
# Benchmark games never play sound on the machine's speakers.
|
||||
env["HORIZON_NO_AUDIO"] = "1"
|
||||
# Nor reach the telemetry server (docs/telemetry.md).
|
||||
# lab.game_env: no sound on the machine's speakers, and scratch folders instead of the player's.
|
||||
# Benchmark games do not reach the telemetry server (docs/telemetry.md).
|
||||
env["HORIZON_TELEMETRY_URL"] = "off"
|
||||
return env
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ generated files stay in .local/. Also the small helpers the harnesses share
|
||||
(free ports, offline UUIDs, VarInts, float bits)."""
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import json
|
||||
@@ -54,6 +55,38 @@ def cargo_target():
|
||||
return ROOT / os.environ.get("CARGO_TARGET_DIR", "target")
|
||||
|
||||
|
||||
_SCRATCH_DIRS = []
|
||||
|
||||
|
||||
def _scratch_dir(prefix):
|
||||
path = tempfile.mkdtemp(prefix=prefix)
|
||||
_SCRATCH_DIRS.append(path)
|
||||
return path
|
||||
|
||||
|
||||
@atexit.register
|
||||
def _remove_scratch_dirs():
|
||||
for path in _SCRATCH_DIRS:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
|
||||
def game_env(base=None, **extra):
|
||||
"""The environment for every run of the game, the launcher or horizon-ctl from a tool or a harness: no audio,
|
||||
and fresh scratch folders for the game's home (logs, saves, options), the launcher's home and the XDG folders,
|
||||
so a run never writes into a real player's folders (the game falls back to them when HORIZON_HOME is unset).
|
||||
A folder the caller already has in `base` (default os.environ) or passes in `extra` is kept. The scratch folders
|
||||
are removed when this process exits."""
|
||||
env = dict(os.environ if base is None else base)
|
||||
env.update({key: str(value) for key, value in extra.items()})
|
||||
env["HORIZON_NO_AUDIO"] = "1"
|
||||
env["HORIZON_LAUNCHER_NO_SELF_UPDATE"] = "1"
|
||||
for key, prefix in (("HORIZON_HOME", "horizon-home-"), ("HORIZON_LAUNCHER_HOME", "horizon-launcher-home-"),
|
||||
("XDG_DATA_HOME", "horizon-xdg-data-"), ("XDG_CONFIG_HOME", "horizon-xdg-config-")):
|
||||
if not env.get(key):
|
||||
env[key] = _scratch_dir(prefix)
|
||||
return env
|
||||
|
||||
|
||||
def write_json(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
+1
-2
@@ -275,8 +275,7 @@ class Game:
|
||||
(self.work / '.local/horizon-client').mkdir(parents=True)
|
||||
os.symlink(resources, self.work / '.local/horizon-client' / lab.VERSION)
|
||||
self.socket = self.work / 'c.sock'
|
||||
env = game_env(os.environ.get('VSCREEN', 'lan-e2e'))
|
||||
env['HORIZON_HOME'] = str(self.work / 'home')
|
||||
env = game_env(os.environ.get('VSCREEN', 'lan-e2e'), home=self.work / 'home')
|
||||
self.d = Cli(ctl, self.socket, self.work, env)
|
||||
self.d.name = name
|
||||
|
||||
|
||||
@@ -167,8 +167,7 @@ def main():
|
||||
os.symlink(bins / name, version / name)
|
||||
os.symlink(resources, version / '.local/horizon-client' / lab.VERSION)
|
||||
(version / '.installed').write_text('')
|
||||
env = dict(os.environ, HORIZON_LAUNCHER_HOME=str(work / 'launcher'), HORIZON_HOME=str(work / 'home'),
|
||||
HORIZON_NO_AUDIO='1', HORIZON_TELEMETRY_URL='off')
|
||||
env = lab.game_env(HORIZON_LAUNCHER_HOME=work / 'launcher', HORIZON_HOME=work / 'home', HORIZON_TELEMETRY_URL='off')
|
||||
m = Mcp(Path(args.launcher).resolve(), env)
|
||||
try:
|
||||
summary = scenario(m, out)
|
||||
|
||||
@@ -33,6 +33,11 @@ import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from . import lab
|
||||
except ImportError:
|
||||
import lab
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@@ -154,8 +159,7 @@ def main():
|
||||
args = ap.parse_args()
|
||||
|
||||
home = Path(tempfile.mkdtemp(prefix="horizon-mod-browser-"))
|
||||
env = dict(os.environ, HORIZON_HOME=str(home), HORIZON_NO_AUDIO="1", HORIZON_NO_HEARTBEAT="1",
|
||||
HORIZON_CONTROL=str(home / "ctl.sock"))
|
||||
env = lab.game_env(HORIZON_HOME=home, HORIZON_NO_HEARTBEAT="1", HORIZON_CONTROL=home / "ctl.sock")
|
||||
launcher = [args.launcher]
|
||||
game = Game(str(Path(args.bin) / "horizon-ctl"), str(Path(args.bin) / "horizon-client"),
|
||||
args.resources, home, env)
|
||||
|
||||
@@ -39,6 +39,7 @@ import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -809,6 +810,12 @@ class LocalExecutor:
|
||||
trace.unlink(missing_ok=True)
|
||||
log = (STATE_DIR / "logs" / f"{name}.log").open("w")
|
||||
environment = run_environment(reuse)
|
||||
# A game the harness starts logs into HORIZON_HOME, which is a real player's folder when it is unset
|
||||
# (lab.game_env keeps this one).
|
||||
game_home = STATE_DIR / "game-home" / name
|
||||
shutil.rmtree(game_home, ignore_errors=True)
|
||||
game_home.mkdir(parents=True)
|
||||
environment.setdefault("HORIZON_HOME", str(game_home))
|
||||
environment["PARITY_SHIM_OUTPUTS"] = str(outputs_listing(name, fresh=True))
|
||||
environment.pop("PARITY_SHARD", None)
|
||||
if shard:
|
||||
|
||||
+1
-1
@@ -400,7 +400,7 @@ def run_horizon(args, distance):
|
||||
for carry in dev_mods(args):
|
||||
command += ["--dev-mod", str(carry)]
|
||||
# Benchmark runs stay out of the live count and send nothing (docs/telemetry.md).
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO="1", HORIZON_TELEMETRY_URL="off")
|
||||
env = lab.game_env(HORIZON_TELEMETRY_URL="off")
|
||||
if args.warm:
|
||||
# A first start to the title screen builds what later starts reuse (the content-mod
|
||||
# root, the block behaviour snapshot). It is not measured: players see it once.
|
||||
|
||||
@@ -120,7 +120,7 @@ class Server:
|
||||
self.process = subprocess.Popen([str(c) for c in command], cwd=directory, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
|
||||
encoding='utf-8', errors='replace',
|
||||
env=dict(os.environ, HORIZON_NO_AUDIO='1'))
|
||||
env=lab.game_env())
|
||||
threading.Thread(target=self._read, daemon=True).start()
|
||||
|
||||
def _read(self):
|
||||
|
||||
@@ -183,7 +183,7 @@ def main():
|
||||
d.run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
d.run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
# Loot table names (D) are vanilla's bookkeeping; the drops themselves are compared.
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
|
||||
@@ -82,7 +82,7 @@ def main():
|
||||
rust_dir = build / 'rust'
|
||||
shutil.rmtree(rust_dir, ignore_errors=True)
|
||||
rust_dir.mkdir()
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO='1', HORIZON_COMMAND_SCRIPT=str(script_path), HORIZON_COMMAND_OUTPUT=str(build / 'rust.jsonl'))
|
||||
env = lab.game_env(HORIZON_COMMAND_SCRIPT=script_path, HORIZON_COMMAND_OUTPUT=build / 'rust.jsonl')
|
||||
run([binary, '--resources', lab.RESOURCES, '--world', 'harness', '--seed', SEED, '--radius', 4], 'rust', cwd=rust_dir, env=env)
|
||||
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').open(encoding='utf-8')]
|
||||
|
||||
@@ -125,7 +125,7 @@ def main():
|
||||
d.run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
d.run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
rust = h.split_cases((work / 'rust.txt').read_text(), ignore)
|
||||
|
||||
@@ -245,7 +245,7 @@ def main():
|
||||
d.run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
d.run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
rust = h.split_cases((work / 'rust.txt').read_text(), ignore)
|
||||
|
||||
@@ -126,7 +126,7 @@ class Server:
|
||||
self.lines = []
|
||||
self.process = subprocess.Popen(list(map(str, command)), cwd=directory, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
|
||||
encoding='utf-8', errors='replace')
|
||||
encoding='utf-8', errors='replace', env=lab.game_env())
|
||||
self.reader = threading.Thread(target=self._read, daemon=True)
|
||||
self.reader.start()
|
||||
|
||||
@@ -352,7 +352,7 @@ def horizon_client_joins(vanilla_server, binary, world, build, reference):
|
||||
server = Server(vanilla_server, directory, label)
|
||||
try:
|
||||
server.wait_for(r'Done \(')
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO='1', HORIZON_NET_REPORT=str(report))
|
||||
env = lab.game_env(HORIZON_NET_REPORT=report)
|
||||
with (build / 'horizon-client-run.log').open('w') as log:
|
||||
subprocess.run([str(c) for c in [binary, '--resources', lab.RESOURCES, '--render-distance', 2,
|
||||
'--server', f'127.0.0.1:{port}', '--name', PLAYER]],
|
||||
|
||||
@@ -417,7 +417,7 @@ def main():
|
||||
run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
# Loot table names (D) are vanilla's bookkeeping; the drops themselves are compared.
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
|
||||
@@ -96,7 +96,7 @@ class Game:
|
||||
self.process = subprocess.Popen(Game.prefix + [
|
||||
str(client), '--resources', str(resources), '--render-distance', '2', '--test-terrain',
|
||||
'--control-socket', str(self.endpoint), '--timeout-seconds', '600',
|
||||
] + Game.extra, cwd=self.home, env=dict(os.environ, HORIZON_HOME=str(self.home), HORIZON_NO_AUDIO='1', **env),
|
||||
] + Game.extra, cwd=self.home, env=lab.game_env(HORIZON_HOME=self.home, **env),
|
||||
stdout=self.log, stderr=subprocess.STDOUT)
|
||||
wait_for(self.endpoint, 'screen', lambda v: v['screen'] == 'title', 120, f'{name} title')
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ def main():
|
||||
d.run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
d.run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
rust = h.split_cases((work / 'rust.txt').read_text(), ignore)
|
||||
|
||||
@@ -71,7 +71,7 @@ def main():
|
||||
str(executable), '--radius', '1', '--frames', '30',
|
||||
'--smoke-report', str(report), '--timeout-seconds', '60']
|
||||
with (root / 'startup-smoke.log').open('w') as log:
|
||||
subprocess.run(command, cwd=lab.ROOT, env=environment, check=True,
|
||||
subprocess.run(command, cwd=lab.ROOT, env=lab.game_env(environment), check=True,
|
||||
stdout=log, stderr=subprocess.STDOUT, timeout=120)
|
||||
result = json.loads(report.read_text())
|
||||
if (result.get('error') is not None or result.get('presented_frames') != 30
|
||||
|
||||
@@ -179,7 +179,7 @@ def run_client(binary, build, port, label, token, discovery):
|
||||
shutil.rmtree(directory, ignore_errors=True)
|
||||
directory.mkdir()
|
||||
report = directory / 'report.json'
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO='1', HORIZON_NET_REPORT=str(report), HORIZON_API_DISCOVERY=discovery)
|
||||
env = lab.game_env(HORIZON_NET_REPORT=report, HORIZON_API_DISCOVERY=discovery)
|
||||
command = [binary, '--resources', lab.RESOURCES, '--render-distance', 2,
|
||||
'--server', f'127.0.0.1:{port}', '--name', PLAYER]
|
||||
if token:
|
||||
@@ -246,7 +246,7 @@ def run_side(label, command, binary, build, services, discovery):
|
||||
props = dict(PROPERTIES, **{'server-port': str(port)})
|
||||
(directory / 'server.properties').write_text(''.join(f'{k}={v}\n' for k, v in props.items()))
|
||||
(directory / 'eula.txt').write_text('eula=true\n')
|
||||
os.environ.update(HORIZON_NO_AUDIO='1', HORIZON_API_DISCOVERY=discovery)
|
||||
os.environ.update(lab.game_env(HORIZON_API_DISCOVERY=discovery))
|
||||
server = Server(command, directory, f'{label}-online')
|
||||
checks = []
|
||||
starts = []
|
||||
|
||||
@@ -330,7 +330,7 @@ def entity_uuid_text(i):
|
||||
|
||||
def vscreen_env():
|
||||
text = subprocess.run(['vscreen', 'env'], capture_output=True, text=True, check=True).stdout
|
||||
env = dict(os.environ)
|
||||
env = lab.game_env()
|
||||
env.pop('DISPLAY', None)
|
||||
for match in re.finditer(r'(\w+)=(\S+)', text.split('unset')[0]):
|
||||
env[match.group(1)] = match.group(2)
|
||||
@@ -346,7 +346,7 @@ def horizon_resave(source, destination, bin_dir):
|
||||
ctl = [str(bin_dir / 'horizon-ctl'), '--socket', str(socket)]
|
||||
extra = []
|
||||
if sys.platform == 'darwin':
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO='1')
|
||||
env = lab.game_env()
|
||||
extra = ['--offscreen']
|
||||
else:
|
||||
alsa = destination / 'null-asound.conf'
|
||||
|
||||
@@ -339,7 +339,7 @@ def main():
|
||||
oracle_harness.run_logged(['cargo', 'build', '--locked', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'],
|
||||
build, 'compile-rust')
|
||||
oracle_harness.run_logged([lab.cargo_target() / 'release/horizon-client', '--resources', resources], build, 'horizon',
|
||||
env={**os.environ, 'HORIZON_SCULK_FIXTURE': f'{inputs},{build / "horizon.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_SCULK_FIXTURE=f'{inputs},{build / "horizon.txt"}'))
|
||||
if args.java_only:
|
||||
return
|
||||
names = {s[0] for _, s in chosen}
|
||||
|
||||
@@ -264,7 +264,7 @@ def main():
|
||||
oh.run_logged([*java, 'keys', build], build, 'keys')
|
||||
services = Services(json.loads((build / 'publickeys.json').read_text()))
|
||||
discovery = services.url + '/minecraft/client'
|
||||
os.environ.update(HORIZON_NO_AUDIO='1', HORIZON_API_DISCOVERY=discovery)
|
||||
os.environ.update(lab.game_env(HORIZON_API_DISCOVERY=discovery))
|
||||
servers = {
|
||||
'vanilla': ['java', '-Xmx2g', '-Duser.timezone=UTC', f'-Dminecraft.api.discovery.host={discovery}',
|
||||
'-cp', dependencies, 'net.minecraft.server.Main', 'nogui'],
|
||||
|
||||
@@ -375,7 +375,7 @@ def main():
|
||||
d.run(['cargo', 'build', '-q', '--release', '-p', 'horizon-client', '--bin', 'horizon-client'], cwd=lab.ROOT,
|
||||
env={**os.environ, 'CARGO_BUILD_JOBS': os.environ.get('CARGO_BUILD_JOBS', '3')})
|
||||
d.run([str(lab.cargo_target() / 'release/horizon-client'), '--resources', str(resources)], cwd=work,
|
||||
env={**os.environ, 'HORIZON_DISPENSE_FIXTURE': f'{work / "cases.jsonl"},{work / "rust.txt"}', 'HORIZON_NO_AUDIO': '1'})
|
||||
env=lab.game_env(HORIZON_DISPENSE_FIXTURE=f'{work / "cases.jsonl"},{work / "rust.txt"}'))
|
||||
ignore = tuple(prefix + ' ' for prefix in args.ignore.split(',') if prefix) + ('D ',)
|
||||
native = h.split_cases((work / 'native.txt').read_text(), ignore)
|
||||
rust = h.split_cases((work / 'rust.txt').read_text(), ignore)
|
||||
|
||||
@@ -24,6 +24,9 @@ import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import lab # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
RESOURCES = ROOT / '.local' / 'horizon-client' / '26.3'
|
||||
RUN = ROOT / '.local' / 'world-reopen'
|
||||
@@ -32,7 +35,7 @@ CYCLES = 3
|
||||
|
||||
def game_env():
|
||||
"""macOS renders offscreen; Linux uses the virtual screen (as test_save_roundtrip_parity)."""
|
||||
env = dict(os.environ, HORIZON_NO_AUDIO='1')
|
||||
env = lab.game_env()
|
||||
if sys.platform == 'darwin':
|
||||
return env, ['--offscreen']
|
||||
subprocess.run(['vscreen', 'up'], check=False, capture_output=True)
|
||||
|
||||
@@ -140,7 +140,7 @@ def main():
|
||||
# One worker thread: the chunks decorated in a fixed order (see above).
|
||||
vanilla_server = ['java', '-Xmx2g', '-Dmax.bg.threads=1', '-cp', dependencies, 'net.minecraft.server.Main', 'nogui']
|
||||
horizon_server = [binary, '--resources', RESOURCES, '--dedicated', '--nogui']
|
||||
os.environ['HORIZON_NO_AUDIO'] = '1'
|
||||
os.environ.update(lab.game_env())
|
||||
|
||||
def one(seed):
|
||||
t0 = time.time()
|
||||
|
||||
Reference in New Issue
Block a user