package agate import ( "context" "os" "path/filepath" "testing" "time" ) // TestFullWorkflow tests a complete workflow of creating snapshots, modifying files, // creating more snapshots, and restoring snapshots. func TestFullWorkflow(t *testing.T) { // Create a temporary directory for tests tempDir, err := os.MkdirTemp("", "agate-test-*") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } defer os.RemoveAll(tempDir) // Create Agate options options := AgateOptions{ WorkDir: tempDir, } // Create Agate instance ag, err := New(options) if err != nil { t.Fatalf("Failed to create Agate instance: %v", err) } defer ag.Close() // Create a data directory dataDir := ag.options.BlobStore.GetActiveDir() if err := os.MkdirAll(dataDir, 0755); err != nil { t.Fatalf("Failed to create data directory: %v", err) } // Create initial test files initialFiles := map[string]string{ filepath.Join(dataDir, "file1.txt"): "Initial content of file 1", filepath.Join(dataDir, "file2.txt"): "Initial content of file 2", filepath.Join(dataDir, "subdir", "file3.txt"): "Initial content of file 3", } // Create subdirectory if err := os.MkdirAll(filepath.Join(dataDir, "subdir"), 0755); err != nil { t.Fatalf("Failed to create subdirectory: %v", err) } // Create the files for path, content := range initialFiles { if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("Failed to create test file %s: %v", path, err) } } // Step 1: Create the first snapshot ctx := context.Background() snapshot1ID, err := ag.SaveSnapshot(ctx, "Snapshot 1", "") if err != nil { t.Fatalf("Failed to create first snapshot: %v", err) } t.Logf("Created first snapshot with ID: %s", snapshot1ID) // Step 2: Modify some files and add a new file modifiedFiles := map[string]string{ filepath.Join(dataDir, "file1.txt"): "Modified content of file 1", filepath.Join(dataDir, "file4.txt"): "Content of new file 4", } for path, content := range modifiedFiles { if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("Failed to modify/create test file %s: %v", path, err) } } // Step 3: Create the second snapshot snapshot2ID, err := ag.SaveSnapshot(ctx, "Snapshot 2", snapshot1ID) if err != nil { t.Fatalf("Failed to create second snapshot: %v", err) } t.Logf("Created second snapshot with ID: %s", snapshot2ID) // Step 4: Delete a file and modify another if err := os.Remove(filepath.Join(dataDir, "file2.txt")); err != nil { t.Fatalf("Failed to delete test file: %v", err) } if err := os.WriteFile(filepath.Join(dataDir, "subdir/file3.txt"), []byte("Modified content of file 3"), 0644); err != nil { t.Fatalf("Failed to modify test file: %v", err) } // Step 5: Create the third snapshot snapshot3ID, err := ag.SaveSnapshot(ctx, "Snapshot 3", snapshot2ID) if err != nil { t.Fatalf("Failed to create third snapshot: %v", err) } t.Logf("Created third snapshot with ID: %s", snapshot3ID) // Step 6: List all snapshots snapshots, err := ag.ListSnapshots(ctx) if err != nil { t.Fatalf("Failed to list snapshots: %v", err) } if len(snapshots) != 3 { t.Errorf("Expected 3 snapshots, got %d", len(snapshots)) } // Step 7: Restore the first snapshot err = ag.RestoreSnapshot(ctx, snapshot1ID) if err != nil { t.Fatalf("Failed to restore first snapshot: %v", err) } t.Logf("Restored first snapshot") // Step 8: Verify the restored files match the initial state for path, expectedContent := range initialFiles { content, err := os.ReadFile(path) if err != nil { t.Fatalf("Failed to read restored file %s: %v", path, err) } if string(content) != expectedContent { t.Errorf("Restored file %s has wrong content: got %s, want %s", path, string(content), expectedContent) } } // Check that file4.txt doesn't exist if _, err := os.Stat(filepath.Join(dataDir, "file4.txt")); !os.IsNotExist(err) { t.Errorf("File4.txt should not exist after restoring first snapshot") } // Step 9: Restore the third snapshot err = ag.RestoreSnapshot(ctx, snapshot3ID) if err != nil { t.Fatalf("Failed to restore third snapshot: %v", err) } t.Logf("Restored third snapshot") // Step 10: Verify the restored files match the final state expectedFiles := map[string]string{ filepath.Join(dataDir, "file1.txt"): "Modified content of file 1", filepath.Join(dataDir, "file4.txt"): "Content of new file 4", filepath.Join(dataDir, "subdir/file3.txt"): "Modified content of file 3", } for path, expectedContent := range expectedFiles { content, err := os.ReadFile(path) if err != nil { t.Fatalf("Failed to read restored file %s: %v", path, err) } if string(content) != expectedContent { t.Errorf("Restored file %s has wrong content: got %s, want %s", path, string(content), expectedContent) } } // Check that file2.txt doesn't exist if _, err := os.Stat(filepath.Join(dataDir, "file2.txt")); !os.IsNotExist(err) { t.Errorf("File2.txt should not exist after restoring third snapshot") } // Step 11: Delete a snapshot err = ag.DeleteSnapshot(ctx, snapshot2ID) if err != nil { t.Fatalf("Failed to delete snapshot: %v", err) } t.Logf("Deleted second snapshot") // Step 12: Verify the snapshot was deleted snapshots, err = ag.ListSnapshots(ctx) if err != nil { t.Fatalf("Failed to list snapshots: %v", err) } if len(snapshots) != 2 { t.Errorf("Expected 2 snapshots after deletion, got %d", len(snapshots)) } for _, snap := range snapshots { if snap.ID == snapshot2ID { t.Errorf("Snapshot 2 should have been deleted") } } } // TestLargeFiles tests creating and restoring snapshots with large files func TestLargeFiles(t *testing.T) { // Skip this test in short mode if testing.Short() { t.Skip("Skipping large file test in short mode") } // Create a temporary directory for tests tempDir, err := os.MkdirTemp("", "agate-test-*") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } defer os.RemoveAll(tempDir) // Create Agate options options := AgateOptions{ WorkDir: tempDir, OpenFunc: func(dir string) error { return nil }, CloseFunc: func() error { return nil }, } // Create Agate instance ag, err := New(options) if err != nil { t.Fatalf("Failed to create Agate instance: %v", err) } defer ag.Close() // Create a data directory dataDir := ag.options.BlobStore.GetActiveDir() if err := os.MkdirAll(dataDir, 0755); err != nil { t.Fatalf("Failed to create data directory: %v", err) } // Create a large file (10 MB) largeFilePath := filepath.Join(dataDir, "large_file.bin") largeFileSize := 10 * 1024 * 1024 // 10 MB largeFile, err := os.Create(largeFilePath) if err != nil { t.Fatalf("Failed to create large test file: %v", err) } // Fill the file with a repeating pattern pattern := []byte("0123456789ABCDEF") buffer := make([]byte, 8192) // 8 KB buffer for i := 0; i < len(buffer); i += len(pattern) { copy(buffer[i:], pattern) } // Write the buffer multiple times to reach the desired size bytesWritten := 0 for bytesWritten < largeFileSize { n, err := largeFile.Write(buffer) if err != nil { largeFile.Close() t.Fatalf("Failed to write to large test file: %v", err) } bytesWritten += n } largeFile.Close() // Create a snapshot ctx := context.Background() startTime := time.Now() snapshotID, err := ag.SaveSnapshot(ctx, "Large File Snapshot", "") if err != nil { t.Fatalf("Failed to create snapshot: %v", err) } duration := time.Since(startTime) t.Logf("Created snapshot with large file in %v", duration) // Modify the large file if err := os.WriteFile(largeFilePath, []byte("Modified content"), 0644); err != nil { t.Fatalf("Failed to modify large file: %v", err) } // Restore the snapshot startTime = time.Now() err = ag.RestoreSnapshot(ctx, snapshotID) if err != nil { t.Fatalf("Failed to restore snapshot: %v", err) } duration = time.Since(startTime) t.Logf("Restored snapshot with large file in %v", duration) // Verify the file size is correct fileInfo, err := os.Stat(largeFilePath) if err != nil { t.Fatalf("Failed to stat restored large file: %v", err) } if fileInfo.Size() != int64(largeFileSize) { t.Errorf("Restored large file has wrong size: got %d, want %d", fileInfo.Size(), largeFileSize) } }