[PATCH v2] rust: alloc: test drop behavior of Vec::drain_all

From: Peder Bergan

Date: Tue Nov 03 2026 - 06:01:49 EST


The drain_all example consumes the entire iterator and uses integers, so
it cannot detect missing drops of unconsumed values or premature drops
of values yielded to the caller.

Add KUnit tests with per-element drop counters for partially consumed,
unconsumed and exhausted drains, plus an empty-vector case. Keep a
yielded value alive after both the drain and vector are dropped to check
that its ownership stays with the caller. Also check that draining
empties the vector without changing its capacity.

Signed-off-by: Peder Bergan <peder.bergan@xxxxxxxxx>
---
v2: Split a long test comment to avoid mail transport line wrapping.

This adds KUnit coverage for the ownership and destruction behavior of
Vec::drain_all. The existing example consumes integers to exhaustion,
which cannot exercise destruction of unconsumed resource-owning values.

The new tests cover partial, absent and complete consumption, plus an
empty vector. In the partial case, the first yielded value stays alive
after both the drain and the vector are dropped. Per-element counters
check that each value is destroyed exactly once by its current owner.

Validation on x86_64 under QEMU:

- The original rust_kvec suite passed before the change.
- All nine tests passed with the change, including a CLIPPY=1 build.
- Making DrainAll::drop temporarily empty caused exactly the partial and
unconsumed tests to fail. Restoring it returned the suite to passing.
- rustfmtcheck and checkpatch --strict --no-signoff passed on the draft.

I used Rust 1.99.1, LLVM 23.1.4, bindgen 0.73.2 and QEMU 11.1.3.
The minimal configuration has an existing unused-function warning in
io/resource.rs, also seen before this change.

OpenAI Codex assisted with coding and testing this issue.
The requested work was to test Vec::drain_all ownership and destruction,
validate the tests under QEMU, and prove that they detect skipped
destruction.

Base: alloc-next at 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4.
The patch changes only the existing KUnit module in kvec.rs.

rust/kernel/alloc/kvec.rs | 89 +++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)

diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index c7546b9da..01d468239 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -1540,6 +1540,95 @@ fn drop(&mut self) {
mod tests {
use super::*;
use crate::prelude::*;
+ use core::cell::Cell;
+
+ struct DropSpy<'a>(&'a Cell<usize>);
+
+ impl Drop for DropSpy<'_> {
+ fn drop(&mut self) {
+ self.0.set(self.0.get() + 1);
+ }
+ }
+
+ #[test]
+ fn test_kvec_drain_all_partial() -> Result {
+ let drops = [const { Cell::new(0) }; 3];
+ let mut v = KVec::new();
+ for counter in &drops {
+ v.push(DropSpy(counter), GFP_KERNEL)?;
+ }
+ let capacity = v.capacity();
+
+ let mut drain = v.drain_all();
+ let first = drain.next().ok_or(EINVAL)?;
+ drop(drain);
+
+ // The caller owns the yielded value.
+ // The drain drops the remaining values.
+ assert_eq!(drops.each_ref().map(Cell::get), [0, 1, 1]);
+ assert!(v.is_empty());
+ assert_eq!(v.capacity(), capacity);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [0, 1, 1]);
+
+ drop(first);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_drain_all_unconsumed() -> Result {
+ let drops = [const { Cell::new(0) }; 3];
+ let mut v = KVec::new();
+ for counter in &drops {
+ v.push(DropSpy(counter), GFP_KERNEL)?;
+ }
+ let capacity = v.capacity();
+
+ drop(v.drain_all());
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ assert!(v.is_empty());
+ assert_eq!(v.capacity(), capacity);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_drain_all_consumed() -> Result {
+ let drops = [const { Cell::new(0) }; 3];
+ let mut v = KVec::new();
+ for counter in &drops {
+ v.push(DropSpy(counter), GFP_KERNEL)?;
+ }
+ let capacity = v.capacity();
+
+ let mut drain = v.drain_all();
+ for value in drain.by_ref() {
+ drop(value);
+ }
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ drop(drain);
+ assert!(v.is_empty());
+ assert_eq!(v.capacity(), capacity);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_drain_all_empty() {
+ let mut v = KVec::<DropSpy<'_>>::new();
+ let capacity = v.capacity();
+ let mut drain = v.drain_all();
+ assert!(drain.next().is_none());
+ drop(drain);
+ assert!(v.is_empty());
+ assert_eq!(v.capacity(), capacity);
+ }

#[test]
fn test_kvec_retain() {

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.56.0