Testing and release evidence

Repository Tests for Local AI Model State

Test artifact metadata, download state, verification, selection, deletion, repair, and concurrent observers.

Review status: Fact and editorial QA complete; owner review pending · 2026-10-06 · Fact IDs: COVE-DOWNLOAD-01, COVE-MODEL-02

Direct answer

Test the local model persistence boundary that exists in the application rather than assuming every codebase has a class named ModelRepository. In this repository, ModelDownloader owns file transfer details and takes a storage directory; ModelManager coordinates model selection, storage lookup, download state, loading, and engine access. Those are related but distinct classes. A test guide may use repository as the general architectural concept, but it must name the concrete owners and avoid promising injectable storage or network providers that the current constructors do not expose. Start with a storage fixture isolated from user data. For ModelDownloader, its constructor accepts a File storage directory, so a temporary test directory can exercise target and temporary file placement without writing a real shared model folder. Record the configured filename and expected size from a test ModelConfig, then assert which artifacts exist after each outcome. The current downloader creates a .tmp file, emits Progress, and attempts to rename the temporary file to the configured final name; its renameTo return value is not checked before emitting Completed. Tests must inspect the final file and bytes rather than infer successful placement or verification from a terminal event alone. The network and platform boundaries need honest treatment. ModelDownloader opens HttpURLConnection internally and also uses Android StatFs and Log; accepting a File directory does not make its collected download flow a platform-free JVM unit. Plain JVM checks can exercise extracted file bookkeeping, while executing this current downloader needs an Android-aware test environment or explicitly documented platform doubles. It does not receive an injected DownloadProvider interface, so a test cannot pass a fake provider through a constructor that does not exist. A narrow connection factory is a possible engineering change; a controlled local HTTP server is an integration-test option in the appropriate harness. Keep filesystem, Android storage queries, and transfer behavior separate, and state how each boundary was supplied. Test download outcomes against the code that is present. The current path performs a storage precheck, writes a temporary artifact, reports byte progress, requests HTTP Range for an existing partial file, and may try a configured mirror after timeout or DNS failure. SHA-256 verification occurs only when ModelConfig contains an expected digest; COVE-MODEL-02 says the current Gemma 4 E2B digest field is empty, so a test must not call that artifact hash-verified. A fixture with a known non-empty test digest can exercise the verification branch without asserting that the shipped config contains one. A digest mismatch currently deletes the temporary file and emits an error; capture that actual behavior. ModelManager state tests require more than checking the enum exists. Its StateFlow starts at NOT_DOWNLOADED, download and load methods update state, and its storage search checks a shared directory and an app-specific directory. loadModel uses a mutex and, on any caught Exception, deletes the selected model file, clears the engine reference, and returns to NOT_DOWNLOADED. That deletion path does not prove that a digest check identified corruption: the failure may be a native initialization problem. Tests should distinguish the observed implementation behavior from a safer future design that preserves a known working artifact after an unrelated load error. Do not overstate concurrency or atomicity. loadModel and setVariant use the load mutex, while release does not acquire that same mutex before closing the engine and setting DOWNLOADED. The downloader is a separate object created from the active storage directory; ModelManager checks its StateFlow before starting background work, but the source alone does not establish every cross-instance or cross-process race. A test can assert a specific ordering for one instance and injected schedule, then note whether concurrent calls from another owner remain untested. StateFlow is a current-state holder and may conflate rapid transitions for a late collector, so assert current state and user-visible outcomes deliberately instead of requiring every intermediate value to be observed by every subscriber. Build a transition matrix from real owner methods: no artifact, a final file in each supported directory, temporary download, progress, completion, download error, variant switch, load success, load failure, release, and delete. For each, record initial filesystem inventory, call, emitted DownloadState values if the downloader is collected, ModelManager state, remaining files, and the next allowed action. Keep model files, partial transfers, and user-created records in separate fixtures. Never claim a model cleanup test proves chat or photo data was preserved unless the test inspects those independent stores. Failure assertions should preserve causality. For timeout and DNS cases, verify whether the configured mirror path was attempted; for an unrelated HTTP or file error, verify the actual terminal emission and remaining temporary file behavior. For digest tests, use a test artifact and known expected value; distinguish no digest configured from a digest pass. For ModelManager.loadModel, record that current code removes the selected file on caught initialization exceptions, while a product may choose a different repair policy after review. This avoids disguising a broad catch as precise corruption detection or describing current behavior as rollback. The final report should name the class under test, storage directory fixture, exact ModelConfig fields, response source, digest state, initial and final files, collected states, and exception category. A temporary-directory unit test can prove local file bookkeeping for its fixture; a controlled HTTP test can prove a response path; neither proves that every Android storage volume, server, device, or model runtime behaves identically. If dependency injection is missing at a boundary, call that a testability limit and propose the smallest interface needed, rather than faking an API in the article.

A resumed transfer completes but model loading then fails

A test begins with a partial model file in a temporary storage directory, resumes the download using a controlled response, and reaches the final path. The next ModelManager load fails during native initialization, and the team must report the current deletion behavior without calling the file digest-invalid.

Decision

Record transfer completion, configured digest presence, load result, state, and file deletion as separate observations; frame artifact preservation as a future policy choice unless a test proves it exists.

Expected outcome

The report distinguishes transfer, optional digest verification, native load, and current recovery behavior without attributing every failure to corruption.

Evidence to collect

The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:

  • Repository contract
  • Storage and failure fixtures
  • State emission order

A release claim is valid only for the recorded configuration and acceptance set. Generalization requires additional records.

Implementation workflow

  1. Name the actual component boundary. Identify whether the subject is ModelDownloader, ModelManager, ModelConfig, or a real repository abstraction. This project currently uses the first two; do not treat ModelManager as a repository class.
  2. Create isolated storage fixtures. Pass ModelDownloader a temporary directory and record file names, expected size, digest setting, and initial partial/final files. Keep fixtures away from shared model storage and user data.
  3. Make network behavior controllable. The current URLConnection construction has no injected provider parameter. Use a narrowly added connection factory or a controlled local server for transfer-path tests; keep purely filesystem assertions separate.
  4. Assert emissions and state per operation. Collect DownloadState and observe ModelManager StateFlow for storage lookup, progress, completion, error, variant selection, load, release, and delete. Account for StateFlow conflation when a collector starts late.
  5. Separate integrity from initialization. Use a known non-empty test digest to exercise mismatch and match paths. Treat an empty configured digest as no hash verification, and do not label a generic native load exception a digest failure.
  6. Record cleanup and limitations. Compare before/after files and state, note current load-failure deletion behavior, and distinguish observed behavior from a proposed safer preservation policy or untested cross-instance race.

Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.

Acceptance criteria

  • Tests name the concrete downloader/manager API, storage fixture, transfer source, config metadata, state observations, and remaining files.
  • No-digest, digest-match, digest-mismatch, download error, and native initialization error are distinct cases and are not reported as equivalent integrity outcomes.
  • Current ModelManager behavior is reported accurately: a caught load exception removes the selected file; rollback or preservation is labeled a proposed policy unless separately implemented and tested.

Decision table

DimensionChoice or evidenceHow to interpret it
ModelDownloader file testTemporary storage directory and collected DownloadStateCan verify the concrete file/progress path; URLConnection is not currently constructor-injected.
ModelManager lifecycle testContext-backed storage lookup, StateFlow, load, variant, release, and deleteExercises the existing coordinator; it is not evidence for a nonexistent ModelRepository class.
Future repository contract testFake filesystem and download-provider interfacesUseful after those seams are introduced; do not claim the current implementation can accept them.

Failure patterns to prevent

  • Reading files directly from UI
  • Mixing user data with model cleanup

Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.

Minimum reproducibility record

LayerRecord
DeviceManufacturer, model, chipset, RAM class, operating-system build
SoftwareApplication version and git commit
ModelFamily, variant, revision, format, file length, hash, quantization
RuntimeName, revision, requested backend, observed backend evidence
WorkloadFixture revision, input hash, prompt hash, output policy
OutcomeCompleted, failed, cancelled, fallback, and privacy-safe diagnostics

Release checklist

  • ☐ The primary query is answered without an unsupported number.
  • ☐ Every artifact and runtime is pinned.
  • ☐ The representative task and failure policy are explicit.
  • ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
  • ☐ User-content and network boundaries are documented.
  • ☐ Result wording applies only to the recorded configuration.
  • ☐ The page links to raw method or evidence when results are added.

Sources and related evidence

Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.

Frequently asked questions

Does GemmaApp currently have a ModelRepository class?

The inspected core code has ModelDownloader and ModelManager; no ModelRepository class was found. Describe those concrete owners and treat repository abstraction as a possible future design, not a current API.

Does a ModelManager load failure prove the model file is corrupt?

No. Current code catches an Exception, deletes the selected file, and returns to NOT_DOWNLOADED. That behavior does not establish that a digest check found corruption or identify the failure cause.

Does an empty SHA-256 field mean the model passed verification?

No. It means the downloader has no configured expected digest to compare. Report that verification was not performed rather than treating the file as hash-verified; describe only the exact test configuration.