test(peer-cli): cover solid streamed installs
NEXT_STEPS item 3 needed solid archive handling to be a deliberate contract instead of an incidental RAR header attribute. Add a tiny real solid RAR fixture and S41 to the extended peer-cli scenarios so the Docker harness proves this path end to end. The scenario verifies the source archive with container-bundled `unrar lt`, streams the install with the injected provider, and then asserts the receiver is installed local-only without a root archive or root `version.ini`. It also compares local payload SHA-256 hashes against `unrar p` output and checks the streamed byte count matches the extracted entries. This keeps the existing one metadata pass plus one sequential payload pass contract covered for solid archives. Test Plan: - just fmt - just test - python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.py - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 --build-image - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 - git diff --check - git diff --cached --check Refs: NEXT_STEPS.md item 3
This commit is contained in:
Binary file not shown.
@@ -0,0 +1 @@
|
||||
20160128
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the peer-cli scenarios S1-S40 through Docker."""
|
||||
"""Run the peer-cli scenarios S1-S41 through Docker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -328,6 +328,7 @@ class Runner:
|
||||
("S37", self.s37_single_source_download_throughput),
|
||||
("S39", self.s39_streamed_install_local_only),
|
||||
("S40", self.s40_streamed_receiver_not_source),
|
||||
("S41", self.s41_solid_archive_streamed_install),
|
||||
]
|
||||
|
||||
for scenario_id, scenario in scenarios:
|
||||
@@ -1171,6 +1172,84 @@ class Runner:
|
||||
f"and download errored '{err['error']}'"
|
||||
)
|
||||
|
||||
def s41_solid_archive_streamed_install(self) -> str:
|
||||
source_dir = self.fixture_root / "s41-solid-source"
|
||||
source_game = source_dir / "cnctw"
|
||||
shutil.copytree(FIXTURES / "fixture-solid" / "cnctw", source_game)
|
||||
|
||||
source = self.peer("s41-solid-source", games_dir=source_dir)
|
||||
assert_peer_rar_archive_solid(source, "cnctw")
|
||||
client = self.peer("s41-solid-client")
|
||||
connect_many(client, [source])
|
||||
wait_remote_game(client, "cnctw", peer_count=1, version="20160128")
|
||||
|
||||
waiter = LineWaiter(len(client.output))
|
||||
client.send({"cmd": "stream-install", "game_id": "cnctw"})
|
||||
client.wait_for(
|
||||
event_is("got-game-files", "cnctw"),
|
||||
timeout=20,
|
||||
description="got solid cnctw files",
|
||||
waiter=waiter,
|
||||
)
|
||||
client.wait_for(
|
||||
event_is("download-finished", "cnctw"),
|
||||
timeout=60,
|
||||
description="solid stream finish cnctw",
|
||||
waiter=waiter,
|
||||
)
|
||||
client.wait_for(
|
||||
event_is("install-finished", "cnctw"),
|
||||
timeout=30,
|
||||
description="solid stream install cnctw",
|
||||
waiter=waiter,
|
||||
)
|
||||
|
||||
game = wait_local_game(client, "cnctw", downloaded=False, installed=True)
|
||||
assert_game_state(
|
||||
game,
|
||||
downloaded=False,
|
||||
installed=True,
|
||||
availability="LocalOnly",
|
||||
)
|
||||
game_root = client.host_games_dir / "cnctw"
|
||||
assert_not_exists(game_root / "version.ini")
|
||||
assert_not_exists(game_root / "cnctw.eti")
|
||||
|
||||
expected = {
|
||||
"bin/cnctw-solid-payload.bin": unrar_entry_sha256(
|
||||
source, "cnctw", "bin/cnctw-solid-payload.bin"
|
||||
),
|
||||
"data/cnctw-solid-assets.dat": unrar_entry_sha256(
|
||||
source, "cnctw", "data/cnctw-solid-assets.dat"
|
||||
),
|
||||
}
|
||||
actual = {
|
||||
rel: sha256_file(game_root / "local" / rel)
|
||||
for rel in expected
|
||||
}
|
||||
if actual != expected:
|
||||
raise ScenarioError(
|
||||
f"solid streamed payload hashes mismatched: {actual} != {expected}"
|
||||
)
|
||||
|
||||
streamed_bytes = sum(
|
||||
int(item.get("data", {}).get("length", 0))
|
||||
for item in client.output
|
||||
if item.get("type") == "event"
|
||||
and item.get("event") == "download-chunk-finished"
|
||||
and item.get("data", {}).get("game_id") == "cnctw"
|
||||
)
|
||||
expected_bytes = sum((game_root / "local" / rel).stat().st_size for rel in expected)
|
||||
if streamed_bytes != expected_bytes:
|
||||
raise ScenarioError(
|
||||
f"solid streamed byte count mismatch: {streamed_bytes} != {expected_bytes}"
|
||||
)
|
||||
|
||||
return (
|
||||
"solid cnctw archive streamed through one local-only install; "
|
||||
f"payload hashes={actual}, bytes={streamed_bytes}"
|
||||
)
|
||||
|
||||
|
||||
def run(command: list[str], description: str) -> subprocess.CompletedProcess[str]:
|
||||
result = subprocess.run(
|
||||
@@ -1307,6 +1386,22 @@ def unrar_entry_sha256(peer: Peer, game_id: str, relative_path: str) -> str:
|
||||
return output.split()[0]
|
||||
|
||||
|
||||
def assert_peer_rar_archive_solid(peer: Peer, game_id: str) -> None:
|
||||
output = peer.docker_exec(
|
||||
"unrar",
|
||||
"lt",
|
||||
"-cfg-",
|
||||
f"/games/{game_id}/{game_id}.eti",
|
||||
).stdout
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("Details:"):
|
||||
if "solid" in stripped.lower():
|
||||
return
|
||||
raise ScenarioError(f"RAR archive is not solid: {game_id}")
|
||||
raise ScenarioError(f"RAR archive details were not reported: {game_id}")
|
||||
|
||||
|
||||
def format_bytes(size: int) -> str:
|
||||
return f"{size / 1024 / 1024 / 1024:.2f} GiB"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user