def test_schedule_later(self): control_ticks = 0 deferred_ticks = 0 deferred_ticked = False loop = Loop(debug=False) async def deferred_task(): nonlocal deferred_ticked, deferred_ticks deferred_ticks = deferred_ticks + 1 deferred_ticked = True async def control_ticker(): nonlocal control_ticks control_ticks = control_ticks + 1 loop.schedule(100, control_ticker) loop.schedule_later(10, deferred_task) while True: loop._step() if deferred_ticked: break self.assertEqual(deferred_ticks, 1) self.assertAlmostEqual(control_ticks, 10, delta=2)
def test_add_task(self): loop = Loop() ran = False async def foo(): nonlocal ran ran = True loop.add_task(foo()) loop._step() self.assertTrue(ran)
def test_delay(self): loop = Loop() complete = False async def foo(): nonlocal complete await loop.delay(0.1) complete = True loop.add_task(foo()) start = time.monotonic() while not complete and time.monotonic() - start < 1: loop._step() self.assertTrue(complete)
def test_run_later(self): loop = Loop() count = 0 async def run_later(): nonlocal count while True: count = count + 1 await _yield_once() # For testing loop.run_later(seconds_to_delay=0.1, awaitable_task=run_later()) self.assertEqual( 0, count, 'count should not increment upon coroutine instantiation') loop._step() self.assertEqual( 0, count, 'count should not increment before waiting long enough') time.sleep( 0.1 ) # Make sure enough time has passed for step to pick up the task loop._step() self.assertEqual(1, count, 'count should increment once per step')
def test_schedule_rate(self): # Checks a bunch of scheduled tasks to make sure they hit their target fixed rate schedule. # Pathological scheduling sees these tasks barge in front of others all the time. Many run # at a fairly high frequency. loop = Loop(debug=False) duration = 1 # Seconds to run the scheduler. (try with 10s if you suspect scheduler drift) tasks = 110 # How many tasks (higher indexes count faster. 110th index goes 100hz) def timer(index): return Duration.of_milliseconds(120 - index) counters = [] for i in range(tasks): async def f(_i): counters[_i] += 1 counters.append(0) loop.schedule(timer(i), f, i) start = time.perf_counter() end = start while end - start < duration: loop._step() end = time.perf_counter() print() expected_tps = 0 actual_tps = 0 # Assert that all the tasks hit their scheduled count, at least within +-5 iterations. for i in range(len(counters)): self.assertAlmostEqual(duration * timer(i).as_frequency(), counters[i], delta=2) expected_tps += timer(i).as_frequency() actual_tps += counters[i] actual_tps /= duration print('expected tps:', expected_tps, 'actual:', actual_tps)
def test_acquire(self): loop = Loop() # Synchronize access to an SPI allowing other tasks to work while waiting spi_bus = 'board.SPI' managed_spi = ManagedSpi(spi_bus, loop=loop) # Configure 3 pins for selecting different chip selects on the shared SPI bus sdcard_spi = managed_spi.cs_handle(FakeDigitalIO('D1')) screen_spi = managed_spi.cs_handle(FakeDigitalIO('D2')) sensor_spi = managed_spi.cs_handle(FakeDigitalIO('D3')) did_read = did_screen = did_sensor = False # Define 3 full while True app loops dependent on 1 shared SPI async def read_sdcard(): nonlocal did_read while True: async with sdcard_spi as spi: # do_something_with(spi) for _ in range(2): self.assertTrue(sdcard_spi.active) self.assertFalse(screen_spi.active) self.assertFalse(sensor_spi.active) await YieldOne() did_read = True await YieldOne() async def update_screen(): nonlocal did_screen while True: # await do_other_work async with screen_spi as spi: for _ in range(2): self.assertFalse(sdcard_spi.active) self.assertTrue(screen_spi.active) self.assertFalse(sensor_spi.active) await YieldOne() did_screen = True await YieldOne() async def read_sensor(): nonlocal did_sensor while True: async with sensor_spi as spi: for _ in range(2): self.assertFalse(sdcard_spi.active) self.assertFalse(screen_spi.active) self.assertTrue(sensor_spi.active) await YieldOne() did_sensor = True await YieldOne() # Add the top level application coroutines loop.add_task(read_sdcard()) loop.add_task(read_sensor()) loop.add_task(update_screen()) # would just use asynccp.add_task() and asynccp.run() but for test let's manually step it through # loop.run() # They didn't run yet self.assertFalse(sdcard_spi.active) self.assertFalse(screen_spi.active) self.assertFalse(sensor_spi.active) loop._step() self.assertTrue(sdcard_spi.active) # sdcard was the first to queue up loop._step() loop._step() loop._step() self.assertTrue(sensor_spi.active) # sensor was the second to queue up loop._step() loop._step() loop._step() self.assertTrue(screen_spi.active) # screen was the last to queue up loop._step() loop._step() self.assertTrue(did_sensor) self.assertTrue(did_screen) self.assertTrue(did_read)
def test_reschedule(self): now = 0 def nanos(): nonlocal now return now set_time_provider(nanos) try: loop = Loop() run_count = 0 async def foo(): nonlocal run_count run_count += 1 scheduled_task = loop.schedule(1000000000, foo) now = 2 self.assertEqual(0, run_count, 'did not run before step') loop._step() self.assertEqual(1, run_count, 'ran only once during step') now = 4 scheduled_task.stop() loop._step() self.assertEqual(1, run_count, 'does not run again while stopped') now = 6 scheduled_task.start() loop._step() self.assertEqual(2, run_count, 'runs again after restarting') now = 7 scheduled_task.change_rate(1000000000 / 10) loop._step() self.assertEqual( 3, run_count, 'this run was already scheduled. the next one will be at 10-step' ) now = 16 loop._step() self.assertEqual(3, run_count, 'expect to run after 10 has passed') now = 17 loop._step() self.assertEqual(4, run_count, 'new schedule rate ran') finally: set_time_provider(time.monotonic_ns)