返回 DeepSeek-Reasonix
projection_sink_test.go
根目录 / internal / taskmonitor / projection_sink_test.go
1 package taskmonitor
2
3 import (
4 "context"
5 "path/filepath"
6 "sync"
7 "testing"
8 "time"
9 )
10
11 type reentrantProjectionSink struct {
12 store *FileStore
13 mu sync.Mutex
14 ran bool
15 done chan error
16 }
17
18 func (s *reentrantProjectionSink) SnapshotChanged(root, taskID string) {
19 s.mu.Lock()
20 if s.ran {
21 s.mu.Unlock()
22 return
23 }
24 s.ran = true
25 s.mu.Unlock()
26 task, err := s.store.GetTask(context.Background(), root, taskID)
27 if err == nil && task != nil {
28 task.Version++
29 task.UpdatedAt = task.UpdatedAt.Add(time.Second)
30 err = s.store.SaveTask(context.Background(), root, *task)
31 }
32 s.done <- err
33 }
34
35 func (*reentrantProjectionSink) EventsChanged(string, string) {}
36
37 func TestProjectionSinkRunsAfterTaskLockRelease(t *testing.T) {
38 t.Parallel()
39 root := t.TempDir()
40 sink := &reentrantProjectionSink{done: make(chan error, 1)}
41 sink.store = NewObservedFileStore(filepath.Join(".reasonix", "tasks"), sink)
42 now := time.Now()
43 err := sink.store.SaveTask(context.Background(), root, TaskSnapshot{SchemaVersion: 1, TaskID: "task", State: TaskStateQueued,
44 Version: 1, CreatedAt: now, UpdatedAt: now})
45 if err != nil {
46 t.Fatal(err)
47 }
48 select {
49 case err := <-sink.done:
50 if err != nil {
51 t.Fatal(err)
52 }
53 case <-time.After(2 * time.Second):
54 t.Fatal("projection sink was invoked while the task lock was still held")
55 }
56 }
57
57 lines GO