Strip ANSI + accept `_create(...)` in devx tests

Two version-compat fixes for the `devx` debugger
test-suite, both about matching upstream output that
got more verbose w/ recent lib releases.

ANSI stripping (`tests/devx/conftest.py`),
- Add `ansi_strip(text)` helper + `_ansi_re` pattern
  (regex per https://stackoverflow.com/a/14693789).
- Apply inside `in_prompt_msg()` + `assert_before()` so
  substring matches against REPL/traceback output stay
  robust to color leakage.
- Motivated by py3.13's colored tracebacks +
  `pdbp`/pygments highlighting leaking ANSI even when
  `PYTHON_COLORS=0` is set in the `spawn` fixture (not
  every renderer in the spawned subproc honors it).
- Replaces the longstanding inline TODO that linked
  the SO answer w/o impl'ing.

trio 0.30+ `Cancelled._create(` match (`test_debugger`),
- In `test_shield_pause` swap the two
  `"raise Cancelled._create()"` assertion patterns →
  `"raise Cancelled._create("` (open-paren form, no
  closing).
- trio >=0.30 raises a multi-line
  `raise Cancelled._create(source=.., reason=..,
  source_task=..)` w/ cancel-reason metadata, so the
  legacy bare-`()` form no longer matches. Inline
  comment documents the trio-version pivot.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
custom_log_levels
Gud Boi 2026-06-02 00:41:06 -04:00
parent 3a45dbd503
commit 3854cf5ecb
2 changed files with 38 additions and 7 deletions

View File

@ -5,6 +5,7 @@
from __future__ import annotations from __future__ import annotations
import platform import platform
import os import os
import re
import signal import signal
import time import time
from typing import ( from typing import (
@ -294,6 +295,26 @@ def expect(
PROMPT = r"\(Pdb\+\)" PROMPT = r"\(Pdb\+\)"
# Strip terminal color / ANSI-VT100 escape sequences so
# substring matching against REPL + traceback output stays
# robust to color leakage — Python 3.13's colored tracebacks,
# `pdbp`'s pygments highlighting, etc. — even when
# `PYTHON_COLORS=0` (set in the `spawn` fixture) isn't honored
# by every renderer in the spawned subproc.
# Regex per https://stackoverflow.com/a/14693789
_ansi_re: re.Pattern = re.compile(
r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])'
)
def ansi_strip(text: str) -> str:
'''
Remove ANSI/VT100 escape sequences from `text`.
'''
return _ansi_re.sub('', text)
def in_prompt_msg( def in_prompt_msg(
child: SpawnBase, child: SpawnBase,
parts: list[str], parts: list[str],
@ -313,7 +334,7 @@ def in_prompt_msg(
''' '''
__tracebackhide__: bool = False __tracebackhide__: bool = False
before: str = str(child.before.decode()) before: str = ansi_strip(str(child.before.decode()))
for part in parts: for part in parts:
if part not in before: if part not in before:
if pause_on_false: if pause_on_false:
@ -333,9 +354,9 @@ def in_prompt_msg(
return True return True
# TODO: todo support terminal color-chars stripping so we can match # NB: color-char stripping (so we can match against call-stack
# against call stack frame output from the the 'll' command the like! # frame output from the `ll` command and the like) is handled by
# -[ ] SO answer for stipping ANSI codes: https://stackoverflow.com/a/14693789 # `ansi_strip()` applied inside `in_prompt_msg()` + below.
def assert_before( def assert_before(
child: SpawnBase, child: SpawnBase,
patts: list[str], patts: list[str],
@ -356,7 +377,7 @@ def assert_before(
err_on_false=True, err_on_false=True,
**kwargs **kwargs
) )
before: str = str(child.before.decode()) before: str = ansi_strip(str(child.before.decode()))
return before return before

View File

@ -1186,7 +1186,12 @@ def test_shield_pause(
"('cancelled_before_pause'", # actor name "('cancelled_before_pause'", # actor name
_repl_fail_msg, _repl_fail_msg,
"trio.Cancelled", "trio.Cancelled",
"raise Cancelled._create()", # trio >=0.30 raises via a multi-line
# `raise Cancelled._create(source=.., reason=..,
# source_task=..)` (cancel-reason metadata), so
# match the open-paren form only, NOT the legacy
# bare `()`.
"raise Cancelled._create(",
# we should be handling a taskc inside # we should be handling a taskc inside
# the first `.port_mortem()` sin-shield! # the first `.port_mortem()` sin-shield!
@ -1204,7 +1209,12 @@ def test_shield_pause(
"('root'", # actor name "('root'", # actor name
_repl_fail_msg, _repl_fail_msg,
"trio.Cancelled", "trio.Cancelled",
"raise Cancelled._create()", # trio >=0.30 raises via a multi-line
# `raise Cancelled._create(source=.., reason=..,
# source_task=..)` (cancel-reason metadata), so
# match the open-paren form only, NOT the legacy
# bare `()`.
"raise Cancelled._create(",
# handling a taskc inside the first unshielded # handling a taskc inside the first unshielded
# `.port_mortem()`. # `.port_mortem()`.