Skip to main content
Glama
atomic_integration_test.go7.13 kB
package process import ( "sync" "sync/atomic" "testing" "time" "unsafe" ) // TestAtomicStateConsistency verifies that atomic state operations maintain consistency func TestAtomicStateConsistency(t *testing.T) { p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Initialize atomic state initialState := ProcessState{ ID: p.ID, Name: p.Name, Script: p.Script, Status: p.Status, StartTime: p.StartTime, } atomic.StorePointer(&p.atomicState, unsafe.Pointer(&initialState)) // Test initial read state := p.GetStateAtomic() if state.Status != StatusRunning { t.Errorf("Expected status %v, got %v", StatusRunning, state.Status) } // Test update p.UpdateStateAtomic(func(state ProcessState) ProcessState { return state.CopyWithStatus(StatusSuccess) }) // Verify update state = p.GetStateAtomic() if state.Status != StatusSuccess { t.Errorf("Expected status %v, got %v", StatusSuccess, state.Status) } // Verify mutex fields are also updated if p.GetStatus() != StatusSuccess { t.Errorf("Mutex-based getter returned wrong status: %v", p.GetStatus()) } } // TestConcurrentAtomicUpdates verifies thread safety under concurrent updates func TestConcurrentAtomicUpdates(t *testing.T) { p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Initialize atomic state initialState := ProcessState{ ID: p.ID, Name: p.Name, Script: p.Script, Status: p.Status, StartTime: p.StartTime, } atomic.StorePointer(&p.atomicState, unsafe.Pointer(&initialState)) const numGoroutines = 100 const updatesPerGoroutine = 1000 var wg sync.WaitGroup successCount := int32(0) failedCount := int32(0) // Launch concurrent updaters for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < updatesPerGoroutine; j++ { // Alternate between success and failed status targetStatus := StatusSuccess if (id+j)%2 == 0 { targetStatus = StatusFailed } p.UpdateStateAtomic(func(state ProcessState) ProcessState { return state.CopyWithStatus(targetStatus) }) // Count the final states if targetStatus == StatusSuccess { atomic.AddInt32(&successCount, 1) } else { atomic.AddInt32(&failedCount, 1) } } }(i) } wg.Wait() // Verify total updates totalUpdates := int(successCount + failedCount) expectedTotal := numGoroutines * updatesPerGoroutine if totalUpdates != expectedTotal { t.Errorf("Expected %d total updates, got %d", expectedTotal, totalUpdates) } // Final state should be either success or failed finalState := p.GetStateAtomic() if finalState.Status != StatusSuccess && finalState.Status != StatusFailed { t.Errorf("Unexpected final status: %v", finalState.Status) } } // TestAtomicExitCodeUpdate tests updating exit code atomically func TestAtomicExitCodeUpdate(t *testing.T) { p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Initialize atomic state initialState := ProcessState{ ID: p.ID, Name: p.Name, Script: p.Script, Status: p.Status, StartTime: p.StartTime, } atomic.StorePointer(&p.atomicState, unsafe.Pointer(&initialState)) // Update with exit code p.UpdateStateAtomic(func(state ProcessState) ProcessState { return state.CopyWithExit(0) }) // Verify state state := p.GetStateAtomic() if state.Status != StatusSuccess { t.Errorf("Expected status %v, got %v", StatusSuccess, state.Status) } if state.ExitCode == nil || *state.ExitCode != 0 { t.Errorf("Expected exit code 0, got %v", state.ExitCode) } if state.EndTime == nil { t.Error("Expected EndTime to be set") } // Test non-zero exit code p.UpdateStateAtomic(func(state ProcessState) ProcessState { return state.CopyWithExit(1) }) state = p.GetStateAtomic() if state.Status != StatusFailed { t.Errorf("Expected status %v, got %v", StatusFailed, state.Status) } if state.ExitCode == nil || *state.ExitCode != 1 { t.Errorf("Expected exit code 1, got %v", state.ExitCode) } } // TestAtomicStateImmutability verifies that states are truly immutable func TestAtomicStateImmutability(t *testing.T) { p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Initialize atomic state initialState := ProcessState{ ID: p.ID, Name: p.Name, Script: p.Script, Status: p.Status, StartTime: p.StartTime, } atomic.StorePointer(&p.atomicState, unsafe.Pointer(&initialState)) // Get state and try to modify it state1 := p.GetStateAtomic() originalStatus := state1.Status // This modification should not affect the stored state state1.Status = StatusFailed // Get state again state2 := p.GetStateAtomic() if state2.Status != originalStatus { t.Errorf("State was mutated! Expected %v, got %v", originalStatus, state2.Status) } } // TestNilAtomicStateFallback tests fallback to mutex when atomic state is nil func TestNilAtomicStateFallback(t *testing.T) { p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Don't initialize atomic state - it should be nil // GetStateAtomic should fallback to mutex-based read state := p.GetStateAtomic() if state.ID != p.ID { t.Errorf("Expected ID %v, got %v", p.ID, state.ID) } if state.Status != p.Status { t.Errorf("Expected status %v, got %v", p.Status, state.Status) } } // TestRaceConditionPrevention uses Go's race detector func TestRaceConditionPrevention(t *testing.T) { if testing.Short() { t.Skip("Skipping race test in short mode") } p := &Process{ ID: "test-123", Name: "test-process", Script: "test", Status: StatusRunning, StartTime: time.Now(), mu: sync.RWMutex{}, } // Initialize atomic state initialState := ProcessState{ ID: p.ID, Name: p.Name, Script: p.Script, Status: p.Status, StartTime: p.StartTime, } atomic.StorePointer(&p.atomicState, unsafe.Pointer(&initialState)) var wg sync.WaitGroup // Concurrent readers for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 100; j++ { state := p.GetStateAtomic() _ = state.Status _ = state.Name _ = state.StartTime } }() } // Concurrent writers for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 100; j++ { p.UpdateStateAtomic(func(state ProcessState) ProcessState { if id%2 == 0 { return state.CopyWithStatus(StatusSuccess) } return state.CopyWithStatus(StatusFailed) }) } }(i) } wg.Wait() }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/standardbeagle/brummer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server