WitNote / internal /bridge /tab_executor_integration_test.go
AUXteam's picture
Upload folder using huggingface_hub
6a7089a verified
package bridge
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
// --- Integration Tests ---
func TestTabExecutor_MultiTabSimulation(t *testing.T) {
// Simulate 3 tabs executing independently
te := NewTabExecutor(4)
results := make(map[string][]int)
var mu sync.Mutex
var wg sync.WaitGroup
tabs := []string{"tab1", "tab2", "tab3"}
for _, tab := range tabs {
for step := 0; step < 5; step++ {
wg.Add(1)
tab, step := tab, step
go func() {
defer wg.Done()
err := te.Execute(context.Background(), tab, func(ctx context.Context) error {
time.Sleep(time.Duration(step) * time.Millisecond)
mu.Lock()
results[tab] = append(results[tab], step)
mu.Unlock()
return nil
})
if err != nil {
t.Errorf("tab %s step %d: %v", tab, step, err)
}
}()
}
}
wg.Wait()
for _, tab := range tabs {
mu.Lock()
steps := results[tab]
mu.Unlock()
if len(steps) != 5 {
t.Errorf("tab %s: expected 5 steps, got %d", tab, len(steps))
}
}
}
func TestTabExecutor_ErrorIsolation(t *testing.T) {
te := NewTabExecutor(4)
// Tab1 fails
err1 := te.Execute(context.Background(), "tab1", func(ctx context.Context) error {
return fmt.Errorf("tab1 error")
})
// Tab2 should still work
err2 := te.Execute(context.Background(), "tab2", func(ctx context.Context) error {
return nil
})
if err1 == nil {
t.Error("expected error from tab1")
}
if err2 != nil {
t.Errorf("tab2 should succeed regardless of tab1: %v", err2)
}
}
func TestTabExecutor_PanicIsolation(t *testing.T) {
te := NewTabExecutor(4)
// Tab1 panics
err1 := te.Execute(context.Background(), "tab1", func(ctx context.Context) error {
panic("tab1 crashed")
})
// Tab2 should still work
err2 := te.Execute(context.Background(), "tab2", func(ctx context.Context) error {
return nil
})
if err1 == nil {
t.Error("expected error from tab1 panic")
}
if err2 != nil {
t.Errorf("tab2 should succeed regardless of tab1 panic: %v", err2)
}
}
// --- Stress Tests ---
func TestTabExecutor_StressHighConcurrency(t *testing.T) {
te := NewTabExecutor(4)
var completed int32
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
tabID := fmt.Sprintf("tab%d", i%10) // 10 unique tabs
go func() {
defer wg.Done()
err := te.Execute(context.Background(), tabID, func(ctx context.Context) error {
time.Sleep(time.Millisecond)
atomic.AddInt32(&completed, 1)
return nil
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}()
}
wg.Wait()
if n := atomic.LoadInt32(&completed); n != 50 {
t.Errorf("expected 50 completions, got %d", n)
}
}
func TestTabExecutor_StressRapidCreateRemove(t *testing.T) {
te := NewTabExecutor(4)
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
tabID := fmt.Sprintf("tab_%d", i)
_ = te.Execute(context.Background(), tabID, func(ctx context.Context) error {
time.Sleep(time.Millisecond)
return nil
})
te.RemoveTab(tabID)
}(i)
}
wg.Wait()
}
func TestTabExecutor_StressSameTabConcurrent(t *testing.T) {
te := NewTabExecutor(8)
var counter int32
var wg sync.WaitGroup
// 30 goroutines all targeting the same tab
for i := 0; i < 30; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = te.Execute(context.Background(), "single_tab", func(ctx context.Context) error {
atomic.AddInt32(&counter, 1)
return nil
})
}()
}
wg.Wait()
if n := atomic.LoadInt32(&counter); n != 30 {
t.Errorf("expected 30 executions, got %d", n)
}
}
// --- TabManager Integration ---
func TestTabManager_ExecuteWithoutExecutor(t *testing.T) {
tm := &TabManager{
tabs: make(map[string]*TabEntry),
snapshots: make(map[string]*RefCache),
executor: nil, // No executor
}
var executed bool
err := tm.Execute(context.Background(), "tab1", func(ctx context.Context) error {
executed = true
return nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !executed {
t.Error("task should execute directly when executor is nil")
}
}
func TestTabManager_ExecuteWithExecutor(t *testing.T) {
tm := &TabManager{
tabs: make(map[string]*TabEntry),
snapshots: make(map[string]*RefCache),
executor: NewTabExecutor(2),
}
var executed bool
err := tm.Execute(context.Background(), "tab1", func(ctx context.Context) error {
executed = true
return nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !executed {
t.Error("task should be executed via executor")
}
}
func TestTabManager_ExecutorAccessor(t *testing.T) {
te := NewTabExecutor(3)
tm := &TabManager{
tabs: make(map[string]*TabEntry),
snapshots: make(map[string]*RefCache),
executor: te,
}
if tm.Executor() != te {
t.Error("Executor() should return the configured TabExecutor")
}
}
func TestTabManager_ExecutorNilAccessor(t *testing.T) {
tm := &TabManager{
tabs: make(map[string]*TabEntry),
snapshots: make(map[string]*RefCache),
}
if tm.Executor() != nil {
t.Error("Executor() should return nil when not configured")
}
}
func TestTabExecutor_ConcurrentRemoveAndExecute(t *testing.T) {
// Verify that concurrent RemoveTab + Execute for the same tab doesn't
// cause a race condition or deadlock.
te := NewTabExecutor(4)
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(2)
tabID := fmt.Sprintf("race_tab_%d", i)
go func() {
defer wg.Done()
_ = te.Execute(context.Background(), tabID, func(ctx context.Context) error {
time.Sleep(time.Millisecond)
return nil
})
}()
go func() {
defer wg.Done()
time.Sleep(500 * time.Microsecond)
te.RemoveTab(tabID)
}()
}
wg.Wait()
}
func TestTabExecutor_RemoveTabDuringActiveExecution(t *testing.T) {
// Verify that RemoveTab waits for an active task to finish before removing.
te := NewTabExecutor(2)
taskStarted := make(chan struct{})
taskDone := make(chan struct{})
var taskCompleted bool
go func() {
_ = te.Execute(context.Background(), "active_tab", func(ctx context.Context) error {
close(taskStarted)
time.Sleep(50 * time.Millisecond)
taskCompleted = true
return nil
})
close(taskDone)
}()
<-taskStarted
// RemoveTab should block until the active task finishes
te.RemoveTab("active_tab")
// After RemoveTab returns, the task should have completed
if !taskCompleted {
t.Error("RemoveTab returned before active task completed")
}
<-taskDone // Wait for Execute goroutine to finish
}
func TestTabExecutor_StatsUnderLoad(t *testing.T) {
te := NewTabExecutor(2)
started := make(chan struct{}, 2)
// Fill both semaphore slots
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_ = te.Execute(context.Background(), fmt.Sprintf("stats_tab_%d", i), func(ctx context.Context) error {
started <- struct{}{}
time.Sleep(100 * time.Millisecond)
return nil
})
}(i)
}
// Wait for both tasks to start
<-started
<-started
stats := te.Stats()
if stats.SemaphoreUsed != 2 {
t.Errorf("expected 2 semaphore slots used, got %d", stats.SemaphoreUsed)
}
if stats.SemaphoreFree != 0 {
t.Errorf("expected 0 semaphore slots free, got %d", stats.SemaphoreFree)
}
if stats.ActiveTabs != 2 {
t.Errorf("expected 2 active tabs, got %d", stats.ActiveTabs)
}
wg.Wait()
}