ktstr_test

Attribute Macro ktstr_test 

Source
#[ktstr_test]
Expand description

Attribute macro that registers a function as a ktstr integration test.

The annotated function must have signature fn(&ktstr::scenario::Ctx) -> anyhow::Result<ktstr::assert::AssertResult>. The macro:

  1. Renames the original function to __ktstr_inner_{name}.
  2. Registers it in the KTSTR_TESTS distributed slice via linkme.
  3. Emits a #[test] wrapper that boots a VM and runs the function inside it.

Every attribute is optional. Most take a key = value form; the thirteen boolean attributes (auto_repro, not_starved, isolation, performance_mode, no_perf_mode, requires_smt, expect_err, allow_inconclusive, fail_on_stall, host_only, ignore, kaslr, wprof) also accept a bare form as shorthand for = true — e.g. #[ktstr_test(host_only)] is equivalent to #[ktstr_test(host_only = true)]. Of the thirteen, auto_repro and kaslr are the two whose default is true, so the bare form is a no-op; auto_repro = false / kaslr = false are the only way to disable each. The other eleven default to false, so the bare form is the meaningful shorthand.

The accepted attributes and their defaults are the fields of ktstr::test_support::KtstrTestEntry (runtime metadata) and ktstr::assert::Assert (checking thresholds). A few are worth calling out because their names differ from the underlying field or because they have nontrivial defaults:

  • llcs = N — number of LLCs (default: inherited from scheduler, or 1).
  • cores = N (default: inherited from scheduler, or 2)
  • threads = N (default: inherited from scheduler, or 1)
  • numa_nodes = N (default: inherited from scheduler, or 1)
  • memory_mib = N — per-test minimum memory in MiB (default: 2048). The framework picks max(total_cpus * 64, 256, memory_mib) MiB at VM-launch time, so for tests with more than 32 vCPUs the cpu-based floor dominates the macro default. Below ~4 vCPUs the absolute 256-MiB floor wins if memory_mib is also below it. Setting memory_mib above the cpu-based floor is only meaningful when the test needs more headroom than the per-cpu budget. The unit is binary mebibytes; the conversion at VM-launch is value << 20 bytes, not decimal megabytes.
  • duration_s = N — scenario run duration in seconds; maps onto KtstrTestEntry::duration
  • watchdog_timeout_s = N — watchdog fire threshold in seconds; maps onto KtstrTestEntry::watchdog_timeout
  • cleanup_budget_ms = N — sub-watchdog cap on host-side VM teardown wall time; maps onto KtstrTestEntry::cleanup_budget as Duration::from_millis(N). Default: None (unenforced).
  • num_snapshots = N — fire N periodic freeze_and_capture(false) boundaries inside the workload’s 10 %–90 % window, stored on the host SnapshotBridge under periodic_NNN. 0 (default) disables periodic capture entirely. Maps onto KtstrTestEntry::num_snapshots; runtime KtstrTestEntry::validate rejects values past the bridge cap (MAX_STORED_SNAPSHOTS), host_only = true, and duration / N settings that would land boundaries closer than 100 ms apart.
  • scheduler = PATH — path to a const Scheduler (typically produced by declare_scheduler!(...)). Maps onto KtstrTestEntry::scheduler, which is typed &'static Scheduler. Default: &Scheduler::EEVDF, the no-scx placeholder that runs under the kernel’s default scheduler.
  • payload = PATH — path to a const Payload used as the primary binary workload (must be PayloadKind::Binary; runtime-enforced). Default: None (scheduler-only test). Coexists with scheduler = PATH — the payload runs under the selected scheduler.
  • workloads = [PATH, PATH, ...] — additional const Payload references composed with the primary via Ctx::payload in the test body. Default: &[]. Must not contain the same path as payload — reject at expansion time to catch the common “fio as primary AND workload” slip.
  • auto_repro = bool (default: true)
  • wprof = bool (default: false; requires the wprof cargo feature on ktstr) — attach /bin/wprof to the test’s VM(s) and ship the Perfetto trace to the host.
  • wprof_args = "..." (requires the wprof cargo feature) — override WprofConfig::default_args. Only meaningful with wprof = true. Parsed as space-separated tokens.
  • host_only = bool (default: false) — run the test function on the host instead of inside a VM
  • no_perf_mode = bool (default: false) — decouple the virtual topology from host hardware. The VM is built with the declared numa_nodes / llcs / cores / threads even on smaller hosts; vCPU pinning, hugepages, NUMA mbind, RT scheduling, and KVM exit suppression are skipped, and gauntlet preset filtering relaxes host-topology checks to the single “host has enough total CPUs” inequality. Mutually exclusive with performance_mode = true. Maps onto KtstrTestEntry::no_perf_mode.
  • post_vm = PATH — host-side callback invoked after vm.run() returns, with access to the full VmResult. Use for assertions that need host-side state — e.g. draining VmResult.snapshot_bridge after a snapshot capture pipeline fires inside the guest. The function must have signature fn(&ktstr::vmm::VmResult) -> anyhow::Result<()>. PATH accepts any Rust path-expression that resolves to a value of that fn type — both free-function refs (my_post_vm_check) AND UFCS method refs (VmResult::assert_wprof_pb_landed) work via Rust’s function-item-to-fn-pointer coercion, so a method that already has the right &self -> Result<()> shape can be pointed at directly without wrapping in a one-line delegating free fn. SUPPRESSED on guest-reported fail — see post_vm_unconditional for the always-runs sibling. Default: None (no callback).
  • post_vm_unconditional = PATH — host-side callback that always runs after vm.run() returns, bypassing the guest-fail suppression that gates post_vm. Same signature as post_vm and PATH accepts the same form: any Rust path-expression resolving to a value of that fn type — both free-function refs AND UFCS method refs (VmResult::assert_wprof_pb_landed-style) work via function-item-to-fn-pointer coercion. Use when the callback must observe host-side state regardless of guest-side outcome (e.g. verifying a sidecar artifact landed even when the guest reported a deliberate fail). The callback is responsible for guarding against missing state when the scheduler crashed before producing it — the canonical guard is if !result.success { return Ok(()); } at the top of the callback body. Setting post_vm_unconditional does NOT invert the test verdict — a guest-reported fail still fails the test even if the unconditional callback returns Ok. Both attributes may be set on the same entry (both errors surface via combine_post_vm_errs when both fire). Default: None (no callback).
  • disk = PATH — path to a const DiskConfig attached to the VM as a virtio-blk device at /dev/vda. Construct via DiskConfig::DEFAULT.with_name("data") or similar const-fn chain (the with_name builder takes &'static str so the full expression is const-evaluable). Maps onto KtstrTestEntry::disk. Default: None (no disk). Mutually exclusive with host_only = truehost_only skips the VM boot that owns the device lifecycle, so a disk attached under host_only would never bind; KtstrTestEntry::validate rejects the pairing at runtime.
  • network = PATH — path to a const NetConfig attaching a virtio-net device (in-VMM loopback backend). Construct via NetConfig::DEFAULT.mac(...) or NetConfig::DEFAULT (const-fn chain). Maps onto KtstrTestEntry::network. Default: None (no NIC). Like disk, mutually exclusive with host_only.
  • config = EXPR — inline scheduler config content, written into the guest at the path declared by the scheduler’s config_file_def. EXPR is either a string literal or a path to a const &'static str (e.g. LAYERED_CONFIG). Maps onto KtstrTestEntry::config_content. Required when the scheduler declares config_file_def; rejected when the scheduler does not. The pairing is enforced at compile time via a const assertion against Payload::config_file_def, and again at runtime by KtstrTestEntry::validate so direct programmatic-entry construction sees the same gate.
  • expect_scx_bpf_error_contains = EXPR — literal-substring matcher applied to the captured scx_bpf_error text in reproducer mode. EXPR is either a string literal or a path to a const &'static str. Maps onto Assert::expect_scx_bpf_error_contains. Requires expect_err = true (rejected at construction otherwise by KtstrTestEntry::validate). Empty strings panic at construction. When both _contains and _matches are set, the evaluator ANDs them — every set matcher must hit.
  • expect_scx_bpf_error_matches = EXPR — regex matcher with the same accepted forms and gating as _contains. Maps onto Assert::expect_scx_bpf_error_matches. Validated at construction: empty patterns, invalid regex syntax, and any pattern satisfying is_match("") all panic immediately. The is_match("") predicate catches two no-op classes with one check: patterns that match every position (e.g. a?, .*, (?:)) trivially pass against any corpus, and patterns that match only the empty string (e.g. ^$) trivially fail against any non-empty corpus — both are equally useless pins. Bare \b slips the gate (no word characters in ""); see Assert::expect_scx_bpf_error_matches for the operator direction.
  • extra_include_files = ["PATH", "PATH", ...] — host-side file paths to bundle into the guest initramfs beyond what the entry’s scheduler / payload / workloads already declare via their own include_files. Use this for test-level dependencies that don’t belong on a specific Payload: auxiliary data files, per-test helper scripts, fixtures. Each element must be a string literal (no expressions). Maps onto KtstrTestEntry::extra_include_files and is unioned with the per-payload specs at run_ktstr_test time via KtstrTestEntry::all_include_files. Default: []. Path resolution: bare names (no /) search PATH; paths containing / are absolute or relative to the test process current directory; directories are walked recursively at test-run time (rejected by cargo ktstr export since the .run packager handles regular files only — recursive directory packaging is a v2 enhancement); a missing file fails loudly at setup with an actionable error naming the missing path.

Duplicate keys: each attribute KEY may appear at most once per #[ktstr_test] invocation; duplicate keys (whether the values match or differ) fail at expansion rather than silently letting the later value win. #[ktstr_test(host_only = false, host_only)] and #[ktstr_test(llcs = 4, llcs = 8)] both fail. The bare form (host_only) and explicit form (host_only = true) of the same attribute collide as well — they refer to the same slot. List values like workloads = [FIO, FIO] are NOT affected by this rule; the duplicate check is on attribute keys, not on values within an array. payload = ... and workloads = [..] keep their tailored messages directing the author to the right home for extras; config = ... and expect_scx_bpf_error_{contains,matches} = ... likewise have tailored wording; every other attribute uses a uniform “duplicate attribute” diagnostic.

Path / list forms: #[ktstr_test(crate::host_only)] (a multi-segment path, whether bare or as a key in crate::host_only = true) is rejected with a targeted message naming both valid forms with concrete examples — the macro only accepts bare single-segment idents because routing dispatches on the ident string against BOOL_ATTR_NAMES or the value-attr table. #[ktstr_test(host_only(false))] (parenthesised arguments) is rejected with a separate targeted message naming the attribute and the two valid forms (= value or bare); the same diagnostic fires for crate::host_only(false) so the operator sees one combined error rather than chasing two.