#[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:
- Renames the original function to
__ktstr_inner_{name}. - Registers it in the
KTSTR_TESTSdistributed slice via linkme. - 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 picksmax(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 ifmemory_mibis also below it. Settingmemory_mibabove 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 isvalue << 20bytes, not decimal megabytes.duration_s = N— scenario run duration in seconds; maps ontoKtstrTestEntry::durationwatchdog_timeout_s = N— watchdog fire threshold in seconds; maps ontoKtstrTestEntry::watchdog_timeoutcleanup_budget_ms = N— sub-watchdog cap on host-side VM teardown wall time; maps ontoKtstrTestEntry::cleanup_budgetasDuration::from_millis(N). Default:None(unenforced).num_snapshots = N— fireNperiodicfreeze_and_capture(false)boundaries inside the workload’s 10 %–90 % window, stored on the hostSnapshotBridgeunderperiodic_NNN.0(default) disables periodic capture entirely. Maps ontoKtstrTestEntry::num_snapshots; runtimeKtstrTestEntry::validaterejects values past the bridge cap (MAX_STORED_SNAPSHOTS),host_only = true, and duration /Nsettings that would land boundaries closer than 100 ms apart.scheduler = PATH— path to aconst Scheduler(typically produced bydeclare_scheduler!(...)). Maps ontoKtstrTestEntry::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 aconst Payloadused as the primary binary workload (must bePayloadKind::Binary; runtime-enforced). Default:None(scheduler-only test). Coexists withscheduler = PATH— the payload runs under the selected scheduler.workloads = [PATH, PATH, ...]— additionalconst Payloadreferences composed with the primary viaCtx::payloadin the test body. Default:&[]. Must not contain the same path aspayload— reject at expansion time to catch the common “fio as primary AND workload” slip.auto_repro = bool(default:true)wprof = bool(default:false; requires thewprofcargo feature on ktstr) — attach/bin/wprofto the test’s VM(s) and ship the Perfetto trace to the host.wprof_args = "..."(requires thewprofcargo feature) — overrideWprofConfig::default_args. Only meaningful withwprof = true. Parsed as space-separated tokens.host_only = bool(default:false) — run the test function on the host instead of inside a VMno_perf_mode = bool(default:false) — decouple the virtual topology from host hardware. The VM is built with the declarednuma_nodes/llcs/cores/threadseven 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 withperformance_mode = true. Maps ontoKtstrTestEntry::no_perf_mode.post_vm = PATH— host-side callback invoked aftervm.run()returns, with access to the fullVmResult. Use for assertions that need host-side state — e.g. drainingVmResult.snapshot_bridgeafter a snapshot capture pipeline fires inside the guest. The function must have signaturefn(&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 — seepost_vm_unconditionalfor the always-runs sibling. Default:None(no callback).post_vm_unconditional = PATH— host-side callback that always runs aftervm.run()returns, bypassing the guest-fail suppression that gatespost_vm. Same signature aspost_vmand 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 isif !result.success { return Ok(()); }at the top of the callback body. Settingpost_vm_unconditionaldoes 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 viacombine_post_vm_errswhen both fire). Default:None(no callback).disk = PATH— path to aconst DiskConfigattached to the VM as a virtio-blk device at/dev/vda. Construct viaDiskConfig::DEFAULT.with_name("data")or similar const-fn chain (thewith_namebuilder takes&'static strso the full expression is const-evaluable). Maps ontoKtstrTestEntry::disk. Default:None(no disk). Mutually exclusive withhost_only = true—host_onlyskips the VM boot that owns the device lifecycle, so adiskattached underhost_onlywould never bind;KtstrTestEntry::validaterejects the pairing at runtime.network = PATH— path to aconst NetConfigattaching a virtio-net device (in-VMM loopback backend). Construct viaNetConfig::DEFAULT.mac(...)orNetConfig::DEFAULT(const-fn chain). Maps ontoKtstrTestEntry::network. Default:None(no NIC). Likedisk, mutually exclusive withhost_only.config = EXPR— inline scheduler config content, written into the guest at the path declared by the scheduler’sconfig_file_def.EXPRis either a string literal or a path to aconst &'static str(e.g.LAYERED_CONFIG). Maps ontoKtstrTestEntry::config_content. Required when the scheduler declaresconfig_file_def; rejected when the scheduler does not. The pairing is enforced at compile time via aconstassertion againstPayload::config_file_def, and again at runtime byKtstrTestEntry::validateso direct programmatic-entry construction sees the same gate.expect_scx_bpf_error_contains = EXPR— literal-substring matcher applied to the capturedscx_bpf_errortext in reproducer mode.EXPRis either a string literal or a path to aconst &'static str. Maps ontoAssert::expect_scx_bpf_error_contains. Requiresexpect_err = true(rejected at construction otherwise byKtstrTestEntry::validate). Empty strings panic at construction. When both_containsand_matchesare 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 ontoAssert::expect_scx_bpf_error_matches. Validated at construction: empty patterns, invalid regex syntax, and any pattern satisfyingis_match("")all panic immediately. Theis_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\bslips the gate (no word characters in""); seeAssert::expect_scx_bpf_error_matchesfor the operator direction.extra_include_files = ["PATH", "PATH", ...]— host-side file paths to bundle into the guest initramfs beyond what the entry’sscheduler/payload/workloadsalready declare via their owninclude_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 ontoKtstrTestEntry::extra_include_filesand is unioned with the per-payload specs atrun_ktstr_testtime viaKtstrTestEntry::all_include_files. Default:[]. Path resolution: bare names (no/) searchPATH; paths containing/are absolute or relative to the test process current directory; directories are walked recursively at test-run time (rejected bycargo ktstr exportsince the.runpackager 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.