[PATCH] rust: alloc: test retain ownership and mutable predicates

From: Peder Bergan

Date: Tue Nov 03 2026 - 07:04:29 EST


The existing retain test exhaustively checks filtering with integers, but
cannot observe when resource-owning elements are destroyed. It also does
not exercise mutation through the predicate.

Add KUnit tests with per-element drop counters for mixed, all-kept and
all-rejected selections, plus an empty-vector case. Check that the
predicate visits each element once in input order and that mutations
remain in the retained values. Verify that rejected values are destroyed
before retain returns and that retained values are destroyed when the
vector is dropped, with every value destroyed exactly once.

Signed-off-by: Peder Bergan <peder.bergan@xxxxxxxxx>
---
This adds KUnit coverage for Vec::retain with resource-owning elements and
mutable predicates. The existing exhaustive test checks integer filtering
but cannot observe destruction or changes made through the predicate.

The new cases cover mixed, all-kept and all-rejected selections, plus an
empty vector. They check callback order and count, retained mutations,
and exactly-once destruction by retain and the vector's destructor.
Production code is unchanged.

On x86_64 under QEMU, the five original tests passed before the change and
all nine passed with it, including a CLIPPY=1 build. Temporarily skipping
destruction in truncate caused exactly the mixed and all-rejected tests
to fail while the original integer test still passed. After restoring
the source, all nine tests passed again. A separate predicate-bypass
experiment also caused the expected callback-count failures.

rustfmtcheck and strict checkpatch with --no-signoff passed on this draft.
The draft applies to alloc-next at
6628e7d4c6b25c8d18f1345e3a20d21829aae7e4. My validation used Rust 1.99.1,
LLVM 23.1.4, bindgen 0.73.2 and QEMU 11.1.3. The baseline has an existing
unused-function warning in io/resource.rs, also present after the change.

OpenAI Codex assisted with coding and testing. The requested work was to
implement and prove that the tests detect skipped destruction.

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

diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index c7546b9da..3a1e09057 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -1540,6 +1540,115 @@ fn drop(&mut self) {
mod tests {
use super::*;
use crate::prelude::*;
+ use core::cell::Cell;
+
+ struct RetainItem<'a> {
+ id: usize,
+ value: usize,
+ drops: &'a Cell<usize>,
+ }
+
+ impl Drop for RetainItem<'_> {
+ fn drop(&mut self) {
+ self.drops.set(self.drops.get() + 1);
+ }
+ }
+
+ fn retain_items(drops: &[Cell<usize>]) -> Result<KVec<RetainItem<'_>>> {
+ let mut v = KVec::new();
+ for (id, counter) in drops.iter().enumerate() {
+ v.push(
+ RetainItem {
+ id,
+ value: id,
+ drops: counter,
+ },
+ GFP_KERNEL,
+ )?;
+ }
+ Ok(v)
+ }
+
+ #[test]
+ fn test_kvec_retain_mixed_drop() -> Result {
+ let drops = [const { Cell::new(0) }; 5];
+ let mut v = retain_items(&drops)?;
+ let mut visited = 0;
+
+ v.retain(|item| {
+ assert_eq!(item.id, visited);
+ visited += 1;
+ item.value += 10;
+ item.id % 2 == 1
+ });
+
+ assert_eq!(visited, 5);
+ assert!(v.iter().map(|item| item.id).eq([1, 3]));
+ assert!(v.iter().map(|item| item.value).eq([11, 13]));
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 0, 1, 0, 1]);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_retain_all_kept_drop() -> Result {
+ let drops = [const { Cell::new(0) }; 3];
+ let mut v = retain_items(&drops)?;
+ let mut visited = 0;
+
+ v.retain(|item| {
+ assert_eq!(item.id, visited);
+ visited += 1;
+ item.value += 10;
+ true
+ });
+
+ assert_eq!(visited, 3);
+ assert!(v.iter().map(|item| item.id).eq([0, 1, 2]));
+ assert!(v.iter().map(|item| item.value).eq([10, 11, 12]));
+ assert_eq!(drops.each_ref().map(Cell::get), [0, 0, 0]);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_retain_all_rejected_drop() -> Result {
+ let drops = [const { Cell::new(0) }; 3];
+ let mut v = retain_items(&drops)?;
+ let mut visited = 0;
+
+ v.retain(|item| {
+ assert_eq!(item.id, visited);
+ visited += 1;
+ false
+ });
+
+ assert_eq!(visited, 3);
+ assert!(v.is_empty());
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+
+ drop(v);
+ assert_eq!(drops.each_ref().map(Cell::get), [1, 1, 1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_kvec_retain_empty() {
+ let mut v = KVec::<RetainItem<'_>>::new();
+ let mut visited = 0;
+
+ v.retain(|_| {
+ visited += 1;
+ false
+ });
+
+ assert_eq!(visited, 0);
+ assert!(v.is_empty());
+ }

#[test]
fn test_kvec_retain() {

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.56.0