1+ package gotaskflow
2+
3+ import (
4+ "bytes"
5+ "sync"
6+ "testing"
7+ "time"
8+ )
9+
10+ // TestObserverOpenSpan tests that openSpan creates a span with correct attributes.
11+ func TestObserverOpenSpan (t * testing.T ) {
12+ obs := newObserver ()
13+ obs .withProfiler (newProfiler ())
14+
15+ node := & innerNode {
16+ name : "test-task" ,
17+ Typ : nodeStatic ,
18+ dependents : []* innerNode {{name : "dep1" }, {name : "dep2" }},
19+ }
20+
21+ s := obs .openSpan (node , nil )
22+ if s == nil {
23+ t .Fatal ("expected non-nil span" )
24+ }
25+ if s .extra .name != "test-task" {
26+ t .Errorf ("expected name 'test-task', got %q" , s .extra .name )
27+ }
28+ if s .extra .typ != nodeStatic {
29+ t .Errorf ("expected type nodeStatic, got %q" , s .extra .typ )
30+ }
31+ if s .parent != nil {
32+ t .Errorf ("expected nil parent, got %v" , s .parent )
33+ }
34+ if len (s .dependents ) != 2 || s .dependents [0 ] != "dep1" || s .dependents [1 ] != "dep2" {
35+ t .Errorf ("expected dependents [dep1, dep2], got %v" , s .dependents )
36+ }
37+ }
38+
39+ // TestObserverOpenSpanNil tests that openSpan returns nil when no profiler or tracer is set.
40+ func TestObserverOpenSpanNil (t * testing.T ) {
41+ obs := newObserver ()
42+
43+ node := & innerNode {
44+ name : "test-task" ,
45+ Typ : nodeStatic ,
46+ }
47+
48+ s := obs .openSpan (node , nil )
49+ if s != nil {
50+ t .Errorf ("expected nil span when no profiler/tracer, got %v" , s )
51+ }
52+ }
53+
54+ // TestObserverCloseSpan tests that closeSpan records the span to profiler.
55+ func TestObserverCloseSpan (t * testing.T ) {
56+ obs := newObserver ()
57+ p := newProfiler ()
58+ obs .withProfiler (p )
59+
60+ node := & innerNode {
61+ name : "test-task" ,
62+ Typ : nodeStatic ,
63+ }
64+
65+ s := obs .openSpan (node , nil )
66+ time .Sleep (1 * time .Millisecond ) // ensure some duration
67+ obs .closeSpan (s , true )
68+
69+ if len (p .spans ) != 1 {
70+ t .Errorf ("expected 1 span in profiler, got %d" , len (p .spans ))
71+ }
72+ if s .cost <= 0 {
73+ t .Errorf ("expected positive cost, got %v" , s .cost )
74+ }
75+ }
76+
77+ // TestObserverCloseSpanNotOk tests that closeSpan does not record span when ok=false (panic).
78+ func TestObserverCloseSpanNotOk (t * testing.T ) {
79+ obs := newObserver ()
80+ p := newProfiler ()
81+ obs .withProfiler (p )
82+
83+ node := & innerNode {
84+ name : "test-task" ,
85+ Typ : nodeStatic ,
86+ }
87+
88+ s := obs .openSpan (node , nil )
89+ time .Sleep (1 * time .Millisecond )
90+ obs .closeSpan (s , false ) // ok=false means panic occurred
91+
92+ if len (p .spans ) != 0 {
93+ t .Errorf ("expected 0 spans in profiler (panicked task), got %d" , len (p .spans ))
94+ }
95+ }
96+
97+ // TestObserverCloseSpanNil tests that closeSpan handles nil span gracefully.
98+ func TestObserverCloseSpanNil (t * testing.T ) {
99+ obs := newObserver ()
100+ obs .withProfiler (newProfiler ())
101+
102+ // Should not panic
103+ obs .closeSpan (nil , true )
104+ }
105+
106+ // TestObserverWithProfiler tests that withProfiler sets the profiler correctly.
107+ func TestObserverWithProfiler (t * testing.T ) {
108+ obs := newObserver ()
109+ if obs .profiler != nil {
110+ t .Error ("expected nil profiler initially" )
111+ }
112+
113+ p := newProfiler ()
114+ obs .withProfiler (p )
115+ if obs .profiler != p {
116+ t .Error ("profiler not set correctly" )
117+ }
118+ }
119+
120+ // TestObserverWithTracer tests that withTracer sets the tracer correctly.
121+ func TestObserverWithTracer (t * testing.T ) {
122+ obs := newObserver ()
123+ if obs .tracer != nil {
124+ t .Error ("expected nil tracer initially" )
125+ }
126+
127+ tr := newTracer ()
128+ obs .withTracer (tr )
129+ if obs .tracer != tr {
130+ t .Error ("tracer not set correctly" )
131+ }
132+ }
133+
134+ // TestObserverSpanParentChain tests that span parent chain is preserved.
135+ func TestObserverSpanParentChain (t * testing.T ) {
136+ obs := newObserver ()
137+ obs .withProfiler (newProfiler ())
138+
139+ parentNode := & innerNode {name : "parent" , Typ : nodeStatic }
140+ childNode := & innerNode {name : "child" , Typ : nodeStatic }
141+
142+ parentSpan := obs .openSpan (parentNode , nil )
143+ childSpan := obs .openSpan (childNode , parentSpan )
144+
145+ if childSpan .parent != parentSpan {
146+ t .Error ("child span should have parent span set" )
147+ }
148+ if parentSpan .parent != nil {
149+ t .Error ("parent span should have nil parent" )
150+ }
151+ }
152+
153+ // TestObserverWithBothProfilerAndTracer tests that both profiler and tracer work together.
154+ func TestObserverWithBothProfilerAndTracer (t * testing.T ) {
155+ obs := newObserver ()
156+ p := newProfiler ()
157+ tr := newTracer ()
158+ obs .withProfiler (p )
159+ obs .withTracer (tr )
160+
161+ node := & innerNode {name : "test-task" , Typ : nodeStatic }
162+ s := obs .openSpan (node , nil )
163+ time .Sleep (1 * time .Millisecond )
164+ obs .closeSpan (s , true )
165+
166+ if len (p .spans ) != 1 {
167+ t .Errorf ("expected 1 span in profiler, got %d" , len (p .spans ))
168+ }
169+ if len (tr .events ) != 1 {
170+ t .Errorf ("expected 1 event in tracer, got %d" , len (tr .events ))
171+ }
172+ }
173+
174+ // TestObserverConcurrent tests concurrent use of observer.
175+ func TestObserverConcurrent (t * testing.T ) {
176+ obs := newObserver ()
177+ p := newProfiler ()
178+ tr := newTracer ()
179+ obs .withProfiler (p )
180+ obs .withTracer (tr )
181+
182+ var wg sync.WaitGroup
183+ n := 100
184+ wg .Add (n )
185+
186+ for i := 0 ; i < n ; i ++ {
187+ go func (i int ) {
188+ defer wg .Done ()
189+ node := & innerNode {
190+ name : "task" ,
191+ Typ : nodeStatic ,
192+ }
193+ s := obs .openSpan (node , nil )
194+ time .Sleep (time .Microsecond )
195+ obs .closeSpan (s , true )
196+ }(i )
197+ }
198+ wg .Wait ()
199+
200+ // Note: profiler merges by attr, so may have fewer than n spans
201+ // But tracer should have all n events
202+ if len (tr .events ) != n {
203+ t .Errorf ("expected %d tracer events, got %d" , n , len (tr .events ))
204+ }
205+ }
206+
207+ // TestObserverIntegrationWithExecutor tests observer works correctly with executor.
208+ func TestObserverIntegrationWithExecutor (t * testing.T ) {
209+ tf := NewTaskFlow ("test-flow" )
210+ a := tf .NewTask ("A" , func () {})
211+ b := tf .NewTask ("B" , func () {})
212+ a .Precede (b )
213+
214+ exec := NewExecutor (4 , WithProfiler ())
215+ exec .Run (tf ).Wait ()
216+
217+ var buf bytes.Buffer
218+ err := exec .Profile (& buf )
219+ if err != nil {
220+ t .Errorf ("unexpected error: %v" , err )
221+ }
222+ if buf .Len () == 0 {
223+ t .Error ("expected profile output, got empty" )
224+ }
225+ }
226+
227+ // TestObserverIntegrationWithTracer tests observer works correctly with tracer.
228+ func TestObserverIntegrationWithTracer (t * testing.T ) {
229+ tf := NewTaskFlow ("test-flow" )
230+ a := tf .NewTask ("A" , func () {})
231+ b := tf .NewTask ("B" , func () {})
232+ a .Precede (b )
233+
234+ exec := NewExecutor (4 , WithTracer ())
235+ exec .Run (tf ).Wait ()
236+
237+ var buf bytes.Buffer
238+ err := exec .Trace (& buf )
239+ if err != nil {
240+ t .Errorf ("unexpected error: %v" , err )
241+ }
242+ if buf .Len () == 0 {
243+ t .Error ("expected trace output, got empty" )
244+ }
245+ }
246+
247+ // TestObserverIntegrationWithBoth tests observer with both profiler and tracer.
248+ func TestObserverIntegrationWithBoth (t * testing.T ) {
249+ tf := NewTaskFlow ("test-flow" )
250+ a := tf .NewTask ("A" , func () {})
251+ b := tf .NewTask ("B" , func () {})
252+ a .Precede (b )
253+
254+ exec := NewExecutor (4 , WithProfiler (), WithTracer ())
255+ exec .Run (tf ).Wait ()
256+
257+ var profileBuf , traceBuf bytes.Buffer
258+ if err := exec .Profile (& profileBuf ); err != nil {
259+ t .Errorf ("unexpected profile error: %v" , err )
260+ }
261+ if err := exec .Trace (& traceBuf ); err != nil {
262+ t .Errorf ("unexpected trace error: %v" , err )
263+ }
264+ if profileBuf .Len () == 0 {
265+ t .Error ("expected profile output" )
266+ }
267+ if traceBuf .Len () == 0 {
268+ t .Error ("expected trace output" )
269+ }
270+ }
0 commit comments