"""Tests for onion-perf-setup.sh (and .ps1 when pwsh exists) against temp instance dirs. Needs `python3 build.py --ship` to have generated config/onion-perf/ in the repo. Run: python3 -m unittest discover -s analysis/client-perf/proposed-configs/tests """ import shutil import subprocess import tempfile import unittest from pathlib import Path REPO = Path(__file__).resolve().parents[4] PERF = REPO / "config" / "onion-perf" SCRIPTS = ["onion-perf-setup.sh", "onion-perf-setup.ps1", "onion-perf-setup.bat"] OLD_OPTIONS = ( "version:3955\n" "maxFps:144\n" "enableVsync:false\n" "renderDistance:12\n" "mipmapLevels:4\n" "soundCategory_master:0.5\n" "key_key.attack:key.mouse.left\n" "key_key.jump:key.keyboard.space\n" 'resourcePacks:["vanilla","file/MyPack.zip"]\n' ) def runners(): """(name, argv prefix) for every available POSIX-sh implementation, plus pwsh.""" out = [("sh", ["sh"])] if shutil.which("dash"): out.append(("dash", ["dash"])) if shutil.which("zsh"): out.append(("zsh-emulate-sh", ["zsh", "--emulate", "sh"])) return out PWSH = shutil.which("pwsh") def tier_options(tier): kv = {} for line in (PERF / tier / "options.txt").read_text().splitlines(): k, _, v = line.partition(":") kv[k] = v return kv class Base: """Mixin; subclasses set self.cmd(script, args) -> argv.""" def setUp(self): if not (PERF / "mid").is_dir(): self.skipTest("config/onion-perf not generated (run build.py --ship)") self.tmp = Path(tempfile.mkdtemp()) self.game = self.tmp / "instance" (self.game / "config").mkdir(parents=True) shutil.copytree(PERF, self.game / "config" / "onion-perf") for s in SCRIPTS: shutil.copy(REPO / s, self.game / s) (self.game / "config" / "DistantHorizons.toml").write_text("old dh\n") (self.game / "config" / "unrelated.toml").write_text("keep me\n") (self.game / "options.txt").write_text(OLD_OPTIONS) def tearDown(self): shutil.rmtree(self.tmp, ignore_errors=True) def run_setup(self, *args, cwd=None): return subprocess.run(self.cmd(*args), cwd=cwd or self.tmp, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=60) def backups(self): d = self.game / "onion-perf-backup" return sorted(d.iterdir()) if d.is_dir() else [] def test_tier_copy_backup_and_merge(self): for tier in ("deck", "mid", "high"): with self.subTest(tier=tier): if tier != "deck": self.tearDown() self.setUp() r = self.run_setup(tier, "--yes") self.assertEqual(r.returncode, 0, r.stdout + r.stderr) src = PERF / tier for f in (src / "config").rglob("*"): if f.is_file(): rel = f.relative_to(src) self.assertEqual((self.game / rel).read_bytes(), f.read_bytes(), rel) self.assertEqual((self.game / "config/unrelated.toml").read_text(), "keep me\n") # backup holds the pre-run files (b,) = self.backups() self.assertEqual((b / "config/DistantHorizons.toml").read_text(), "old dh\n") self.assertEqual((b / "options.txt").read_text(), OLD_OPTIONS) self.assertFalse((b / "config/unrelated.toml").exists()) # options merged key by key want = tier_options(tier) lines = (self.game / "options.txt").read_text().splitlines() kv = dict(line.partition(":")[::2] for line in lines) for k in ("version", "maxFps", "enableVsync"): self.assertEqual(kv[k], dict(x.partition(":")[::2] for x in OLD_OPTIONS.splitlines())[k], k) for k, v in want.items(): if k not in ("version", "maxFps", "enableVsync"): self.assertEqual(kv[k], v, k) for keep in ("soundCategory_master:0.5", "key_key.attack:key.mouse.left", "key_key.jump:key.keyboard.space", 'resourcePacks:["vanilla","file/MyPack.zip"]'): self.assertIn(keep, lines) self.assertEqual(len(lines), len(set(line.partition(":")[0] for line in lines))) # existing lines keep their positions; appended keys go at the end self.assertEqual(lines[0], "version:3955") self.assertTrue(lines[3].startswith("renderDistance:")) if want["mipmapLevels"] != "4": self.assertIn(f"mipmapLevels: 4 -> {want['mipmapLevels']}", r.stdout) else: self.assertNotIn("mipmapLevels:", r.stdout) self.assertIn("(unset) ->", r.stdout) def test_value_with_colon_replaced_whole(self): # split on the FIRST ':' only: a tier value containing ':' replaces as a whole tier_opt = self.game / "config/onion-perf/high/options.txt" tier_opt.write_text(tier_opt.read_text() + "onionProbe:part1:part2\n") (self.game / "options.txt").write_text("version:3955\nonionProbe:wrong\n") r = self.run_setup("high", "--yes") self.assertEqual(r.returncode, 0, r.stderr) lines = (self.game / "options.txt").read_text().splitlines() self.assertIn("onionProbe:part1:part2", lines) self.assertNotIn("onionProbe:wrong", lines) self.assertIn("onionProbe: wrong -> part1:part2", r.stdout) def test_idempotent(self): self.assertEqual(self.run_setup("deck", "--yes").returncode, 0) snap = {p: p.read_bytes() for p in self.game.rglob("*") if p.is_file() and "onion-perf-backup" not in p.parts} r = self.run_setup("deck", "--yes") self.assertEqual(r.returncode, 0, r.stderr) self.assertIn("nothing changed", r.stdout) self.assertEqual(len(self.backups()), 1) for p, data in snap.items(): self.assertEqual(p.read_bytes(), data, p) def test_default_tier_is_mid_when_noninteractive(self): r = self.run_setup("--yes") self.assertEqual(r.returncode, 0, r.stderr) self.assertIn("tier 'mid'", r.stdout) self.assertEqual((self.game / "config/DistantHorizons.toml").read_bytes(), (PERF / "mid/config/DistantHorizons.toml").read_bytes()) def test_missing_options(self): (self.game / "options.txt").unlink() r = self.run_setup("high", "--yes") self.assertEqual(r.returncode, 0, r.stderr) self.assertIn("options.txt not found", r.stdout) self.assertFalse((self.game / "options.txt").exists()) def test_crlf_options_preserved(self): (self.game / "options.txt").write_bytes(OLD_OPTIONS.replace("\n", "\r\n").encode()) r = self.run_setup("mid", "--yes") self.assertEqual(r.returncode, 0, r.stderr) data = (self.game / "options.txt").read_bytes() self.assertNotIn(b"\r\r", data) self.assertEqual(data.count(b"\n"), data.count(b"\r\n")) self.assertIn(b"key_key.attack:key.mouse.left\r\n", data) self.assertIn(b"biomeBlendRadius:1\r\n", data) def test_lf_options_preserved(self): self.run_setup("mid", "--yes") self.assertNotIn(b"\r", (self.game / "options.txt").read_bytes()) def test_bad_tier(self): r = self.run_setup("ultra", "--yes") self.assertNotEqual(r.returncode, 0) self.assertEqual((self.game / "config/DistantHorizons.toml").read_text(), "old dh\n") def test_missing_onion_perf(self): shutil.rmtree(self.game / "config" / "onion-perf") r = self.run_setup("mid", "--yes") self.assertNotEqual(r.returncode, 0) self.assertIn("Update the modpack", r.stderr) self.assertEqual(self.backups(), []) def test_needs_yes_when_noninteractive(self): r = self.run_setup("mid") self.assertNotEqual(r.returncode, 0) self.assertIn("--yes", r.stderr) self.assertEqual((self.game / "config/DistantHorizons.toml").read_text(), "old dh\n") def make_sh_case(name, prefix): class Case(Base, unittest.TestCase): def cmd(self, *args): return [*prefix, str(self.game / "onion-perf-setup.sh"), *args] def test_run_from_game_dir_with_relative_path(self): r = subprocess.run([*prefix, "onion-perf-setup.sh", "mid", "--yes"], cwd=self.game, stdin=subprocess.DEVNULL, capture_output=True, text=True) self.assertEqual(r.returncode, 0, r.stderr) Case.__name__ = Case.__qualname__ = f"Sh_{name.replace('-', '_')}" return Case for _n, _p in runners(): globals()[f"Sh_{_n.replace('-', '_')}"] = make_sh_case(_n, _p) @unittest.skipUnless(PWSH, "pwsh not installed: onion-perf-setup.ps1 untested here") class Pwsh(Base, unittest.TestCase): def cmd(self, *args): return [PWSH, "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(self.game / "onion-perf-setup.ps1"), *args] class Packaging(unittest.TestCase): def test_bat_is_crlf_and_calls_ps1(self): data = (REPO / "onion-perf-setup.bat").read_bytes() self.assertEqual(data.count(b"\n"), data.count(b"\r\n")) self.assertIn(b'-File "%~dp0onion-perf-setup.ps1" %*', data) def test_scripts_ascii(self): for s in SCRIPTS: (REPO / s).read_bytes().decode("ascii") def test_mid_matches_default_options(self): do = REPO / "config" / "defaultoptions" self.assertEqual((PERF / "mid/options.txt").read_bytes(), (do / "options.txt").read_bytes()) for f in (PERF / "mid/config").rglob("*"): if f.is_file(): rel = f.relative_to(PERF / "mid") self.assertEqual(f.read_bytes(), (do / "extra" / rel).read_bytes(), rel) self.assertFalse((PERF / "mid/config/modernfix-mixins.properties").exists()) if __name__ == "__main__": unittest.main()