You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d9c1641
Browse filesBrowse the repository at this point in the historyBrowse files
docs(AGENTS.md): test.sh over Makefile, ic-cdk 0.20 guidance, Rust env vars (#1422)
* docs(AGENTS.md): test.sh over Makefile, ic-cdk 0.20, Rust env vars
Makefile → test.sh:
- Rename section and update all templates/checklist references
- Use #!/usr/bin/env bash + set -e; plain $var syntax; no @/$$
- Add delta-based balance check pattern for idempotency
- Update CI workflow template and README structure template
Rust additions:
- Always use ic-cdk 0.20; ic-ledger-types 0.16 required for 0.20 compat
(0.15 + 0.20 causes ic-cdk-executor links conflict)
- ic_cdk::export_candid!() required at end of every lib.rs
- icp-dev-env-rust:1.0.1+ required (0.1.4 candid-extractor fails on
ic0::cost_call imported by ic-cdk ≥ 0.19)
- ic_cdk::api::env_var_value for runtime env vars (not env!() or
std::env::var())
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(AGENTS.md): use icp canister top-up command directly in README (no script)
* docs(AGENTS.md): clarify 30t is an example cycle amount, not a fixed requirement
* docs(AGENTS.md): pin all dev-env images to v1.0.1
* docs(AGENTS.md): add per-environment canister config guidance and checklist item
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
├── package.json # npm workspaces root pointing to frontend/
61
61
├── mops.toml # Motoko only
@@ -143,10 +143,47 @@ canisters:
143
143
```
144
144
145
145
- With `candid:` specified: the recipe reads the committed `.did` file and embeds it as WASM metadata (no `candid-extractor` needed).
146
-
- Without `candid:`: `candid-extractor`(available in `icp-dev-env-rust:0.3.2+`) extracts the interface directly from the compiled WASM. For backend-only examples, omit `candid:` and do not commit `backend.did`.
146
+
- Without `candid:`: `candid-extractor`extracts the interface directly from the compiled WASM. For backend-only examples, omit `candid:` and do not commit `backend.did`.
147
147
148
148
**Canister names are always `backend` and `frontend`.** Never use names like `<example>_backend`, `internet_identity_app_backend`, etc.
149
149
150
+
### Per-environment canister configuration
151
+
152
+
When an example interacts with a well-known external canister whose principal differs by environment (e.g. the ICP ledger mainnet vs. TESTICP on staging), use the `environments` block to inject the right value per deployment rather than hardcoding it:
icp-cli injects these as WASM metadata before `cargo build`. Read them at **runtime** via `ic_cdk::api::env_var_value("ICP_LEDGER_CANISTER_ID")` (Rust) or `Runtime.envVar<system>("ICP_LEDGER_CANISTER_ID")` (Motoko) — not via `env!()` or `std::env::var()`.
184
+
185
+
**When to apply this pattern**: judge whether staging/production environments make sense for the specific example. Apply it when the example hardcodes an external canister principal that has a test counterpart (TESTICP, testnet URLs, staging governance canisters, etc.). Skip it for self-contained examples that don't call external canisters.
186
+
150
187
### Motoko naming conventions
151
188
152
189
- **Top-level actor:** name it after its logical role, not generically — e.g. `actor TodoList`, `actor CanisterFactory`, not `actor Backend`.
@@ -229,6 +266,25 @@ candid = "0.10"
229
266
ic-cdk = "0.20"
230
267
```
231
268
269
+
Always use `ic-cdk = "0.20"` unless a dependency forces a lower version. When using `ic-ledger-types`, use `"0.16"` (requires `ic-cdk = "^0.19"`) — version `0.15` pins `ic-cdk = "^0.18"` which conflicts with `0.20` via an `ic-cdk-executor` `links` constraint.
270
+
271
+
**`ic_cdk::export_candid!()` is required** at the end of every Rust canister `lib.rs`. Without it, `candid-extractor` cannot find the `get_candid_pointer` export and the build fails.
272
+
273
+
**CI image**: use `ghcr.io/dfinity/icp-dev-env-rust:1.0.1` or later. Earlier images bundle `candid-extractor 0.1.4` which fails with `Error: unknown import: ic0::cost_call` for any WASM compiled with ic-cdk ≥ 0.19.
274
+
275
+
### Environment variables in Rust canisters
276
+
277
+
Use `ic_cdk::api::env_var_value("VAR_NAME")` to read icp-cli-injected variables at **runtime** from WASM metadata. This is the Rust equivalent of `Runtime.envVar<system>` in Motoko:
Do **not** use `env!()` (compile-time macro, fails if the var is not set during `cargo build`) or `std::env::var()` (no OS environment in WASM).
287
+
232
288
### Management canister (Rust)
233
289
234
290
Use the [`ic-cdk-management-canister`](https://crates.io/crates/ic-cdk-management-canister) crate instead of `ic_cdk::api::management_canister` (removed in ic-cdk 0.17+):
Every example must have a `Makefile` with a `test` target that exercises the deployed canister via `icp canister call`. Write a numbered test for every public function. Include state-assertion tests for mutating operations: call the mutating function, then call a read function and assert the stored value changed.
255
-
256
-
```makefile
257
-
.PHONY: test
308
+
## test.sh
258
309
259
-
test:
260
-
@echo "=== Test 1: <description of what is tested> ==="
Every example must have a `test.sh` bash script that exercises the deployed canister via `icp canister call`. Write a numbered test for every public function. Include state-assertion tests for mutating operations: call the mutating function, then call a read function and assert the stored value changed.
265
311
266
-
@echo "=== Test 2: <mutating operation returns expected value> ==="
Using `test.sh` instead of `Makefile` avoids Make-specific syntax pitfalls (`$$`, `\\` continuations, `@` prefix) and works natively in Git Bash on Windows.
271
313
272
-
@echo "=== Test 3: <state persisted after mutation> ==="
- Tests must call the `backend` canister by that name.
280
335
- Always pass explicit Candid args, including `'()'` for zero-argument calls — omitting args triggers an interactive prompt that blocks CI.
281
336
- Use `grep -q` to assert on output content.
282
337
- Number each test (`=== Test N: ... ===`) so CI logs are easy to scan.
283
338
- For examples that create child canisters, capture the returned principal and call the child directly by ID.
284
-
- **Query functions**: always pass `--query` to `icp canister call` for `public query func` and `public composite query func` methods — this matches the original dfx `--query` flag and avoids unnecessary update calls.
285
-
- **Async/time-dependent behavior**: if the example's observable result depends on timers, heartbeats, or polling (e.g. a hook that fires after memory fills up), use a polling loop in the Makefile rather than a one-shot call:
286
-
287
-
```makefile
288
-
test:
289
-
@echo "=== Polling for <condition> (up to 60s) ==="
- **Query functions**: always pass `--query` to `icp canister call` for `public query func` and `public composite query func` methods.
340
+
- **Balance checks**: use delta-based assertions (record before, act, assert delta) rather than checking absolute values — this keeps tests idempotent across re-runs regardless of prior state:
299
341
300
-
- **Canister settings**: if the example requires non-default canister settings (e.g. `wasm_memory_limit`, `wasm_memory_threshold`), apply them via `icp canister settings update` in a `configure` target and make `test` depend on it:
- If `icp deploy --cycles 30t` is required (see below), add a `topup` target too:
365
+
- **Canister settings**: if the example requires non-default canister settings (e.g. `wasm_memory_limit`, `wasm_memory_threshold`), apply them at the top of `test.sh` before the tests:
- If `icp deploy --cycles 30t` is required (see below), document `icp canister top-up --amount 30t backend` in the README so users can replenish cycles when needed. (`30t` is an example amount — adjust to the example's actual consumption.)
If the example has a frontend with a Vite dev server: `npm run dev`(hot reload during frontend development)
417
470
418
-
> If tests fail with an out-of-cycles error, run `make topup` to add 30 trillion cycles to the backend canister and retry. Only relevant for examples that create child canisters.
471
+
> If tests fail with an out-of-cycles error, run `icp canister top-up --amount 30t backend` to replenish cycles and retry (`30t` is an example amount). Only relevant for examples that create child canisters.
Images are published at `ghcr.io/dfinity/icp-dev-env-{motoko,rust,all}`. All devcontainer configs and CI workflows reference the pinned tag (e.g. `0.3.2`). When a new release is cut, update the tag in:
494
+
Images are published at `ghcr.io/dfinity/icp-dev-env-{motoko,rust,all}`. Current pinned version: **`1.0.1`**. All devcontainer configs and CI workflows reference the pinned tag. When a new release is cut, update the tag in:
442
495
-`.devcontainer/devcontainer.json`
443
496
-`.github/workflows/*.yml`
444
497
@@ -469,15 +522,17 @@ When migrating an existing example:
469
522
-[ ] Run `mops check --fix` in the example directory and commit any auto-fixes (Motoko)
470
523
-[ ] If the example uses the management canister: add `ic = "4.0.0"` dependency and replace `ic:aaaaa-aa` / `actor("aaaaa-aa")` with `import { ic } "mo:ic"` (Motoko)
471
524
-[ ] If the Rust example uses the management canister: add `ic-cdk-management-canister = "0.1.1"` dependency and replace `ic_cdk::api::management_canister` with the appropriate function from that crate
472
-
-[ ] If the example creates child canisters: use `icp deploy --cycles 30t` in the CI workflow and README, and add a `make topup` target
473
-
-[ ] If the example requires non-default canister settings (memory limits, freezing threshold, etc.): add a `configure` target in the Makefile and make `test` depend on it
525
+
-[ ]**Judge whether per-environment configuration makes sense**: if the example calls an external canister whose principal differs by environment (e.g. ICP ledger vs. TESTICP), add an `environments` block to `icp.yaml`. Skip for self-contained examples.
526
+
-[ ] If the example creates child canisters: use `icp deploy --cycles 30t` in the CI workflow and README
527
+
-[ ] If the example requires non-default canister settings (memory limits, freezing threshold, etc.): apply them via `icp canister settings update` at the top of `test.sh`
0 commit comments