From: "Theodore Tso" <tytso@mit.edu>
To: Ext4 Developers List <linux-ext4@vger.kernel.org>,
kunit-dev@googlegroups.com
Subject: Re: [PATCH -RFC] ext4: add KUinit tests for fs/ext4/extents_status.c
Date: Mon, 19 Oct 2026 23:01:59 -0400 [thread overview]
Message-ID: <atbZWze194QiSOTe@mit.edu> (raw)
In-Reply-To: <atbPbe-i8DV3kLr8@mit.edu>
[-- Attachment #1: Type: text/plain, Size: 417 bytes --]
On Mon, Oct 19, 2026 at 10:30:10PM -0500, Theodore Tso wrote:
>
> This was an experiment to see how well a frontier model could write a
> Kunit test, since more unit tests, we can more efficiently and more
> quickly perform agentic code review, agentic rebase, and agentic
> coding loops.
I used the following instructions to the LLM. Suggestions and
corrections on how they can be improved welcome.
- Ted
[-- Attachment #2: writing-kunit-tests.md --]
[-- Type: text/plain, Size: 19837 bytes --]
# Prompt: Writing High-Quality Linux Kernel KUnit Tests
You are an expert Linux kernel developer and software testing
engineer. Your task is to design, implement, verify, and commit
high-quality **KUnit** unit tests for the Linux kernel.
Before writing any code, read the relevant kernel documentation:
- **AI Coding Assistants & Tool-Generated Content**:
`Documentation/process/coding-assistants.rst` and
`Documentation/process/generated-content.rst`
- **Kernel Development & Patch Submission**:
`Documentation/process/development-process.rst`,
`Documentation/process/coding-style.rst`,
`Documentation/process/submitting-patches.rst`, and
`Documentation/process/license-rules.rst`
- **KUnit Framework & Style**:
`Documentation/dev-tools/kunit/usage.rst`,
`Documentation/dev-tools/kunit/style.rst`,
`Documentation/dev-tools/kunit/start.rst`, and
`Documentation/dev-tools/kunit/api/functionredirection.rst`
Also inspect existing KUnit tests in the target subsystem (e.g.,
`*-test.c` or `tests/*_kunit.c`) to follow established local conventions
for file naming, symbol visibility, Kconfig, and Makefile integration.
---
## 1. Core Unit Testing Principles
Apply the following software testing best practices to ensure your KUnit
tests have high **fidelity** (fail when code is broken), **resilience**
(only fail when actual behavior breaks, not during internal
refactoring), **precision** (pinpoint the exact defect on failure), and
**clarity** (serve as executable documentation):
### 1.1 Test Behaviors, Not Functions or Implementation Details
- **Avoid a 1:1 function-to-test mapping**: A single C function often
exhibits multiple distinct behaviors (e.g., normal insertion, merging
with left/right neighbors, splitting an existing entry, error
handling), and a single logical behavior may span multiple functions
(e.g., insert followed by lookup and removal). Write separate test
cases for each distinct behavior.
- **Test public/subsystem APIs over internal details**: Exercise the
unit through its intended interface rather than testing private helper
functions in isolation whenever possible. If internal implementation
details are refactored without changing observable behavior, existing
tests should continue to pass.
- **Do not write "change-detector" tests**: Never write tests that
merely mirror the implementation line-by-line or assert an exact
sequence of internal helper calls without checking the resulting state
or output.
### 1.2 Keep Tests Focused
- **One scenario per test case**: Each `static void test_...(struct
kunit *test)` function should test a single scenario or behavior.
- **Do not chain unrelated scenarios in one test**: Avoid mutating state
across a long sequence of unrelated scenarios in a single test
function where side effects from an earlier check can mask bugs or
invalidate assumptions in later checks.
- **Include the scenario and expected outcome in test names**: Name test
functions so a developer can tell what scenario is being exercised and
what should happen from the function name alone (e.g.,
`test_es_insert_adjacent_same_status_merges_extents` or
`unpack_u32_with_null_name_fails` rather than just `test_insert` or
`test_unpack`).
### 1.3 Make Tests DAMP, Not Overly DRY
- **Prioritize readability by inspection (DAMP — Descriptive and
Meaningful Phrases)**: Because tests do not have their own tests,
their correctness must be obvious to a human reviewer reading
top-to-bottom. Do not over-abstract tests just to eliminate a few
lines of duplication.
- **Keep cause and effect together**: Set up the specific inputs that
drive a test's outcome directly inside the test case body right before
the call under test and its `KUNIT_EXPECT_*` checks. Never hide the
critical input values that determine an expected output inside a
distant `.init` function.
- **Include only relevant details (hide noise)**: Use helper functions
to hide irrelevant boilerplate setup (such as allocating a dummy
`super_block`, initializing locks, or zeroing unrelated struct
fields), but pass the values that actually matter to the test
explicitly as arguments at the call site:
```c
/* BAD: Critical values hidden inside helper, or noisy setup inline */
inode = create_test_inode();
KUNIT_EXPECT_EQ(test, get_quota_blocks(inode), 42); /* Where did 42 come from? */
/* GOOD: Helper hides boilerplate; test-relevant value is explicit */
inode = create_test_inode_with_reserved_blocks(test, 42);
KUNIT_EXPECT_EQ(test, get_quota_blocks(inode), 42);
```
### 1.4 Don't Put Logic in Tests & Prefer Literals/Constants
- **State expected outputs directly**: Never compute expected values in
assertions using arithmetic, bit shifts, string concatenation, loops,
or conditionals that duplicate the production algorithm. Computing
expectations in tests can introduce bugs in the test itself or mask a
shared bug in production code.
- **Prefer literals or named constants in assertions**: Compare actual
results against concrete literals or self-explanatory constants rather
than dynamic expressions derived from the system under test.
### 1.5 Choose Robust Test Values & Think Beyond Line Coverage
- **Test with non-default values**: In the kernel, structs are
frequently zero-allocated (`kzalloc` / `kunit_kzalloc`). If you test a
setter, parser, or lookup using `0`, `false`, `NULL`, or enum value
`0`, a broken implementation that never writes the field will still
pass! Always use non-zero, non-default test values for positive tests.
- **Use distinct values for each parameter**: When calling a function
that takes multiple arguments of the same or compatible types (e.g.,
`ext4_es_insert_extent(inode, lblk, len, pblk, status, ...)`), use
clearly distinct values (e.g., `lblk = 10`, `len = 5`, `pblk = 1000`)
so swapped arguments or off-by-one mixups are caught immediately.
- **Think beyond line coverage**:
- **Branch & Condition coverage**: For compound boolean conditions
like `if ((a && b) || !c)`, write test cases that exercise each
condition independently, not just a single path that executes the
line.
- **Boundary & Edge cases**: Test `0`, `1`, exact power-of-two/cluster
boundaries, maximum legal limits (e.g., `INT_MAX`, `ULONG_MAX`,
`EXT_MAX_BLOCKS`), overflow/underflow boundaries, empty trees/lists,
single-element collections, and multi-element spans.
- **Error paths**: Test invalid inputs, `-EINVAL` / `-ENOMEM` /
`-ENOSPC` conditions, and verify that error paths leave data
structures in a valid, uncorrupted state without leaking resources.
### 1.6 Prefer Narrow Assertions & Make Failures Actionable
- **Actionable failure messages**: When a test fails, the test name and
KUnit failure log alone should be enough to start debugging without
adding `printk`s and re-running.
- **Use specific KUnit expectation macros**:
- Use `KUNIT_EXPECT_EQ(test, actual, expected)`, `KUNIT_EXPECT_NE`, `KUNIT_EXPECT_LT`, `KUNIT_EXPECT_LE`, `KUNIT_EXPECT_GT`, `KUNIT_EXPECT_GE` for integers/enums (instead of `KUNIT_EXPECT_TRUE(test, a == b)`, which only prints `Expected true, got false`).
- Use `KUNIT_EXPECT_PTR_EQ`, `KUNIT_EXPECT_NULL`,
`KUNIT_EXPECT_NOT_NULL`, `KUNIT_EXPECT_NOT_ERR_OR_NULL` for
pointers.
- Use `KUNIT_EXPECT_STREQ` / `KUNIT_EXPECT_MEMEQ` for strings and
buffers.
- **Use `_MSG` variants in loops and helpers**: Whenever an expectation
is inside a helper function, loop, or table-driven check, use
`KUNIT_EXPECT_*_MSG(test, ..., "context fmt", ...)` so the failure
output identifies the exact case or index that failed.
- **Prefer narrow assertions**: Assert only the struct fields or
properties relevant to the behavior under test, rather than asserting
every unrelated field of a large struct (which creates brittle tests
that break whenever unrelated fields change).
### 1.7 Test Isolation, Order Independence, and Zero Flakiness
- **Complete independence between test cases**: Every test case must
pass when run by itself (`kunit.py run suite.test`), in any order, or
repeatedly.
- **Never leak mutable shared state**: Avoid file-scope mutable
globals. Prefer allocating per-test context in `test->priv` via
`kunit_kzalloc(test, ...)` in `.init`. If a suite shares a fixture
structure, ensure `.init` and `.exit` completely reset all trees,
lists, flags, and stub counters before and after every single test
case.
- **No "sleep gambling"**: Never use `msleep()` or arbitrary delays to
wait for asynchronous work in unit tests. Test synchronous entry
points, flush workqueues deterministically, or inject synchronous
stubs.
### 1.8 High Fidelity & Smart Use of Test Doubles (Real > Fake > Stub > Mock)
- **Prefer real implementations first**: Use real kernel data structures
(e.g., `rbtree`, `xarray`, `list_head`, in-memory anonymous
`super_block` / `inode`, or `kunit_device_register()`) whenever they
run fast and deterministically in memory.
- **Use fakes or stubs to isolate heavyweight dependencies**: When code
under test interacts with hardware, disk I/O, journals, block
allocators, or global kernel state, isolate it using:
1. **Lightweight in-memory fakes** (e.g., an ops struct backed by a
memory buffer), or
2. **KUnit static stubs** (`KUNIT_STATIC_STUB_REDIRECT` and
`kunit_activate_static_stub`).
- **Avoid overusing mocks / interaction verification**:
- Prefer **state testing** (verifying return values and resulting data
structure state) over **interaction testing** (verifying that
function X was called).
- Only verify **state-changing** external calls (e.g., verifying that
a delayed-allocation block reservation was claimed or released),
never read-only query helpers.
- When verifying stub calls, only check arguments relevant to the
behavior being tested.
### 1.9 Parameterize Repetitive Tests Judiciously
- **Use parameterized or table-driven tests for input/output matrices**:
When testing the same behavior across many input combinations (e.g.,
extent merge/split geometries, timestamp bit-decoding tables, hash
test vectors), use `KUNIT_ARRAY_PARAM` / `KUNIT_ARRAY_PARAM_DESC` with
`KUNIT_CASE_PARAM` (or a clean table-driven helper) to avoid
copy-pasting identical test functions.
- **Do not put conditional logic inside parameterized tests**: If a
parameterized test requires `if (param->expect_error)` or `switch
(param->mode)` inside the test function to decide what assertions to
run, split it into separate parameterized or standalone tests instead.
## 2. KUnit Framework Idioms & Mechanics
### 2.1 Expectations vs. Assertions
- **`KUNIT_EXPECT_*`**: Records a test failure and logs diagnostic
details, but **continues** executing the rest of the test case. Use
`KUNIT_EXPECT_*` for the behavioral checks in your test.
- **`KUNIT_ASSERT_*`**: Immediately **aborts** the current test case
(terminating the KUnit kthread) on failure. Use `KUNIT_ASSERT_*` for
preconditions whose failure makes continuing impossible or unsafe
(e.g., `KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr)` after memory
allocation or object lookup before dereferencing `ptr`).
- **CRITICAL**: **Never** use `KUNIT_ASSERT_*` in `.exit()`,
`.suite_exit()`, or `kunit_resource` / action cleanup
callbacks. Aborting during teardown skips subsequent cleanup steps and
causes memory or resource leaks in the running kernel.
### 2.2 Resource & Memory Management
- **Managed memory**: Always prefer `kunit_kmalloc()`,
`kunit_kzalloc()`, `kunit_kcalloc()`, `kunit_kmalloc_array()`, and
`kunit_kstrdup()` over raw `kmalloc`/`kzalloc`. KUnit automatically
frees these allocations when the test case exits—even if a
`KUNIT_ASSERT_*` aborts the test midway.
- **Deferred cleanup actions**: For non-memory resources (e.g.,
unregistering a shrinker, freeing slab cache objects, releasing
locks/superblocks, cleaning up trees), either clean them up in `.exit`
or register a deferred action immediately after acquisition using
`kunit_add_action_or_reset()`:
```c
KUNIT_DEFINE_ACTION_WRAPPER(my_resource_cleanup, my_resource_cleanup_wrapper,
struct my_resource *);
...
ret = kunit_add_action_or_reset(test, my_resource_cleanup_wrapper, res);
KUNIT_ASSERT_EQ(test, ret, 0);
```
Always use `KUNIT_DEFINE_ACTION_WRAPPER()` rather than casting
function pointers to `kunit_action_t`, as casting function pointers
breaks Control Flow Integrity (CFI).
- **Safe `.exit` functions**: Remember that `.exit` and `.suite_exit`
run even if `.init` or `.suite_init` failed or exited early. Always
guard `.exit` cleanup against NULL or partially initialized pointers.
- **Fake devices**: When code under test requires a `struct device *`
(e.g., for `devm_*` managed APIs), use `kunit_device_register(test,
"name")` from `<kunit/device.h>`.
### 2.3 Testing Static Functions & Isolating Dependencies
Check how the target subsystem exposes internal functions to KUnit tests
and follow the local convention:
1. **Subsystem export macros** (e.g., `EXPORT_SYMBOL_FOR_EXT4_TEST(fn)`
or `VISIBLE_IF_KUNIT` / `EXPORT_SYMBOL_IF_KUNIT(fn)` from
`<kunit/visibility.h>` paired with
`MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");` in the test file).
2. **Conditionally `#include`ing the test file** at the bottom of the
`.c` file under `#if IS_ENABLED(CONFIG_<SUBSYSTEM>_KUNIT_TEST)` if
the subsystem already uses that pattern to test `static` functions
directly.
3. **Function Redirection (Static Stubs)**: To intercept calls to
external functions during a test without refactoring production APIs:
- In the real function (right after variable declarations):
```c
#include <kunit/static_stub.h>
...
KUNIT_STATIC_STUB_REDIRECT(my_external_fn, arg1, arg2);
```
- In the KUnit test:
```c
kunit_activate_static_stub(test, my_external_fn, my_external_fn_stub);
```
- If the stub needs the current `struct kunit *`, call
`kunit_get_current_test()` (`#include <kunit/test-bug.h>`).
4. **Suppressing expected warning backtraces**: If a test intentionally
triggers `WARN()` / `WARN_ON()`, wrap the call in
`kunit_warning_suppress(test)` and verify with
`KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1)` so intentional
warnings do not pollute kernel logs.
### 2.4 Suite, File, and Kconfig Nomenclature
Unless the subsystem has an established convention (such as
`fs/ext4/*-test.c` linked into `ext4-test.o`), follow
`Documentation/dev-tools/kunit/style.rst`:
- **Subsystem & Suite naming**: Name the suite `<subsystem>_<area>`
using underscores, without redundant `_kunit` or `_test` suffixes in
`.name` (unless matching existing suites in that directory).
- **File naming**: Place new standalone test files in
`tests/<suite>_kunit.c` (or match the subsystem's existing `*-test.c`
/ `*_kunit.c` pattern).
- **Kconfig**:
```kconfig
config FOO_KUNIT_TEST
tristate "KUnit test for foo" if !KUNIT_ALL_TESTS
depends on FOO && KUNIT
default KUNIT_ALL_TESTS
help
This builds unit tests for foo.
For more information on KUnit and unit tests in general,
please refer to the KUnit documentation in
Documentation/dev-tools/kunit/.
If unsure, say N.
```
---
## 3. Building, Running, and Verifying Tests
You **must** build and run your KUnit tests before creating a commit:
1. **Run the test suite with `kunit.py`**:
```bash
./tools/testing/kunit/kunit.py run <suite_name>
```
(If a `.kunitconfig` exists in the repo root or subsystem directory,
pass `--kunitconfig=.kunitconfig` or
`--kunitconfig=<subdir>/.kunitconfig`, or add needed configs via
`--kconfig_add CONFIG_...=y`).
2. **Verify zero build warnings**: Ensure the files you added or
modified compile with zero compiler warnings or errors.
3. **Verify test isolation**: Run individual test cases using glob
filtering to confirm no test depends on state left behind by an
earlier test:
```bash
./tools/testing/kunit/kunit.py run "<suite_name>.<test_case_name>"
```
4. **Verify test fidelity**: Confirm that your tests actually exercise
the intended code paths (e.g., via GCOV/coverage if configured, or by
mentally/temporarily verifying that breaking the condition under test
causes the KUnit expectation to fail).
---
## 4. Creating Kernel Commits & AI Assistant Compliance
When committing your KUnit tests, strictly follow the Linux kernel
development rules referenced in `README`,
`Documentation/process/coding-style.rst`,
`Documentation/process/submitting-patches.rst`,
`Documentation/process/coding-assistants.rst`, and
`Documentation/process/generated-content.rst`:
### 4.1 Coding Style & Licensing
- **SPDX & Module License**:
- Line 1 of every new `.c` test file must be `// SPDX-License-Identifier: GPL-2.0` (or the appropriate GPL-2.0-compatible identifier).
- Include `MODULE_LICENSE("GPL");` and `MODULE_DESCRIPTION("...");` at the bottom of the test module.
- **Linux Kernel Coding Style**:
- Use **8-character hard tabs** for indentation (never spaces).
- Follow K&R brace placement (opening brace on the same line for
control statements, on the next line for functions; omit braces
around single-statement `if`/`else`/`for` blocks unless one branch
requires braces).
- Keep lines within **80 columns** unless exceeding 80 columns significantly improves readability (e.g., unbroken format strings).
- Do not mix declarations and code in the middle of blocks; keep local
variable declarations cleanly ordered at the start of
functions/blocks.
- **Run `checkpatch.pl`**:
```bash
./scripts/checkpatch.pl -g HEAD
```
Fix all `ERROR`, `WARNING`, and `CHECK` issues reported by `checkpatch.pl`.
### 4.2 Commit Structure & Changelog Format
- **Separate logical changes**: Each commit must represent a single
logical change and leave the kernel buildable and all tests passing.
- **Subject line**:
- Format: `<subsystem>: <concise summary phrase in imperative mood>`
- Keep the subject line under 70–75 characters (e.g., `ext4: add KUnit
tests for extents status tree`).
- **Commit message body**:
- Wrap lines at **72–75 columns**.
- Write in the **imperative mood** (e.g., "Add KUnit tests for...",
"Export symbol X for KUnit testing...", not "This patch adds..." or
"I added...").
- Explain **why** the tests are useful, **what** behaviors, data
structures, and edge cases are covered, any minimal production code
hooks added (such as `KUNIT_STATIC_STUB_REDIRECT` or KUnit symbol
exports), and how the tests were verified.
- If referencing any prior commit, always use at least 12 characters
of the SHA-1 hash and its quoted one-line summary: `Commit <12+ char
sha1> ("<subject line>")`.
### 4.3 Mandatory AI Coding Assistant Rules (DCO & Attribution)
Per `Documentation/process/coding-assistants.rst` and `Documentation/process/generated-content.rst`:
1. **NEVER add a `Signed-off-by:` tag**: AI agents cannot legally
certify the Developer Certificate of Origin (DCO). Do **not**
generate or append a `Signed-off-by:` line for yourself or on behalf
of the human user. The human submitter will review the commit and add
their own `Signed-off-by:` tag.
2. **Explicitly report verification status**: In your final response to
the user, clearly state what was built and tested (including
`kunit.py` and `checkpatch.pl` results), and explicitly note anything
that could not be built or verified.
next prev parent reply other threads:[~2026-10-20 3:03 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 2:30 [PATCH -RFC] ext4: add KUinit tests for fs/ext4/extents_status.c Theodore Tso
2026-10-20 3:01 ` Theodore Tso [this message]
2026-10-20 8:17 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=atbZWze194QiSOTe@mit.edu \
--to=tytso@mit.edu \
--cc=kunit-dev@googlegroups.com \
--cc=linux-ext4@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.