1111from posthog import Posthog
1212from posthog .client import Client
1313from posthog .contexts import identify_context , new_context , set_context_session
14- from posthog .test .tracing .helpers import FakeTimer
14+ from posthog .test .tracing .helpers import SPAN_ID , TRACE_ID
1515from posthog .tracing ._transport import OK
1616from posthog .tracing ._span import NOOP_SPAN , RecordingSpan , Span
1717from posthog .version import VERSION
1818
1919FAKE_API_KEY = "phc_test_key"
20- TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736"
21- SPAN_ID = "00f067aa0ba902b7"
2220
2321
2422def make_client (** kwargs ):
@@ -33,11 +31,15 @@ def mock_session(status_code=200):
3331 return session
3432
3533
36- @pytest .fixture
37- def no_timers ():
38- # No background drain racing the test.
39- with mock .patch .object (threading , "Timer" , FakeTimer ):
40- yield
34+ def slow_send (requests , delay = 0.2 ):
35+ """A sender that records each payload and takes ``delay`` seconds to answer."""
36+
37+ def send (pipeline_client , payload ):
38+ requests .append (payload )
39+ time .sleep (delay )
40+ return OK
41+
42+ return send
4143
4244
4345@pytest .fixture (autouse = True )
@@ -135,6 +137,14 @@ def test_a_failed_init_is_not_retried_on_the_next_call(self):
135137 assert resolve .call_count == 1
136138 client .shutdown ()
137139
140+ def test_a_non_callable_hook_turns_tracing_off (self , caplog ):
141+ caplog .set_level ("ERROR" , logger = "posthog" )
142+ client = make_client (traces = {"before_span_send" : "scrub" })
143+ assert client .start_span ("x" ) is NOOP_SPAN
144+ assert "Error initializing traces" in caplog .text
145+ assert "not callable" in caplog .text
146+ client .shutdown ()
147+
138148 def test_never_starts_a_pipeline_on_a_client_without_traces (self ):
139149 client = make_client ()
140150 client .flush ()
@@ -370,19 +380,13 @@ def test_flush_resolves_when_the_span_export_fails(self):
370380 client .shutdown ()
371381
372382 def test_flush_stops_starting_span_requests_once_its_budget_is_spent (
373- self , no_timers
383+ self , fake_timers
374384 ):
375385 client = make_client (traces = {"max_export_batch_size" : 1 })
376386 client .start_span ("x" ).end ()
377387 client .start_span ("y" ).end ()
378388 requests = []
379-
380- def slow_send (pipeline_client , payload ):
381- requests .append (payload )
382- time .sleep (0.2 )
383- return OK
384-
385- client ._traces ._exporter ._send = slow_send
389+ client ._traces ._exporter ._send = slow_send (requests )
386390 client .flush (timeout_seconds = 0.05 )
387391 assert len (requests ) == 1
388392 assert len (client ._traces ._exporter ._queue ) == 1
@@ -404,7 +408,7 @@ def test_flush_without_a_timeout_drains_every_queued_span(self):
404408 assert client ._traces ._exporter ._queue == []
405409 client .shutdown ()
406410
407- def test_flush_sends_spans_while_events_are_still_draining (self , no_timers ):
411+ def test_flush_sends_spans_while_events_are_still_draining (self , fake_timers ):
408412 client = make_client (traces = {}, sync_mode = False )
409413 client .start_span ("x" ).end ()
410414 span_sent = threading .Event ()
@@ -421,7 +425,7 @@ def send(pipeline_client, payload):
421425 assert overlapped and all (overlapped )
422426 client .shutdown ()
423427
424- def test_flush_sends_spans_inline_when_no_thread_can_start (self , no_timers ):
428+ def test_flush_sends_spans_inline_when_no_thread_can_start (self , fake_timers ):
425429 client = make_client (traces = {})
426430 client .start_span ("x" ).end ()
427431 with mock .patch ("posthog.client.threading.Thread" ) as thread :
@@ -432,6 +436,32 @@ def test_flush_sends_spans_inline_when_no_thread_can_start(self, no_timers):
432436 assert len (spans_from (payload )) == 1
433437 client .shutdown ()
434438
439+ def test_flush_starts_no_span_thread_when_nothing_is_queued (self , fake_timers ):
440+ client = make_client (traces = {})
441+ client .start_span ("x" ).end ()
442+ client .flush ()
443+ with mock .patch ("posthog.client.threading.Thread" ) as thread :
444+ client .flush ()
445+ thread .assert_not_called ()
446+ client .shutdown ()
447+
448+ def test_exit_flushes_the_lanes_before_an_inline_span_flush (self , fake_timers ):
449+ client = make_client (traces = {}, sync_mode = False )
450+ client .start_span ("x" ).end ()
451+ order = []
452+ client ._traces .flush = lambda timeout : order .append ("spans" )
453+ for lane in client ._lanes :
454+ lane .flush = lambda timeout , _lane = lane : order .append ("lanes" )
455+ with (
456+ mock .patch ("posthog.client.threading.Thread" ) as thread ,
457+ mock .patch ("posthog.client._atexit_deadline" , None ),
458+ ):
459+ thread .return_value .start .side_effect = RuntimeError ("no threads" )
460+ client ._atexit ()
461+ assert order [0 ] == "lanes"
462+ assert order [- 1 ] == "spans"
463+ client .shutdown ()
464+
435465 def test_shutdown_flushes_pending_spans (self ):
436466 client = make_client (traces = {})
437467 client .start_span ("x" ).end ()
@@ -444,33 +474,27 @@ def test_shutdown_flushes_pending_spans(self):
444474 assert client ._traces ._exporter ._queue == []
445475
446476 def test_shutdown_bounds_the_final_span_flush_and_warns_about_the_rest (
447- self , no_timers , caplog
477+ self , fake_timers , caplog
448478 ):
449479 caplog .set_level ("WARNING" , logger = "posthog" )
450480 client = make_client (traces = {"max_export_batch_size" : 1 })
451481 for name in ("a" , "b" , "c" ):
452482 client .start_span (name ).end ()
453483 requests = []
454-
455- def slow_send (pipeline_client , payload ):
456- requests .append (payload )
457- time .sleep (0.2 )
458- return OK
459-
460- client ._traces ._exporter ._send = slow_send
484+ client ._traces ._exporter ._send = slow_send (requests )
461485 with mock .patch ("posthog.client._TRACES_SHUTDOWN_FLUSH_SECONDS" , 0.05 ):
462486 client .shutdown ()
463487 assert len (requests ) == 1
464488 assert any ("Discarding 2 span(s)" in r .getMessage () for r in caplog .records )
465489
466- def test_tracing_is_inert_after_shutdown (self , no_timers ):
490+ def test_tracing_is_inert_after_shutdown (self , fake_timers ):
467491 client = make_client (traces = {})
468492 client .start_span ("before" ).end ()
469493 client .shutdown ()
470494 assert client .start_span ("late" ) is NOOP_SPAN
471495 assert client ._traces ._exporter ._flush_timer is None
472496
473- def test_shutdown_closes_a_pipeline_still_initializing (self , no_timers ):
497+ def test_shutdown_closes_a_pipeline_still_initializing (self , fake_timers ):
474498 client = make_client (traces = {})
475499 shutdown = threading .Thread (target = client .shutdown )
476500 resolve = posthog .client .resolve_traces_config
@@ -488,13 +512,13 @@ def resolve_while_shutting_down(*args):
488512 assert client ._traces ._closed
489513 assert client ._traces ._exporter ._flush_timer is None
490514
491- def test_tracing_never_starts_after_shutdown (self , no_timers ):
515+ def test_tracing_never_starts_after_shutdown (self , fake_timers ):
492516 client = make_client (traces = {})
493517 client .shutdown ()
494518 assert client .start_span ("late" ) is NOOP_SPAN
495519 assert client ._traces is None
496520
497- def test_exit_drains_spans_the_timer_would_have_sent (self , no_timers ):
521+ def test_exit_drains_spans_the_timer_would_have_sent (self , fake_timers ):
498522 client = make_client (traces = {}, sync_mode = False )
499523 client .start_span ("x" ).end ()
500524 session = mock_session ()
@@ -516,7 +540,7 @@ def test_exit_drains_spans_the_timer_would_have_sent(self, no_timers):
516540 assert session .post .called
517541
518542 def test_exit_flushes_spans_alongside_events_that_use_up_the_budget (
519- self , no_timers
543+ self , fake_timers
520544 ):
521545 client = make_client (traces = {}, sync_mode = False )
522546 client .start_span ("x" ).end ()
@@ -535,7 +559,7 @@ def slow_lane_flush(timeout_seconds):
535559 assert session .post .called
536560 client .shutdown ()
537561
538- def test_exit_does_not_wait_on_a_hung_span_request (self , no_timers , caplog ):
562+ def test_exit_does_not_wait_on_a_hung_span_request (self , fake_timers , caplog ):
539563 caplog .set_level ("WARNING" , logger = "posthog" )
540564 client = make_client (traces = {}, sync_mode = False )
541565 client .start_span ("x" ).end ()
@@ -570,7 +594,7 @@ def hung_post(*args, **kwargs):
570594 "sync_mode, hook" , [(False , "_atexit" ), (True , "_atexit_spans" )]
571595 )
572596 def test_exit_warns_about_spans_it_could_not_send (
573- self , no_timers , caplog , sync_mode , hook
597+ self , fake_timers , caplog , sync_mode , hook
574598 ):
575599 caplog .set_level ("WARNING" , logger = "posthog" )
576600 client = make_client (traces = {}, sync_mode = sync_mode )
@@ -591,18 +615,13 @@ def test_exit_warns_about_spans_it_could_not_send(
591615
592616 @pytest .mark .parametrize ("sync_mode" , [True , False ])
593617 def test_an_app_exit_hook_registered_earlier_still_gets_to_flush_spans (
594- self , no_timers , caplog , sync_mode
618+ self , fake_timers , caplog , sync_mode
595619 ):
596620 caplog .set_level ("WARNING" , logger = "posthog" )
597621 hooks = []
598622 holder = {}
599623 requests = []
600624
601- def slow_send (pipeline_client , payload ):
602- requests .append (payload )
603- time .sleep (0.2 )
604- return OK
605-
606625 with (
607626 mock .patch ("posthog.client.atexit.register" , side_effect = hooks .append ),
608627 mock .patch ("posthog.client._ATEXIT_FLUSH_TIMEOUT_SECONDS" , 0.1 ),
@@ -614,7 +633,7 @@ def slow_send(pipeline_client, payload):
614633 traces = {"max_export_batch_size" : 1 }, sync_mode = sync_mode
615634 )
616635 client .start_span ("a" ).end ()
617- client ._traces ._exporter ._send = slow_send
636+ client ._traces ._exporter ._send = slow_send ( requests )
618637 client .start_span ("b" ).end ()
619638 client .start_span ("c" ).end ()
620639 assert len (hooks ) == 2
0 commit comments