mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-06 11:28:38 +08:00
216 lines
5.0 KiB
Go
216 lines
5.0 KiB
Go
package harness
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"ragflow/internal/harness/graph/types"
|
|
)
|
|
|
|
func TestNewStateGraph(t *testing.T) {
|
|
sg := NewStateGraph(map[string]interface{}{})
|
|
if sg == nil {
|
|
t.Fatal("expected non-nil StateGraph")
|
|
}
|
|
}
|
|
|
|
func TestNewMemorySaver(t *testing.T) {
|
|
ms := NewMemorySaver()
|
|
if ms == nil {
|
|
t.Fatal("expected non-nil MemorySaver")
|
|
}
|
|
}
|
|
|
|
func TestCompileSimpleGraph(t *testing.T) {
|
|
sg := NewStateGraph(map[string]interface{}{"value": ""})
|
|
sg.AddNode("echo", func(ctx context.Context, state interface{}) (interface{}, error) {
|
|
return state, nil
|
|
})
|
|
sg.AddEdge(Start, "echo")
|
|
sg.AddEdge("echo", End)
|
|
|
|
cg, err := sg.Compile()
|
|
if err != nil {
|
|
t.Fatalf("Compile failed: %v", err)
|
|
}
|
|
if cg == nil {
|
|
t.Fatal("expected non-nil compiled graph")
|
|
}
|
|
|
|
// Verify graph structure
|
|
if cg.GetGraph() == nil {
|
|
t.Fatal("expected non-nil underlying graph")
|
|
}
|
|
}
|
|
|
|
func TestCompileWithRecursionLimit(t *testing.T) {
|
|
sg := NewStateGraph(map[string]interface{}{})
|
|
sg.AddNode("n", func(ctx context.Context, state interface{}) (interface{}, error) {
|
|
return state, nil
|
|
})
|
|
sg.AddEdge(Start, "n")
|
|
sg.AddEdge("n", End)
|
|
|
|
cg, err := sg.Compile(WithRecursionLimit(5))
|
|
if err != nil {
|
|
t.Fatalf("Compile failed: %v", err)
|
|
}
|
|
if cg == nil {
|
|
t.Fatal("expected non-nil compiled graph")
|
|
}
|
|
}
|
|
|
|
func TestStateGraphNodeOperations(t *testing.T) {
|
|
sg := NewStateGraph(map[string]interface{}{})
|
|
sg.AddNode("agent", func(ctx context.Context, state interface{}) (interface{}, error) {
|
|
return state, nil
|
|
})
|
|
|
|
node, ok := sg.GetNode("agent")
|
|
if !ok {
|
|
t.Fatal("expected to find node 'agent'")
|
|
}
|
|
if node.Name != "agent" {
|
|
t.Errorf("expected node name 'agent', got '%s'", node.Name)
|
|
}
|
|
}
|
|
|
|
func TestNewCommand(t *testing.T) {
|
|
cmd := NewCommand()
|
|
if cmd == nil {
|
|
t.Fatal("expected non-nil Command")
|
|
}
|
|
}
|
|
|
|
func TestNewSend(t *testing.T) {
|
|
s := NewSend("target", "arg")
|
|
if s == nil {
|
|
t.Fatal("expected non-nil Send")
|
|
}
|
|
if s.Node != "target" {
|
|
t.Errorf("expected Node='target', got '%s'", s.Node)
|
|
}
|
|
}
|
|
|
|
func TestDefaultRetryPolicy(t *testing.T) {
|
|
rp := DefaultRetryPolicy()
|
|
if rp.MaxAttempts == 0 {
|
|
t.Error("expected non-zero MaxAttempts in default retry policy")
|
|
}
|
|
}
|
|
|
|
// TestGraph_DAG_SlowBranchMerge verifies AllPredecessor mode: merge waits for
|
|
// slow branch. Uses pregel engine (via harness.init() which sets PregelRunFunc)
|
|
// and channel-based state (pregel-compatible pattern).
|
|
func TestGraph_DAG_SlowBranchMerge(t *testing.T) {
|
|
var orderMu sync.Mutex
|
|
var order []string
|
|
slowStarted := make(chan struct{})
|
|
fastDone := make(chan struct{})
|
|
releaseSlow := make(chan struct{})
|
|
mergeRan := make(chan struct{}, 1)
|
|
record := func(name string) {
|
|
orderMu.Lock()
|
|
order = append(order, name)
|
|
orderMu.Unlock()
|
|
}
|
|
|
|
sg := NewStateGraph(map[string]any{})
|
|
|
|
// Start → dispatch → {fast, slow} → merge
|
|
sg.AddNode("dispatch", func(ctx context.Context, state any) (any, error) {
|
|
record("dispatch")
|
|
return map[string]any{"messages": []string{"dispatch done"}}, nil
|
|
})
|
|
sg.AddNode("fast", func(ctx context.Context, state any) (any, error) {
|
|
record("fast")
|
|
close(fastDone)
|
|
return map[string]any{"messages": []string{"fast done"}}, nil
|
|
})
|
|
sg.AddNode("slow", func(ctx context.Context, state any) (any, error) {
|
|
close(slowStarted)
|
|
<-releaseSlow
|
|
record("slow")
|
|
return map[string]any{"messages": []string{"slow done"}}, nil
|
|
})
|
|
sg.AddNode("merge", func(ctx context.Context, state any) (any, error) {
|
|
select {
|
|
case mergeRan <- struct{}{}:
|
|
default:
|
|
}
|
|
record("merge")
|
|
return map[string]any{"messages": []string{"merge done"}}, nil
|
|
})
|
|
|
|
sg.AddEdge(Start, "dispatch")
|
|
sg.AddEdge("dispatch", "fast")
|
|
sg.AddEdge("dispatch", "slow")
|
|
sg.AddEdge("fast", "merge")
|
|
sg.AddEdge("slow", "merge")
|
|
sg.AddEdge("merge", End)
|
|
|
|
compiled, err := sg.Compile(
|
|
WithNodeTriggerMode(types.NodeTriggerAllPredecessor),
|
|
WithRecursionLimit(10),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
_, err := compiled.Invoke(context.Background(), map[string]any{})
|
|
done <- err
|
|
}()
|
|
|
|
select {
|
|
case <-slowStarted:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("slow branch never started")
|
|
}
|
|
|
|
select {
|
|
case <-fastDone:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("fast branch never completed")
|
|
}
|
|
|
|
select {
|
|
case <-mergeRan:
|
|
t.Fatal("BUG: merge ran before slow branch completed")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
|
|
close(releaseSlow)
|
|
|
|
select {
|
|
case err = <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("compiled graph did not finish after slow branch release")
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(order) != 4 {
|
|
t.Errorf("expected 4 nodes (dispatch+fast+slow+merge), got %d: %v", len(order), order)
|
|
}
|
|
slowIdx, mergeIdx := -1, -1
|
|
for i, name := range order {
|
|
switch name {
|
|
case "slow":
|
|
slowIdx = i
|
|
case "merge":
|
|
mergeIdx = i
|
|
}
|
|
}
|
|
if mergeIdx < slowIdx {
|
|
t.Errorf("BUG: merge before slow branch. merge=%d, slow=%d", mergeIdx, slowIdx)
|
|
} else {
|
|
t.Log("DAG slow-branch merge: merge correctly waited for slow branch")
|
|
}
|
|
}
|