acdream/tests/AcDream.Launcher.Tests
Erik 00d1278228 feat(launcher): LU1 — stop hashing 28 GB before the launcher window appears
Measured on the user's machine: %LOCALAPPDATA%\acdream\pak\acdream.pak is
29,908,271,024 bytes and SHA-256 over it takes 24.1 s at 1.16 GB/s. App
.OnFrameworkInitializationCompleted ran exactly that hash synchronously,
before constructing the window, and the digest came back identical to the one
install.json already recorded. So the launcher took roughly half a minute to
appear in order to re-confirm a fact that had not changed. A friend does not
see it only because they have no package installed yet — verification
short-circuits at "nothing installed" — so it would hit them the moment
first-run setup finished.

Startup now checks the cheap facts (size, last-write time) and skips only the
hash, and only when a previous FULL hash of that same file agreed with the
install record. Everything that should hash still does: install, update, the
crash-recovery backup path, and a new explicit "Verify files" button.

The remembered fact lives in a SIDECAR (install.verification.json), not as a
new field on the install record: LauncherInstallRecordStore reads install.json
with JsonUnmappedMemberHandling.Disallow, so a new property there would make an
older launcher build reject the record outright and demand a fresh ~28 GB bake
after a rollback. An unknown sidecar is simply ignored by builds that predate
it. The cache type never throws — it sits in front of a guarantee, so every
failure mode (missing, corrupt, unknown schema, unwritable) degrades to
"hash it again" rather than to a failed launch.

Two subtleties worth keeping:
- The write time is re-read after the hash and the entry is only written when
  it is unchanged. A writer racing a multi-second hash would otherwise be
  remembered under the OLD timestamp, and the next startup would trust a
  digest that never covered those bytes.
- A hash that disagrees with the record invalidates the entry, so a stale
  "verified" fact cannot outlive the evidence that produced it.

Tests: PreparedAssetVerificationCacheTests (10) counts hash invocations through
the store's injectable hasher and covers second-startup skip, forced full
verification, touched package, same-size silent corruption, resize, a cache
digest that disagrees with the record, three unreadable-cache shapes, and
backup recovery still hashing. Plus two LauncherWindowViewModel tests for the
Verify files command. Launcher.Core 335 passed, Launcher 57 passed.

Note for the first run after this ships: the very first startup still pays one
full hash to learn the digest for the installed file, and every startup after
that is instant.

Campaign LU slice LU1. Plan: docs/plans/2026-08-19-launcher-usability-campaign.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:40:34 +02:00
..
AcDream.Launcher.Tests.csproj test: serialize Launcher.Tests via an assembly attribute, not xunit.runner.json 2026-08-19 14:06:24 +02:00
LauncherProjectBoundaryTests.cs ci: Gitea pipeline — gate on both self-hosted runners, publish alpha releases 2026-08-19 10:35:36 +02:00
LauncherStartupOptionsTests.cs fix(launcher): harden Campaign LA11 gate evidence 2026-08-15 01:08:41 +02:00
LauncherUpdateCompositionTests.cs test(launcher): Campaign LA — headless MainWindow view tests close #399 2026-08-15 07:54:49 +02:00
LauncherUpdateViewModelTests.cs feat(launcher): implement verified atomic updates 2026-08-14 22:09:34 +02:00
LauncherWindowViewModelTests.cs feat(launcher): LU1 — stop hashing 28 GB before the launcher window appears 2026-08-19 18:40:34 +02:00
MainWindowViewTests.cs test: lane the one Avalonia test that needs a real desktop session 2026-08-19 14:15:21 +02:00
packages.neutral.lock.json build: make release restore reproducible 2026-08-18 10:29:00 +02:00
TestAppBuilder.cs test: serialize Launcher.Tests via an assembly attribute, not xunit.runner.json 2026-08-19 14:06:24 +02:00