|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import locale |
5 | 6 | import logging |
6 | 7 | import re |
7 | 8 | import sys |
@@ -713,3 +714,51 @@ def test_raise_if_stderr_str_shape_exact(session: libtmux.Session) -> None: |
713 | 714 | assert str(excinfo.value) == "last-window: no last window" |
714 | 715 | assert excinfo.value.args == ("no last window",) |
715 | 716 | assert excinfo.value.subcommand == "last-window" |
| 717 | + |
| 718 | + |
| 719 | +@pytest.mark.skipif( |
| 720 | + sys.flags.utf8_mode != 0, |
| 721 | + reason="PYTHONUTF8 mode forces UTF-8, masking the locale bug", |
| 722 | +) |
| 723 | +def test_tmux_cmd_format_separator_survives_non_utf8_locale( |
| 724 | + session: Session, |
| 725 | +) -> None: |
| 726 | + """FORMAT_SEPARATOR must survive a non-UTF-8 locale round-trip through tmux_cmd. |
| 727 | +
|
| 728 | + Regression test for the encoding bug introduced in commit 1a5e69a2 |
| 729 | + (``tmux_cmd: Remove console_to_str(), use text=True``). When |
| 730 | + ``subprocess.Popen`` receives ``text=True`` without an explicit |
| 731 | + ``encoding="utf-8"``, CPython falls back to the process locale encoding. On |
| 732 | + a ``C`` locale the FORMAT_SEPARATOR character U+241E (UTF-8 bytes |
| 733 | + ``e2 90 9e``) is decoded as escaped bytes, corrupting every |
| 734 | + ``parse_output()`` call downstream. |
| 735 | +
|
| 736 | + This test guards the explicit ``encoding="utf-8"`` passed to |
| 737 | + ``subprocess.Popen`` in ``tmux_cmd.__init__``. |
| 738 | + """ |
| 739 | + from libtmux.formats import FORMAT_SEPARATOR |
| 740 | + from libtmux.neo import get_output_format, parse_output |
| 741 | + |
| 742 | + server = session.server |
| 743 | + |
| 744 | + tmux_version = str(get_version(tmux_bin=server.tmux_bin)) |
| 745 | + _fields, fmt_str = get_output_format("list-sessions", tmux_version) |
| 746 | + |
| 747 | + old_lc_ctype = locale.setlocale(locale.LC_CTYPE) |
| 748 | + try: |
| 749 | + locale.setlocale(locale.LC_CTYPE, "C") |
| 750 | + proc = server.cmd("list-sessions", f"-F{fmt_str}") |
| 751 | + finally: |
| 752 | + locale.setlocale(locale.LC_CTYPE, old_lc_ctype) |
| 753 | + assert proc.stdout |
| 754 | + |
| 755 | + line = proc.stdout[0] |
| 756 | + |
| 757 | + assert FORMAT_SEPARATOR in line, ( |
| 758 | + f"FORMAT_SEPARATOR U+241E not found in output; " |
| 759 | + f"got {line[:80]!r}... (likely decoded with wrong encoding)" |
| 760 | + ) |
| 761 | + |
| 762 | + result = parse_output(line, "list-sessions", tmux_version) |
| 763 | + assert isinstance(result, dict) |
| 764 | + assert "session_id" in result |
0 commit comments