mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-01 13:33:48 +08:00
### 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
194 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|