1+ import contextlib
12import mock
23import threading
34
@@ -15,6 +16,20 @@ class TestTracingContext(TestCase):
1516 Tests related to the ``Context`` class that hosts the trace for the
1617 current execution flow.
1718 """
19+ @contextlib .contextmanager
20+ def override_partial_flush (self , ctx , enabled , min_spans ):
21+ original_enabled = ctx ._partial_flush_enabled
22+ original_min_spans = ctx ._partial_flush_min_spans
23+
24+ ctx ._partial_flush_enabled = enabled
25+ ctx ._partial_flush_min_spans = min_spans
26+
27+ try :
28+ yield
29+ finally :
30+ ctx ._partial_flush_enabled = original_enabled
31+ ctx ._partial_flush_min_spans = original_min_spans
32+
1833 def test_add_span (self ):
1934 # it should add multiple spans
2035 ctx = Context ()
@@ -101,6 +116,163 @@ def test_get_trace_empty(self):
101116 ok_ (trace is None )
102117 ok_ (sampled is None )
103118
119+ def test_partial_flush (self ):
120+ """
121+ When calling `Context.get`
122+ When partial flushing is enabled
123+ When we have just enough finished spans to flush
124+ We return the finished spans
125+ """
126+ tracer = get_dummy_tracer ()
127+ ctx = Context ()
128+
129+ # Create a root span with 5 children, all of the children are finished, the root is not
130+ root = Span (tracer = tracer , name = 'root' )
131+ ctx .add_span (root )
132+ for i in range (5 ):
133+ child = Span (tracer = tracer , name = 'child_{}' .format (i ), trace_id = root .trace_id , parent_id = root .span_id )
134+ child ._parent = root
135+ child ._finished = True
136+ ctx .add_span (child )
137+ ctx .close_span (child )
138+
139+ with self .override_partial_flush (ctx , enabled = True , min_spans = 5 ):
140+ trace , sampled = ctx .get ()
141+
142+ self .assertIsNotNone (trace )
143+ self .assertIsNotNone (sampled )
144+
145+ self .assertEqual (len (trace ), 5 )
146+ self .assertEqual (
147+ set (['child_0' , 'child_1' , 'child_2' , 'child_3' , 'child_4' ]),
148+ set ([span .name for span in trace ])
149+ )
150+
151+ # Ensure we clear/reset internal stats as expected
152+ self .assertEqual (ctx ._finished_spans , 0 )
153+ self .assertEqual (ctx ._trace , [root ])
154+ with self .override_partial_flush (ctx , enabled = True , min_spans = 5 ):
155+ trace , sampled = ctx .get ()
156+ self .assertIsNone (trace )
157+ self .assertIsNone (sampled )
158+
159+ def test_partial_flush_too_many (self ):
160+ """
161+ When calling `Context.get`
162+ When partial flushing is enabled
163+ When we have more than the minimum number of spans needed to flush
164+ We return the finished spans
165+ """
166+ tracer = get_dummy_tracer ()
167+ ctx = Context ()
168+
169+ # Create a root span with 5 children, all of the children are finished, the root is not
170+ root = Span (tracer = tracer , name = 'root' )
171+ ctx .add_span (root )
172+ for i in range (5 ):
173+ child = Span (tracer = tracer , name = 'child_{}' .format (i ), trace_id = root .trace_id , parent_id = root .span_id )
174+ child ._parent = root
175+ child ._finished = True
176+ ctx .add_span (child )
177+ ctx .close_span (child )
178+
179+ with self .override_partial_flush (ctx , enabled = True , min_spans = 1 ):
180+ trace , sampled = ctx .get ()
181+
182+ self .assertIsNotNone (trace )
183+ self .assertIsNotNone (sampled )
184+
185+ self .assertEqual (len (trace ), 5 )
186+ self .assertEqual (
187+ set (['child_0' , 'child_1' , 'child_2' , 'child_3' , 'child_4' ]),
188+ set ([span .name for span in trace ])
189+ )
190+
191+ # Ensure we clear/reset internal stats as expected
192+ self .assertEqual (ctx ._finished_spans , 0 )
193+ self .assertEqual (ctx ._trace , [root ])
194+ with self .override_partial_flush (ctx , enabled = True , min_spans = 5 ):
195+ trace , sampled = ctx .get ()
196+ self .assertIsNone (trace )
197+ self .assertIsNone (sampled )
198+
199+ def test_partial_flush_too_few (self ):
200+ """
201+ When calling `Context.get`
202+ When partial flushing is enabled
203+ When we do not have enough finished spans to flush
204+ We return no spans
205+ """
206+ tracer = get_dummy_tracer ()
207+ ctx = Context ()
208+
209+ # Create a root span with 5 children, all of the children are finished, the root is not
210+ root = Span (tracer = tracer , name = 'root' )
211+ ctx .add_span (root )
212+ for i in range (5 ):
213+ child = Span (tracer = tracer , name = 'child_{}' .format (i ), trace_id = root .trace_id , parent_id = root .span_id )
214+ child ._parent = root
215+ child ._finished = True
216+ ctx .add_span (child )
217+ ctx .close_span (child )
218+
219+ # Test with having 1 too few spans for partial flush
220+ with self .override_partial_flush (ctx , enabled = True , min_spans = 6 ):
221+ trace , sampled = ctx .get ()
222+
223+ self .assertIsNone (trace )
224+ self .assertIsNone (sampled )
225+
226+ self .assertEqual (len (ctx ._trace ), 6 )
227+ self .assertEqual (ctx ._finished_spans , 5 )
228+ self .assertEqual (
229+ set (['root' , 'child_0' , 'child_1' , 'child_2' , 'child_3' , 'child_4' ]),
230+ set ([span .name for span in ctx ._trace ])
231+ )
232+
233+ def test_partial_flush_remaining (self ):
234+ """
235+ When calling `Context.get`
236+ When partial flushing is enabled
237+ When we have some unfinished spans
238+ We keep the unfinished spans around
239+ """
240+ tracer = get_dummy_tracer ()
241+ ctx = Context ()
242+
243+ # Create a root span with 5 children, all of the children are finished, the root is not
244+ root = Span (tracer = tracer , name = 'root' )
245+ ctx .add_span (root )
246+ for i in range (10 ):
247+ child = Span (tracer = tracer , name = 'child_{}' .format (i ), trace_id = root .trace_id , parent_id = root .span_id )
248+ child ._parent = root
249+ ctx .add_span (child )
250+
251+ # CLose the first 5 only
252+ if i < 5 :
253+ child ._finished = True
254+ ctx .close_span (child )
255+
256+ with self .override_partial_flush (ctx , enabled = True , min_spans = 5 ):
257+ trace , sampled = ctx .get ()
258+
259+ # Assert partially flushed spans
260+ self .assertTrue (len (trace ), 5 )
261+ self .assertIsNotNone (sampled )
262+ self .assertEqual (
263+ set (['child_0' , 'child_1' , 'child_2' , 'child_3' , 'child_4' ]),
264+ set ([span .name for span in trace ])
265+ )
266+
267+ # Assert remaining unclosed spans
268+ self .assertEqual (len (ctx ._trace ), 6 )
269+ self .assertEqual (ctx ._finished_spans , 0 )
270+ self .assertEqual (
271+ set (['root' , 'child_5' , 'child_6' , 'child_7' , 'child_8' , 'child_9' ]),
272+ set ([span .name for span in ctx ._trace ]),
273+ )
274+
275+
104276 def test_finished (self ):
105277 # a Context is finished if all spans inside are finished
106278 ctx = Context ()
0 commit comments