aboutsummaryrefslogtreecommitdiff
path: root/runtime/task_test.go
diff options
context:
space:
mode:
authorSangTran-127 <tranquangsang12.7@gmail.com>2026-08-08 17:24:01 +0700
committerSangTran-127 <tranquangsang12.7@gmail.com>2026-08-08 17:24:01 +0700
commit0958e46c3651f2c8b6d76bcffbd8820063647d59 (patch)
tree1e917ed409eddc0b3c29fd602e551778c96c8dc6 /runtime/task_test.go
parentacc0b6fbaace61a3befd7f726f9d80fb69391126 (diff)
downloadgoflink-0958e46c3651f2c8b6d76bcffbd8820063647d59.tar.gz
goflink-0958e46c3651f2c8b6d76bcffbd8820063647d59.zip
refactor: add context support to transport operators and runtime task management
Diffstat (limited to 'runtime/task_test.go')
-rw-r--r--runtime/task_test.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/runtime/task_test.go b/runtime/task_test.go
new file mode 100644
index 0000000..f27a5eb
--- /dev/null
+++ b/runtime/task_test.go
@@ -0,0 +1,140 @@
+package runtime
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "goflink/core"
+ "goflink/transport"
+)
+
+// map(x*10) -> filter(>20), end to end.
+func TestPipeline(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ source := make(chan core.StreamRecord[int])
+ mid := make(chan core.StreamRecord[int])
+ sink := make(chan core.StreamRecord[int])
+
+ mapped := RunTask(ctx,
+ transport.NewLocalReceiveChannel(source),
+ transport.NewLocalEmitChannel(mid),
+ core.NewMapOperator(func(in int) (int, error) { return in * 10, nil }),
+ )
+ filtered := RunTask(ctx,
+ transport.NewLocalReceiveChannel(mid),
+ transport.NewLocalEmitChannel(sink),
+ core.NewFilterOperator(func(in int) bool { return in > 20 }),
+ )
+
+ go func() {
+ defer close(source)
+ for i := 1; i <= 5; i++ {
+ source <- core.NewDataRecord("k", i, int64(i))
+ }
+ }()
+
+ var got []int
+ for record := range sink {
+ got = append(got, record.Value)
+ }
+
+ want := []int{30, 40, 50}
+ if len(got) != len(want) {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ for i := range want {
+ if got[i] != want[i] {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ }
+
+ for _, errCh := range []<-chan error{mapped, filtered} {
+ if err := <-errCh; err != nil {
+ t.Fatalf("task failed: %v", err)
+ }
+ }
+}
+
+// A cancelled ctx must unblock a task parked in Receive.
+func TestCancelStopsTask(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ source := make(chan core.StreamRecord[int])
+ sink := make(chan core.StreamRecord[int], 1)
+
+ done := RunTask(ctx,
+ transport.NewLocalReceiveChannel(source),
+ transport.NewLocalEmitChannel(sink),
+ core.NewMapOperator(func(in int) (int, error) { return in, nil }),
+ )
+
+ cancel()
+
+ if err := <-done; !errors.Is(err, context.Canceled) {
+ t.Fatalf("got %v, want context.Canceled", err)
+ }
+}
+
+// A user function error must reach the caller with its cause intact.
+func TestProcessErrorPropagates(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ boom := errors.New("boom")
+ source := make(chan core.StreamRecord[int], 1)
+ sink := make(chan core.StreamRecord[int], 1)
+
+ done := RunTask(ctx,
+ transport.NewLocalReceiveChannel(source),
+ transport.NewLocalEmitChannel(sink),
+ core.NewMapOperator(func(in int) (int, error) { return 0, boom }),
+ )
+
+ source <- core.NewDataRecord("k", 1, 0)
+
+ if err := <-done; !errors.Is(err, boom) {
+ t.Fatalf("got %v, want %v", err, boom)
+ }
+}
+
+// closeProbe records the ctx.Err() its Close saw.
+type closeProbe struct {
+ closeCtxErr error
+}
+
+func (o *closeProbe) Open(ctx context.Context) error { return nil }
+
+func (o *closeProbe) Process(ctx context.Context, r core.StreamRecord[int], out core.Emitter[int]) error {
+ return out.Emit(ctx, r)
+}
+
+func (o *closeProbe) Close(ctx context.Context) error {
+ o.closeCtxErr = ctx.Err()
+ return nil
+}
+
+// Close must get a live ctx even when the task was killed by cancellation,
+// otherwise an operator that flushes on shutdown loses whatever it buffered.
+func TestCloseGetsLiveContext(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ source := make(chan core.StreamRecord[int])
+ sink := make(chan core.StreamRecord[int], 1)
+
+ probe := &closeProbe{}
+ done := RunTask(ctx,
+ transport.NewLocalReceiveChannel(source),
+ transport.NewLocalEmitChannel(sink),
+ probe,
+ )
+
+ cancel()
+ <-done // Close has already run by the time this channel closes
+
+ if probe.closeCtxErr != nil {
+ t.Fatalf("Close got a dead ctx (%v), so it cannot flush", probe.closeCtxErr)
+ }
+}