Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ mod tests {
std::iter::from_fn(|| listener.accept().ok()).count()
}

/// Test that discovery identifies a control-socket peer once and does
/// not reconnect on later passes until the socket is rebound.
/// Test that discovery identifies a control-socket peer once and does not
/// reconnect on later passes until the socket is rebound.
#[tokio::test]
async fn test_discovery_reuses_identified_control_socket_peer() {
let socket_dir = tempfile::tempdir().unwrap();
Expand Down
8 changes: 4 additions & 4 deletions src/backends/qemu/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ mod tests {
use super::*;

proptest! {
/// Test that vQ round-robin mapping spreads queues across IOThreads
/// as evenly as possible.
/// Test that vQ round-robin mapping spreads queues across IOThreads as
/// evenly as possible.
#[test]
fn test_qemu_round_robin_partitions_queues(
ids in prop::collection::vec("[a-z]{1,4}", 0..8),
Expand Down Expand Up @@ -373,8 +373,8 @@ mod tests {
}
}

/// Test that managed `iotN` threads are kept in id order and other ids are
/// dropped.
/// Test that managed `iotN` threads are kept in id order and other ids
/// are dropped.
#[test]
fn test_qemu_topology_keeps_managed_threads_in_id_order() {
let body = r#"
Expand Down
58 changes: 21 additions & 37 deletions src/backends/qemu/libvirt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,10 @@ fn parse_qmp_envelope(cmd: &str, body: &str) -> Result<Box<RawValue>, QemuError>
mod tests {
use super::*;

/// Test that domain XML detection accepts virtio-scsi model
/// variants and rejects others.
/// Test that domain XML detection accepts virtio-scsi model variants and
/// rejects others.
#[test]
fn xml_matches_virtio_scsi_variants() {
fn test_libvirt_xml_matches_virtio_scsi_variants() {
let a = r#"<controller type='scsi' index='0' model='virtio-scsi'>"#;
let b = r#"<controller type="scsi" index="0" model="virtio-scsi"/>"#;
let c = r#"<controller type='scsi' model='lsi'/>"#;
Expand All @@ -457,18 +457,17 @@ mod tests {
assert!(!xml_has_virtio_scsi(d));
}

/// Test that a QMP success envelope yields the return payload.
/// Test that parsing the qmp envelope does not modify the payload.
#[test]
fn parse_envelope_extracts_return() {
fn test_libvirt_qmp_return_envelope_yields_the_payload_unchanged() {
let body = r#"{"return":{"foo":42}}"#;
let v = parse_qmp_envelope("query", body).unwrap();
assert_eq!(v.get(), r#"{"foo":42}"#);
}

/// Test that a QMP error envelope becomes a typed error with
/// class/desc.
/// Test that a QMP error envelope becomes a typed error with class/desc.
#[test]
fn parse_envelope_surfaces_qmp_error() {
fn test_libvirt_qmp_error_envelope_preserves_class_and_desc() {
let body = r#"{"error":{"class":"GenericError","desc":"boom"}}"#;
let err = parse_qmp_envelope("query", body).unwrap_err();
match err {
Expand All @@ -480,40 +479,25 @@ mod tests {
}
}

/// Test that blockstats counters are summed across disks.
/// Test that device counters are added, with flush and unmap folded into
/// other I/O.
#[test]
fn parse_blockstats_sums_across_devices() {
fn test_libvirt_blockstats_sums_devices() {
let body = r#"[
{"stats": {
"rd_operations": 100,
"wr_operations": 200,
"flush_operations": 3,
"unmap_operations": 1,
"rd_bytes": 4096,
"wr_bytes": 8192
}},
{"stats": {
"rd_operations": 50,
"wr_operations": 25,
"flush_operations": 0,
"unmap_operations": 0,
"rd_bytes": 2048,
"wr_bytes": 1024
}}
{"stats":{"rd_bytes":10,"wr_bytes":20,"rd_operations":1,"wr_operations":2,"flush_operations":3,"unmap_operations":4}},
{"stats":{"rd_bytes":5,"wr_bytes":6,"rd_operations":7,"wr_operations":8,"flush_operations":9,"unmap_operations":10}}
]"#;
let p = parse_blockstats(body).unwrap().unwrap();
assert_eq!(p.read_io_count, 150);
assert_eq!(p.write_io_count, 225);
assert_eq!(p.other_io_count, 4);
assert_eq!(p.read_bytes_total, 6144);
assert_eq!(p.write_bytes_total, 9216);
assert_eq!(p.total_io_count(), 150 + 225 + 4);
let perf = parse_blockstats(body).unwrap().unwrap();
assert_eq!(perf.read_io_count, 8);
assert_eq!(perf.write_io_count, 10);
assert_eq!(perf.other_io_count, 26);
assert_eq!(perf.read_bytes_total, 15);
assert_eq!(perf.write_bytes_total, 26);
}

/// Test that empty blockstats parses as unavailable/`None`.
/// Test that parsing an empty blockstats array doesn't return a sample.
#[test]
fn parse_blockstats_empty_returns_unavailable() {
let p = parse_blockstats("[]").unwrap();
assert!(p.is_none());
fn test_libvirt_blockstats_empty_array_is_none() {
assert!(parse_blockstats("[]").unwrap().is_none());
}
}
25 changes: 10 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,23 +365,19 @@ pub fn dump_default_config() -> String {
mod tests {
use super::*;

/// Test that default `Config` validates and uses the threshold
/// engine with a 10s poll.
/// Test that the default `Config` validates.
#[test]
fn default_config_is_valid() {
fn test_config_default_is_valid() {
let mut cfg = Config::default();
assert_eq!(cfg.engine, "threshold");
assert_eq!(cfg.scale_poll_secs, 10);
validate_config(&mut cfg).unwrap().finish().unwrap();
}

/// Test that `validate_config` rejects an empty engine name.
/// Test that an empty engine does not validate.
#[test]
fn validate_rejects_empty_engine() {
fn test_config_rejects_empty_engine() {
let mut cfg = Config::default();
cfg.engine.clear();
let err = validate_config(&mut cfg).err().unwrap().to_string();
assert!(err.contains("engine must be non-empty"));
assert!(validate_config(&mut cfg).is_err());
}

/// Test that `validate_config` rejects a zero `scale_poll_secs`.
Expand Down Expand Up @@ -475,7 +471,7 @@ mod tests {
/// Test that minimal valid JSON loads into `Config` with the
/// expected engine, poll, and paths.
#[test]
fn load_config_round_trips_required_fields() {
fn test_config_load_round_trips_every_field() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.json");
std::fs::write(
Expand All @@ -502,7 +498,7 @@ mod tests {
/// Test that loading JSON with an unknown field returns
/// `ConfigError::SerdeJson`.
#[test]
fn load_config_rejects_unknown_fields() {
fn test_config_load_rejects_unknown_fields() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.json");
std::fs::write(
Expand All @@ -511,7 +507,7 @@ mod tests {
"engine": "threshold",
"engine_config_dir": "/engines",
"backend_config_dir": "/backends",
"scale_poll_secs": 1,
"scale_poll_secs": 1.0,
"not_a_real_field": true
}"#,
)
Expand All @@ -520,11 +516,10 @@ mod tests {
let err = load_or_default::<Config>(PrefixedPath::new(path.to_str().unwrap())).unwrap_err();
assert!(matches!(err, ConfigError::SerdeJson(_)));
}

/// Test that pretty-printed default JSON includes engine, poll
/// interval, and engine config path.
#[test]
fn dump_default_config_contains_engine() {
fn test_dump_default_config_contains_engine() {
let dumped = dump_default_config();
assert!(dumped.contains(r#""engine": "threshold""#));
assert!(dumped.contains(r#""scale_poll_secs": 10"#));
Expand All @@ -534,7 +529,7 @@ mod tests {
/// Test that serde defaults for min/max threads, host CPU ceiling,
/// and cooldown match `Config::default()`.
#[test]
fn controller_policy_defaults_round_trip() {
fn test_controller_policy_defaults_round_trip() {
let cfg: Config = serde_json::from_str(r#"{"engine": "foo", "engine_config_dir": "/path/to/engines", "backend_config_dir": "/path/to/backends", "scale_poll_secs": 42, "vm_state_path": "/path/to/vm-state.json"}"#).unwrap();
assert_eq!(cfg.min_thread_count, 1);
assert_eq!(cfg.max_thread_count, 8);
Expand Down
35 changes: 19 additions & 16 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,11 +1142,10 @@ mod tests {
async fn close(&self) {}
}

/// Test that actuation will not raise the pool above the VM vCPU
/// count.
/// Test that actuation will not raise the pool above the VM vCPU count.
#[rstest]
#[tokio::test]
async fn actuation_enforces_vcpu_cap(#[from(mockdir)] _mockdir: MockDir) {
async fn test_controller_actuation_enforces_vcpu_cap(#[from(mockdir)] _mockdir: MockDir) {
let cfg = Config {
vm_state_path: PrefixedPath::new("/ownership.json"),
cooldown_secs: 0,
Expand Down Expand Up @@ -1209,7 +1208,7 @@ mod tests {
/// and blocks scale-up when host CPU is above the ceiling.
#[rstest]
#[tokio::test]
async fn actuation_enforces_controller_bounds_and_host_ceiling(
async fn test_controller_actuation_enforces_controller_bounds_and_host_ceiling(
#[from(mockdir)] _mockdir: MockDir,
) {
let cfg = Config {
Expand Down Expand Up @@ -1281,7 +1280,9 @@ mod tests {
#[cfg(feature = "threshold-engine")]
#[rstest]
#[tokio::test]
async fn revert_arms_cooldown_blocking_immediate_retry(#[from(mockdir)] _mockdir: MockDir) {
async fn test_controller_revert_arms_cooldown_blocking_immediate_retry(
#[from(mockdir)] _mockdir: MockDir,
) {
let cfg = Config {
vm_state_path: PrefixedPath::new("/ownership.json"),
cooldown_secs: 30,
Expand Down Expand Up @@ -1338,7 +1339,7 @@ mod tests {
#[cfg(feature = "threshold-engine")]
#[rstest]
#[tokio::test]
async fn revert_bypasses_existing_cooldown(#[from(mockdir)] _mockdir: MockDir) {
async fn test_controller_revert_bypasses_existing_cooldown(#[from(mockdir)] _mockdir: MockDir) {
let cfg = Config {
vm_state_path: PrefixedPath::new("/ownership.json"),
cooldown_secs: 60,
Expand Down Expand Up @@ -1550,8 +1551,8 @@ mod tests {
))
}

/// Test that the per-VM status line has a fixed number of
/// space-separated fields whether or not samples are available.
/// Test that the per-VM status line has a fixed number of space-separated
/// fields whether or not samples are available.
#[test]
fn test_per_vm_status_line_has_fixed_space_separated_fields() {
let mut status = InstanceStatus::default();
Expand All @@ -1570,11 +1571,13 @@ mod tests {
assert!(full.contains(" 1 2 3 4 - - - - "), "{full}");
}

/// Test that discovery sync adds new VMs, retains existing ones,
/// and removes disappeared ones.
/// Test that discovery sync adds new VMs, retains existing ones, and
/// removes disappeared ones.
#[rstest]
#[tokio::test]
async fn sync_instances_adds_retains_and_removes(#[from(mockdir)] _mockdir: MockDir) {
async fn test_constroller_sync_instances_adds_retains_and_removes(
#[from(mockdir)] _mockdir: MockDir,
) {
let added = Arc::new(AtomicUsize::new(0));
let removed = Arc::new(AtomicUsize::new(0));
let evaluated = Arc::new(AtomicUsize::new(0));
Expand Down Expand Up @@ -1635,8 +1638,8 @@ mod tests {
(per_vm_log, dir, guard)
}

/// Return the records of one VM's per-VM log that carry an `event`
/// field, without timestamps.
/// Return the records of one VM's per-VM log that carry an `event` field,
/// without timestamps.
fn per_vm_log_events(per_vm_log: &PerVmLog, vm: &str) -> Vec<String> {
std::fs::read_to_string(per_vm_log.path(vm).unwrap())
.unwrap()
Expand All @@ -1647,11 +1650,11 @@ mod tests {
.collect()
}

/// Test that one tick refreshes state, runs the engine, and drops
/// instances that fail refresh.
/// Test that one tick refreshes state, runs the engine, and drops instances
/// that fail to refresh.
#[rstest]
#[tokio::test]
async fn tick_refreshes_evaluates_and_drops_failed_instances(
async fn test_controller_tick_refreshes_evaluates_and_drops_failed_instances(
#[from(mockdir)] _mockdir: MockDir,
) {
let added = Arc::new(AtomicUsize::new(0));
Expand Down
8 changes: 4 additions & 4 deletions src/engines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ mod tests {
/// Test that `ScaleAction::target`/`Display` cover None/Up/Down
/// variants.
#[test]
fn scale_action_target_and_display() {
fn test_scale_action_target_and_display() {
assert_eq!(ScaleAction::None.target(), None);
assert_eq!(ScaleAction::Up(4).target(), Some(4));
assert_eq!(ScaleAction::Down(2).target(), Some(2));
Expand All @@ -314,7 +314,7 @@ mod tests {
/// Test that `InstanceDecision::new` stores the VM id and
/// `ScaleAction` unchanged.
#[test]
fn instance_decision_new_preserves_fields() {
fn test_instance_decision_new_preserves_fields() {
let decision = InstanceDecision::new("vm-1", ScaleAction::Up(5));
assert_eq!(decision.instance_id, "vm-1");
assert_eq!(decision.decision, ScaleAction::Up(5));
Expand All @@ -323,7 +323,7 @@ mod tests {
/// Test that registry loads the threshold engine by name and errors
/// on unknown engines.
#[test]
fn load_registered_engine_finds_threshold_and_rejects_unknown() {
fn test_load_registered_engine_finds_threshold_and_rejects_unknown() {
let dir = PrefixedPath::new("/tmp/missing-engines");
let mut durations = crate::config::Config::default().poll_period().converter();
let engine = load_registered_engine(&dir, "threshold", &mut durations).unwrap();
Expand Down Expand Up @@ -399,7 +399,7 @@ mod tests {
/// Test that default fleet `evaluate` skips ineligible VMs and only
/// plans eligible ones.
#[tokio::test]
async fn default_fleet_evaluation_skips_ineligible_instances() {
async fn test_default_fleet_evaluation_skips_ineligible_instances() {
let engine = PerInstanceEngine {
calls: AtomicUsize::new(0),
};
Expand Down
Loading
Loading