@@ -79,35 +79,70 @@ def test_task_executes_until_stopped():
7979 assert no_more_items is True
8080
8181
82- class _MutableDelay (DelaySource ):
83- """A delay source a test can move between invocations."""
82+ class _RecordingDelay (DelaySource ):
83+ """A delay source that records each read, so a test can see when the task
84+ asks for a wait rather than only what the action saw."""
8485
85- def __init__ (self , seconds : float ):
86+ def __init__ (self , seconds : float , events : list ):
8687 self .seconds = seconds
88+ self ._events = events
8789
8890 @property
8991 def next_delay (self ) -> float :
92+ self ._events .append (('read' , self .seconds ))
9093 return self .seconds
9194
9295
9396def test_the_task_reads_the_delay_source_after_every_invocation ():
94- """A value the action decides takes effect on the next wait."""
95- reads = Queue ()
96- delays = _MutableDelay (0.01 )
97+ """One read per invocation, after it. A task that read the source once up
98+ front would show a read before the first invocation, and would never see
99+ the value the action set."""
100+ events : list = []
101+ delays = _RecordingDelay (0.01 , events )
97102
98103 def do_task ():
99- reads . put ( delays . seconds )
100- delays .seconds = 0.02 # what the next wait must use
104+ events . append ( 'invoke' )
105+ delays .seconds = 0.02
101106
102- task = RepeatingTask ("ldclient.testing.mutable -delay" , delays , 0 , do_task )
107+ task = RepeatingTask ("ldclient.testing.recording -delay" , delays , 0 , do_task )
103108 try :
104109 task .start ()
105- assert reads . get ( True , 1 ) == 0.01
106- assert reads . get ( True , 1 ) == 0.02
107- assert reads . get ( True , 1 ) == 0.02
110+ deadline = time . time () + 2
111+ while events . count ( 'invoke' ) < 3 and time . time () < deadline :
112+ time . sleep ( 0.005 )
108113 finally :
109114 task .stop ()
110115
116+ # Reads and invocations alternate, starting with an invocation, and every
117+ # read sees 0.02 -- the initial 0.01 is never read.
118+ assert events [:5 ] == ['invoke' , ('read' , 0.02 ), 'invoke' , ('read' , 0.02 ), 'invoke' ]
119+
120+
121+ def test_the_interval_starts_when_the_callback_returns ():
122+ """A slow callback must not shorten its own wait: one invocation to the
123+ next is the interval plus however long the callback took."""
124+ work = 0.15
125+ interval = 0.15
126+ starts = Queue ()
127+
128+ def do_task ():
129+ starts .put (time .time ())
130+ time .sleep (work )
131+
132+ task = RepeatingTask .at_interval ("ldclient.testing.slow-callback" , interval , 0 , do_task )
133+ try :
134+ first = None
135+ task .start ()
136+ first = starts .get (True , 2 )
137+ second = starts .get (True , 2 )
138+ finally :
139+ task .stop ()
140+
141+ # Measuring the interval from the start of the callback would give about
142+ # `interval`; measuring from its return gives interval + work. The 10%
143+ # slack is for scheduling noise, and leaves the two regimes far apart.
144+ assert (second - first ) >= (interval + work ) * 0.9
145+
111146
112147def test_whatever_the_action_returns_is_ignored ():
113148 """Guards big-segment polling, whose action returns a status object."""
0 commit comments