Skip to content

fix(tests): pass guest_system_font, exec_arguments and unwrap_self under a non-ASCII TEMP on Windows - #12993

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
JJenkx:windows/tests-non-ascii-temp
Oct 30, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
JJenkx:windows/tests-non-ascii-temp

Conversation

@JJenkx

@JJenkx JJenkx commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What

When %TEMP% contains a non-ASCII character, as it does for any Windows profile like C:\Users\José, the full suite on Windows fails 7 tests that pass otherwise. A path with spaces alone is fine. This PR fixes the three that are pure test bugs. Each of them mixes the two narrow encodings on MinGW: std::filesystem::path::string() and path(const char*) are UTF-8, while fopen, getenv and console tools use the ANSI or OEM code page.

  • guest_system_font (GuestSystemFont.cpp:122) opened its stderr capture file with std::fopen(path.string().c_str(), "wb"). It now uses _wfopen(path.c_str(), L"wb") on Windows and fopen(path.c_str()) elsewhere.
  • exec_arguments: the GuestExecHost.cpp fixture built the second program's environment with std::getenv, which returns ANSI bytes there. execve then rejects that environment with exec: argument is not valid UTF-8, which is correct, since guest strings are UTF-8. A small Utf8Environment helper now reads LD_LIBRARY_PATH and ANYPS5_LOADEXEC_TEST_ROOT through GetEnvironmentVariableW and converts them to UTF-8. POSIX keeps getenv. Root() already read the root this way.
  • unwrap_self: test_new_output_inherits_the_destination_directory_acl ran icacls.exe with text=True, which Python 3.15's UTF-8 mode decodes as UTF-8. icacls writes the OEM code page, so the reader thread raised UnicodeDecodeError, stdout came back None, and the assertion message itself raised TypeError. Both calls now pass encoding="oem", errors="replace". The test only runs on Windows, where that codec exists.

Probe with the same toolchain (TEMP=C:\x\tëmpünï):

path::string bytes: 74 c3 ab          (UTF-8)
fopen(path.string()) -> FAILED
ofstream(path) -> opened
getenv(TEMP) bytes: 74 eb 6d          (code page 1252)
fopen(getenv(TEMP)) -> opened
path(getenv(TEMP)) -> filesystem_error: Cannot convert character sequence: Illegal byte sequence

Not in this PR: video_out_telemetry and the three agc_worker_sampler_* tests also fail under such a TEMP. Their tests pass a UTF-8 path.string() into production APIs that take a narrow path and open it with the ANSI fopen/ofstream. In production those paths come from getenv, also ANSI, so production is consistent there. A fix means changing those APIs to take paths (and reading the variables with the wide API), so it belongs in a separate PR.

No production code changes here. The first exec from a title directory under such a path already works, since the runtime uses the wide APIs (GetModuleFileNameW, temp_directory_path).

Tested

Windows 11 Enterprise 10.0.22621 (code page 1252), Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Python 3.15, Ninja, Release, BUILD_TESTING=ON, no Vulkan driver visible.

  • Full suite on main bb04a4f1ea with TEMP=TMP=C:\x\tëmp dir ünï: 11 failures, against 3 with the normal TEMP (ngs2_pitchshift, guest_filesystem, guest_freebsd_clocks, all with open fixes). The other 8 are the 7 tests named above and guest_job_manager_worker_count, which fails under parallel load (fix(libSceJobManager): spin idle workers without taking the manager lock #12085). With TEMP=C:\x\temp with space all 7 pass, and with C:\x\tëmpünï all 7 fail.
  • With this PR (on main af4e595327), unwrap_self, guest_system_font and exec_arguments pass 3 of 3 (ctest --repeat until-fail:3) both with TEMP=C:\x\tëmpünï and with the normal TEMP.
  • python tools/check_conventions.py --base origin/main: all checks passed.
  • Not run here: Linux and macOS. The changed lines there are fopen(path.c_str()) in place of fopen(path.string().c_str()), which gives the same bytes on POSIX, and the getenv branch of Utf8Environment.

Coverage clarification — 2026-10-30

The two trophy tests are registered under BUILD_TESTING, including Windows (guest_np_trophy_data and guest_np_trophy_stats); the build definitions provide their Windows DLL-search paths. There is a distinction in the cited fopen calls: trophy data and trophy stats create a root with temp_directory_path(), then call std::filesystem::current_path(root). Every call to their Write helper passes an ASCII relative name, such as app0/sce_sys/param.json. The non-ASCII TEMP prefix therefore is not converted to narrow bytes by those fopen(path.string()) calls. That is a source-level explanation, not proof of a historical pass.

The same distinction applies to GuestFilesystem.cpp:924: sized is formed from the ASCII relative anyps5-filesystem-test-... root at line571. Separate earlier Windows helpers do use TEMP, and a failure there can prevent reaching line924.

I could not recover the original Windows per-test build/CTest manifest or verbose/JUnit logs from the retained local evidence, so I cannot establish whether those exact trophy tests were built, executed or passed in the reported non-ASCII-TEMP full run. Their absence from the seven-failure list is not enough to establish that. The retained exact-head Linux run selected only guest_system_font, exec_arguments and unwrap_self (three passes each); it adds no coverage of these trophy/filesystem cases or Windows TEMP.

The explicit follow-up is to recover the original Windows per-test receipts, or qualify these cases in a future authorized Windows session with effective TEMP/TMP/CWD and exact build/test manifests retained. Windows remains paused; no new Windows execution or source change was made for this audit. AI-assisted: yes (Codex).

Earlier-head independent Windows validation, reported by bunbeans2-arch for topic ee5055268b04dd00c8c236289dffd56744cdd96f against main f7f03daf52: Windows 11 Pro 22631, i9-9900K, MSYS2 UCRT64 GCC 16.1, Release. guest_system_font and exec_arguments pass 3/3 for each of the é and outside-code-page Ж temporary paths, while main fails 3/3; normal-profile controls pass. unwrap_self retains 26/78 subtest failures on both arms due to locale decoding of UTF-8 paths on Python 3.13; this is not a full-suite pass or a fix for those baseline failures. This is the contributor's run, not a new local Windows execution.

Additional correction at 2365b1010c4577e2a5d2594aad8769e98b434be9, a normal fast-forward from ee5055268b04dd00c8c236289dffd56744cdd96f: nine test subprocesses now decode the CLI's UTF-8 output explicitly. This addresses the remaining unwrap_self failures in the earlier-head Windows report above. The existing C++ path fixes and byte captures of Windows icacls output are unchanged.

Linux Python synthetic verification of the exact parent and change, full 78-test suite:

Parent decoder / temporary path Parent Change
Native default / ASCII passes passes
CP1252 model / é 26 failed subtest assertions passes
CP1252 model / Ж 26 failed subtest assertions passes

Every accepted run has zero errors and the same two pre-existing platform skips (Windows ACL inheritance and supplementary-group inheritance). Each successful change run executes/passes 76 applicable tests. CP1252 is modeled only at the parent subprocess decoder on Linux; this is not a native Windows run. Conventions and whitespace checks pass. Initial partial-tree control staging omitted reference imports; the accepted matrix uses the complete pinned parent tree.

Source pins, nine-line patch, reproduction script and full six-arm logs/results, SHA-256 7d4bf9df1fa9bd588e222b304f36edd7c5a91b7d66e163abf4b9872317f82a32 (upload/download verified). All inputs are synthetic; no game files or binaries included. AI-assisted: yes (Codex).

Qualification update — 2026-10-30, repeat 2

Fresh actual Windows qualification at exact current public 2365b1010c4577e2a5d2594aad8769e98b434be9: Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, Python 3.15.1, native Windows ACP1252 and OEM437. Ran the unchanged complete 78-test unwrap_self suite separately with ASCII, é and Ж TEMP/TMP paths, PYTHONUTF8=0 and -X utf8=0. Effective locale and stdout encoding are cp1252, UTF8 mode is 0, and PYTHONIOENCODING is unset; raw metadata records CWD and all three TEMP paths.

Each context passes all 75 applicable cases with 3 existing Windows POSIX group/permission skips, zero failures/errors. The ACL/icacls case executes and passes in each context; its OEM decoding behavior is unchanged. This directly qualifies the published UTF8 subprocess-decoding fix on Windows and supersedes the earlier 26-error contributor result tied to its previous head for this fixture. No source/test changes, emulation of Windows locale, game/SDK/GPU or performance claim.

The initial incomplete source-archive setup errors are retained separately and are not counted as source failures or passes; the final archive includes and preflights the actual relinker fixture import chain. Complete successful harness: Windows FIFO request 177453aaad2e4943a7cf8e92ae271074. Evidence: https://github.com/JJenkx/AnyPS5/releases/download/linux-validation-20261023/owned-current-windows-20261030-repeat2.tar.gz, SHA256 e423c97373758bf59239e02c138b74f35f1a45488793bc92878a4c836f94a584. AI-assisted: yes (Codex).

AI-assisted: yes (Codex).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code and Codex)

🤖 Generated with Claude Code

…der a non-ASCII TEMP on Windows

With %TEMP% outside ASCII (a profile like C:\Users\José), three tests
fail on Windows because they mix the two narrow encodings: MinGW's
std::filesystem::path::string() is UTF-8, while fopen, getenv and console
tools use the ANSI or OEM code page.

- guest_system_font opened its stderr capture file with
  fopen(path.string()); it now uses _wfopen on Windows.
- The exec_arguments fixture built the second program's environment from
  getenv, which returns ANSI bytes that execve rejects as invalid UTF-8;
  it now reads LD_LIBRARY_PATH and ANYPS5_LOADEXEC_TEST_ROOT as UTF-8
  through GetEnvironmentVariableW.
- unwrap_self decoded icacls output as UTF-8 (Python 3.15 UTF-8 mode);
  icacls writes the OEM code page, so it is decoded as "oem".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Linux run at this PR's exact head ee5055268b (ee5055268b04dd00c8c236289dffd56744cdd96f), not merged onto anything else; its base on main is af4e595327.

Arch Linux, kernel 7.2.9, Ryzen 9 7950X, g++ (GCC) 16.2.1 20260810, glibc 2.44, x86-64. Release, BUILD_TESTING=ON, Ninja -j4, private worktree.

  • ctest -R '^(guest_system_font|exec_arguments|unwrap_self)$': passes in each of 3 runs (9 passes).

The fixes target a non-ASCII TEMP on Windows; this run shows the three tests still pass on Linux at this head. The non-ASCII Windows case is not exercised here.

@rooklz rooklz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this on macOS at ee5055268b. I didn't run the real Windows suite.

  • The base af4e595327 is in main 951f374c87. git merge-tree against main is clean, git diff --check is clean, and check_conventions.py passes. pr_overlap.py found 0 of 1447 open PRs sharing files or exports.
  • x86_64-w64-mingw32-g++ and clang++ -target x86_64-apple-macos with -fsyntax-only -std=gnu++20 report 0 errors on both changed C++ files.
  • I built a static mingw probe and ran it under wine, using a directory temp_directory_path() / L"tëmpünï". fopen(path.string().c_str(), "wb") fails and _wfopen(path.c_str(), L"wb") opens. I also copied Utf8Environment in unchanged and gave it a value of C:\x\tëmpünï\😀. It returns 43 3a 5c 78 5c 74 c3 ab 6d 70 c3 bc 6e c3 af 5c f0 9f 98 80, which is correct UTF-8 including the non-BMP surrogate pair. An unset variable returns false.
  • tools/tests/test_unwrap_self.py on Python 3.13 macOS: 78 OK, 1 skipped (the win32-only ACL test).

Optional, not blocking: the same std::fopen(path.string().c_str(), "wb") on a temp_directory_path() root also appears in GuestNpTrophyData.cpp:86 and GuestNpTrophyStats.cpp:104. Both tests are registered for Windows, and neither is in your list of 7. Were they not built in that run, or did they pass? GuestFilesystem.cpp:924 has the same pattern, but guest_filesystem already fails for another reason, so this one is hidden.

AI-assisted: yes

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

The two trophy tests are registered under BUILD_TESTING, including Windows (guest_np_trophy_data and guest_np_trophy_stats); the build definitions provide their Windows DLL-search paths. There is a distinction in the cited fopen calls: trophy data and trophy stats create a root with temp_directory_path(), then call std::filesystem::current_path(root). Every call to their Write helper passes an ASCII relative name, such as app0/sce_sys/param.json. The non-ASCII TEMP prefix therefore is not converted to narrow bytes by those fopen(path.string()) calls. That is a source-level explanation, not proof of a historical pass.

The same distinction applies to GuestFilesystem.cpp:924: sized is formed from the ASCII relative anyps5-filesystem-test-... root at line571. Separate earlier Windows helpers do use TEMP, and a failure there can prevent reaching line924.

I could not recover the original Windows per-test build/CTest manifest or verbose/JUnit logs from the retained local evidence, so I cannot establish whether those exact trophy tests were built, executed or passed in the reported non-ASCII-TEMP full run. Their absence from the seven-failure list is not enough to establish that. The retained exact-head Linux run selected only guest_system_font, exec_arguments and unwrap_self (three passes each); it adds no coverage of these trophy/filesystem cases or Windows TEMP.

The explicit follow-up is to recover the original Windows per-test receipts, or qualify these cases in a future authorized Windows session with effective TEMP/TMP/CWD and exact build/test manifests retained. Windows remains paused; no new Windows execution or source change was made for this audit. AI-assisted: yes (Codex).

@bunbeans2-arch

Copy link
Copy Markdown
Contributor

Windows run with a non-ASCII TEMP, including a character outside the ANSI code page. The PR head ee5055268b was merged onto main f7f03da.

Windows 11 Pro 10.0.22631 (code page 1252), i9-9900K, MSYS2 UCRT64 GCC 16.1.0, Python 3.13.5, Release, BUILD_TESTING=ON. 3 runs per TEMP, ctest -j 2.

TEMP=TMP= test main this PR
...\e_é guest_system_font, exec_arguments fail 3/3 pass 3/3
...\zh_Ж (outside code page 1252) guest_system_font, exec_arguments fail 3/3 pass 3/3
normal profile both pass pass

So Ж is covered too, not only é.

unwrap_self fails 26 of 78 subtests under both TEMP values, the same on main and with this PR. On Python 3.13 the icacls decode error does not occur. The remaining failures are the FAIL: <source>: ... assertions: stderr holds the path with its UTF-8 bytes read as cp1252 (e_é, zh_Ж), because text=True decodes with the locale encoding (cp1252 here) on Python 3.13. That is outside what this PR changes.

Separate from this PR: 16 other tests abort under the Ж TEMP because host std::tmpfile() returns NULL there (#13083). #13104 covers 15 of them in different files, so it does not overlap with this PR.

AI-assisted: yes (Claude Code)

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

@bunbeans2-arch thanks for the second machine and the out-of-code-page Ж case. The new head 2365b1010c decodes the CLI output in test_unwrap_self.py as UTF-8 (for the Python 3.13 locale-encoding failures you found). It passes here on the other Python too.

Windows 11 22621 (code page 1252), Python 3.15, tools/tests/test_unwrap_self.py at 2365b1010c:

TEMP=TMP= result
C:\x\tëmpünï 78 tests, OK (3 skipped)
C:\x\zh_Ж 78 tests, OK (3 skipped)
normal profile 78 tests, OK (3 skipped)

AI-assisted: yes (Claude Code).

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the remaining unwrap_self diagnostic-decoding failures reported on the earlier head. Published 2365b1010c4577e2a5d2594aad8769e98b434be9 by normal fast-forward, retaining every existing C++/ACL change.

The CLI already forces UTF-8 output; nine test subprocesses now request UTF-8 when decoding it. The exact parent produces 26 failed subtest assertions under each CP1252-parent/Unicode-TEMP model (é, Ж); the change passes both plus the native-default control. Each change run passes 76 applicable tests in the 78-test suite, with the same two existing platform skips and zero errors. This is Linux execution with a parent-decoder model; no new native Windows run is claimed.

The description contains the complete matrix and download-verified reproduction evidence, SHA-256 7d4bf9df1fa9bd588e222b304f36edd7c5a91b7d66e163abf4b9872317f82a32. Conventions and whitespace pass. AI-assisted: yes (Codex).

@JJenkx

JJenkx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Qualification update — 2026-10-30, repeat 2

Fresh actual Windows qualification at exact current public 2365b1010c4577e2a5d2594aad8769e98b434be9: Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, Python 3.15.1, native Windows ACP1252 and OEM437. Ran the unchanged complete 78-test unwrap_self suite separately with ASCII, é and Ж TEMP/TMP paths, PYTHONUTF8=0 and -X utf8=0. Effective locale and stdout encoding are cp1252, UTF8 mode is 0, and PYTHONIOENCODING is unset; raw metadata records CWD and all three TEMP paths.

Each context passes all 75 applicable cases with 3 existing Windows POSIX group/permission skips, zero failures/errors. The ACL/icacls case executes and passes in each context; its OEM decoding behavior is unchanged. This directly qualifies the published UTF8 subprocess-decoding fix on Windows and supersedes the earlier 26-error contributor result tied to its previous head for this fixture. No source/test changes, emulation of Windows locale, game/SDK/GPU or performance claim.

The initial incomplete source-archive setup errors are retained separately and are not counted as source failures or passes; the final archive includes and preflights the actual relinker fixture import chain. Complete successful harness: Windows FIFO request 177453aaad2e4943a7cf8e92ae271074. Evidence: https://github.com/JJenkx/AnyPS5/releases/download/linux-validation-20261023/owned-current-windows-20261030-repeat2.tar.gz, SHA256 e423c97373758bf59239e02c138b74f35f1a45488793bc92878a4c836f94a584. AI-assisted: yes (Codex).

AI-assisted: yes (Codex).

@adriadam10
adriadam10 merged commit 096edc0 into boykopovar:main Oct 30, 2026
16 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the new commit against the earlier review: decoding the unwrap_self.py CLI output as UTF-8 is right, since the CLI reconfigures its streams to UTF-8 (tools/unwrap_self.py:421). The _wfopen, GetEnvironmentVariableW and icacls OEM-decoding changes are unchanged, POSIX paths are the same, and the Windows CI job is green. Confirmed on Windows by @bunbeans2-arch with a non-ASCII TEMP; merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants