Skip to content
88 changes: 71 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ concurrency:

jobs:

# Tests for code integrity and code formatting
lint:
name: Linting
runs-on: ubuntu-latest
Expand All @@ -38,6 +39,7 @@ jobs:
run: |
ruff format --check .

# Test that make sure the docs build without errors or warnings
docs:
name: Docs
runs-on: ubuntu-latest
Expand All @@ -58,24 +60,21 @@ jobs:
cd docs
make html SPHINXOPTS="-W --keep-going"

tests:
name: ${{ matrix.name }}
# Test majority of code for a variety of Python versions
test-pythons:
name: 'Test Python ${{ matrix.pyversion }} on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Test py310
os: ubuntu-latest
- os: ubuntu-latest
pyversion: '3.10'
- name: Test py311
os: ubuntu-latest
- os: ubuntu-latest
pyversion: '3.11'
- name: Test py312
os: ubuntu-latest
- os: ubuntu-latest
pyversion: '3.12'
- name: Test py313
os: ubuntu-latest
- os: ubuntu-latest
pyversion: '3.13'
steps:
- uses: actions/checkout@v4
Expand All @@ -92,15 +91,15 @@ jobs:
run: |
pytest -v tests

# Test that wgpu is indeed optional
test-without-wgpu:
name: ${{ matrix.name }} without wgpu
name: 'Test Python ${{ matrix.pyversion }} without wgpu'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Test py313
os: ubuntu-latest
- os: ubuntu-latest
pyversion: '3.13'
steps:
- uses: actions/checkout@v4
Expand All @@ -125,8 +124,60 @@ jobs:
"
pytest -v tests

# Test for the few GUI frameworks that we support.
test-gui-frameworks:
name: 'Test gui ${{ matrix.guilib }}'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- guilib: 'glfw'
backendname: 'glfw'
loopname: 'AsyncioLoop'
- guilib: 'PySide6'
backendname: 'pyside6'
loopname: 'PySide6Loop'
- guilib: 'PyQt6'
backendname: 'pyqt6'
loopname: 'PyQt6Loop'
- guilib: 'PyQt5'
backendname: 'pyqt5'
loopname: 'PyQt5Loop'
# - guilib: 'wxPython' CI tries to build wx from scratch
# backendname: 'wx'
# loopname: 'WxLoop'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install xvfb and Vulkan drivers
run: |
sudo apt update -y -qq
sudo apt install -y xvfb
sudo apt install --no-install-recommends -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- name: Install package and dev dependencies
run: |
python -m pip install --upgrade pip
pip install .[tests]
pip install ${{ matrix.guilib }}
rm -r rendercanvas
- name: Test import
run: |
xvfb-run python -c 'import rendercanvas.${{ matrix.backendname }}'
# - name: Test loop - Unfortunately, all Qt libs abort on the first test.
# run: |
# xvfb-run pytest -v tests/test_loop.py -k ${{ matrix.loopname }}
- name: Test GUI canvas
if: matrix.backendname == 'glfw'
run: |
xvfb-run pytest -v tests/test_gui_${{ matrix.backendname }}.py

# Test that the examples run without error, and screenshots match
test-examples:
name: Test examples ${{ matrix.pyversion }}
name: Test examples on ${{ matrix.pyversion }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -144,8 +195,8 @@ jobs:
python-version: 3.12
- name: Install llvmpipe and lavapipe for offscreen canvas
run: |
sudo apt-get update -y -qq
sudo apt-get install --no-install-recommends -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
sudo apt update -y -qq
sudo apt install --no-install-recommends -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- name: Install dev dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -164,6 +215,7 @@ jobs:
name: screenshots{{ matrix.pyversion }}
path: examples/screenshots

# Test that rendercanvas works with pyinstaller
test-pyinstaller:
name: Test pyinstaller
runs-on: ubuntu-latest
Expand All @@ -183,6 +235,7 @@ jobs:
pushd $HOME
pytest -v --pyargs rendercanvas.__pyinstaller

# Build package
build-release:
name: Build release artifacts
runs-on: ubuntu-latest
Expand Down Expand Up @@ -224,10 +277,11 @@ jobs:
path: dist
name: dist

# Publish to Github and Pypi
publish:
name: Publish release to Github and Pypi
runs-on: ubuntu-latest
needs: [tests, build-release]
needs: [test-pythons, build-release]
if: success() && startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
Expand Down
2 changes: 1 addition & 1 deletion docs/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ somewhat when the render area is large.
Support for wx
--------------

RenderCanvas has support for wxPython.
RenderCanvas has support for wxPython. However, because of wx's specific behavior, this backend is less well tested than the other backends.
For a toplevel widget, the ``rendercanvas.wx.RenderCanvas`` class can be imported. If you want to
embed the canvas as a subwidget, use ``rendercanvas.wx.RenderWidget`` instead.

Expand Down
2 changes: 1 addition & 1 deletion rendercanvas/qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def _rc_init(self):
self._app = QtWidgets.QApplication([])
# We do detect when the canvas-widget is closed, and also when *our* toplevel wrapper is closed,
# but when embedded in an application, it seems hard/impossible to detect the canvas being closed
# when the app closes. So we explicitly detect that instead.
# when the app closes. So we explicitly detect the app-closing instead.
# Note that we should not use app.setQuitOnLastWindowClosed(False), because we (may) rely on the
# application's closing mechanic.
self._app.aboutToQuit.connect(lambda: self.stop(force=True))
Expand Down
21 changes: 16 additions & 5 deletions tests/test_glfw.py → tests/test_gui_glfw.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
like the canvas context and surface texture.
"""

import os
import time
import weakref
import asyncio
Expand All @@ -24,10 +25,13 @@ def setup_module():


def teardown_module():
import glfw
from rendercanvas.glfw import poll_glfw_briefly

poll_glfw_briefly()
pass # Do not glfw.terminate() because other tests may still need glfw

# Terminate; otherwise it gets in the way of tests for the Qt loop.
glfw.terminate()


def test_is_canvas_base():
Expand Down Expand Up @@ -123,6 +127,10 @@ def run_briefly():
device = wgpu.gpu.request_adapter_sync().request_device_sync()
draw_frame1 = _get_draw_function(device, canvas)

allowed_frames = (1,)
if os.getenv("CI"):
allowed_frames = (1, 2, 3)

frame_counter = 0

def draw_frame2():
Expand All @@ -135,22 +143,25 @@ def draw_frame2():
run_briefly()
# There should have been exactly one draw now
# This assumes ondemand scheduling mode
assert frame_counter == 1
assert frame_counter in allowed_frames
frame_counter = 0

# Ask for a lot of draws
for i in range(5):
canvas.request_draw()
# Process evens for a while
run_briefly()
# We should have had just one draw
assert frame_counter == 2
assert frame_counter in allowed_frames
frame_counter = 0

# Change the canvas size
canvas.set_logical_size(300, 200)
canvas.set_logical_size(400, 300)
# We should have had just one draw
# We should have had just one draw, but sometimes (more so on CI) we can have more
run_briefly()
assert frame_counter == 3
assert frame_counter in allowed_frames
frame_counter = 0

# Stopping
assert not loop_task.done()
Expand Down
Loading