Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
almarklein committed Nov 21, 2024
1 parent f873ad9 commit 7bfb572
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 9 deletions.
11 changes: 7 additions & 4 deletions rendercanvas/_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def get_canvases(self):
schedulers = []

for scheduler in self._schedulers:
canvas = scheduler._get_canvas()
canvas = scheduler.get_canvas()
if canvas is not None:
canvases.append(canvas)
schedulers.append(scheduler)
Expand All @@ -199,7 +199,6 @@ def _tick(self):
return

# Should we stop?

if not self._schedulers:
# Stop when there are no more canvases
self._rc_stop()
Expand Down Expand Up @@ -265,6 +264,9 @@ def run(self):
if self._is_inside_run:
raise RuntimeError("loop.run() is not reentrant.")

# Make sure that the internal timer is running, even if no canvases.
self._gui_timer.start(0.1)

# Register interrupt handler
prev_sig_handlers = self.__setup_interrupt()

Expand Down Expand Up @@ -448,7 +450,8 @@ def __init__(self, canvas, events, loop, *, mode="ondemand", min_fps=1, max_fps=
# Register this scheduler/canvas at the loop object
loop._register_scheduler(self)

def _get_canvas(self):
def get_canvas(self):
"""Get the canvas, or None if it is closed or gone."""
canvas = self._canvas_ref()
if canvas is None or canvas.is_closed():
# Pretty nice, we can send a close event, even if the canvas no longer exists
Expand Down Expand Up @@ -489,7 +492,7 @@ def _tick(self):
self._last_tick_time = time.perf_counter()

# Get canvas or stop
if (canvas := self._get_canvas()) is None:
if (canvas := self.get_canvas()) is None:
return

# Process events, handlers may request a draw
Expand Down
131 changes: 126 additions & 5 deletions tests/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,146 @@
Some tests for the base loop and asyncio loop.
"""

import time
import signal
import threading

from rendercanvas.asyncio import AsyncioLoop
from testutils import run_tests


def run_loop_briefly():
class FakeCanvas:
def __init__(self, refuse_close):
self.refuse_close = refuse_close
self.is_closed = False

def _rc_close(self):
# Called by the loop to close a canvas
if not self.refuse_close:
self.is_closed = True


class FakeScheduler:
def __init__(self, refuse_close=False):
self._canvas = FakeCanvas(refuse_close)

def get_canvas(self):
if self._canvas and not self._canvas.is_closed:
return self._canvas

def close_canvas(self):
self._canvas = None


def test_run_loop_and_close_bc_no_canvases():
# Run the loop without canvas; closes immediately
loop = AsyncioLoop()
loop.call_later(0.1, print, "hi from loop!")
loop.run()


def test_run_loop_and_close_canvases():
# After all canvases are closed, it can take one tick before its detected.

loop = AsyncioLoop()

scheduler1 = FakeScheduler()
scheduler2 = FakeScheduler()
loop._register_scheduler(scheduler1)
loop._register_scheduler(scheduler2)

loop.call_later(0.1, print, "hi from loop!")
loop.call_later(0.1, scheduler1.close_canvas)
loop.call_later(0.3, scheduler2.close_canvas)

t0 = time.time()
loop.run()
et = time.time() - t0

print(et)
assert 0.25 < et < 0.45


def test_run_loop_and_close_with_method():
# Close, then wait at most one tick to close canvases, and another to conform close.
loop = AsyncioLoop()

scheduler1 = FakeScheduler()
scheduler2 = FakeScheduler()
loop._register_scheduler(scheduler1)
loop._register_scheduler(scheduler2)

loop.call_later(0.1, print, "hi from loop!")
loop.call_later(0.3, loop.stop)

t0 = time.time()
loop.run()
et = time.time() - t0

print(et)
assert 0.25 < et < 0.55


def test_run_loop_and_interrupt():
# Interrupt, calls close, can take one tick to close canvases, and anoter to conform close.

loop = AsyncioLoop()

scheduler1 = FakeScheduler()
scheduler2 = FakeScheduler()
loop._register_scheduler(scheduler1)
loop._register_scheduler(scheduler2)

loop.call_later(0.1, print, "hi from loop!")
loop.call_later(0.2, loop.stop)

def interrupt_soon():
time.sleep(0.3)
signal.raise_signal(signal.SIGINT)

t = threading.Thread(target=interrupt_soon)
t.start()

t0 = time.time()
loop.run()
et = time.time() - t0
t.join()

print(et)
assert 0.25 < et < 0.55


def test_run_loop_and_interrupt_harder():
# In the next tick after the second interupt, it stops the loop without closing the canvases

loop = AsyncioLoop()

scheduler1 = FakeScheduler(refuse_close=True)
scheduler2 = FakeScheduler(refuse_close=True)
loop._register_scheduler(scheduler1)
loop._register_scheduler(scheduler2)

loop.call_later(0.1, print, "hi from loop!")

def interrupt_soon():
time.sleep(0.3)
signal.raise_signal(signal.SIGINT)
time.sleep(0.3)
signal.raise_signal(signal.SIGINT)

t = threading.Thread(target=interrupt_soon)
t.start()

t0 = time.time()
loop.run()
et = time.time() - t0
t.join()

def test_loop_main():
run_loop_briefly()
print(et)
assert 0.6 < et < 0.75


def test_loop_threaded():
t = threading.Thread(target=run_loop_briefly)
t = threading.Thread(target=test_run_loop_and_close_with_method)
t.start()
t.join()

Expand Down

0 comments on commit 7bfb572

Please sign in to comment.