From 1c72bf5e64a292afbefd30744a2ccefd749d0843 Mon Sep 17 00:00:00 2001 From: Renz Date: Fri, 7 Nov 2025 13:46:55 +0000 Subject: [PATCH] Add comprehensive unit tests for ETA estimator - 12 test functions covering all estimator functionality - Tests for progress tracking, time calculations, formatting - Tests for edge cases (zero items, no progress, etc.) - All tests passing (12/12) --- internal/progress/estimator_test.go | 259 ++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 internal/progress/estimator_test.go diff --git a/internal/progress/estimator_test.go b/internal/progress/estimator_test.go new file mode 100644 index 0000000..006a8f1 --- /dev/null +++ b/internal/progress/estimator_test.go @@ -0,0 +1,259 @@ +package progress + +import ( + "testing" + "time" +) + +func TestNewETAEstimator(t *testing.T) { + estimator := NewETAEstimator("Test Operation", 10) + + if estimator.operation != "Test Operation" { + t.Errorf("Expected operation 'Test Operation', got '%s'", estimator.operation) + } + + if estimator.totalItems != 10 { + t.Errorf("Expected totalItems 10, got %d", estimator.totalItems) + } + + if estimator.itemsComplete != 0 { + t.Errorf("Expected itemsComplete 0, got %d", estimator.itemsComplete) + } + + if estimator.startTime.IsZero() { + t.Error("Expected startTime to be set") + } +} + +func TestUpdateProgress(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + + estimator.UpdateProgress(5) + if estimator.itemsComplete != 5 { + t.Errorf("Expected itemsComplete 5, got %d", estimator.itemsComplete) + } + + estimator.UpdateProgress(8) + if estimator.itemsComplete != 8 { + t.Errorf("Expected itemsComplete 8, got %d", estimator.itemsComplete) + } +} + +func TestGetProgress(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + + // Test 0% progress + if progress := estimator.GetProgress(); progress != 0 { + t.Errorf("Expected 0%%, got %.2f%%", progress) + } + + // Test 50% progress + estimator.UpdateProgress(5) + if progress := estimator.GetProgress(); progress != 50.0 { + t.Errorf("Expected 50%%, got %.2f%%", progress) + } + + // Test 100% progress + estimator.UpdateProgress(10) + if progress := estimator.GetProgress(); progress != 100.0 { + t.Errorf("Expected 100%%, got %.2f%%", progress) + } + + // Test zero division + zeroEstimator := NewETAEstimator("Test", 0) + if progress := zeroEstimator.GetProgress(); progress != 0 { + t.Errorf("Expected 0%% for zero totalItems, got %.2f%%", progress) + } +} + +func TestGetElapsed(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + + // Wait a bit + time.Sleep(100 * time.Millisecond) + + elapsed := estimator.GetElapsed() + if elapsed < 100*time.Millisecond { + t.Errorf("Expected elapsed time >= 100ms, got %v", elapsed) + } +} + +func TestGetETA(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + + // No progress yet, ETA should be 0 + if eta := estimator.GetETA(); eta != 0 { + t.Errorf("Expected ETA 0 for no progress, got %v", eta) + } + + // Simulate 5 items completed in 5 seconds + estimator.startTime = time.Now().Add(-5 * time.Second) + estimator.UpdateProgress(5) + + eta := estimator.GetETA() + // Should be approximately 5 seconds (5 items remaining at 1 sec/item) + if eta < 4*time.Second || eta > 6*time.Second { + t.Errorf("Expected ETA around 5s, got %v", eta) + } +} + +func TestFormatProgress(t *testing.T) { + estimator := NewETAEstimator("Test", 13) + + // Test at 0% + if result := estimator.FormatProgress(); result != "0/13 (0%)" { + t.Errorf("Expected '0/13 (0%%)', got '%s'", result) + } + + // Test at 38% + estimator.UpdateProgress(5) + if result := estimator.FormatProgress(); result != "5/13 (38%)" { + t.Errorf("Expected '5/13 (38%%)', got '%s'", result) + } + + // Test at 100% + estimator.UpdateProgress(13) + if result := estimator.FormatProgress(); result != "13/13 (100%)" { + t.Errorf("Expected '13/13 (100%%)', got '%s'", result) + } +} + +func TestFormatDuration(t *testing.T) { + tests := []struct { + duration time.Duration + expected string + }{ + {500 * time.Millisecond, "< 1s"}, + {5 * time.Second, "5s"}, + {65 * time.Second, "1m"}, // 5 seconds not shown (<=5) + {125 * time.Second, "2m"}, // 5 seconds not shown (<=5) + {3 * time.Minute, "3m"}, + {3*time.Minute + 3*time.Second, "3m"}, // < 5 seconds not shown + {3*time.Minute + 10*time.Second, "3m 10s"}, // > 5 seconds shown + {90 * time.Minute, "1h 30m"}, + {120 * time.Minute, "2h"}, + {150 * time.Minute, "2h 30m"}, + } + + for _, tt := range tests { + result := FormatDuration(tt.duration) + if result != tt.expected { + t.Errorf("FormatDuration(%v) = '%s', expected '%s'", tt.duration, result, tt.expected) + } + } +} + +func TestFormatETA(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + + // No progress - should show "calculating..." + if result := estimator.FormatETA(); result != "calculating..." { + t.Errorf("Expected 'calculating...', got '%s'", result) + } + + // With progress + estimator.startTime = time.Now().Add(-10 * time.Second) + estimator.UpdateProgress(5) + + result := estimator.FormatETA() + if result != "~10s remaining" { + t.Errorf("Expected '~10s remaining', got '%s'", result) + } +} + +func TestFormatElapsed(t *testing.T) { + estimator := NewETAEstimator("Test", 10) + estimator.startTime = time.Now().Add(-45 * time.Second) + + result := estimator.FormatElapsed() + if result != "45s" { + t.Errorf("Expected '45s', got '%s'", result) + } +} + +func TestGetFullStatus(t *testing.T) { + estimator := NewETAEstimator("Backing up cluster", 13) + + // Just started (0 items) + result := estimator.GetFullStatus("Backing up cluster") + if result != "Backing up cluster | 0/13 | Starting..." { + t.Errorf("Unexpected result for 0 items: '%s'", result) + } + + // With progress + estimator.startTime = time.Now().Add(-30 * time.Second) + estimator.UpdateProgress(5) + + result = estimator.GetFullStatus("Backing up cluster") + // Should contain all components + if len(result) < 50 { // Reasonable minimum length + t.Errorf("Result too short: '%s'", result) + } + + // Check it contains key elements (format may vary slightly) + if !contains(result, "5/13") { + t.Errorf("Result missing progress '5/13': '%s'", result) + } + if !contains(result, "38%") { + t.Errorf("Result missing percentage '38%%': '%s'", result) + } + if !contains(result, "Elapsed:") { + t.Errorf("Result missing 'Elapsed:': '%s'", result) + } + if !contains(result, "ETA:") { + t.Errorf("Result missing 'ETA:': '%s'", result) + } +} + +func TestGetFullStatusWithZeroItems(t *testing.T) { + estimator := NewETAEstimator("Test Operation", 0) + estimator.startTime = time.Now().Add(-5 * time.Second) + + result := estimator.GetFullStatus("Test Operation") + // Should only show elapsed time when no items to track + if !contains(result, "Test Operation") || !contains(result, "Elapsed:") { + t.Errorf("Unexpected result for 0 total items: '%s'", result) + } + if contains(result, "0/0") { + t.Errorf("Should not show 0/0 progress: '%s'", result) + } +} + +func TestEstimateSizeBasedDuration(t *testing.T) { + // Test 100MB with 1 core + duration := EstimateSizeBasedDuration(100*1024*1024, 1) + // Should be around 1.2 minutes (1 minute base + 20% buffer) + if duration < 60*time.Second || duration > 90*time.Second { + t.Errorf("Expected ~1.2 minutes for 100MB/1core, got %v", duration) + } + + // Test 100MB with 8 cores (should be faster) + duration8cores := EstimateSizeBasedDuration(100*1024*1024, 8) + if duration8cores >= duration { + t.Errorf("Expected faster with more cores: %v vs %v", duration8cores, duration) + } + + // Test larger file + duration1GB := EstimateSizeBasedDuration(1024*1024*1024, 1) + if duration1GB <= duration { + t.Errorf("Expected longer duration for larger file: %v vs %v", duration1GB, duration) + } +} + +// Helper function +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || + len(s) > len(substr) && ( + s[:len(substr)] == substr || + s[len(s)-len(substr):] == substr || + indexHelper(s, substr) >= 0)) +} + +func indexHelper(s, substr string) int { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return i + } + } + return -1 +}