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 <philipp.schuster@cyberus-technology.de>
This commit is contained in:
Philipp Schuster
2026-04-08 17:16:19 +02:00
committed by Rob Bradford
parent c99ed77d1a
commit b9c3cfb14d

View File

@@ -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}");
}
}