Files
ragflow/internal/harness/graph/channels/channels_test.go
Yingfeng 706e0d2d06 Refactor harness framework (#16271)
### What problem does this PR solve?

- Tools management
- Pregel engine wrapper for better usage
- UT race
- Coding style

### Type of change

- [x] Refactoring
2026-06-23 20:18:04 +08:00

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"])
}
}