Files
ragflow/internal/harness/graph/pregel/pregel_boundary_engine_test.go
Yingfeng 5b0b86c276 More resilient graph engine (#16325)
### What problem does this PR solve?

- OpenTelemetry integration
- Checkpoint conformance tests
- State inspector API
- Callbacks
- A series of fault injection tests
- Pregel integration tests

### Type of change

- [x] Refactoring
2026-06-24 23:05:07 +08:00

194 lines
5.9 KiB
Go

// Package pregel provides boundary condition tests for the engine.
package pregel
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"ragflow/internal/harness/graph/channels"
"ragflow/internal/harness/graph/checkpoint"
"ragflow/internal/harness/graph/constants"
graphPkg "ragflow/internal/harness/graph/graph"
"ragflow/internal/harness/graph/types"
)
func TestBoundary_EmptyStateGraph(t *testing.T) {
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddNode("nop", func(ctx context.Context, state any) (any, error) {
return state, nil
})
_ = sg.AddEdge(constants.Start, "nop")
_ = sg.AddEdge("nop", constants.End)
engine := NewEngine(sg, WithRecursionLimit(10))
result, err := engine.RunSync(context.Background(), map[string]any{})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
_ = result
}
func TestBoundary_NilConfig(t *testing.T) {
sg := newSimpleGraph(t)
engine := NewEngine(sg, WithRecursionLimit(10))
result, err := engine.RunSync(context.Background(), map[string]any{"value": "x"})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
_ = result
}
func TestBoundary_NoTasksStillCompletes(t *testing.T) {
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddNode("only", func(ctx context.Context, state any) (any, error) {
return state, nil
})
_ = sg.AddEdge(constants.Start, "only")
_ = sg.AddEdge("only", constants.End)
engine := NewEngine(sg, WithRecursionLimit(10))
result, err := engine.RunSync(context.Background(), map[string]any{})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
_ = result
}
func TestBoundary_BinOpWithCheckpointer(t *testing.T) {
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddChannel("sum", channels.NewBinaryOperatorAggregate(0, func(a, b any) any {
return a.(int) + b.(int)
}))
sg.AddNode("add1", func(ctx context.Context, state any) (any, error) {
return map[string]any{"sum": 5}, nil
})
sg.AddNode("add2", func(ctx context.Context, state any) (any, error) {
return map[string]any{"sum": 10}, nil
})
_ = sg.AddEdge(constants.Start, "add1")
_ = sg.AddEdge("add1", "add2")
_ = sg.AddEdge("add2", constants.End)
ms := checkpoint.NewMemorySaver()
tid := "binop-cp"
cfg := &types.RunnableConfig{
Configurable: map[string]interface{}{constants.ConfigKeyThreadID: tid},
}
engine := NewEngine(sg, WithRecursionLimit(10), WithCheckpointer(ms), WithConfig(cfg))
result, err := engine.RunSync(context.Background(), map[string]any{})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
m := result.(map[string]any)
if m["sum"].(int) != 15 {
t.Fatalf("expected sum=15, got %v", m["sum"])
}
}
func TestBoundary_ManyIndependentCheckpointers(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 30; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
ms := checkpoint.NewMemorySaver()
tid := fmt.Sprintf("indep-cp-%d", idx)
cfg := &types.RunnableConfig{
Configurable: map[string]interface{}{constants.ConfigKeyThreadID: tid},
}
engine := NewEngine(newSimpleGraph(t), WithRecursionLimit(10), WithCheckpointer(ms), WithConfig(cfg))
_, err := engine.RunSync(context.Background(), map[string]any{"value": "x"})
if err != nil {
t.Errorf("engine %d: %v", idx, err)
}
}(i)
}
wg.Wait()
}
func TestBoundary_NodeContextDeadline(t *testing.T) {
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddChannel("value", channels.NewLastValue(""))
sg.AddNode("slow", func(ctx context.Context, state any) (any, error) {
select {
case <-time.After(5 * time.Second):
return state, nil
case <-ctx.Done():
return nil, ctx.Err()
}
})
_ = sg.AddEdge(constants.Start, "slow")
_ = sg.AddEdge("slow", constants.End)
engine := NewEngine(sg, WithRecursionLimit(10))
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
_, err := engine.RunSync(ctx, map[string]any{"value": "x"})
if err == nil {
t.Fatal("expected deadline exceeded")
}
}
func TestBoundary_SequentialChannelAccumulator(t *testing.T) {
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddChannel("counter", channels.NewBinaryOperatorAggregate(0, func(a, b any) any {
return a.(int) + b.(int)
}))
sg.AddNode("a", func(ctx context.Context, state any) (any, error) {
return map[string]any{"counter": 1}, nil
})
sg.AddNode("b", func(ctx context.Context, state any) (any, error) {
return map[string]any{"counter": 2}, nil
})
_ = sg.AddEdge(constants.Start, "a")
_ = sg.AddEdge("a", "b")
_ = sg.AddEdge("b", constants.End)
engine := NewEngine(sg, WithRecursionLimit(10))
result, err := engine.RunSync(context.Background(), map[string]any{})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
m := result.(map[string]any)
if m["counter"].(int) != 3 {
t.Fatalf("expected counter=3, got %v", m["counter"])
}
}
func TestBoundary_RetryInterruptCheckpointer(t *testing.T) {
var attempts atomic.Int32
sg := graphPkg.NewStateGraph(map[string]any{})
sg.AddChannel("value", channels.NewLastValue(""))
sg.AddNode("flaky", func(ctx context.Context, state any) (any, error) {
n := attempts.Add(1)
if n < 3 {
return nil, fmt.Errorf("transient %d", n)
}
return state, nil
})
_ = sg.AddEdge(constants.Start, "flaky")
_ = sg.AddEdge("flaky", constants.End)
ms := checkpoint.NewMemorySaver()
tid := "retry-int-cp"
cfg := &types.RunnableConfig{
Configurable: map[string]interface{}{constants.ConfigKeyThreadID: tid},
}
rp := types.DefaultRetryPolicy()
rp.MaxAttempts = 5
engine := NewEngine(sg, WithRecursionLimit(10), WithCheckpointer(ms), WithConfig(cfg), WithRetryPolicy(&rp), WithInterrupts("*"))
_, err := engine.RunSync(context.Background(), map[string]any{"value": "x"})
if err == nil {
t.Fatal("expected interrupt")
}
t.Logf("retry+interrupt+cp: %d attempts", attempts.Load())
}
func TestBoundary_MaxRecursionLimit(t *testing.T) {
engine := NewEngine(newSimpleGraph(t), WithRecursionLimit(1<<31-1))
_, err := engine.RunSync(context.Background(), map[string]any{"value": "x"})
if err != nil {
t.Fatalf("RunSync: %v", err)
}
}