forked from ryyst/kalzu-value-store
Add conflict resolution testing and verify functionality
Added: - test_conflict.go utility to create timestamp collision scenarios - Verified sophisticated conflict resolution works correctly Test Results: ✅ Successfully created conflicting data with identical timestamps ✅ Conflict resolution triggered during sync cycle ✅ Majority vote system activated (2-node scenario) ✅ Oldest node tie-breaker correctly applied ✅ Remote data won based on older joined timestamp ✅ Local data was properly replaced with winning version ✅ Detailed logging showed complete decision process Logs showed the complete flow: 1. "Timestamp collision detected, starting conflict resolution" 2. "Starting conflict resolution with majority vote" 3. "Resolved conflict using oldest node tie-breaker" 4. "Conflict resolved: remote data wins" 5. "Conflict resolved, updated local data" The sophisticated conflict resolution system works exactly as designed! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
101
test_conflict.go
Normal file
101
test_conflict.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
badger "github.com/dgraph-io/badger/v4"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StoredValue matches the structure in main.go
|
||||||
|
type StoredValue struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test utility to create conflicting data directly in BadgerDB
|
||||||
|
func createConflictingData(dataDir1, dataDir2 string) error {
|
||||||
|
// Same timestamp, different UUIDs
|
||||||
|
timestamp := time.Now().UnixMilli()
|
||||||
|
path := "test/conflict/data"
|
||||||
|
|
||||||
|
// Data for node1
|
||||||
|
data1 := json.RawMessage(`{"message": "from node1", "value": 100}`)
|
||||||
|
uuid1 := uuid.New().String()
|
||||||
|
|
||||||
|
// Data for node2 (same timestamp, different UUID and content)
|
||||||
|
data2 := json.RawMessage(`{"message": "from node2", "value": 200}`)
|
||||||
|
uuid2 := uuid.New().String()
|
||||||
|
|
||||||
|
// Store in node1's database
|
||||||
|
err := storeConflictData(dataDir1, path, timestamp, uuid1, data1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to store in node1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in node2's database
|
||||||
|
err = storeConflictData(dataDir2, path, timestamp, uuid2, data2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to store in node2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Created conflict scenario:\n")
|
||||||
|
fmt.Printf("Path: %s\n", path)
|
||||||
|
fmt.Printf("Timestamp: %d\n", timestamp)
|
||||||
|
fmt.Printf("Node1 UUID: %s, Data: %s\n", uuid1, string(data1))
|
||||||
|
fmt.Printf("Node2 UUID: %s, Data: %s\n", uuid2, string(data2))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeConflictData(dataDir, path string, timestamp int64, uuid string, data json.RawMessage) error {
|
||||||
|
opts := badger.DefaultOptions(dataDir + "/badger")
|
||||||
|
opts.Logger = nil
|
||||||
|
db, err := badger.Open(opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
storedValue := StoredValue{
|
||||||
|
UUID: uuid,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBytes, err := json.Marshal(storedValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Update(func(txn *badger.Txn) error {
|
||||||
|
// Store main data
|
||||||
|
if err := txn.Set([]byte(path), valueBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store timestamp index
|
||||||
|
indexKey := fmt.Sprintf("_ts:%020d:%s", timestamp, path)
|
||||||
|
return txn.Set([]byte(indexKey), []byte(uuid))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Println("Usage: go run test_conflict.go <data_dir1> <data_dir2>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := createConflictingData(os.Args[1], os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Conflict data created successfully!")
|
||||||
|
fmt.Println("Start your nodes and trigger a sync to see conflict resolution in action.")
|
||||||
|
}
|
Reference in New Issue
Block a user