Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
MeshTerm died building its screen on Windows with a ctypes type error, and on a double-clicked build the traceback and the window it printed to were destroyed together — which is why this needed a log file before it could be looked at. `ctypes.windll.kernel32` is a cache. Every caller in the process receives the same object and each function on it is a shared attribute, so declaring `.argtypes` is not configuration, it is a process-wide mutation where the last writer wins. The emoji-width probe declared `GetConsoleScreenBufferInfo` as taking a pointer to its own copy of the screen-buffer struct; prompt_toolkit then called that same function object with a pointer to its own copy — same layout, different class — and ctypes refused it. Nothing here reaches for `ctypes.windll` any more. Five call sites take private handles from `core/win32dll.py`, built by constructing `WinDLL` directly so each one gets its own function cache and a declaration made for one purpose cannot be seen by anything else. Each call returns a fresh handle, because two parts of MeshTerm disagreeing about a signature is the same bug in miniature. The regression test was checked against the defect rather than assumed to cover it: with the old line restored it fails, and with the fix it passes. A test that passes either way would only have recorded that somebody thought about the problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
3.2 KiB
Python
82 lines
3.2 KiB
Python
"""Tests for the private Windows DLL handles.
|
|
|
|
These pin a crash that only ever appeared in a frozen build on Windows, which is the
|
|
worst place for a bug to live: no traceback survived, because the window it was printed
|
|
in was destroyed by the same exit that printed it.
|
|
|
|
``ctypes.windll.kernel32`` is a cache shared by the whole process. MeshTerm's emoji-width
|
|
probe declared ``GetConsoleScreenBufferInfo`` as taking a pointer to its own copy of the
|
|
screen-buffer struct; prompt_toolkit then called that same function object with a pointer
|
|
to *its* copy — same layout, different class — and ctypes refused the call while the app
|
|
was building its screen.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from meshterm.core import win32dll
|
|
|
|
pytestmark = pytest.mark.skipif(sys.platform != "win32", reason="Windows libraries")
|
|
|
|
|
|
def test_each_handle_is_its_own() -> None:
|
|
"""Two callers get two objects, so neither can see the other's declarations."""
|
|
assert win32dll.kernel32() is not win32dll.kernel32()
|
|
assert win32dll.user32() is not win32dll.user32()
|
|
|
|
|
|
def test_the_shared_windll_is_the_thing_being_avoided() -> None:
|
|
"""The premise: ``ctypes.windll`` hands the same function object to every caller.
|
|
|
|
If this ever stops being true the module is unnecessary — but until then it is the
|
|
reason it exists, so it is worth asserting rather than assuming.
|
|
"""
|
|
first = ctypes.windll.kernel32.GetConsoleScreenBufferInfo
|
|
second = ctypes.windll.kernel32.GetConsoleScreenBufferInfo
|
|
assert first is second
|
|
|
|
|
|
def test_declaring_a_signature_does_not_reach_the_shared_one() -> None:
|
|
"""The regression itself: our declarations must not be visible to anyone else.
|
|
|
|
Before this module existed, the assignment below rewrote the signature that
|
|
prompt_toolkit's console output later called through, and the application died on
|
|
startup with ``expected LP__CSBI instance instead of pointer to
|
|
CONSOLE_SCREEN_BUFFER_INFO``.
|
|
"""
|
|
|
|
class _MineAlone(ctypes.Structure):
|
|
_fields_ = [("whatever", ctypes.c_int)]
|
|
|
|
shared = ctypes.windll.kernel32.GetConsoleScreenBufferInfo
|
|
before = shared.argtypes
|
|
|
|
private = win32dll.kernel32()
|
|
private.GetConsoleScreenBufferInfo.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.POINTER(_MineAlone),
|
|
]
|
|
|
|
assert ctypes.windll.kernel32.GetConsoleScreenBufferInfo.argtypes == before
|
|
assert private.GetConsoleScreenBufferInfo is not shared
|
|
|
|
|
|
def test_the_probe_leaves_the_shared_signature_alone() -> None:
|
|
"""End to end: running the emoji probe must not disturb the process-wide function.
|
|
|
|
The narrow assertion above can pass while the real call site still reaches for
|
|
``ctypes.windll``, so this exercises the code that actually crashed.
|
|
"""
|
|
from meshterm.ui.tui import emoji_width
|
|
|
|
shared = ctypes.windll.kernel32.GetConsoleScreenBufferInfo
|
|
before = shared.argtypes
|
|
# No console under pytest, so this returns None rather than measuring. That is fine:
|
|
# the declarations happen before the first call either way, which is what matters.
|
|
emoji_width._win_probe_width("x") # noqa: SLF001 - the point is this private path
|
|
assert ctypes.windll.kernel32.GetConsoleScreenBufferInfo.argtypes == before
|