Skip to content

Commit 70e49a2

Browse files
authored
fix: ensure sys.std* has utf-8 encoding (#771)
This change: * fixes the encoding issue * setup the CI to run on windows and macos to ensure it works. * replace os.linesep by "\n" Fixes MRGFY-5769 Change-Id: I4b9928f563539fc89cb6f917b21457eea2d581e2
1 parent f1ea2a7 commit 70e49a2

File tree

9 files changed

+127
-26
lines changed

9 files changed

+127
-26
lines changed

.github/workflows/ci.yaml

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,34 @@ on:
1010
jobs:
1111
test:
1212
timeout-minutes: 5
13-
runs-on: ubuntu-24.04
13+
strategy:
14+
matrix:
15+
os: [ubuntu-24.04, windows-2025, macos-15]
16+
runs-on: ${{ matrix.os }}
1417
steps:
1518
- uses: actions/[email protected]
1619
- uses: actions/[email protected]
1720
with:
1821
python-version: ">=3.10"
19-
- run: |
22+
23+
- name: Setup
24+
shell: bash
25+
run: |
2026
# nosemgrep: generic.ci.security.use-frozen-lockfile.use-frozen-lockfile-pip
2127
pip install -r requirements-poetry.txt
22-
poetry install --sync
23-
poetry run poe linters
24-
poetry run poe test
25-
poetry build
28+
poetry sync
29+
30+
- name: Linters
31+
if: matrix.os == 'ubuntu-24.04'
32+
shell: bash
33+
run: poetry run poe linters
34+
35+
- name: Tests
36+
shell: bash
37+
env:
38+
PYTHONUTF8: "${{ matrix.os == 'windows-2025' && 1 || 0 }}"
39+
run: poetry run poe test
40+
41+
- name: Build
42+
shell: bash
43+
run: poetry build

.mergify.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
extends: .github
22
shared:
33
check_runs: &CheckRuns
4-
- check-success=test
4+
- check-success=test (ubuntu-24.04)
5+
- check-success=test (windows-2022)
6+
- check-success=test (mac-15)
57

68
queue_rules:
79
- name: dependencies

mergify_cli/ci/cli.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import sys
32

43
import click
@@ -307,13 +306,13 @@ async def _process_junit_files( # noqa: PLR0913
307306
)
308307

309308
if quarantine_final_failure_message is None:
310-
click.echo(f"{os.linesep}🎉 Verdict")
309+
click.echo("\n🎉 Verdict")
311310
click.echo(
312311
f"• Status: ✅ OK — all {nb_failing_spans} failures are quarantined (ignored for CI status)",
313312
)
314313
quarantine_exit_error_code = 0
315314
else:
316-
click.echo(f"{os.linesep}❌ Verdict")
315+
click.echo("\n❌ Verdict")
317316
click.echo(
318317
f"• Status: 🔴 FAIL — {quarantine_final_failure_message}",
319318
)

mergify_cli/ci/quarantine.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import dataclasses
2-
import os
32
import typing
43

54
import click
@@ -38,7 +37,7 @@ async def check_and_update_failing_spans(
3837
Returns the number of failing tests that are not quarantined.
3938
"""
4039

41-
click.echo(f"{os.linesep}🛡️ Quarantine")
40+
click.echo("\n🛡️ Quarantine")
4241

4342
failing_spans = [
4443
span

mergify_cli/cli.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515

1616
from __future__ import annotations
1717

18+
import os
19+
import subprocess
20+
import sys
21+
1822
import click
1923
import click.decorators
2024
import click_default_group
@@ -43,5 +47,22 @@ def cli(
4347
cli.add_command(ci_cli_mod.ci)
4448

4549

50+
def enforce_utf8_mode() -> None:
51+
if sys.flags.utf8_mode:
52+
return
53+
54+
argv = [sys.executable, "-X", "utf8"]
55+
argv.extend(subprocess._args_from_interpreter_flags()) # type: ignore[attr-defined] # noqa: SLF001
56+
argv.extend(sys.argv)
57+
58+
os.execv(argv[0], argv) # noqa: S606
59+
60+
4661
def main() -> None:
62+
# NOTE:
63+
# It's unlikely this day that a platform does not support unicode.
64+
# But on windows, the default encoding may not be an unicode one.
65+
# Let's try our best by forcing utf-8 and if it's impossible, just returns escaped character
66+
if os.name == "nt":
67+
enforce_utf8_mode()
4768
cli()

mergify_cli/tests/ci/test_upload.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ async def test_junit_upload_http_error_console(
120120
)
121121
captured = capsys.readouterr()
122122
assert (
123-
'• ❌ Error uploading spans: Failed to export batch code: 422, reason: {"detail":\n"Not enabled on this repository"}'
123+
"• ❌ Error uploading spans: Failed to export batch code: 422, reason:"
124124
in captured.out
125125
)
126+
# Open Telemetry does not render JSON the same way between Windows and Linux
127+
assert "Not enabled on this repository" in captured.out

mergify_cli/tests/test_cli.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os
2+
import pathlib
3+
import subprocess
4+
import sys
5+
import textwrap
6+
7+
8+
def test_reexec_enables_utf8_and_prints_emoji(
9+
tmp_path: pathlib.Path,
10+
) -> None:
11+
"""
12+
Run in a child process so os.execv can safely replace it.
13+
We patch mymodule.cli inside the child (no mocks), then call main().
14+
We assert:
15+
- sys.flags.utf8_mode == 1 at print time
16+
- the emoji is present in stdout
17+
"""
18+
script = tmp_path / "runner.py"
19+
script.write_text(
20+
textwrap.dedent(
21+
"""
22+
import sys
23+
import os
24+
25+
from mergify_cli import cli
26+
27+
# Make cli() print a probe + the emoji
28+
def _cli():
29+
print(f"utf8_mode={int(sys.flags.utf8_mode)}")
30+
print("✅")
31+
32+
cli.cli = _cli
33+
cli.main()
34+
""",
35+
),
36+
encoding="utf-8",
37+
)
38+
39+
# Force utf8_mode OFF initially so enforce_utf8_mode triggers on Windows.
40+
env = os.environ.copy()
41+
env["PYTHONUTF8"] = "0" # ensure not already in UTF-8 mode
42+
43+
proc = subprocess.run(
44+
[sys.executable, str(script)],
45+
check=False,
46+
env=env,
47+
capture_output=True,
48+
text=True,
49+
)
50+
51+
assert proc.returncode == 0, proc.stderr
52+
stdout = proc.stdout
53+
if os.name == "nt":
54+
# After re-exec with -X utf8, utf8_mode should be 1 at print time
55+
assert "utf8_mode=1" in stdout
56+
else:
57+
# No reexec on linux, no need the utf8 mode
58+
assert "utf8_mode=0" in stdout
59+
60+
assert "✅" in stdout

poe.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ sequence = [
1717
]
1818

1919
[tool.poe.tasks.setup]
20+
env.CI.default = ""
2021
help = "Sync poetry virtualenv"
21-
executor = { type = "simple" }
22-
cmd = "./tools/poetry-install.sh"
22+
control.expr = "${CI}.strip().lower()"
23+
24+
[[tool.poe.tasks.setup.switch]]
25+
case = ["true", "1", "y", "yes"]
26+
env = { POETRY_VIRTUALENVS_OPTIONS_NO_PIP = "true", POETRY_VIRTUALENVS_OPTIONS_NO_SETUPTOOLS = "true" }
27+
executor = { type = "simple" }
28+
cmd = "poetry sync --no-cache"
29+
30+
[[tool.poe.tasks.setup.switch]]
31+
case = ["", "false", "0", "n", "no"]
32+
executor = { type = "simple" }
33+
cmd = "poetry sync"

tools/poetry-install.sh

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)