diff --git a/apps/studio/tests/test_scene3d_gl_smoke.py b/apps/studio/tests/test_scene3d_gl_smoke.py index bc32e91..fad0bc4 100644 --- a/apps/studio/tests/test_scene3d_gl_smoke.py +++ b/apps/studio/tests/test_scene3d_gl_smoke.py @@ -298,6 +298,69 @@ def test_release_is_idempotent_and_a_new_context_gets_the_scene_back(gl_widget, assert _drawn(_render(widget, qt_app)).size, "the scene is not on screen again" +def test_a_software_renderer_is_refused_with_its_capabilities(gl_widget, monkeypatch): + """A board must not be signed off on llvmpipe, and the report has to say so.""" + from orisys_studio.scene3d import gl_backend + + widget, _, _ = gl_widget + monkeypatch.setattr(gl_backend, "is_software_renderer", lambda vendor, renderer: True) + with pytest.raises(gl_backend.ThreeDUnsupported) as excinfo: + gl_backend.GLDispatch().bind_current_context(widget.context()) + capabilities = excinfo.value.capabilities + assert capabilities.software_renderer is True + assert any(item.code == "gl_unsupported" for item in capabilities.diagnostics) + assert "software" in excinfo.value.diagnostic.message.lower() + + +def test_a_broken_shader_takes_the_view_down_not_the_application(gl_widget, qt_app, monkeypatch): + """Shader compilation failure is a diagnostic and an empty view, nothing more.""" + from orisys_studio.scene3d import gl_backend + from orisys_studio.scene3d.camera import OrbitCamera + from orisys_studio.scene3d.widget import Sensor3DWidget + + widget, _, _ = gl_widget + capabilities = widget.capabilities() + assert capabilities is not None + + # The renderer refuses to come up, and says why. + def broken_source(profile, program): + return "#version 330 core\nvoid main() { this is not glsl }\n", "#version 330 core\n" + + monkeypatch.setattr(gl_backend, "shader_source", broken_source) + renderer = gl_backend.GLRenderer() + widget.makeCurrent() + try: + with pytest.raises(Exception) as excinfo: + renderer.initialize(capabilities) + finally: + widget.doneCurrent() + item = getattr(excinfo.value, "diagnostic", None) + assert item is not None and item.code == "shader_compile_failed", excinfo.value + assert not renderer.is_initialized, "a failed initialize must not claim to be up" + + # The widget turns the same failure into a state: a diagnostic, an empty view, + # and no exception on the way out. + monkeypatch.undo() + + class FailingRenderer(gl_backend.GLRenderer): + def initialize(self, capabilities): + raise gl_backend.error("shader_compile_failed", "forced for this test") + + failing = Sensor3DWidget(FailingRenderer(), OrbitCamera()) + seen: list = [] + failing.diagnosticRaised.connect(seen.append) + failing.resize(120, 90) + failing.show() + failing.repaint() + qt_app.processEvents() + failing.repaint() # a second paint of a failed view must be a no-op + qt_app.processEvents() + assert failing.unavailable + assert seen and seen[0].code == "shader_compile_failed" + failing.close() + failing.deleteLater() + + def test_the_probe_reports_this_machine_truthfully(): """The probe's contract: exit 0 only for a GPU-driven context.""" import importlib.util