mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-17 05:07:23 +08:00
### What problem does this PR solve? - Tools management - Pregel engine wrapper for better usage - UT race - Coding style ### Type of change - [x] Refactoring
354 lines
8.0 KiB
Go
354 lines
8.0 KiB
Go
package channels
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"ragflow/internal/harness/graph/errors"
|
|
)
|
|
|
|
func TestLastValue(t *testing.T) {
|
|
ch := NewLastValue("")
|
|
ch.SetKey("test")
|
|
|
|
// Empty channel should return error
|
|
_, err := ch.Get()
|
|
if err == nil {
|
|
t.Error("Expected error for empty channel")
|
|
}
|
|
if !errors.IsEmptyChannelError(err) {
|
|
t.Errorf("Expected EmptyChannelError, got %T", err)
|
|
}
|
|
|
|
// Update with single value
|
|
updated, err := ch.Update([]interface{}{"hello"})
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !updated {
|
|
t.Error("Expected updated to be true")
|
|
}
|
|
|
|
val, err := ch.Get()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if val != "hello" {
|
|
t.Errorf("Expected 'hello', got %v", val)
|
|
}
|
|
|
|
// Update with multiple values - should error (LastValue only accepts one value per step)
|
|
_, err = ch.Update([]interface{}{"a", "b", "c"})
|
|
if err == nil {
|
|
t.Error("Expected error for multiple values in LastValue")
|
|
}
|
|
|
|
// IsAvailable should return true
|
|
if !ch.IsAvailable() {
|
|
t.Error("Expected IsAvailable to be true")
|
|
}
|
|
|
|
// Copy should have same value
|
|
copyCh := ch.Copy()
|
|
copyVal, _ := copyCh.Get()
|
|
if copyVal != "hello" {
|
|
t.Errorf("Expected copied value 'hello', got %v", copyVal)
|
|
}
|
|
}
|
|
|
|
func TestTopic(t *testing.T) {
|
|
// Topic without accumulation
|
|
ch := NewTopic("", false)
|
|
ch.SetKey("messages")
|
|
|
|
// Add values - note: each Update flattens and adds
|
|
ch.Update([]interface{}{"a"})
|
|
ch.Update([]interface{}{"b"})
|
|
ch.Update([]interface{}{"c"})
|
|
|
|
val, _ := ch.Get()
|
|
vals := val.([]interface{})
|
|
if len(vals) != 1 || vals[0] != "c" {
|
|
t.Errorf("Expected 1 value ['c'], got %v", vals)
|
|
}
|
|
|
|
// Topic with accumulation
|
|
ch2 := NewTopic(0, true)
|
|
ch2.Update([]interface{}{1, 2})
|
|
ch2.Update([]interface{}{3})
|
|
val, _ = ch2.Get()
|
|
ints := val.([]interface{})
|
|
if len(ints) != 3 {
|
|
t.Errorf("Expected 3 values with accumulation, got %d", len(ints))
|
|
}
|
|
}
|
|
|
|
func TestBinaryOperatorAggregate(t *testing.T) {
|
|
// Sum operator - use float64 for JSON compatibility
|
|
sumOp := func(a, b interface{}) interface{} {
|
|
af, _ := a.(float64)
|
|
bf, _ := b.(float64)
|
|
return af + bf
|
|
}
|
|
|
|
ch := NewBinaryOperatorAggregate(float64(0), sumOp)
|
|
ch.SetKey("total")
|
|
|
|
// Update with values
|
|
ch.Update([]interface{}{float64(10), float64(20), float64(30)})
|
|
|
|
val, _ := ch.Get()
|
|
if val != float64(60) {
|
|
t.Errorf("Expected sum 60, got %v", val)
|
|
}
|
|
|
|
// Copy should have same value
|
|
copyCh := ch.Copy()
|
|
copyVal, _ := copyCh.Get()
|
|
if copyVal != float64(60) {
|
|
t.Errorf("Expected copied value 60, got %v", copyVal)
|
|
}
|
|
|
|
// List append operator
|
|
listOp := func(a, b interface{}) interface{} {
|
|
list := a.([]interface{})
|
|
if newList, ok := b.([]interface{}); ok {
|
|
return append(list, newList...)
|
|
}
|
|
return append(list, b)
|
|
}
|
|
|
|
listCh := NewBinaryOperatorAggregate([]interface{}{}, listOp)
|
|
listCh.Update([]interface{}{[]interface{}{"a", "b"}})
|
|
listCh.Update([]interface{}{[]interface{}{"c"}})
|
|
|
|
listVal, _ := listCh.Get()
|
|
list := listVal.([]interface{})
|
|
if len(list) != 3 {
|
|
t.Errorf("Expected 3 items, got %d", len(list))
|
|
}
|
|
}
|
|
|
|
func TestEphemeralValue(t *testing.T) {
|
|
// Ephemeral with guard
|
|
ch := NewEphemeralValue("", true)
|
|
ch.SetKey("temp")
|
|
|
|
// Empty with guard should error
|
|
_, err := ch.Get()
|
|
if !errors.IsEmptyChannelError(err) {
|
|
t.Error("Expected EmptyChannelError for guarded ephemeral")
|
|
}
|
|
|
|
// Set and get
|
|
ch.Update([]interface{}{"value"})
|
|
val, _ := ch.Get()
|
|
if val != "value" {
|
|
t.Errorf("Expected 'value', got %v", val)
|
|
}
|
|
|
|
// Second get should error (value is cleared)
|
|
_, err = ch.Get()
|
|
if !errors.IsEmptyChannelError(err) {
|
|
t.Error("Expected EmptyChannelError after read")
|
|
}
|
|
|
|
// Ephemeral without guard
|
|
ch2 := NewEphemeralValue("", false)
|
|
val2, _ := ch2.Get()
|
|
if val2 != nil {
|
|
t.Errorf("Expected nil, got %v", val2)
|
|
}
|
|
}
|
|
|
|
func TestUntrackedValue(t *testing.T) {
|
|
ch := NewUntrackedValue("")
|
|
ch.SetKey("untracked")
|
|
|
|
ch.Update([]interface{}{"test"})
|
|
val, _ := ch.Get()
|
|
if val != "test" {
|
|
t.Errorf("Expected 'test', got %v", val)
|
|
}
|
|
|
|
// Checkpoint should return Missing
|
|
cp := ch.Checkpoint()
|
|
if !IsMissing(cp) {
|
|
t.Error("Expected Missing from checkpoint of untracked value")
|
|
}
|
|
|
|
// FromCheckpoint should return empty channel
|
|
restored := ch.FromCheckpoint(cp)
|
|
_, err := restored.Get()
|
|
if !errors.IsEmptyChannelError(err) {
|
|
t.Error("Expected empty channel after restoring from checkpoint")
|
|
}
|
|
}
|
|
|
|
func TestAnyValue(t *testing.T) {
|
|
ch := NewAnyValue(nil)
|
|
ch.SetKey("any")
|
|
|
|
// Can receive multiple values, keeps last
|
|
ch.Update([]interface{}{"first", 42, true})
|
|
val, _ := ch.Get()
|
|
if val != true {
|
|
t.Errorf("Expected true (last value), got %v", val)
|
|
}
|
|
}
|
|
|
|
func TestNamedBarrierValue(t *testing.T) {
|
|
ch := NewNamedBarrierValue(nil, []string{"node_a", "node_b", "node_c"})
|
|
ch.SetKey("barrier")
|
|
|
|
// Initially not available
|
|
if ch.IsAvailable() {
|
|
t.Error("Barrier should not be available initially")
|
|
}
|
|
|
|
// Add one node
|
|
_, err := ch.Update([]interface{}{"node_a"})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if ch.IsAvailable() {
|
|
t.Error("Barrier should not be available after one node")
|
|
}
|
|
|
|
// Add remaining nodes
|
|
_, err = ch.Update([]interface{}{"node_b", "node_c"})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !ch.IsAvailable() {
|
|
t.Error("Barrier should be available after all nodes")
|
|
}
|
|
|
|
// Get should return nil when available
|
|
val, err := ch.Get()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if val != nil {
|
|
t.Errorf("Expected nil, got %v", val)
|
|
}
|
|
|
|
// Consume should reset
|
|
consumed := ch.Consume()
|
|
if !consumed {
|
|
t.Error("Consume should return true")
|
|
}
|
|
if ch.IsAvailable() {
|
|
t.Error("Barrier should be reset after consume")
|
|
}
|
|
}
|
|
|
|
func TestNamedBarrierValueAfterFinish(t *testing.T) {
|
|
ch := NewNamedBarrierValueAfterFinish(nil, []string{"a", "b"})
|
|
ch.SetKey("barrier_finish")
|
|
|
|
// Initially not available (finished=false).
|
|
if ch.IsAvailable() {
|
|
t.Error("barrier should not be available initially (finished=false)")
|
|
}
|
|
|
|
// Add nodes but not yet finished.
|
|
ch.Update([]interface{}{"a"})
|
|
ch.Update([]interface{}{"b"})
|
|
if ch.IsAvailable() {
|
|
t.Error("barrier should not be available before Finish is called")
|
|
}
|
|
|
|
// Finish should trigger availability.
|
|
finished := ch.Finish()
|
|
if !finished {
|
|
t.Error("Finish should return true")
|
|
}
|
|
if !ch.IsAvailable() {
|
|
t.Error("barrier should be available after Finish + all names seen")
|
|
}
|
|
}
|
|
|
|
func TestBaseChannelGetVersion(t *testing.T) {
|
|
ch := NewLastValue("")
|
|
if v := ch.GetVersion(); v != 0 {
|
|
t.Errorf("new channel version should be 0, got %d", v)
|
|
}
|
|
ch.SetVersion(42)
|
|
if v := ch.GetVersion(); v != 42 {
|
|
t.Errorf("expected version 42, got %d", v)
|
|
}
|
|
// Concurrent read/write should not race (atomic).
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for i := 0; i < 100; i++ {
|
|
ch.SetVersion(i)
|
|
}
|
|
close(done)
|
|
}()
|
|
for i := 0; i < 100; i++ {
|
|
_ = ch.GetVersion()
|
|
}
|
|
<-done
|
|
}
|
|
|
|
func TestRegistry(t *testing.T) {
|
|
reg := NewRegistry()
|
|
|
|
// Register channels
|
|
ch1 := NewLastValue("")
|
|
ch1.SetKey("ch1")
|
|
reg.Register("ch1", ch1)
|
|
|
|
ch2 := NewLastValue(0)
|
|
ch2.SetKey("ch2")
|
|
reg.Register("ch2", ch2)
|
|
|
|
if reg.Len() != 2 {
|
|
t.Errorf("Expected 2 channels, got %d", reg.Len())
|
|
}
|
|
|
|
// Update channels
|
|
writes := map[string][]interface{}{
|
|
"ch1": {"hello"},
|
|
"ch2": {42},
|
|
}
|
|
err := reg.UpdateChannels(writes)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Get values
|
|
values, err := reg.GetValues()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if values["ch1"] != "hello" {
|
|
t.Errorf("Expected 'hello', got %v", values["ch1"])
|
|
}
|
|
if values["ch2"] != 42 {
|
|
t.Errorf("Expected 42, got %v", values["ch2"])
|
|
}
|
|
|
|
// Create and restore checkpoint
|
|
checkpoint := reg.CreateCheckpoint()
|
|
if len(checkpoint) != 2 {
|
|
t.Errorf("Expected 2 entries in checkpoint, got %d", len(checkpoint))
|
|
}
|
|
|
|
// Modify channels
|
|
reg.UpdateChannels(map[string][]interface{}{
|
|
"ch1": {"modified"},
|
|
})
|
|
|
|
// Restore checkpoint
|
|
err = reg.RestoreFromCheckpoint(checkpoint)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
values, _ = reg.GetValues()
|
|
if values["ch1"] != "hello" {
|
|
t.Errorf("Expected restored value 'hello', got %v", values["ch1"])
|
|
}
|
|
}
|