# ic-testkit package changelog and migration guide
This file ships in the crate archive so upgrades can be completed without the
repository checkout. The complete historical changelog remains at
<https://github.com/dragginzgame/ic-testkit/blob/main/CHANGELOG.md>.
## 0.14.7
This patch release avoids Cargo metadata copies and repeated validation of
baseline receipt IDs, simplifies artifact schema checks, and gives executable
test fixtures one shared owner. Public APIs and persisted layouts are unchanged;
existing `0.14` callers need no source migration. Repository-owned format
identifiers remain `v1`.
- Cargo metadata indexes, dependency traversal, and semantic projection borrow
identifiers and strings from the parsed document. Package selection no
longer collects a temporary match list. Feature isolation, malformed-input
diagnostics, conservative projection fallback, source guards, and exact
fingerprints are preserved.
- Baseline-derived restore receipts reuse snapshot capture's deterministic
ordering and duplicate validation without rebuilding a set. Empty receipt
sets remain invalid; caller-supplied IDs still receive full validation.
- Artifact validation checks canonical output names against the declared count
without rebuilding expected filename sets. Root-entry checks borrow allowed
names. Strict schema and filename rejection, regular-file checks, failure
diagnostics, filesystem errors, and persisted filenames are preserved.
- Unit and integration tests use one executable-fixture writer. Child-process
writing and waiting prevent inherited writable script handles from causing
intermittent Linux "Text file busy" launches in integration fixtures too.
Real Cargo wrapper coverage and production command execution are unchanged.
Twenty-two targeted unit checks, ten artifact/Wasm integration checks, and six
live PocketIC 16 baseline-pool checks pass. Coverage includes concurrent prepared
readers, source-race invalidation, cross-process artifact retention and exact
build coordination, staged and cached schema rejection, native non-UTF-8 names
on Unix, receipt validation and recovery, and 100 consecutive restores without
reconstruction.
Targeted Clippy, formatting, diff, and source-package inclusion checks also pass.
No downstream suite speedup is claimed; full pre-push validation remains
maintainer-owned.
## 0.14.6
This patch release shares immutable Cargo input lists, simplifies batch and
artifact hashing ownership, and makes fixture-pool states explicit. Public APIs
and persisted layouts are unchanged; existing `0.14` callers need no source
migration. Repository-owned format identifiers remain `v1`.
- Cargo input snapshot clones share immutable input and exclusion lists while
retaining independent timing values. Sessions no longer repeat an already
established timing reset. Source revalidation and prepared-reader lease
invalidation retain their behavior.
- Wasm batches borrow group membership and filter pending entries without
temporary index lists or a copied workspace path. Environment grouping,
feature isolation, indexed failures, and progress reporting are preserved.
- Labeled-path hashing owns the single collection needed for deterministic
sorting. Artifact transactions borrow filesystem paths, and watched ICP
inputs borrow labels. Native path ordering, exclusions, digest framing, and
cache identities are unchanged.
- Input and tool label validation shares one rule with separate namespaces.
Duplicates within a namespace are rejected before cache acquisition; an
input and a tool may still have the same label.
- Bounded pools replace validity flags with explicit slot states. Unwind
recovery preserves both its cause and any retained value, including panics
before population. Safe teardown, FIFO scheduling, cancellation, capacity,
explicit invalidation, and restore-failure recovery remain intact.
- Executable unit-test fixtures are written in a child process and awaited
before use. Parallel test subprocesses cannot inherit a writable script
handle, avoiding intermittent Linux "Text file busy" launches while retaining
real Cargo wrapper coverage. Production command execution is unchanged.
Thirty-one targeted unit checks, seven artifact/Wasm integration checks, and
ten live PocketIC 16 checks pass. Coverage includes concurrent prepared readers,
source-race invalidation, cross-process artifact coordination, pool scheduling,
panic and restore-failure recovery, and 100 consecutive restores without
reconstruction. Targeted Clippy, formatting, and diff checks also pass. No
downstream suite speedup is claimed; full pre-push validation remains
maintainer-owned.
## 0.14.5
This patch release consolidates Cargo input safety checks and simplifies warm
baseline reuse, temporary Wasm specification ownership, diagnostic state, and
benchmark discovery. Public APIs and persisted layouts are unchanged; existing
`0.14` callers need no source migration. Repository-owned format identifiers
remain `v1`.
- Wasm batches borrow caller specifications and grouping paths instead of
copying them for input resolution. Sessions and prepared snapshots still own
identities retained across calls. Feature isolation, metadata/environment
grouping, concurrent prepared readers, and source-lease invalidation retain
their existing behavior.
- Standalone builds and batches share input discovery, shared-target boundary
validation, and generated-directory exclusions. Checks still precede hashing,
maintenance, and builds; unsafe entries preserve their input-discovery errors
without blocking compatible batch entries or deleting source files.
- Warm baseline reuse compares captured canister IDs directly with restore
receipts, allocating complete diagnostic ID lists only on mismatch.
Successful preparation leaves lifecycle metadata unchanged instead of
clearing an already-empty invalidation reason. Reset coverage, readiness,
validation, explicit invalidation, and unwind recovery retain their behavior.
- Diagnostic log rendering derives omitted-record counts and accumulates raw
byte totals in one traversal. Empty records, independent record/byte bounds,
lossy UTF-8 handling, compact truncation text, and failure reporting are
preserved.
- Previous benchmark-run discovery selects the latest eligible match without
collecting and sorting all candidates. Metadata timestamp priority, numeric
indices, command filtering, and skipping unreadable or malformed metadata
remain unchanged.
Sixteen batch/input-resolution unit checks, four real Wasm build checks, five
diagnostics unit checks, three benchmark checks, and nine live PocketIC 16
checks pass. Baseline-pool coverage includes 100 consecutive restores without
reconstruction, receipt
mismatch recovery, explicit invalidation, failed recovery diagnostics, and
caller and recipe-hook panics. Benchmark coverage includes metadata schema,
timestamp priority, numeric indices, command filtering, and missing or malformed
candidates. Targeted Clippy, formatting, and diff checks also pass. No downstream
suite speedup is claimed; full pre-push validation remains maintainer-owned.
## 0.14.4
This patch release consolidates snapshot preparation and removes duplicated Wasm
input state. Public APIs and persisted layouts are unchanged; existing `0.14`
callers need no source migration. Repository-owned format identifiers remain
`v1`.
- Both snapshot capture APIs share complete duplicate validation and
deterministic ordering before issuing management calls. Temporary sender
vectors are removed while preserving explicit-sender and controller-fallback
contracts.
- Partial-capture rollback consumes its snapshot set instead of copying IDs.
Cleanup continues across failures and retains rejection and panic diagnostics.
- Wasm semantic hashing derives its path subset from the authoritative
validation list. A duplicated path list and fingerprint-mode wrapper are
removed; conservative fallback and workspace projection semantics remain.
- Cargo input revalidation borrows existing labels and paths rather than
reconstructing owned copies. Raw-source mutation guards, exclusions, native
path ordering, semantic digests, and exact cache identities are unchanged.
Seven focused live PocketIC 16 checks, one funding-policy unit check, six digest
checks, and seven artifact-input checks pass. Public snapshot behavior tests
replace private validation-helper tests. Targeted Clippy, formatting, and diff
checks also pass. No downstream suite speedup is claimed; full pre-push
validation remains maintainer-owned.
## 0.14.3
This patch release simplifies benchmark processing and Wasm cache finalization.
Public APIs and persisted layouts are unchanged; existing `0.14` callers need
no source migration. Repository-owned format identifiers remain `v1`.
- Benchmark pairing borrows markers until constructing owned spans or
diagnostics. Aggregation stores group identity only in map keys, and
comparison and Markdown lookups borrow existing row keys. Nested pairing,
suite boundaries, deterministic ordering, overflow rejection, missing rows,
and duplicate-row precedence retain their existing behavior.
- Cold builds and missing exact-cache reconstruction use one success/failure
finalization path. Successful entries survive, incomplete entries are cleaned
up, and original errors, cleanup diagnostics, and timings are preserved.
- Canister installation moves the existing label into failure diagnostics
instead of cloning it before every install. Original causes and caller-owned
PocketIC instances remain available after failures.
- Digest text has one hexadecimal formatter that writes directly to its
destination. The owned-string API and persisted stamps, manifests, and cache
paths keep the same lowercase, zero-padded representation.
Twenty-five benchmark integration checks, one overflow check, three Wasm
cleanup/reconstruction checks, four artifact handoff checks, two live PocketIC
16 install checks, six digest checks, and two stamp/manifest checks pass.
Targeted Clippy, formatting, and Wasm compile checks also pass. No downstream
suite speedup is claimed; full pre-push validation remains maintainer-owned.
## 0.14.2
This patch release simplifies fixture and artifact ownership and adds opt-in
fixture performance measurements. Public library APIs and persisted layouts are
unchanged; existing `0.14` callers need no source migration. Repository-owned
format identifiers remain `v1`.
- Standalone fixture pools derive rebuild reasons from shared slot state,
removing duplicated invalidation metadata while preserving restore-failure
rebuilding and distinct panic recovery outcomes.
- Wasm build records expose the cache path held by their retention owner instead
of allocating a second copy. Cloned records continue retaining immutable
artifacts until their last drop.
- The repository includes a Linux benchmark comparing fresh fixtures with
baseline pools of capacity one and two using a caller-supplied PocketIC 16
binary. It validates every task and reports phase timings, setup/teardown,
sampled process-tree RSS, raw samples, and provenance. The workload and
measured capacity tradeoffs are documented in the repository's
[fixture benchmark guide](https://github.com/dragginzgame/ic-testkit/blob/main/docs/fixture-reuse-benchmark.md).
This tooling is outside ordinary tests/CI.
- Observed Cargo subprocess tests read their fixtures through the existing
system shell, avoiding intermittent Linux "Text file busy" launch failures
under parallel load. Output forwarding, captured diagnostics, exit events,
and host build-progress notifications retain real subprocess coverage;
production Cargo execution is unchanged.
Seven targeted PocketIC checks and four artifact handoff checks pass, covering
reuse, capacity, restore failures, panic recovery, cloned records, cross-process
retention, pruning, and terminated consumers. Two Python sampler checks,
targeted Clippy, and formatting also pass. The measurements compare fixture
strategies; they do not establish a library-version or downstream suite speedup.
Five focused output/progress checks pass, along with 500 parallel repetitions
of the two affected subprocess tests (1,000 test executions).
## 0.14.1
This patch release reduces artifact acquisition and fixture-pool overhead without
changing public APIs or persisted layouts. Existing `0.14.0` callers need no
source migration. Repository-owned cache, stamp, and digest identifiers remain
`v1`.
- Wasm stamps and transactional manifests reject mismatched format/build
identities before hashing outputs. Matching entries still require full content
validation and exact metadata; live retained entries cannot be replaced during
corruption recovery.
- Input hashing borrows declared paths and Unix-native filename bytes, and
computes directory sort keys once per entry. Native ordering and Windows
UTF-16 little-endian encoding retain their existing digest semantics.
- Digest-cache hits compare borrowed exclusion paths without reconstructing
cloned lists. Changes to relevant exclusions still rehash inputs, excluded
input roots are rejected, and external symlinks retain conservative checks.
- Cache size scans queue only directories while preserving logical file sizes
and counting symlink bytes without following their targets.
- Bounded fixture pools register and claim FIFO slots under one coordinator
lock, with one optional cancellation ticket and safe unwind cleanup. Capacity,
waiter ordering, cancellation wakeups, and invalidation remain unchanged.
Targeted checks cover native filenames, exact digests and stamps, corruption
recovery, retained consumers, pruning, cross-process handoff, FIFO cancellation,
and PocketIC 16 reuse with 100 consecutive baseline restores. Whole-suite
performance improvements are not yet measured.
## 0.14.0
Standalone fixture pools own one builder, removing the per-acquisition builder
that warm slots ignored. This is a source API hard cut. Snapshot funding,
capacity, restoration, recovery, guard types, and persisted formats are unchanged.
Repository-owned format identifiers remain `v1`.
### Migration
| `CachedStandaloneCanisterFixturePool::<N>::new()` or `default()` | `CachedStandaloneCanisterFixturePool::<N>::new(build_fixture)` |
| `pool.acquire(build_fixture)` | `pool.acquire()` |
| Pass a closure capturing configuration to every acquisition | Own it once with `CachedStandaloneCanisterFixturePool::<N, _>::new(move || build_fixture(&config))`. |
Static pools can use a function pointer or a noncapturing closure:
```rust
use ic_testkit::pic::{CachedStandaloneCanisterFixturePool, StandaloneCanisterFixture};
static POOL: CachedStandaloneCanisterFixturePool<8> =
CachedStandaloneCanisterFixturePool::<8>::new(build_fixture);
fn build_fixture() -> StandaloneCanisterFixture {
// Install and seed the canister here.
todo!()
}
let (fixture, outcome) = POOL.acquire()?;
```
The builder must produce the same Wasm, initialization, topology, and seeded
state each time it runs. Use separate pools for different recipes. Cold and
replacement slots invoke the owned builder; warm acquisitions restore the
captured snapshot. Apply the constructor and acquisition changes together when
downstream suites adopt `0.14`.
For statics that chain `with_restore_funding`, specify the constructor capacity
as above so Rust selects the default function-pointer builder type before
applying the funding policy.
### Implementation simplification
Wasm batches parse Cargo package, membership, and dependency indexes once per
resolution group. Each specification still selects its own dependency closure
and validates its filesystem inputs; differing features remain separate groups.
Standalone Wasm builds use the same parsed representation.
CI runs the canister integration target through the ordinary test stage, removing
its separate repeat and unused preliminary fixture build. `make test-canisters`
remains a focused entry point whose tests acquire their own artifacts;
`make build-test-canisters` remains available for manual builds.
Release-push guards exercise clean, dirty, untracked, stale-tag, and failed-push
behavior using harmless command doubles, replacing the exact recipe-text check.
## 0.13.0
This release gives benchmark identity and averages one authoritative
representation and verifies canister restore evidence without copying it into
non-snapshot reset receipts. The source API changes are hard cuts. Report schemas
and persisted cache layouts are unchanged; cache, stamp, protocol, and digest
identifiers remain `v1`.
### Migration
| `BenchmarkAggregateRow::suite`, `BenchmarkComparisonRow::suite`, or `BenchmarkAggregateError::suite` field | Call `suite()`. The label derives from the private scope; use `is_all_suites()` to distinguish an authored `ALL` suite from the cross-suite aggregate. |
| Read or assign `BenchmarkAggregateRow::average` | Read `average()`. Averages derive from `total` and `runs`; there is no separately writable average. |
| `ResetRequirements::try_new([CanisterSnapshots, CanisterCycles(policy), ...])` | `ResetRequirements::try_new(policy, [...])`, passing only non-snapshot requirements in the collection. Read the explicit cycle policy with `cycle_policy()`; `get()` and `iter()` cover non-snapshot domains. |
| Snapshot/cycle variants of `ResetDomainKind`, `ResetRequirement`, or `ResetAchievement` | Snapshot restoration is unconditional. Report restored canisters and achieved cycle policy in `CanisterRestoreReceipt`; report other guarantees in `ResetReceipt`. |
| Inspect snapshot/cycle achievements in `PreparedBaseline::Restored::reset` | Inspect its `canisters` receipt with `canister_ids()` and `cycle_policy()`. The `reset` receipt contains only non-snapshot achievements. |
| Match cycle failures through `ResetPolicyMismatch` | Match `CyclePolicyMismatch { required, achieved }`. `ResetPolicyMismatch` still reports non-snapshot policy mismatches. |
| Match `UndeclaredRequiredResetDomain` | Remove this constructor-error branch. The required cycle policy is a constructor argument, and complete snapshot restoration is enforced during preparation. |
For example:
```rust
use ic_testkit::pic::{
CycleResetPolicy, ResetRequirement, ResetRequirements, TimeResetPolicy,
};
let requirements = ResetRequirements::try_new(
CycleResetPolicy::PreserveCurrent,
[ResetRequirement::PocketIcTime(TimeResetPolicy::PreserveCurrent)],
)?;
```
Update downstream recipes when adopting this release. Restore, non-snapshot
reset, readiness, and final validation keep their ordering. Canister-set and
cycle-policy mismatches retain the `ResetCoverageMismatch` rebuild reason;
recoverable preparation failures still permit one rebuild, and failed recovery
retains both failures.
### Simplification and verification
The observed Cargo output/heartbeat test keeps its fixture running until the
observer receives a heartbeat, with a bounded timeout. This removes a scheduling
race caused by a fixed sleep under parallel test load; runtime heartbeat behavior
is unchanged.
Benchmark labels and comparison keys derive from one scope. Report writers and
comparisons calculate averages from totals and runs, preserving CSV columns and
named-`ALL` identity. Arithmetic overflow checks remain in place.
Wasm batch acquisition and reporting use one failure-details representation,
owned alongside each error. Existing public result/accessor and `into_parts()`
signatures, partial timings, captured output, entry order, and retained successful
records are preserved.
Targeted checks cover benchmark updates and report schemas, restored canister-set
and cycle-policy mismatches, recovery and panic invalidation, 100 consecutive
restores, mixed Wasm batch results, captured Cargo diagnostics, retained-output
handoff, and concurrent prepared readers. PocketIC 16 baseline reuse and isolated
dead-server recovery, Clippy, rustdoc, formatting, and Wasm compilation pass.
## 0.12.0
This update tightens artifact path boundaries, fixes Unix managed-server
descendant cleanup, and rejects benchmark aggregate overflow. Cache, stamp,
protocol and digest identifiers remain `v1`.
### Migration
These source API and validation changes are hard cuts:
| `aggregate_benchmark_spans(...) -> BenchmarkAggregateReport` | `Result<BenchmarkAggregateReport, BenchmarkAggregateError>`; handle or propagate overflow before comparing aggregates or writing reports. |
| Override `CARGO_TARGET_DIR` in `with_extra_env` or pass `--target-dir` in `with_cargo_profile_args` | Select the exact target with `WasmBuildSpec::new` or the shared target with `with_shared_incremental_target`; command overrides return `InvalidSpec` before acquisition. |
| Configure one public artifact output beneath another | Use distinct, non-nested output destinations; overlapping destinations are rejected during preparation. |
### Fixes and simplification
Observed Cargo output capture retries interrupted pipe reads, preserving captured
bytes while propagating permanent read errors.
Compact Cargo feature arguments (`-Fextra` and `-F=extra`) now reach metadata
resolution as well as compilation. Optional dependencies enabled by these
arguments are watched inputs, and batches resolve distinct feature graphs
separately.
Benchmark aggregation rejects counter and run-count overflow with a typed
error identifying the scope, span and counter. No partial, wrapped or saturated
totals are returned. `is_all_suites()` distinguishes a cross-suite failure from
a named suite `ALL`.
Shared-target maintenance now rejects layouts that could delete retained exact
Wasm artifacts. Batches maintain each resolved target directory once, including
workspace-relative paths and filesystem aliases. Relative exact targets use the
caller's working directory consistently for Cargo output and cache operations.
Conflicting `CARGO_TARGET_DIR` environment or `--target-dir` command overrides
are rejected before acquisition; select target directories through the build spec.
Artifact preparation rejects nested file destinations. Output boundary checks
validate the directory entry replaced by atomic publication rather than a final
symlink's referent; declared input and tool symlink entries remain protected.
On Unix, managed-server teardown terminates descendants in its owned process
group on handle drop, startup failure, and natural server exit. Cleanup reserves
the leader's PID until after signaling the group, then reaps the child.
Benchmark metadata now derives its JSON fields from `BenchmarkRunMetadata`.
Existing JSON field names, object shape, field types, optional fields and integer
bounds are preserved; invalid metadata returns `InvalidData` with Serde field
diagnostics.
Wasm warm hits share input validation and retained-record completion, and batch
input reuse has one internal representation for sessions and prepared snapshots.
Batch entry points call the same runner directly, and transport errors and panic
payloads share one message classifier.
Batch maintenance-policy errors use the common report-entry construction while
retaining their failure phases and timings. Internal attempts store the phase
and timings together; standalone and batch resolution share toolchain
identification.
ICP readiness delegates file validation to the watched-input snapshot.
Background server reaping retains the original managed-server owner and its
startup files. Shared-target lock acquisition owns progress events for both
ordinary builds and scheduled maintenance.
### Verification and documentation
Targeted regressions cover retained-cache and output boundaries, relative target
paths, Cargo override rejection, benchmark overflow and metadata validation,
compact-feature dependency discovery and batch grouping, and observed
session/prepared-reader batches. Unix descendant cleanup is checked
on drop, startup timeout, natural exit and background reaping, with a real
PocketIC 16 startup/reaping check. Maintenance behavior tests use outcome and
filesystem assertions, with a positive-interval control for heartbeat validation.
Installation examples select `0.11`. The README documents the updated path and
cleanup rules and explains recipe-pool timings for profiling long suites.
`POCKET-IC.md` refreshes upstream tracking.
## 0.11.0
This minor release fixes cache-hit input races and batch failure isolation,
consolidates baseline reuse and installation APIs, and makes operation failures
and benchmark aggregate identity explicit. The API and CSV schema changes are
hard cuts; cache, stamp, protocol, and digest identifiers remain `v1`.
### Migration
Update downstream code for these API and report-schema changes:
| `restore_or_rebuild_cached_pocket_ic_baseline` and `CachedPocketIcBaselineGuard` | `CachedPocketIcBaselinePool::new(capacity, recipe)` and `acquire()`. Use capacity one for sequential reuse; acquisition returns a typed outcome and an exclusive lease. |
| `create_and_install_with_args` and `try_create_and_install_with_args` | `create_and_install(InstallSpec::new(...))` and `try_create_and_install(InstallSpec::new(...))` |
| `CanisterInstallError::canister_id() -> Principal` | `Option<Principal>`; creation failures have no id. Inspect `phase()` for `CreateCanister`, `AddCycles`, or `InstallCode`. |
| `CanisterInstallError::new(id, message)` / `labeled(...)` | `new(phase, optional_id, optional_label, PocketIcOperationError)` |
| Snapshot panic variants' `message` field | `source: PocketIcOperationError`; inspect `message()` and `is_transport()` on the cause, or follow `Error::source()`. |
| `WasmBuildError::InputsChangedDuringBuild` and `ArtifactCacheError::InputsChangedDuringBuild` | `InputsChangedDuringAcquisition` |
| `comparison.csv` without aggregate scope | Leading `scope` column, containing `suite` or `all`. Named suite `ALL` and the cross-suite aggregate are distinct. |
`CachedPocketIcBaseline<T>` remains the owned snapshot-and-metadata value used
by recipe pools. A recipe declares its restore/reset/readiness/validation
contract and recovery policy; pool leases prevent another acquisition from
replacing or mutating the slot during recovery.
Fallible installation now captures upstream failures during creation and cycle
funding as well as installation. Standalone errors retain the caller's PocketIC
instance at every failed stage. Snapshot and installation errors retain a shared
operation cause so `is_dead_pocket_ic_transport_error` can classify it through
contextual wrappers without broadening the strict transport parser.
Ordinary warm Wasm acquisitions reject inputs that change after initial
resolution, including conservative workspace inputs. Immutable-source sessions
and prepared readers retain their explicit lease contract and skip repeated
warm validation. Batches report input hashing/discovery failures per entry and
continue with valid compatible entries. Input-race errors invalidate leased
readers as before.
Server diagnostic reads allocate only the bounded log prefix. Benchmark indices
continue past `9999`, previous-run selection compares them numerically, and
exhaustion returns an error rather than reusing an existing path. Empty commit
hashes consistently use the `unknown` directory prefix.
### Repository tooling and documentation
Release CI cleanup now matches the selected server binary's device/inode,
including renamed binaries, alongside its private port-file path. Unknown or
unavailable executable identities retain scratch without signalling unrelated
processes. Focused process/socket regressions cover configured and default
binary selection, identity failures and ownership races. This changes repository
release tooling; the runtime changes are described above.
README examples and API guidance are updated against the implementation, with
all 27 Rust examples checked. The documentation index separates current usage
from historical design records. Targeted regressions cover the cache, transport,
installation, output-read, and benchmark boundaries described above, alongside
existing live PocketIC recovery and concurrency checks.
## 0.10.4
The repository's release CI runner now stops invocation-owned PocketIC servers
before removing its temporary directory. This prevents server HTTP adapter
teardown from panicking on sockets already deleted by release cleanup. The
published crate's runtime API and dependency selection are unchanged.
The isolated PocketIC teardown patch and development probe are removed.
Instance teardown improvements will wait for a future upstream release; the
repository does not maintain a patched PocketIC client. Seven targeted
process/socket regressions cover the repository's release cleanup behavior.
Fixture sockets use short relative bind paths to support nested release
temporary directories without exceeding the Unix socket pathname limit.
## 0.10.3
The repository includes an isolated PocketIC 16.0.0 upstream teardown proposal
and repeatable synthetic HTTP probe. Seven targeted parent tests qualify
fallible shutdown deadlines, acknowledgement checks, ownership retained for
retry, bounded best-effort drop and borrowed gateway cleanup.
This is a development experiment. The published crate still uses the registry
PocketIC dependency and adds no production shutdown API or teardown fix.
The original Busy/tick cause remains unproven. The experiment was subsequently
removed in 0.10.4 in favor of waiting for an upstream release.
## 0.10.2
The repository and CI now use Rust 1.99.0. The published MSRV remains Rust
1.88.0.
Transport classification now requires a maintained reqwest error shape with a
PocketIC instance URL and recognized transport source, or a structured testkit
call transport kind. Generic `channel closed` / `ConnectionRefused` application
text, quoted error variants and bare I/O errors do not qualify. Use the public
classifier only for PocketIC-originating errors; it remains a heuristic rather
than proof of a dead instance. Unrelated call panics retain their original
payload.
Empty/nonempty collection assertions comply with Rust 1.99's `assert_is_empty`
lint and show collection values on failure.
Heartbeat tests now use event coordination. A synthetic HTTP/subprocess test
demonstrates that PocketIC 16's instance destructor waits for DELETE, independently
of the construction deadline and operation budget. This records an upstream
limitation; no bounded teardown API or simulator wrapper is added.
## 0.10.1
`PocketIcManagedServer::process_id()` exposes the owned server child's OS PID
for caller-managed resource monitoring alongside its URL and captured output.
It identifies only the child, does not establish liveness, and retains no
ownership when copied. The OS may reuse it after the child exits and is reaped.
## 0.10.0
This minor release hard-cuts artifact consumption to retained exact outputs,
fixing the lifetime gaps reported by IcyDB in issue #2. The original missing
input's precise racing operation has not been established.
`WasmBuildRecord::artifacts()` and `ArtifactCacheRecord::artifacts()` now return
read-only exact-cache paths instead of caller-selected materialization paths.
A successful cold build or warm hit acquires retention before releasing its
producer locks. Keep its record, outcome, or batch report alive while consuming
those paths. Cloning a record shares retention; cloning a path or an
`ArtifactCacheArtifact` descriptor does not. `exact_cache_path()` is also
protected for the Wasm record's lifetime.
Age/size pruning skips live entries across threads and processes. Configured
bounds may be exceeded while consumers retain entries; after the last owner
drops, the next maintenance pass can reclaim them normally. OS locks release
on process exit, including a crash, without stale pins. A corrupt retained
entry fails closed instead of being replaced. Treat exact paths as read-only;
manual cache deletion and external mutation are outside the ownership contract.
All cache and digest formats remain `v1`.
### Hard-cut migration
| Read the configured compiler output after a build | Read `outcome.record().artifacts()` and keep the outcome or a cloned record alive through post-link commit |
| Read the configured post-link destination later | Keep the returned `ArtifactCacheRecord` and read its artifacts until staging/reading finishes |
| Reduce successful batch results to indexes or paths | Keep the report, move out successful outcomes, or clone their records; later failed entries do not invalidate successful records |
| Transform an artifact in place | Write into the post-link transaction's output staging paths |
Materialization still populates configured destinations, but those remain
mutable and may be replaced by another acquisition. No compatibility accessor
or alternate cache protocol is added. Declared-input/tool hashing errors now
include the failing path. Source-mutation and input-identity checks remain in
force.
The `artifacts` module documentation demonstrates retained Wasm-to-post-link
handoff. IcyDB must adopt the release in both single and batch flows and rerun
its concurrent lifecycle tests; that downstream validation is not claimed here.
## 0.9.1
The workspace `toml` dependency moves from 0.9 to 1, with the refreshed
lockfile resolving `toml` to 1.1.6.
Release CI no longer runs `cargo clean` after a successful gate. Cargo build
artifacts are preserved for incremental reuse after success as well as for
diagnosis after failure; only the release wrapper's isolated temporary
directory is removed. Release-flow guards keep the standalone `make clean`
target outside CI and reject Cargo cleanup from CI, release, and publish
scripts.
## 0.9.0
The workspace now uses PocketIC 16. Managed servers started through
`PocketIcStartupConfig::spawn` no longer receive a ten-minute `--hard-ttl` by
default, matching the upstream lifetime policy. An active test suite is not
terminated merely because ten minutes have elapsed; PocketIC's activity-based
soft TTL still bounds an orphaned server, and a caller-owned
`PocketIcManagedServer` is still terminated and waited for on drop.
PocketIC 16 also waits for the first certified time update before returning
from automatic-progress startup, accounts for mocked HTTP response cycle spend,
rejects mocked HTTP reject messages larger than 1 KiB, and adds flexible HTTP
mocking plus the `SubnetCoolingDown` and `CanisterStatusAccessDenied` error
codes. These remain part of the direct upstream runtime surface; ic-testkit
does not add parallel wrappers for them.
The complete host-only upstream crate is now re-exported as
`ic_testkit::pocket_ic`. Use that path for native PocketIC types outside the
focused `ic_testkit::pic` convenience surface:
```rust
use ic_testkit::pocket_ic::{
CanisterSettings, CreateCanisterParams, PocketIc,
common::rest::{BlobCompression, IcpFeatures, IcpFeaturesConfig},
};
```
These are the types from the exact PocketIC dependency selected by ic-testkit;
there is no copied type or parallel wrapper. The complete re-export, like
`ic_testkit::pic`, is unavailable on `wasm32`.
Call `with_server_hard_ttl(duration)` when an absolute server deadline is
required. Subsecond explicit values remain invalid.
### Hard-cut migration
| `PocketIcStartupConfig::server_hard_ttl() -> Duration` returned the default ten-minute hard TTL | `server_hard_ttl() -> Option<Duration>` returns `None` by default and `Some(duration)` after `with_server_hard_ttl(duration)` |
There is no compatibility accessor or implicit ten-minute fallback. Startup
and instance-creation deadlines remain independently bounded by
`PocketIcStartupConfig::timeout`.
## 0.8.9
`WasmBuildInputSnapshot::prepare_assuming_sources_immutable` resolves a fixed
set of exact `WasmBuildSpec` values once under a caller-held source
write-exclusion guard. Its `build_batch` and `build_batch_with_progress`
methods take `&self`, so separate sequential batches may read the prepared
inputs concurrently. Reader metrics distinguish prepared reuse from ordinary
batch and mutable-session reuse.
Every reader specification must have been declared during preparation;
`SpecificationNotPrepared` rejects an undeclared entry before progress or build
work. A detected post-build input mutation invalidates the snapshot for every
later reader, and publication is coordinated with that shared invalidation
boundary. Ordinary batch calls remain independently resolved. Do not construct
a snapshot with an unrelated token: the borrowed value is a lifetime boundary,
not guard-provenance validation performed by `ic-testkit`.
## 0.8.8
`WasmBuildSession::new(&guard)` is hard-cut to
`WasmBuildSession::assume_sources_immutable(&guard)`. The constructor name now
makes the caller assertion explicit: `ic-testkit` lifetime-binds the session to
the supplied reference but cannot prove that the value is a genuine workspace
write-exclusion guard. An unrelated token still violates the contract and can
permit stale reuse. There is no `new` alias or deprecated bridge.
A concurrent-reader resolution snapshot remains a documented future design,
not an ambient cache or parallel batch mode. It would prepare a declared spec
set under one genuine source lease, freeze resolution state before sharing,
propagate invalidation to every reader, retain existing Cargo target locking,
and require consumer benchmarks before implementation.
## 0.8.7
Managed spawn now allocates stdout, stderr, and the server-owned port path
inside a unique private directory, but creates only the output files before
launch. The actual `--port-file` path remains absent until PocketIC publishes
it; a missing path is treated as pending readiness. This fixes PocketIC 15,
which exits successfully without starting when the supplied port path already
exists.
`PocketIcStartupConfig::start_managed_server` returns a caller-owned
`PocketIcManagedServer`. Its `url()` can feed any number of bounded
`PocketIcStartupConfig::connect` calls in a serial suite, `output()` returns
the first 16 KiB of lossy stdout/stderr per stream with an omitted-byte marker,
and dropping the handle terminates and waits for the managed child. Keep the
handle alive until its connected instances are dropped. This is explicit
process-local ownership rather than a process-global server or an implicit
retry path. CI spanning multiple Cargo or test-runner processes should retain
an external runner-owned server and use bounded connect mode in each process.
An ignored real-server regression test accepts the exact caller-resolved
binary through `IC_TESTKIT_POCKET_IC_SERVER`. It verifies port publication,
bounded instance construction, owned shutdown, and startup-directory cleanup
without adding binary discovery or download behavior to the crate.
`WasmBuildSession` is an explicit caller-owned cross-call input snapshot. Its
constructor borrows a source write-exclusion guard for the session lifetime;
the caller must ensure that all Cargo/rustc executables, manifests and Cargo
configuration, discovered sources, declared additional inputs, and relevant
environment values are immutable while the session exists. Exact resolution
snapshots and content digests may then be reused by separate sequential
`build_batch` or `build_batch_with_progress` calls. Ordinary batch functions
retain their current per-call validation, and there is no ambient or
process-global cache.
If revalidation around a Cargo build detects an input mutation, the session is
permanently invalidated, all pending pre-race snapshots are discarded, and a
later call returns `WasmBuildBatchContractError::SourceLeaseInvalidated`.
`WasmBuildSession::metrics` exposes retained snapshots, successful snapshot
reuses, and invalidation state; each batch separately reports
`input_resolution_session_reuses`.
Failed Wasm batch entries now expose `WasmBuildFailurePhase` and partial
`WasmBuildFailureTimings` through `WasmBuildBatchFailure::phase` and `timings`.
The timings retain completed exact/shared coordination, tool identity, Cargo
metadata, input discovery, content hashing, shared maintenance, Cargo,
publication, exact-cache maintenance, explicit cleanup, and total wall time.
Successful build-record timing remains unchanged.
### Hard-cut migration
| `WasmBuildBatchEntry::into_parts() -> (usize, String, Result<_, _>, Duration)` | Destructure `(index, label, result, failure_details, entry_elapsed)`; failed entries carry `Some(WasmBuildFailureDetails)` |
| `WasmBuildBatchFailure` exposes only label/index/error/elapsed | Use `phase()` and `timings()` for the primary failed phase and partial work |
| Separate batch calls always resolve their inputs independently | Hold the real source write-exclusion guard and call `WasmBuildSession::assume_sources_immutable(&guard)` when the immutable-source contract can be guaranteed |
There is no four-field `into_parts` alias, deprecated session-free overload,
implicit guard, global cache, or reset-after-race shim. Batches remain
sequential and collect-all; recipe and observer panics continue unwinding.
## 0.8.6
Wasm batches now require `LabeledWasmBuildSpec`. Labels must be nonempty and
unique and are retained in canonical report entries, successful outcomes,
failures, progress events, and shared-target maintenance outcomes. Label
preflight completes before metadata resolution, progress, or build work;
labels do not alter exact Wasm fingerprints. Valid entries remain sequential
and collect-all.
Diagnostic batch labels now follow the same contract. Empty or duplicate
labels return `CanisterDiagnosticsBatchContractError` before any status or log
call starts. Valid targets retain their exact controllers and continue after
independent failures as before.
`PocketIcBuilderExt::try_build` now requires an explicit
`PocketIcStartupConfig`. `spawn` launches one exact caller-resolved binary,
detects child exit while waiting for readiness or instance construction,
terminates the child at the complete startup deadline, and returns structured
errors with bounded lossy stdout/stderr. `connect` applies the same construction
deadline to a caller-owned existing server. Both policies force an explicit
server URL onto the upstream builder, so it cannot spawn an unobservable child.
ic-testkit does not discover, download, cache, or compatibility-check server
binaries.
Exact Cargo Wasm identity now uses a validated semantic workspace projection.
The projection retains selected resolve nodes, enabled features, exact external
source/checksum/revision identity, effective package fields, workspace
profiles/resolver/lints, selected local sources, tools, Cargo configuration,
and declared inputs. An unrelated host-only workspace dependency or lockfile
update can therefore reuse the same selected Wasm entry.
The complete workspace manifest and lockfile remain conservative validation
inputs. `ResolvedCargoBuildInputs::validation_digest` exposes that raw mutation
guard; `input_digest` is now semantic cache identity. Cargo builds and attached
artifact transactions reject any raw input change during their operation.
Workspace-root packages and local packages outside the normalizable workspace
boundary fall back to the complete input identity.
### Hard-cut migration
| Wasm batch functions accept `&[WasmBuildSpec]` and return `WasmBuildBatchReport` | Wrap every spec with `LabeledWasmBuildSpec`; handle `Result<WasmBuildBatchReport, WasmBuildBatchContractError>` |
| `results()`, `into_results()`, and parallel `entry_elapsed()` access | Use canonical `entries()` or `into_entries()`; each `WasmBuildBatchEntry` owns index, label, result, and elapsed time |
| Wasm `outcomes()` and shared maintenance yield indexed tuples; failures have no label | Use the structured entry accessors `index()`, `label()`, `outcome()` or `error()`, and `entry_elapsed()` where available |
| Batch progress variants contain only an index | Match the required `label` field as well, or use `..` when the label is intentionally ignored |
| Diagnostics batch returns `CanisterDiagnosticsBatchReport` directly | Handle `Result<CanisterDiagnosticsBatchReport, CanisterDiagnosticsBatchContractError>`; labels must be nonempty and unique |
| Configure `with_server_binary`/`with_server_url`, then call `try_build()` | Call `try_build(PocketIcStartupConfig::spawn(path, timeout))` or `try_build(PocketIcStartupConfig::connect(url, timeout))` |
| Read `PocketIcStartupError::message()` | Match the structured `PocketIcStartupError` variants and their public fields |
No index-only overloads, parallel label slices, deprecated report accessors,
zero-argument `try_build`, implicit binary fallback, or compatibility aliases
are retained.
### Semantic identity migration
- Treat `ResolvedCargoBuildInputs::input_digest` and
`WasmBuildRecord::input_digest` as semantic selected-build identity. Use the
new `validation_digest` when retaining or comparing a conservative raw input
snapshot.
- Expect one new exact key for workspaces eligible for projection. Repository
digest domains and cache formats remain `v1`; no legacy key lookup, shim,
alias, or dual reader is retained.
- Continue declaring build-script, procedural-macro, source-include, or tool
inputs that live outside Cargo's selected package graph.
## 0.8.5
`0.8.5` hard-cuts generic artifact batches to caller-labeled specifications.
`LabeledArtifactCacheSpec` labels must be nonempty and unique; they are retained
in cache-miss callbacks and every ordered report entry. Labels are report and
composition identity only and do not alter exact artifact-cache keys. Invalid
label structure returns `ArtifactCacheBatchContractError` before any entry
starts.
`ArtifactCacheBatchFailureTimings` distinguishes preparation, callback,
explicit abort cleanup, commit, and total time. The failure also exposes its
primary `ArtifactCacheBatchFailurePhase`. Commit timing includes cleanup owned
internally by a failed commit. Recipe panics still unwind, and independent
entries remain sequential and collect-all.
`CanisterDiagnosticsBatchEntry::entry_elapsed` retains each target's complete
diagnostic collection time, while `CanisterDiagnosticsBatchReport::total`
retains total sequential batch time. The compact renderer includes both. Exact
controllers, bounded logs, collect-all behavior, and the absence of anonymous
fallback remain unchanged.
### Hard-cut migration
| `build_artifact_caches_batch(&[ArtifactCacheSpec], FnMut(usize, ...)) -> ArtifactCacheBatchReport<E>` | Wrap specs with `LabeledArtifactCacheSpec`; the callback receives `&str`; handle `Result<ArtifactCacheBatchReport<E>, ArtifactCacheBatchContractError>` |
| `results()`, `into_results()`, and `entry_elapsed()` parallel report slices | `entries()` and `into_entries()` return canonical `ArtifactCacheBatchEntry<E>` values with `index()`, `label()`, `result()`, and `entry_elapsed()` |
| `outcomes()` yields `(usize, &ArtifactCacheOutcome)` | Yields `ArtifactCacheBatchOutcomeEntry`; use `index()`, `label()`, `outcome()`, and `entry_elapsed()` |
| `ArtifactCacheBatchFailure` without phase timing fields | Match the hard-cut variants with `..`, then use `phase()` and `timings()`; failed-entry views also expose `label()` and `timings()` |
| `CanisterDiagnosticsBatchEntry::into_parts() -> (String, CanisterDiagnosticsReport)` | Returns `(String, CanisterDiagnosticsReport, Duration)`; borrowed callers may use `entry_elapsed()` and the batch `total()` |
No index-only overloads, parallel label sidecars, tuple aliases, or deprecated
bridges are retained.
The Wasm resolver already discovers the selected local dependency closure, but
the complete workspace manifest and lockfile remain exact inputs because
workspace inheritance, profiles, patches, resolver state, external revisions,
build scripts, proc macros, and includes can cross the apparent closure. A
future narrower fingerprint must use a validated semantic projection with a
conservative fallback. Likewise, digest reuse across incompatible batch groups
requires an explicit caller-held immutable-source lease or validated snapshot;
`0.8.5` adds no ambient or unsafe digest cache.
## 0.8.4
`0.8.4` adds sequential collect-all diagnostics for caller-labeled exact
requests. `PocketIcDiagnosticsExt::collect_canister_diagnostics_batch` attempts
every target and returns ordered `CanisterDiagnosticsBatchEntry` values. Each
entry retains its label, exact controller-aware request, independent status and
log outcomes, bounded lossy UTF-8 log content, and omitted-byte/record counts.
An earlier rejection, dead PocketIC transport, or panic does not prevent later
entries from being attempted. There is no anonymous retry and
`dump_canister_debug` is not restored.
Wasm and generic artifact collect-all reports now expose structured failed
entries with specification index, error or failure, and complete entry wall
time. Generic reports also retain an ordered `entry_elapsed` value for every
success and failure. Detailed partial phase timings for failed preparation or
commit paths remain a future error-contract change.
### Hard-cut migration
| `WasmBuildBatchReport::failures()` yields `(usize, &WasmBuildError)` | Yields `WasmBuildBatchFailure`; use `index()`, `error()`, and `entry_elapsed()` |
| `ArtifactCacheBatchReport::failures()` yields `(usize, &ArtifactCacheBatchFailure<E>)` | Yields `ArtifactCacheBatchFailedEntry<E>`; use `index()`, `failure()`, and `entry_elapsed()` |
The tuple iterators have no aliases or deprecated bridges. Generic batch input
hashing is not memoized across entries because current preparation rehashes to
detect mutations. Safe reuse requires a caller-supplied source-immutability
lease or explicit revalidation. Caller-supplied stable artifact entry keys are
likewise reserved for one future labeled-spec hard cut rather than a parallel
key sidecar.
## 0.8.3
`0.8.3` is a behavior-preserving code-hygiene patch. It consolidates optional
phase-timing aggregation and the indexed result iteration shared by collect-all
batch reports, removing duplicate internal implementations.
Release tooling now uses one reader for the `[workspace.package]` version
across Make, changelog, bump, tag, publish, and release guards. Exact stable
versions are required for release operations while bump preparation retains
its prerelease-compatible parsing.
There are no public API, cache-format, schema, or runtime behavior changes in
this patch, and no migration or pre-1.0 API hard cut is required.
## 0.8.2
`0.8.2` is a release-process and CI-stability patch. It makes exact-cache
lock-heartbeat coverage scheduling-independent and ensures the complete release
gate runs before version metadata changes. A committed, tagged release is no
longer subjected to a redundant second validation pass that can strand a local
patch version after failure.
There are no public API, cache-format, schema, or runtime behavior changes in
this patch.
## 0.8.1
`0.8.1` continues the pre-1.0 hard-cut policy with structured controller-aware
diagnostics, collect-all generic artifact batches, and aggregate batch
observability. Removed APIs have no aliases, deprecated bridges, dual entry
points, or compatibility readers.
### Changes
- `build_artifact_caches_batch` is sequential collect-all and returns
`ArtifactCacheBatchReport<E>`. Preparation, callback, and commit failures are
indexed and later independent entries continue.
- Wasm and generic artifact reports expose aggregate built/reused/failed
counters and successful timings. Wasm metrics also distinguish compatible
input-resolution runs from reused snapshots.
- `WasmBuildBatchReport::entry_elapsed` retains wall time for every entry,
including failures. Detailed phase timings remain available on successful
records; partial failed-phase timing is a documented follow-up.
- `PocketIcDiagnosticsExt::collect_canister_diagnostics` takes exact,
independent status and log senders and returns a structured report. Status
and logs retain separate success/failure results. Log content is bounded
lossy UTF-8 with explicit omitted-record and omitted-byte counts.
- The anonymous-only, printing `dump_canister_debug` entry point is removed.
Install-failure diagnostics pass the install sender through and remain
best-effort so they cannot replace the original failure.
### Additional hard-cut migration
| `Result<ArtifactCacheBatchOutcome, ArtifactCacheBatchError<E>>` | `ArtifactCacheBatchReport<E>` with indexed `ArtifactCacheBatchFailure<E>` values |
| `PocketIcDiagnosticsExt::dump_canister_debug(canister_id, context)` | `collect_canister_diagnostics(CanisterDiagnosticsRequest::new(canister_id, status_sender, log_sender))`; inspect `status` and `logs`, then use `Display` or `render_compact` when text is needed |
Compatible Wasm input memoization remains scoped to one batch call. There is no
silent global cache or cross-call session in `0.8.1`. A proposed explicit
session must require a caller-guaranteed source-immutability lease (or pay for
revalidation); it is documented rather than partially implemented.
## 0.8.0
`0.8.0` is a pre-1.0 hard cut. It adds collect-all Wasm batches, compatible
input resolution reuse within one batch, and public immutable exact-cache
paths. Removed APIs have no aliases or deprecated bridges.
### Migration
| Wasm batch returned `Result<WasmBuildBatchOutcome, WasmBuildBatchError>` | It returns `WasmBuildBatchReport`; inspect `results`, indexed `outcomes`/`failures`, and `is_success` |
| `WasmBuildCachePrunePolicy`, `WasmBuildCachePruneReport`, `WasmBuildCacheMaintenance` | `ArtifactCachePrunePolicy`, `ArtifactCachePruneReport`, `ArtifactCacheMaintenance` |
| `CargoHeartbeat { elapsed }` | `Heartbeat { phase: WasmBuildProgressPhase::CargoBuild, elapsed }` |
| Wasm builders ending in `_os` | Use `with_cargo_profile_args`, `with_extra_env`, and `with_inherited_env` directly |
| `with_additional_input_paths` | `with_additional_inputs` |
| Transaction builders ending in `_os` | Use `with_arguments`, `with_environment`, and `with_unset_environment` directly |
| `CachedStandaloneCanisterFixturePool::acquire_with_outcome` | `acquire`, which returns the structured lifecycle outcome |
| Boolean result from fixture-pool `acquire` | Call `outcome.is_reused()` on the structured result |
| `PocketIcCapturedSnapshotExt` | Import `PocketIcSnapshotExt`; it owns both controller-fallback and exact-sender methods |
| `WasmBuildTimings::input_resolution_detail` plus aggregate `input_resolution` | `input_resolution` returns `WasmInputResolutionTimings` directly; call `.total()` for the aggregate |
| Panicking `build_wasm_canisters` wrapper | Construct `WasmBuildSpec` and call `build_wasm_canisters_cached` |
Wasm batch functions no longer return a top-level error. Handle all entries
after the sequential batch completes:
```rust,no_run
# use ic_testkit::artifacts::{WasmBuildSpec, build_wasm_canisters_cached_batch};
# let specs: Vec<WasmBuildSpec> = Vec::new();
let report = build_wasm_canisters_cached_batch(&specs);
for (index, error) in report.failures() {
eprintln!("Wasm build {index} failed: {error}");
}
```
All repository-owned cache, stamp, and digest-domain identifiers remain at
`v1`. No migration reader is provided; disposable build caches may rebuild
under the current `v1` semantics.