redox-driver-core: F5 — regression tests for single enumeration per probe
Adds two AtomicUsize-counting bus regression tests that verify each registered bus's enumerate_devices() is called exactly once per DeviceManager::enumerate() call. The concurrent path (>=4 devices, max_concurrent_probes > 1) used to call manager.enumerate() recursively (G8 in the v3.0 assessment); the v2.2 sweep moved the concurrent path to ConcurrentDeviceManager:: from_devices() which takes pre-enumerated devices, but no test was added to lock in the contract. Tests: - enumerate_calls_each_bus_enumerate_devices_exactly_once: multi-bus setup with 4 PCI + 2 platform devices; PCI crosses the concurrent threshold; both buses must be enumerated exactly once. - enumerate_calls_each_bus_exactly_once_per_call_repeated: repeated enumerate() calls must each enumerate every bus exactly once (matters for retry_deferred integration). Adds CountingBus struct (test-only) with an Arc<AtomicUsize> call counter; existing MockBus / MockDriver helpers retained. 35 redox-driver-core tests pass (was 33).
This commit is contained in:
@@ -652,6 +652,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Counting bus variant that records how many times
|
||||
/// `enumerate_devices` was called. Used by the single-enumeration
|
||||
/// regression test to verify the concurrent path does NOT
|
||||
/// re-walk registered buses.
|
||||
struct CountingBus {
|
||||
name: &'static str,
|
||||
devices: Vec<DeviceInfo>,
|
||||
call_count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Bus for CountingBus {
|
||||
fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
fn enumerate_devices(&self) -> Result<Vec<DeviceInfo>, BusError> {
|
||||
self.call_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
Ok(self.devices.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct MockDriver {
|
||||
name: &'static str,
|
||||
description: &'static str,
|
||||
@@ -768,4 +790,160 @@ mod tests {
|
||||
if bus == "pci" && *device_count == 1
|
||||
)));
|
||||
}
|
||||
|
||||
/// Regression test for F5: the concurrent probe path must NOT
|
||||
/// re-walk registered buses. Each bus's `enumerate_devices` should
|
||||
/// fire exactly once per `DeviceManager::enumerate()` call, even
|
||||
/// when the workload crosses the threshold into concurrent mode.
|
||||
///
|
||||
/// Historically this was a bug (G8 in the v3.0 assessment): the
|
||||
/// concurrent path used to call `manager.enumerate()` recursively,
|
||||
/// doubling bus-enumeration work on multi-bus systems.
|
||||
#[test]
|
||||
fn enumerate_calls_each_bus_enumerate_devices_exactly_once() {
|
||||
let mut manager = DeviceManager::new(config());
|
||||
// The default config() has max_concurrent_probes=4 and the
|
||||
// workload_threshold is 4 devices. With 4 PCI devices and 2
|
||||
// platform devices, the PCI bus crosses the concurrent threshold.
|
||||
let count_a = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
let count_b = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
manager.register_bus(Box::new(CountingBus {
|
||||
name: "pci",
|
||||
devices: (0..4)
|
||||
.map(|i| {
|
||||
use crate::device::{DeviceId, DeviceInfo};
|
||||
DeviceInfo {
|
||||
id: DeviceId {
|
||||
bus: "pci".to_string(),
|
||||
path: format!("0000:00:0{i}.0"),
|
||||
},
|
||||
vendor: Some(0x1234),
|
||||
device: Some(0x5678),
|
||||
class: Some(0x02),
|
||||
subclass: Some(0x00),
|
||||
prog_if: None,
|
||||
revision: None,
|
||||
subsystem_vendor: None,
|
||||
subsystem_device: None,
|
||||
raw_path: format!("/scheme/pci/0000:00:0{i}.0"),
|
||||
description: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
call_count: std::sync::Arc::clone(&count_a),
|
||||
}));
|
||||
manager.register_bus(Box::new(CountingBus {
|
||||
name: "platform",
|
||||
devices: (0..2)
|
||||
.map(|i| {
|
||||
use crate::device::{DeviceId, DeviceInfo};
|
||||
DeviceInfo {
|
||||
id: DeviceId {
|
||||
bus: "platform".to_string(),
|
||||
path: format!("plat{i}"),
|
||||
},
|
||||
vendor: None,
|
||||
device: None,
|
||||
class: None,
|
||||
subclass: None,
|
||||
prog_if: None,
|
||||
revision: None,
|
||||
subsystem_vendor: None,
|
||||
subsystem_device: None,
|
||||
raw_path: format!("/scheme/platform/plat{i}"),
|
||||
description: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
call_count: std::sync::Arc::clone(&count_b),
|
||||
}));
|
||||
// Register a low-priority driver that matches all of the above
|
||||
// devices so every device enters the probe path (and forces the
|
||||
// concurrent threshold to be met for the PCI bus).
|
||||
manager.register_driver(Box::new(MockDriver {
|
||||
name: "catchall",
|
||||
description: "catch-all",
|
||||
priority: 1,
|
||||
matches: vec![DriverMatch {
|
||||
vendor: None,
|
||||
device: None,
|
||||
class: None,
|
||||
subclass: None,
|
||||
prog_if: None,
|
||||
subsystem_vendor: None,
|
||||
subsystem_device: None,
|
||||
}],
|
||||
}));
|
||||
|
||||
let _ = manager.enumerate();
|
||||
|
||||
assert_eq!(
|
||||
count_a.load(std::sync::atomic::Ordering::SeqCst),
|
||||
1,
|
||||
"pci bus must be enumerated exactly once per manager.enumerate() call"
|
||||
);
|
||||
assert_eq!(
|
||||
count_b.load(std::sync::atomic::Ordering::SeqCst),
|
||||
1,
|
||||
"platform bus must be enumerated exactly once per manager.enumerate() call"
|
||||
);
|
||||
}
|
||||
|
||||
/// Second call to `enumerate()` should also enumerate each bus
|
||||
/// exactly once — verifies the regression test does not flake
|
||||
/// when called multiple times (e.g. during retry_deferred).
|
||||
#[test]
|
||||
fn enumerate_calls_each_bus_exactly_once_per_call_repeated() {
|
||||
let mut manager = DeviceManager::new(config());
|
||||
let count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
manager.register_bus(Box::new(CountingBus {
|
||||
name: "pci",
|
||||
devices: (0..4)
|
||||
.map(|i| {
|
||||
use crate::device::{DeviceId, DeviceInfo};
|
||||
DeviceInfo {
|
||||
id: DeviceId {
|
||||
bus: "pci".to_string(),
|
||||
path: format!("0000:00:0{i}.0"),
|
||||
},
|
||||
vendor: Some(0x1234),
|
||||
device: Some(0x5678),
|
||||
class: Some(0x02),
|
||||
subclass: Some(0x00),
|
||||
prog_if: None,
|
||||
revision: None,
|
||||
subsystem_vendor: None,
|
||||
subsystem_device: None,
|
||||
raw_path: format!("/scheme/pci/0000:00:0{i}.0"),
|
||||
description: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
call_count: std::sync::Arc::clone(&count),
|
||||
}));
|
||||
manager.register_driver(Box::new(MockDriver {
|
||||
name: "catchall",
|
||||
description: "catch-all",
|
||||
priority: 1,
|
||||
matches: vec![DriverMatch {
|
||||
vendor: None,
|
||||
device: None,
|
||||
class: None,
|
||||
subclass: None,
|
||||
prog_if: None,
|
||||
subsystem_vendor: None,
|
||||
subsystem_device: None,
|
||||
}],
|
||||
}));
|
||||
|
||||
for _ in 0..3 {
|
||||
let _ = manager.enumerate();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
count.load(std::sync::atomic::Ordering::SeqCst),
|
||||
3,
|
||||
"each call to manager.enumerate() should enumerate each bus exactly once"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user