From b9c3cfb14d5d8a6503f34b06447974d15668eb5c Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 8 Apr 2026 17:16:19 +0200 Subject: [PATCH] vm-migration: context: move unit tests into sub module This helps to better separate the unit tests from the new ones in the following commit. On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster --- vm-migration/src/context.rs | 311 ++++++++++++++++++------------------ 1 file changed, 157 insertions(+), 154 deletions(-) diff --git a/vm-migration/src/context.rs b/vm-migration/src/context.rs index d7680821c..69f9f3730 100644 --- a/vm-migration/src/context.rs +++ b/vm-migration/src/context.rs @@ -238,169 +238,172 @@ impl Display for MemoryMigrationContext { #[cfg(test)] mod unit_tests { - use std::time::{Duration, Instant}; - use super::*; - use crate::protocol::MemoryRange; + mod memory_migration_ctx_tests { + use std::time::{Duration, Instant}; - fn make_table(bytes: u64) -> MemoryRangeTable { - let mut table = MemoryRangeTable::default(); - if bytes > 0 { - table.push(MemoryRange { - gpa: 0, - length: bytes, - }); + use super::*; + use crate::protocol::MemoryRange; + + fn make_table(bytes: u64) -> MemoryRangeTable { + let mut table = MemoryRangeTable::default(); + if bytes > 0 { + table.push(MemoryRange { + gpa: 0, + length: bytes, + }); + } + table } - table - } - /// A controlled migration scenario with fixed timing offsets. - /// - /// ```text - /// migration_begin - /// + 1.0s -> iteration_begin - /// + 1.1s -> transfer_begin - /// + 2.0s -> transfer ends (transfer_duration = 0.9s) - /// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s) - /// ``` - struct Scenario { - migration_begin: Instant, - iteration_begin: Instant, - transfer_begin: Instant, - transfer_duration: Duration, - } + /// A controlled migration scenario with fixed timing offsets. + /// + /// ```text + /// migration_begin + /// + 1.0s -> iteration_begin + /// + 1.1s -> transfer_begin + /// + 2.0s -> transfer ends (transfer_duration = 0.9s) + /// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s) + /// ``` + struct Scenario { + migration_begin: Instant, + iteration_begin: Instant, + transfer_begin: Instant, + transfer_duration: Duration, + } - impl Scenario { - /// We use a fixed point in the past so all offsets are in the past too, - /// meaning elapsed() calls in the code under test will be >= our durations. - const FIXPOINT_PAST: Duration = Duration::from_secs(10); + impl Scenario { + /// We use a fixed point in the past so all offsets are in the past too, + /// meaning elapsed() calls in the code under test will be >= our durations. + const FIXPOINT_PAST: Duration = Duration::from_secs(10); - fn new() -> Self { - // Use a fixed point in the past so all offsets are in the past too, - // meaning elapsed() calls in the code under test will be >= our durations. - let migration_begin = Instant::now() - Self::FIXPOINT_PAST; - Self { - migration_begin, - iteration_begin: migration_begin + Duration::from_millis(1000), - transfer_begin: migration_begin + Duration::from_millis(1100), - transfer_duration: Duration::from_millis(900), + fn new() -> Self { + // Use a fixed point in the past so all offsets are in the past too, + // meaning elapsed() calls in the code under test will be >= our durations. + let migration_begin = Instant::now() - Self::FIXPOINT_PAST; + Self { + migration_begin, + iteration_begin: migration_begin + Duration::from_millis(1000), + transfer_begin: migration_begin + Duration::from_millis(1100), + transfer_duration: Duration::from_millis(900), + } + } + + fn make_ctx(&self) -> MemoryMigrationContext { + let mut ctx = MemoryMigrationContext::new(); + // Override migration_begin with our controlled value. + ctx.migration_begin = self.migration_begin; + ctx } } - fn make_ctx(&self) -> MemoryMigrationContext { - let mut ctx = MemoryMigrationContext::new(); - // Override migration_begin with our controlled value. - ctx.migration_begin = self.migration_begin; - ctx + #[test] + fn before_transfer_updates_begin_and_bytes() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096)); + + assert_eq!(ctx.iteration_begin, s.iteration_begin); + assert_eq!(ctx.current_iteration_total_bytes, 4096); + } + + #[test] + fn before_transfer_estimated_downtime() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Empty table -> zero downtime regardless of bandwidth + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0)); + assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO)); + + // No bandwidth yet -> None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); + + // 1024 B/s, 1024 bytes -> 1s + ctx.bandwidth_bytes_per_second = 1024.0; + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1))); + } + + #[test] + fn after_transfer_updates_timing_and_bandwidth() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + + assert_eq!(ctx.transfer_begin, s.transfer_begin); + assert_eq!(ctx.transfer_duration, Some(s.transfer_duration)); + // 1024 bytes / 0.9s + assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9); + // iteration_duration = time from iteration_begin until now (>= transfer_duration) + assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration); + // Zero transfer_duration -> bandwidth is 0.0, no division by zero + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO); + assert_eq!(ctx.bandwidth_bytes_per_second, 0.0); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } + + #[test] + fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: no bandwidth yet -> downtime is None + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); + assert_eq!(ctx.estimated_downtime, None); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024); + + // Iteration 1: bandwidth now known -> downtime is Some + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048)); + assert!(ctx.estimated_downtime.is_some()); + ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); + assert_eq!(ctx.total_sent_bytes, 1024 + 2048); + + // Check finalize() sets migration duration + assert_eq!(ctx.migration_duration, None); + ctx.finalize(); + assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); + } + + #[test] + /// The display format is specifically crafted to be very insightful in logs. + /// Therefore, we have a dedicated test for that format. + fn display_format() { + let s = Scenario::new(); + let mut ctx = s.make_ctx(); + + // Iteration 0: 1 MiB in 1s + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + ctx.iteration += 1; + + // Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw + ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024)); + ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); + + ctx.migration_duration = Some(Duration::from_secs(2)); + let out = ctx.to_string(); + + assert_eq!( + out, + "iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s" + ); + + // Should change elapsed() time! + // Since this is at least 10s, we never face timing issues in CI! + ctx.finalize(); + let out2 = ctx.to_string(); + assert_ne!(out2, out, "elapsed time should have changed! is={out2}"); } } - - #[test] - fn before_transfer_updates_begin_and_bytes() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096)); - - assert_eq!(ctx.iteration_begin, s.iteration_begin); - assert_eq!(ctx.current_iteration_total_bytes, 4096); - } - - #[test] - fn before_transfer_estimated_downtime() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - // Empty table -> zero downtime regardless of bandwidth - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0)); - assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO)); - - // No bandwidth yet -> None - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, None); - - // 1024 B/s, 1024 bytes -> 1s - ctx.bandwidth_bytes_per_second = 1024.0; - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1))); - } - - #[test] - fn after_transfer_updates_timing_and_bandwidth() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - - assert_eq!(ctx.transfer_begin, s.transfer_begin); - assert_eq!(ctx.transfer_duration, Some(s.transfer_duration)); - // 1024 bytes / 0.9s - assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9); - // iteration_duration = time from iteration_begin until now (>= transfer_duration) - assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration); - // Zero transfer_duration -> bandwidth is 0.0, no division by zero - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO); - assert_eq!(ctx.bandwidth_bytes_per_second, 0.0); - - // Check finalize() sets migration duration - assert_eq!(ctx.migration_duration, None); - ctx.finalize(); - assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); - } - - #[test] - fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - // Iteration 0: no bandwidth yet -> downtime is None - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024)); - assert_eq!(ctx.estimated_downtime, None); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - assert_eq!(ctx.total_sent_bytes, 1024); - - // Iteration 1: bandwidth now known -> downtime is Some - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048)); - assert!(ctx.estimated_downtime.is_some()); - ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration); - assert_eq!(ctx.total_sent_bytes, 1024 + 2048); - - // Check finalize() sets migration duration - assert_eq!(ctx.migration_duration, None); - ctx.finalize(); - assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST)); - } - - #[test] - /// The display format is specifically crafted to be very insightful in logs. - /// Therefore, we have a dedicated test for that format. - fn display_format() { - let s = Scenario::new(); - let mut ctx = s.make_ctx(); - - // Iteration 0: 1 MiB in 1s - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); - ctx.iteration += 1; - - // Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw - ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024)); - ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1)); - - ctx.migration_duration = Some(Duration::from_secs(2)); - let out = ctx.to_string(); - - assert_eq!( - out, - "iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s" - ); - - // Should change elapsed() time! - // Since this is at least 10s, we never face timing issues in CI! - ctx.finalize(); - let out2 = ctx.to_string(); - assert_ne!(out2, out, "elapsed time should have changed! is={out2}"); - } }