package performance import ( "bytes" "context" "io" "runtime" "testing" "time" ) func TestRestoreConfig(t *testing.T) { t.Run("DefaultConfig", func(t *testing.T) { cfg := DefaultRestoreConfig() if cfg.ParallelTables <= 0 { t.Error("ParallelTables should be > 0") } if cfg.DecompressionWorkers <= 0 { t.Error("DecompressionWorkers should be > 0") } if cfg.BatchSize <= 0 { t.Error("BatchSize should be > 0") } }) t.Run("AggressiveConfig", func(t *testing.T) { cfg := AggressiveRestoreConfig() if cfg.ParallelTables <= 0 { t.Error("ParallelTables should be > 0") } if cfg.DisableIndexes != true { t.Error("DisableIndexes should be true for aggressive config") } if cfg.DisableConstraints != true { t.Error("DisableConstraints should be true for aggressive config") } }) } func TestRestoreMetrics(t *testing.T) { metrics := NewRestoreMetrics() // Simulate some work metrics.CompressedBytes.Store(1000) metrics.DecompressedBytes.Store(5000) metrics.RowsRestored.Store(100) metrics.TablesRestored.Store(5) metrics.DecompressionTime.Store(int64(100 * time.Millisecond)) metrics.DataLoadTime.Store(int64(200 * time.Millisecond)) time.Sleep(10 * time.Millisecond) metrics.EndTime = time.Now() summary := metrics.Summary() if summary.CompressedBytes != 1000 { t.Errorf("expected 1000 compressed bytes, got %d", summary.CompressedBytes) } if summary.DecompressedBytes != 5000 { t.Errorf("expected 5000 decompressed bytes, got %d", summary.DecompressedBytes) } if summary.RowsRestored != 100 { t.Errorf("expected 100 rows, got %d", summary.RowsRestored) } if summary.TablesRestored != 5 { t.Errorf("expected 5 tables, got %d", summary.TablesRestored) } } func TestRestoreSummaryString(t *testing.T) { summary := RestoreSummary{ Duration: 10 * time.Second, ThroughputMBs: 350.0, // Above target CompressedBytes: 1000000, DecompressedBytes: 3500000000, // 3.5GB RowsRestored: 1000000, TablesRestored: 50, DecompressionTime: 3 * time.Second, DataLoadTime: 6 * time.Second, IndexRebuildTime: 1 * time.Second, MeetsTarget: true, } str := summary.String() if str == "" { t.Error("summary string should not be empty") } if len(str) < 100 { t.Error("summary string seems too short") } } func TestStreamingDecompressor(t *testing.T) { // Create compressed data testData := make([]byte, 100*1024) // 100KB for i := range testData { testData[i] = byte(i % 256) } var compressed bytes.Buffer w, err := NewParallelGzipWriter(&compressed, DefaultCompressionConfig()) if err != nil { t.Fatalf("failed to create writer: %v", err) } if _, err := w.Write(testData); err != nil { t.Fatalf("failed to write: %v", err) } if err := w.Close(); err != nil { t.Fatalf("failed to close: %v", err) } // Decompress metrics := NewRestoreMetrics() cfg := DefaultRestoreConfig() sd := NewStreamingDecompressor(bytes.NewReader(compressed.Bytes()), cfg, metrics) var decompressed bytes.Buffer err = sd.Decompress(context.Background(), &decompressed) if err != nil { t.Fatalf("decompression failed: %v", err) } if !bytes.Equal(decompressed.Bytes(), testData) { t.Error("decompressed data does not match original") } if metrics.DecompressedBytes.Load() == 0 { t.Error("metrics should track decompressed bytes") } } func TestParallelTableRestorer(t *testing.T) { cfg := DefaultRestoreConfig() cfg.ParallelTables = 4 metrics := NewRestoreMetrics() ptr := NewParallelTableRestorer(cfg, metrics) tableCount := 10 for i := 0; i < tableCount; i++ { tableName := "test_table" ptr.RestoreTable(context.Background(), tableName, func() error { time.Sleep(time.Millisecond) return nil }) } errs := ptr.Wait() if len(errs) != 0 { t.Errorf("expected no errors, got %d", len(errs)) } if metrics.TablesRestored.Load() != int64(tableCount) { t.Errorf("expected %d tables, got %d", tableCount, metrics.TablesRestored.Load()) } } func TestGetPostgresOptimizations(t *testing.T) { cfg := AggressiveRestoreConfig() opt := GetPostgresOptimizations(cfg) if len(opt.PreRestoreSQL) == 0 { t.Error("expected pre-restore SQL") } if len(opt.PostRestoreSQL) == 0 { t.Error("expected post-restore SQL") } if len(opt.CommandArgs) == 0 { t.Error("expected command args") } } func TestGetMySQLOptimizations(t *testing.T) { cfg := AggressiveRestoreConfig() opt := GetMySQLOptimizations(cfg) if len(opt.PreRestoreSQL) == 0 { t.Error("expected pre-restore SQL") } if len(opt.PostRestoreSQL) == 0 { t.Error("expected post-restore SQL") } } func TestFormatBytes(t *testing.T) { tests := []struct { bytes int64 expected string }{ {0, "0 B"}, {500, "500 B"}, {1024, "1.0 KB"}, {1536, "1.5 KB"}, {1048576, "1.0 MB"}, {1073741824, "1.0 GB"}, } for _, tt := range tests { result := formatBytes(tt.bytes) if result != tt.expected { t.Errorf("formatBytes(%d) = %s, expected %s", tt.bytes, result, tt.expected) } } } // Benchmarks func BenchmarkStreamingDecompressor(b *testing.B) { // Create compressed data testData := make([]byte, 10*1024*1024) // 10MB for i := range testData { testData[i] = byte(i % 256) } var compressed bytes.Buffer w, _ := NewParallelGzipWriter(&compressed, DefaultCompressionConfig()) w.Write(testData) w.Close() compressedData := compressed.Bytes() cfg := DefaultRestoreConfig() b.SetBytes(int64(len(testData))) b.ResetTimer() for i := 0; i < b.N; i++ { sd := NewStreamingDecompressor(bytes.NewReader(compressedData), cfg, nil) sd.Decompress(context.Background(), io.Discard) } } func BenchmarkParallelTableRestorer(b *testing.B) { cfg := DefaultRestoreConfig() cfg.ParallelTables = runtime.NumCPU() b.ResetTimer() for i := 0; i < b.N; i++ { ptr := NewParallelTableRestorer(cfg, nil) for j := 0; j < 10; j++ { ptr.RestoreTable(context.Background(), "table", func() error { return nil }) } ptr.Wait() } }