#!/usr/bin/env python3 """Strict Core menu project extension in both source and packed validators.""" from pathlib import Path import re import sys import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT/'tools')) import headless_game as packed import source_bundle as bundle import packed_external as merged from external_addons import DIRECT from project_schema import parse_project CURRENT = (ROOT/'addons/core/addon.gproj').read_text() MENU = CURRENT[CURRENT.index(' Configurations {'):CURRENT.rindex('}')] MINIMAL = CURRENT[:CURRENT.index(' Configurations {')]+'}\n' CORE_PROJECT = 'addons/core/addon.gproj' MENU_FILE = 'addons/core/Configs/ERR/AdminMenus.conf' class ProjectSchemaTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.payload = {name: (ROOT/name).read_bytes() for name in bundle.inventory(ROOT)} def setUp(self): self.temp = tempfile.TemporaryDirectory(prefix='erri-project-schema-') self.addCleanup(self.temp.cleanup) self.candidate = Path(self.temp.name) (self.candidate/'data.pak').write_bytes(b'SYNTHETIC PACKAGE') (self.candidate/'resourceDatabase.rdb').write_bytes(b'SYNTHETIC DATABASE') def packed(self, text, project='core'): (self.candidate/'addon.gproj').write_text(text) return packed.packed_inventory(self.candidate, project) def source(self, text): payload = dict(self.payload) payload[CORE_PROJECT] = text.encode() return bundle.validate(payload, ROOT) def test_old_minimal_and_exact_owned_menu_extension_are_accepted(self): for text in (MINIMAL, CURRENT, re.sub(r'\s+', ' ', CURRENT)): with self.subTest(text=text): match = parse_project(text, allow_core_menu=True) self.assertIsNotNone(match) self.assertEqual(match.group(1, 2), ('ERR_Core', packed.PACKED_GUID)) legacy_text = text for guid in DIRECT: legacy_text = legacy_text.replace('\"'+guid+'\"', '') self.assertEqual(set(self.packed(legacy_text)), packed.PACKED_FILES) with self.assertRaises(ValueError): self.packed(text) self.assertEqual(set(self.source(text)['core']['dependencies']), {packed.PACKED_BASE, *DIRECT}) self.assertIsNotNone(parse_project(MINIMAL)) self.assertIsNone(parse_project(CURRENT)) def test_menu_extension_cannot_redirect_override_or_add_unreviewed_settings(self): mutations = ( CURRENT.replace('B48C7FA12692DE03', 'FFFFFFFFFFFFFFFF'), CURRENT.replace('A7B19F416E541000', 'FFFFFFFFFFFFFFFF'), CURRENT.replace('50A48858902D5F64', 'FFFFFFFFFFFFFFFF'), CURRENT.replace('Configs/ERR/ObserverInput.conf', 'Configs/Foreign/Input.conf'), CURRENT.replace('50C890C996B0A6FC', 'FFFFFFFFFFFFFFFF'), CURRENT.replace('Configs/ERR/AdminMenus.conf', 'Configs/Foreign/Menu.conf'), CURRENT.replace('MenuConfigs {', 'MenuConfigs +{'), CURRENT.replace('MenuConfigs {', 'MenuConfigs -{'), CURRENT.replace('C747AFB6B750CE9A', 'FFFFFFFFFFFFFFFF'), CURRENT.replace('Configs/System/chimeraMenus.conf', 'Configs/Foreign/Menu.conf'), CURRENT.replace('"{C747AFB6B750CE9A}Configs/System/chimeraMenus.conf"', ''), CURRENT.replace('GameProjectConfig PC', 'GameProjectConfig HEADLESS'), CURRENT.replace('GameProjectConfig PC', 'GameProjectConfig XBOX_SERIES'), CURRENT.replace('GameProjectConfig PC', 'UnknownConfig PC'), CURRENT.replace('MenuManagerSettings MenuManagerSettings', 'MenuManagerSettings OtherSettings'), CURRENT.replace(' MenuConfigs', ' UnknownSetting 1\n MenuConfigs'), CURRENT.replace('"{B48C7FA12692DE03}Configs/ERR/AdminMenus.conf"', '"{B48C7FA12692DE03}Configs/ERR/AdminMenus.conf" "{FFFFFFFFFFFFFFFF}Extra.conf"'), CURRENT.replace(MENU, MENU+MENU), CURRENT+'GameProject {}\n', ) for text in mutations: with self.subTest(text=text): self.assertIsNone(parse_project(text, allow_core_menu=True)) with self.assertRaises(ValueError): self.packed(text) with self.assertRaisesRegex(ValueError, 'identity/dependency'): self.source(text) def test_identity_and_exact_dependencies_remain_required_with_extension(self): for text in ( CURRENT.replace('ERR_Core', 'ERR_Chat'), CURRENT.replace(packed.PACKED_GUID, 'FFFFFFFFFFFFFFFF'), CURRENT.replace(packed.PACKED_BASE, '5F16D7E4A1CBE075'), CURRENT.replace('Dependencies {', 'Dependencies { "58D0FB3206B6F859"'), CURRENT.replace('Dependencies {', 'Dependencies { "5192138CB9D5B911"'), ): with self.subTest(text=text): with self.assertRaises(ValueError): self.packed(text) with self.assertRaisesRegex(ValueError, 'identity/dependency'): self.source(text) def test_merged_packed_menu_extension(self): folder = self.candidate/'core' folder.mkdir() for name in ('data.pak', 'resourceDatabase.rdb'): (self.candidate/name).rename(folder/name) (folder/'addon.gproj').write_text(CURRENT) self.assertEqual(set(merged.inventory(self.candidate)), {'core/'+name for name in merged.FILES}) (folder/'addon.gproj').write_text(CURRENT.replace('Configs/ERR/AdminMenus.conf', 'Configs/Foreign/Menu.conf')) with self.assertRaises(ValueError): merged.inventory(self.candidate) def test_source_menu_reference_must_resolve_to_its_owned_resource(self): for name in ('addons/core/Configs/ERR/ObserverInput.conf',): payload = dict(self.payload) payload.pop(name) payload.pop(name+'.meta') with self.assertRaisesRegex(ValueError, 'unresolved or nonvanilla'): bundle.validate(payload, ROOT) payload = dict(self.payload) payload.pop(MENU_FILE) payload.pop(MENU_FILE+'.meta') with self.assertRaisesRegex(ValueError, 'unresolved or nonvanilla'): bundle.validate(payload, ROOT) payload = dict(self.payload) payload[MENU_FILE+'.meta'] = payload[MENU_FILE+'.meta'].replace(b'B48C7FA12692DE03', b'FFFFFFFFFFFFFFFF') with self.assertRaisesRegex(ValueError, 'unresolved or nonvanilla'): bundle.validate(payload, ROOT) if __name__ == '__main__': unittest.main()