From 9f5527bdb8151494bad16b8f6bd6565db6153755 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Sat, 8 Aug 2026 19:21:26 -0700 Subject: [PATCH] Stop _gl_context from converting test failures into skips _gl_context() yields from inside a try whose handler turns everything into a skip: try: ...create context and texture... yield int(tex_id.value), int(target) except Exception as e: # Convert any pyglet/GL creation failure into a clean skip pytest.skip(f"Could not create GL context/texture: {type(e).__name__}: {e}") @contextmanager re-raises the with-body's exception at the yield point, so `except Exception` also catches failures from the test body. AssertionError is an Exception; pytest's Skipped is a BaseException, so the replacement skip escapes cleanly and the run is reported as SKIPPED. That means neither assertion in test_cuda_gl_register_image_smoketest can fail the suite: assert name in acceptable, f"cudaGraphicsGLRegisterImage returned {name}" assert int(resource) != 0 A genuinely wrong cudaGraphicsGLRegisterImage return is reported as "Could not create GL context/texture: AssertionError: ...". The comment on the handler says "creation failure", which is what it was meant to cover. Move the context/texture creation into _create_gl_texture() so the handler wraps only that, and keep the yield outside it. Cleanup stays in the outer finally, so a partially built context is still torn down. tex_id is now initialized, which also removes an UnboundLocalError that the finally block was silently swallowing when creation failed early. Adds test_gl_context_lets_body_failures_fail, which stubs _create_gl_texture() and asserts an AssertionError raised in the with-body propagates. It needs no GL context, no display and no GPU. --- cuda_bindings/tests/test_graphics_apis.py | 91 +++++++++++++++-------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index 8b74d8d2a1d..0047e316306 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -12,15 +12,14 @@ from cuda.bindings import runtime as cudart -@contextlib.contextmanager -def _gl_context(): - """ - Yield a (tex_id, tex_target) with a current GL context. +def _create_gl_texture(): + """Create a hidden/headless GL context plus a tiny texture to register. + + Returns ``(win, tex_id, target)``. Raises on any pyglet/GL failure. Tries: 1) Windows: hidden WGL window (no EGL) 2) Linux with DISPLAY/wayland: hidden window 3) Linux headless: EGL headless if available - Skips if none work. """ pyglet = pytest.importorskip("pyglet") @@ -32,41 +31,55 @@ def _gl_context(): # Create a minimal offscreen/hidden context win = None + if not pyglet.options.get("headless"): + # Hidden window path (WGL on Windows, GLX/WLS on Linux) + from pyglet import gl + + config = gl.Config(double_buffer=False) + win = pyglet.window.Window(visible=False, config=config) + win.switch_to() + else: + # Headless EGL path; pyglet will arrange a pbuffer-like headless context + from pyglet.gl import headless # noqa: F401 + + # Make a tiny texture so we have a real GL object to register + from pyglet.gl import gl as _gl + + tex_id = _gl.GLuint(0) + target = _gl.GL_TEXTURE_2D + _gl.glGenTextures(1, ctypes.byref(tex_id)) + _gl.glBindTexture(target, tex_id.value) + _gl.glTexParameteri(target, _gl.GL_TEXTURE_MIN_FILTER, _gl.GL_NEAREST) + _gl.glTexParameteri(target, _gl.GL_TEXTURE_MAG_FILTER, _gl.GL_NEAREST) + width, height = 16, 16 + _gl.glTexImage2D(target, 0, _gl.GL_RGBA8, width, height, 0, _gl.GL_RGBA, _gl.GL_UNSIGNED_BYTE, None) + + return win, tex_id, target + + +@contextlib.contextmanager +def _gl_context(): + """Yield a (tex_id, tex_target) with a current GL context, or skip.""" + win = None + tex_id = None try: - if not pyglet.options.get("headless"): - # Hidden window path (WGL on Windows, GLX/WLS on Linux) - from pyglet import gl - - config = gl.Config(double_buffer=False) - win = pyglet.window.Window(visible=False, config=config) - win.switch_to() - else: - # Headless EGL path; pyglet will arrange a pbuffer-like headless context - from pyglet.gl import headless # noqa: F401 - - # Make a tiny texture so we have a real GL object to register - from pyglet.gl import gl as _gl - - tex_id = _gl.GLuint(0) - _gl.glGenTextures(1, ctypes.byref(tex_id)) - target = _gl.GL_TEXTURE_2D - _gl.glBindTexture(target, tex_id.value) - _gl.glTexParameteri(target, _gl.GL_TEXTURE_MIN_FILTER, _gl.GL_NEAREST) - _gl.glTexParameteri(target, _gl.GL_TEXTURE_MAG_FILTER, _gl.GL_NEAREST) - width, height = 16, 16 - _gl.glTexImage2D(target, 0, _gl.GL_RGBA8, width, height, 0, _gl.GL_RGBA, _gl.GL_UNSIGNED_BYTE, None) + try: + win, tex_id, target = _create_gl_texture() + except Exception as e: + # Convert any pyglet/GL creation failure into a clean skip. + # The yield below is deliberately NOT inside this handler: + # @contextmanager re-raises the with-body's exception at the yield, + # so catching there would turn a failing assertion in the test into + # a skip, and the test could never fail. + pytest.skip(f"Could not create GL context/texture: {type(e).__name__}: {e}") yield int(tex_id.value), int(target) - - except Exception as e: - # Convert any pyglet/GL creation failure into a clean skip - pytest.skip(f"Could not create GL context/texture: {type(e).__name__}: {e}") finally: # Best-effort cleanup try: from pyglet.gl import gl as _gl - if tex_id.value: + if tex_id is not None and tex_id.value: _gl.glDeleteTextures(1, ctypes.byref(tex_id)) except Exception: # noqa: S110 pass @@ -77,6 +90,20 @@ def _gl_context(): pass +@pytest.mark.agent_authored(model="claude-opus-5") +def test_gl_context_lets_body_failures_fail(monkeypatch): + """A failing assertion inside the with-body must not be reported as a skip.""" + + class FakeTexId: + value = 7 + + monkeypatch.setattr(sys.modules[__name__], "_create_gl_texture", lambda: (None, FakeTexId(), 0x0DE1)) + + with pytest.raises(AssertionError, match="body failure"), _gl_context() as (tex_id, tex_target): + assert (tex_id, tex_target) == (7, 0x0DE1) + raise AssertionError("body failure") + + @pytest.mark.parametrize( "flags", [