diff options
Diffstat (limited to 'scripts/test_scripts')
| -rw-r--r-- | scripts/test_scripts/builds-download-upload-test.py | 196 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/AndroidClient.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/IOSClient.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/LinuxServer.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/PS4Client.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/Switch2Client.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/SwitchClient.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/WindowsClient.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/metadatas/XB1Client.json | 9 | ||||
| -rw-r--r-- | scripts/test_scripts/oplog-import-export-test.py | 228 |
10 files changed, 496 insertions, 0 deletions
diff --git a/scripts/test_scripts/builds-download-upload-test.py b/scripts/test_scripts/builds-download-upload-test.py new file mode 100644 index 000000000..f03528d98 --- /dev/null +++ b/scripts/test_scripts/builds-download-upload-test.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""Test script for builds download/upload operations.""" + +from __future__ import annotations + +import argparse +import platform +import subprocess +import sys +from pathlib import Path +from typing import NamedTuple + +_PLATFORM = "windows" if sys.platform == "win32" else "macosx" if sys.platform == "darwin" else "linux" +_ARCH = "x64" if sys.platform == "win32" else platform.machine().lower() +_EXE_SUFFIX = ".exe" if sys.platform == "win32" else "" + + +class Build(NamedTuple): + name: str + bucket: str + id: str + + +BUILDS = [ + Build("XB1Client", "fortnitegame.staged-build.fortnite-main.xb1-client", "09a7616c1a388dfe6056aa57"), + Build("WindowsClient", "fortnitegame.staged-build.fortnite-main.windows-client", "09a762c81e2cf213142d0ce5"), + Build("SwitchClient", "fortnitegame.staged-build.fortnite-main.switch-client", "09a75bf9c3ce75bce09f644f"), + Build("LinuxServer", "fortnitegame.staged-build.fortnite-main.linux-server", "09a750ac155eb3e3b62e87e0"), + Build("Switch2Client", "fortnitegame.staged-build.fortnite-main.switch2-client", "09a78f3df07b289691ec5710"), + Build("PS4Client", "fortnitegame.staged-build.fortnite-main.ps4-client", "09a76ea92ad301d4724fafad"), + Build("IOSClient", "fortnitegame.staged-build.fortnite-main.ios-client", "09a7816fa26c23362fef0c5d"), + Build("AndroidClient", "fortnitegame.staged-build.fortnite-main.android-client", "09a76725f1620d62c6be06e4"), +] + +ZEN_EXE: Path = Path(f"./build/{_PLATFORM}/{_ARCH}/release/zen{_EXE_SUFFIX}") +ZEN_METADATA_DIR: Path = Path(__file__).resolve().parent / "metadatas" + +ZEN_PORT = 8558 +ZEN_CACHE_PORT = 8559 +ZEN_CACHE = f"http://127.0.0.1:{ZEN_CACHE_PORT}" +ZEN_PARTIAL_REQUEST_MODE = "true" + +SERVER_ARGS: tuple[str, ...] = ( + "--http", "asio", + "--gc-cache-duration-seconds", "1209600", + "--gc-interval-seconds", "21600", + "--gc-low-diskspace-threshold", "2147483648", + "--cache-bucket-limit-overwrites", +) + + +def run(cmd: list[str | Path]) -> None: + try: + subprocess.run(cmd, check=True) + except FileNotFoundError: + sys.exit(f"error: executable not found: {cmd[0]}") + except subprocess.CalledProcessError as e: + sys.exit(f"error: command failed with exit code {e.returncode}:\n {' '.join(str(x) for x in e.cmd)}") + + +def stop_server(label: str, port: int) -> None: + """Stop a zen server. Tolerates failures so it is safe to call from finally blocks.""" + print(f"--------- stopping {label}") + try: + subprocess.run([ZEN_EXE, "down", "--port", str(port)]) + except OSError as e: + print(f"warning: could not stop {label}: {e}", file=sys.stderr) + print() + + +def start_server(label: str, data_dir: Path, port: int, extra_args: list[str] | None = None) -> None: + print(f"--------- starting {label} {data_dir}") + run([ + ZEN_EXE, "up", "--port", str(port), "--show-console", "--", + f"--data-dir={data_dir}", + *SERVER_ARGS, + *(extra_args or []), + ]) + print() + + +def wipe_or_create(label: str, path: Path) -> None: + if path.exists(): + print(f"--------- cleaning {label} {path}") + run([ZEN_EXE, "wipe", "-y", path]) + else: + print(f"--------- creating {label} {path}") + path.mkdir(parents=True, exist_ok=True) + print() + + +def check_prerequisites() -> None: + if not ZEN_EXE.is_file(): + sys.exit(f"error: zen executable not found: {ZEN_EXE}") + if not ZEN_METADATA_DIR.is_dir(): + sys.exit(f"error: metadata directory not found: {ZEN_METADATA_DIR}") + for build in BUILDS: + metadata = ZEN_METADATA_DIR / f"{build.name}.json" + if not metadata.is_file(): + sys.exit(f"error: metadata file not found: {metadata}") + + +def main() -> None: + global ZEN_EXE + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "positional_path", + nargs="?", + default=None, + type=Path, + metavar="DATA_PATH", + help="root path for all data directories (positional shorthand for --data-path)", + ) + parser.add_argument( + "zen_exe_positional", + nargs="?", + default=None, + type=Path, + metavar="ZEN_EXE_PATH", + help="path to zen executable (positional shorthand for --zen-exe-path)", + ) + parser.add_argument( + "--data-path", + default=Path(Path(__file__).stem + "_datadir"), + type=Path, + metavar="PATH", + help=f"root path for all data directories (default: {Path(__file__).stem}_datadir)", + ) + parser.add_argument( + "--zen-exe-path", + default=ZEN_EXE, + type=Path, + metavar="PATH", + help=f"path to zen executable (default: {ZEN_EXE})", + ) + args = parser.parse_args() + + data_path = args.positional_path + if data_path is None: + data_path = args.data_path + + ZEN_EXE = args.zen_exe_positional + if ZEN_EXE is None: + ZEN_EXE = args.zen_exe_path + zen_system_dir = data_path / "system" + zen_download_dir = data_path / "Download" + zen_cache_data_dir = data_path / "ZenBuildsCache" + zen_upload_dir = data_path / "Upload" + zen_chunk_cache_path = data_path / "ChunkCache" + + check_prerequisites() + + start_server("cache zenserver", zen_cache_data_dir, ZEN_CACHE_PORT, ["--buildstore-enabled"]) + try: + wipe_or_create("download folder", zen_download_dir) + wipe_or_create("system folder", zen_system_dir) + + for build in BUILDS: + print(f"--------- importing {build.name} build") + run([ + ZEN_EXE, "builds", "download", + "--host", "https://jupiter.devtools.epicgames.com", + "--namespace", "fortnite.oplog", + "--bucket", build.bucket, + "--build-id", build.id, + "--local-path", zen_download_dir / build.name, + f"--zen-cache-host={ZEN_CACHE}", + f"--allow-partial-block-requests={ZEN_PARTIAL_REQUEST_MODE}", + "--verify", + "--system-dir", zen_system_dir, + ]) + print() + + wipe_or_create("upload folder", zen_upload_dir) + + for build in BUILDS: + print(f"--------- exporting {build.name} build") + run([ + ZEN_EXE, "builds", "upload", + "--storage-path", zen_upload_dir, + "--build-id", build.id, + "--local-path", zen_download_dir / build.name, + "--verify", + "--system-dir", zen_system_dir, + "--metadata-path", str(ZEN_METADATA_DIR / f"{build.name}.json"), + "--create-build", + "--chunking-cache-path", zen_chunk_cache_path, + ]) + print() + finally: + stop_server("cache zenserver", ZEN_CACHE_PORT) + + +if __name__ == "__main__": + main() diff --git a/scripts/test_scripts/metadatas/AndroidClient.json b/scripts/test_scripts/metadatas/AndroidClient.json new file mode 100644 index 000000000..378d0454d --- /dev/null +++ b/scripts/test_scripts/metadatas/AndroidClient.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 AndroidClient", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "Android", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/IOSClient.json b/scripts/test_scripts/metadatas/IOSClient.json new file mode 100644 index 000000000..fb0f9a342 --- /dev/null +++ b/scripts/test_scripts/metadatas/IOSClient.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 IOSClient", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "IOS", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/LinuxServer.json b/scripts/test_scripts/metadatas/LinuxServer.json new file mode 100644 index 000000000..02ae2d970 --- /dev/null +++ b/scripts/test_scripts/metadatas/LinuxServer.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 LinuxServer", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "Linux", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/PS4Client.json b/scripts/test_scripts/metadatas/PS4Client.json new file mode 100644 index 000000000..6e49e3e5e --- /dev/null +++ b/scripts/test_scripts/metadatas/PS4Client.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 PS4Client", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "PS4", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/Switch2Client.json b/scripts/test_scripts/metadatas/Switch2Client.json new file mode 100644 index 000000000..41732e7bc --- /dev/null +++ b/scripts/test_scripts/metadatas/Switch2Client.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 Switch2Client", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "Switch2", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/SwitchClient.json b/scripts/test_scripts/metadatas/SwitchClient.json new file mode 100644 index 000000000..49362f23e --- /dev/null +++ b/scripts/test_scripts/metadatas/SwitchClient.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 SwitchClient", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "Switch", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/WindowsClient.json b/scripts/test_scripts/metadatas/WindowsClient.json new file mode 100644 index 000000000..c7af270c2 --- /dev/null +++ b/scripts/test_scripts/metadatas/WindowsClient.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 Windows Client", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "Windows", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/metadatas/XB1Client.json b/scripts/test_scripts/metadatas/XB1Client.json new file mode 100644 index 000000000..36fb45801 --- /dev/null +++ b/scripts/test_scripts/metadatas/XB1Client.json @@ -0,0 +1,9 @@ +{ + "name": "++Fortnite+Main-CL-50966326 XB1Client", + "branch": "ZenBuildTest2", + "baselineBranch": "ZenBuildTest2", + "platform": "XB1", + "project": "Fortnite", + "changelist": 50966326, + "buildType": "staged-build" +} diff --git a/scripts/test_scripts/oplog-import-export-test.py b/scripts/test_scripts/oplog-import-export-test.py new file mode 100644 index 000000000..51593d5a9 --- /dev/null +++ b/scripts/test_scripts/oplog-import-export-test.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +"""Test script for oplog import/export operations.""" + +from __future__ import annotations + +import argparse +import platform +import subprocess +import sys +from pathlib import Path +from typing import NamedTuple + +_PLATFORM = "windows" if sys.platform == "win32" else "macosx" if sys.platform == "darwin" else "linux" +_ARCH = "x64" if sys.platform == "win32" else platform.machine().lower() +_EXE_SUFFIX = ".exe" if sys.platform == "win32" else "" + + +class Build(NamedTuple): + name: str + bucket: str + id: str + + +BUILDS = [ + Build("XB1Client", "fortnitegame.oplog.fortnite-main.xb1client", "09a75f7f3b7517653dcdaaa4"), + Build("WindowsClient", "fortnitegame.oplog.fortnite-main.windowsclient", "09a75d977ef944ecfd0eddfd"), + Build("SwitchClient", "fortnitegame.oplog.fortnite-main.switchclient", "09a74d03b3598ec94cfd2644"), + Build("XSXClient", "fortnitegame.oplog.fortnite-main.xsxclient", "09a76c2bbd6cd78f4d40d9ea"), + Build("Switch2Client", "fortnitegame.oplog.fortnite-main.switch2client", "09a7686b3d9faa78fb24a38f"), + Build("PS4Client", "fortnitegame.oplog.fortnite-main.ps4client", "09a75b72d1c260ed26020140"), + Build("LinuxServer", "fortnitegame.oplog.fortnite-main.linuxserver", "09a747f5e0ee83a04be013e6"), + Build("IOSClient", "fortnitegame.oplog.fortnite-main.iosclient", "09a75f677e883325a209148c"), + Build("Android_ASTCClient", "fortnitegame.oplog.fortnite-main.android_astcclient", "09a7422c08c6f37becc7d37f"), +] + +ZEN_EXE: Path = Path(f"./build/{_PLATFORM}/{_ARCH}/release/zen{_EXE_SUFFIX}") + +ZEN_PORT = 8558 +ZEN_CACHE_PORT = 8559 +ZEN_CACHE = f"http://127.0.0.1:{ZEN_CACHE_PORT}" +ZEN_CACHE_POPULATE = "true" +ZEN_PARTIAL_REQUEST_MODE = "true" + +SERVER_ARGS: tuple[str, ...] = ( + "--http", "asio", + "--gc-cache-duration-seconds", "1209600", + "--gc-interval-seconds", "21600", + "--gc-low-diskspace-threshold", "2147483648", + "--cache-bucket-limit-overwrites", +) + + +def run(cmd: list[str | Path]) -> None: + try: + subprocess.run(cmd, check=True) + except FileNotFoundError: + sys.exit(f"error: executable not found: {cmd[0]}") + except subprocess.CalledProcessError as e: + sys.exit(f"error: command failed with exit code {e.returncode}:\n {' '.join(str(x) for x in e.cmd)}") + + +def stop_server(label: str, port: int) -> None: + """Stop a zen server. Tolerates failures so it is safe to call from finally blocks.""" + print(f"--------- stopping {label}") + try: + subprocess.run([ZEN_EXE, "down", "--port", str(port)]) + except OSError as e: + print(f"warning: could not stop {label}: {e}", file=sys.stderr) + print() + + +def start_server(label: str, data_dir: Path, port: int, extra_args: list[str] | None = None) -> None: + print(f"--------- starting {label} {data_dir}") + run([ + ZEN_EXE, "up", "--port", str(port), "--show-console", "--", + f"--data-dir={data_dir}", + *SERVER_ARGS, + *(extra_args or []), + ]) + print() + + +def wipe_or_create(label: str, path: Path) -> None: + if path.exists(): + print(f"--------- cleaning {label} {path}") + run([ZEN_EXE, "wipe", "-y", path]) + else: + print(f"--------- creating {label} {path}") + path.mkdir(parents=True, exist_ok=True) + print() + + +def check_prerequisites() -> None: + if not ZEN_EXE.is_file(): + sys.exit(f"error: zen executable not found: {ZEN_EXE}") + + +def setup_project(port: int) -> None: + """Create the FortniteGame project and all oplogs on the server at the given port.""" + print("--------- creating FortniteGame project") + run([ZEN_EXE, "project-create", f"--hosturl=127.0.0.1:{port}", "FortniteGame", "--force-update"]) + print() + + for build in BUILDS: + print(f"--------- creating {build.name} oplog") + run([ZEN_EXE, "oplog-create", f"--hosturl=127.0.0.1:{port}", "FortniteGame", build.name, "--force-update"]) + print() + + +def main() -> None: + global ZEN_EXE + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "positional_path", + nargs="?", + default=None, + type=Path, + metavar="DATA_PATH", + help="root path for all data directories (positional shorthand for --data-path)", + ) + parser.add_argument( + "zen_exe_positional", + nargs="?", + default=None, + type=Path, + metavar="ZEN_EXE_PATH", + help="path to zen executable (positional shorthand for --zen-exe-path)", + ) + parser.add_argument( + "--data-path", + default=Path(Path(__file__).stem + "_datadir"), + type=Path, + metavar="PATH", + help=f"root path for all data directories (default: {Path(__file__).stem}_datadir)", + ) + parser.add_argument( + "--zen-exe-path", + default=ZEN_EXE, + type=Path, + metavar="PATH", + help=f"path to zen executable (default: {ZEN_EXE})", + ) + args = parser.parse_args() + + data_path = args.positional_path + if data_path is None: + data_path = args.data_path + + ZEN_EXE = args.zen_exe_positional + if ZEN_EXE is None: + ZEN_EXE = args.zen_exe_path + zen_data_dir = data_path / "DDC" / "OplogsZen" + zen_cache_data_dir = data_path / "DDC" / "ZenBuildsCache" + zen_import_data_dir = data_path / "DDC" / "OplogsZenImport" + export_dir = data_path / "Export" / "FortniteGame" + + check_prerequisites() + + start_server("cache zenserver", zen_cache_data_dir, ZEN_CACHE_PORT, ["--buildstore-enabled"]) + try: + wipe_or_create("zenserver data", zen_data_dir) + start_server("zenserver", zen_data_dir, ZEN_PORT) + try: + setup_project(ZEN_PORT) + + for build in BUILDS: + print(f"--------- importing {build.name} oplog") + run([ + ZEN_EXE, "oplog-import", + f"--hosturl=127.0.0.1:{ZEN_PORT}", + "FortniteGame", build.name, + "--clean", + "--builds", "https://jupiter.devtools.epicgames.com", + "--namespace", "fortnite.oplog", + "--bucket", build.bucket, + "--builds-id", build.id, + f"--zen-cache-host={ZEN_CACHE}", + f"--zen-cache-upload={ZEN_CACHE_POPULATE}", + f"--allow-partial-block-requests={ZEN_PARTIAL_REQUEST_MODE}", + ]) + print() + + print(f"--------- validating {build.name} oplog") + run([ZEN_EXE, "oplog-validate", f"--hosturl=127.0.0.1:{ZEN_PORT}", "FortniteGame", build.name]) + print() + + wipe_or_create("export folder", export_dir) + + for build in BUILDS: + print(f"--------- exporting {build.name} oplog") + run([ + ZEN_EXE, "oplog-export", + f"--hosturl=127.0.0.1:{ZEN_PORT}", + "FortniteGame", build.name, + "--file", export_dir, + "--forcetempblocks", + ]) + print() + finally: + stop_server("zenserver", ZEN_PORT) + + wipe_or_create("alternate zenserver data", zen_import_data_dir) + start_server("import zenserver", zen_import_data_dir, ZEN_PORT) + try: + setup_project(ZEN_PORT) + + for build in BUILDS: + print(f"--------- importing {build.name} oplog") + run([ + ZEN_EXE, "oplog-import", + f"--hosturl=127.0.0.1:{ZEN_PORT}", + "FortniteGame", build.name, + "--file", export_dir, + ]) + print() + + print(f"--------- validating {build.name} oplog") + run([ZEN_EXE, "oplog-validate", f"--hosturl=127.0.0.1:{ZEN_PORT}", "FortniteGame", build.name]) + print() + finally: + stop_server("alternative zenserver", ZEN_PORT) + finally: + stop_server("cache zenserver", ZEN_CACHE_PORT) + + +if __name__ == "__main__": + main() |