Repository navigation
hip-tests: write contract test artifacts to the temp dir, not cwd - #11734
Conversation
The graph_debug and library_file contract tests built bare relative output paths, so the files landed in the current working directory. That only works when cwd happens to be writable. Running an installed test tree as an unprivileged user it is not: /opt/rocm/share/hip/catch_tests is root:root drwxr-xr-x, so hipGraphDebugDotPrint() and the code-object write fail on permissions before the API under test is meaningfully exercised. The failure is recorded as an ordinary assertion failure with no indication that the cause was the filesystem, which makes it easy to misread as a runtime regression on whatever target happens to be under test. Both paths now go through std::filesystem::temp_directory_path(), which is writable by definition and honours TMPDIR. Changes: - graph_debug: UniqueDotPath() returns a path under the temp directory. - library_file: WriteCodeObjectFile() writes the code object under the temp directory, and its stale "current working directory" comment is corrected. - library_file: the filename gains a per-process token. The old fixed name was adequate in a per-run cwd but not in a shared temp dir, where a leftover file owned by another user is not writable. This mirrors what UniqueDotPath() already does in the graph_debug contract. - Both files include <filesystem>; library_file also includes <cstdint> for the intptr_t cast it now performs. The tests already removed these files by the returned path, so cleanup needed no change. catch/CMakeLists.txt sets CMAKE_CXX_STANDARD 17, and contract executables are built through hip_add_exe_to_target(), which already links stdc++fs on UNIX (catch/cmake/hip-tests.cmake), so no CMake or linkage change is required. Note: catch/unit/memory/mempool_common.hh has the same defect class - it binds an IPC socket at a bare relative sun_path and gets EACCES from a read-only cwd. It is not touched here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
✅ All Policy Checks Passed
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🚫 Please fix the failed policies before requesting reviews. The following policy checks failed:
The |
There was a problem hiding this comment.
🟡 Changes recommended
Temporary artifact names are not atomically or reliably unique across concurrent test processes.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates HIP contract tests to write generated artifacts to the system temporary directory instead of the current working directory.
Changes:
- Uses
std::filesystem::temp_directory_path(). - Adds artifact naming tokens and required headers.
- Preserves existing cleanup behavior.
File summaries
| File | Summary |
|---|---|
projects/hip-tests/catch/contract/library_file/test_hip_library_file_contract.cc |
Writes code objects under the temporary directory. |
projects/hip-tests/catch/contract/graph_debug/test_hip_graph_debug_contract.cc |
Writes graph debug output under the temporary directory. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…sers/iassiour/hip-tests-contract-tmpdir
…t naming Extends the contract-test fix to the two remaining places that build bare relative paths, and simplifies the naming introduced earlier. mempool_common.hh and hip_vmm_common.hh bind an AF_UNIX socket at a bare relative sun_path, so they get EACCES from a read-only cwd for the same reason the contract tests did. Both now build the path under std::filesystem::temp_directory_path() via a shared ipcSocketPath(pid) helper, guarded per platform (!HT_WIN and __linux__ respectively) since AF_UNIX paths are POSIX-only here. sun_path is a fixed-size buffer, and the temp directory prefix makes the path longer, so both call sites now reject an over-long path explicitly instead of letting strcpy truncate it into a different, silently wrong socket name. Artifact naming in the contract tests is simplified to the PID, replacing the address-of-a-local token used previously. The PID is stable within a process and distinct between concurrent test binaries, which is what these names need, and it reads better than a hashed stack address. Changes: - mempool_common.hh, hip_vmm_common.hh: ipcSocketPath(pid) helper placing the socket under the temp directory; platform guards; explicit length check against sizeof(sun_path) - 1 at both the bind and the connect site. - graph_debug, library_file: name artifacts by PID; drop the temp-directory helper in favour of the direct temp_directory_path() call. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Explicit socket-path validation uses perror without setting errno, producing misleading failure messages.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review details
Suppressed comments (4)
projects/hip-tests/catch/unit/memory/mempool_common.hh:657
- Because the temp-directory prefix can make this path exceed
sun_path, the length check below is now reachable. That branch returns without closingserver_fdor deletinghandle, so a longTMPDIRleaks both resources; clean up the allocated socket/handle before returning (and report the local length error withoutperror).
std::string nameStr = ipcSocketPath(getpid());
const char *name = nameStr.c_str();
projects/hip-tests/catch/unit/memory/mempool_common.hh:742
- The same newly reachable over-length-path branch in
openSocket()returns without closingsockor deletinghandle. With a sufficiently longTMPDIR, every client construction leaks these resources; make the failure path release them before returning.
std::string nameStr = ipcSocketPath(getpid());
const char *name = nameStr.c_str();
projects/hip-tests/catch/unit/virtualMemoryManagement/hip_vmm_common.hh:90
- Because the temp-directory prefix can make this path exceed
sun_path, the length check below is now reachable. That branch returns without closingserver_fdor deletinghandle, so a longTMPDIRleaks both resources and leaves the test process in a bad state; clean up the allocated socket/handle before returning (and report the local length error withoutperror).
std::string nameStr = ipcSocketPath(getpid());
const char *name = nameStr.c_str();
projects/hip-tests/catch/unit/virtualMemoryManagement/hip_vmm_common.hh:152
- The same newly reachable over-length-path branch in
openSocket()returns without closingsockor deletinghandle. With a sufficiently longTMPDIR, every client construction leaks these resources; make the failure path release them before returning.
std::string nameStr = ipcSocketPath(getpid());
const char *name = nameStr.c_str();
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
485505e to
56b390d
Compare
Addresses review feedback on the sun_path length checks. These checks are local validation, not syscall failures, so errno is not set when they trip. perror() appends strerror(errno) regardless, so the message came out with a stale suffix - typically "... Name too large: Success" - which reads as a contradiction and hides what actually went wrong. Print with fprintf(stderr, ...) instead, which says exactly what happened and nothing more. Applies to all three length checks in each of mempool_common.hh and hip_vmm_common.hh: the two added by this PR (the bind-site and connect-site guards on the client address) and the pre-existing guard on the server address, which has the same defect and is converted here for consistency rather than leaving two spellings of the same check side by side. The perror() calls that follow a real socket()/bind()/send()/recv() failure are unchanged, since errno is meaningful there. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
56b390d to
e8ba70c
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review details
Suppressed comments (4)
projects/hip-tests/catch/unit/memory/mempool_common.hh:681
- When the temp-directory prefix makes the socket path exceed
sun_path, this new early return occurs aftersocket()andhandle = new ipcHdl; neither resource is released. A longTMPDIRtherefore leaks a file descriptor and heap allocation on every server setup attempt. Closeserver_fdand delete/resethandlebefore returning (and apply the same cleanup to the client-side length check).
size_t len = strlen(name);
if (len > (sizeof(servaddr.sun_path) - 1)) {
fprintf(stderr, "Socket failure: Cannot bind provided name to socket. Name too large\n");
return -1;
projects/hip-tests/catch/unit/memory/mempool_common.hh:747
- This new over-length-path return is reached after creating both
handleandsock, so an invalidTMPDIRleaks the socket descriptor and heap allocation. Release both resources (and resethandle) before returning; the server-side check above needs the corresponding cleanup as well.
if (strlen(name) > (sizeof(cliaddr.sun_path) - 1)) {
fprintf(stderr, "Socket failure: Cannot bind provided name to socket. Name too large\n");
return -1;
}
projects/hip-tests/catch/unit/virtualMemoryManagement/hip_vmm_common.hh:116
- When the temp-directory prefix makes the socket path exceed
sun_path, this new early return occurs aftersocket()andhandle = new ipcHdl; neither resource is released. A longTMPDIRtherefore leaks a file descriptor and heap allocation on every server setup attempt. Closeserver_fdand delete/resethandlebefore returning (and apply the same cleanup to the client-side length check).
size_t len = strlen(name);
if (len > (sizeof(servaddr.sun_path) - 1)) {
fprintf(stderr, "Socket failure: Cannot bind provided name to socket. Name too large\n");
return -1;
projects/hip-tests/catch/unit/virtualMemoryManagement/hip_vmm_common.hh:157
- This new over-length-path return is reached after creating both
handleandsock, so an invalidTMPDIRleaks the socket descriptor and heap allocation. Release both resources (and resethandle) before returning; the server-side check above needs the corresponding cleanup as well.
if (strlen(name) > (sizeof(cliaddr.sun_path) - 1)) {
fprintf(stderr, "Socket failure: Cannot bind provided name to socket. Name too large\n");
return -1;
}
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
Moving the IPC socket path into the temp directory means it can now exceed sizeof(sun_path) - 1, so the over-length check in these helpers is reachable for the first time. That early return left the descriptor open and the ipcHdl allocation live. The neighbouring socket() and bind() failure returns have the same omission and are fixed alongside it, so every failure path in these four functions now cleans up consistently. Changes: - mempool_common.hh: close the fd and delete/reset handle on the socket(), over-length and bind() failure returns in createSocket() and openSocket() - hip_vmm_common.hh: same four-site cleanup in createSocket() and openSocket() Verified by extracting the patched functions verbatim into a standalone harness and counting /proc/self/fd across 200 iterations of each helper: path length | before | after 40 (normal) 400 200 (remainder held by live handles) 120 (over-long) 400 0 The 120-byte arm is the newly reachable branch; it leaked one fd and one ipcHdl per call and now leaks neither. Addresses the resource-leak findings raised in review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review details
Suppressed comments (2)
projects/hip-tests/catch/contract/graph_debug/test_hip_graph_debug_contract.cc:35
- The shared-temp filename is still only keyed by device and
getpid(). That is not unique when binaries run in different PID namespaces (a common setup for installed/containerized tests), and a stale file can also be reused after PID recycling; one run can therefore truncate or read another run's DOT artifact. Reserve a unique temp filename atomically (or add a collision-resistant token) before passing it tohipGraphDebugDotPrint.
return (std::filesystem::temp_directory_path() /
("hip_contract_graph_debug_" + std::to_string(device) + "_" +
std::to_string(getpid()) + ".dot"))
.string();
projects/hip-tests/catch/contract/library_file/test_hip_library_file_contract.cc:90
- The code-object path is still only keyed by the caller-provided suffix and
getpid(), so separate PID namespaces or PID reuse can select the same file in the shared temp directory. Sinceofstreamtruncates an existing path, concurrent tests can overwrite each other's code object. Create/reserve a collision-resistant temporary path atomically and retain that path for the HIP load and cleanup.
const std::string path =
(std::filesystem::temp_directory_path() /
("hip-contract-library-file-" + std::string(suffix) + "-" +
std::to_string(getpid()) + ".code"))
.string();
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
Both IPC test headers carried their own copy of the socket path helper, naming the AF_UNIX rendezvous by bare pid. Move the naming into a single header and give it a tag identifying the mount namespace, so peers that do not share a temp directory cannot collide on identical pids. Both ends derive the tag independently rather than exchanging it, the same way the shared-memory segment derives the parent identity via getParentProcessId(). That is what makes it survive SpawnProc, which fork()s and then execvp()s: the child re-runs the code in a fresh process image and computes the same value. A generated per-run token does not have that property. Changes: - Add include/hip_test_ipc_common.hh with NamespaceTag() and SocketPath() - Drop the duplicated ipcSocketPath() from mempool_common.hh and hip_vmm_common.hh and route all six call sites through the shared helper - Use the in-tree fs:: alias from hip_test_filesystem.hh instead of std::filesystem directly, matching the rest of the suite and keeping the experimental-filesystem fallback intact Verified: parent, fork() child and execvp() child compute the same tag; three concurrently running containers compute three different tags. Path is 33 of 107 sun_path bytes. Namespace inode numbers are recycled after a namespace is destroyed, so the tag is unique among concurrent peers rather than over all time, which is the property needed here; this is noted in the header. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Split the IPC socket path work out of this PR. It addresses the same read-only-cwd problem but in a different set of tests, and it carries its own design discussion; it will follow separately so the contract test fix can land on its own. Changes: - Restore mempool_common.hh and hip_vmm_common.hh to their pre-PR state - Remove include/hip_test_ipc_common.hh - Use the in-tree fs:: alias from hip_test_filesystem.hh rather than std::filesystem directly. The suite provides that header to fall back to <experimental/filesystem>, so going straight to std::filesystem would not build on toolchains that only have the experimental one. The PR is now the two contract test files only: they write their generated dot graph and code object under the temp directory instead of the current working directory, which may be read-only in an installed test tree. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
A pid-derived name in the temp directory is predictable, so on a shared temp directory it can be pre-seeded with a symlink that the export or the ofstream then follows. Use a random suffix instead, matching how hip_test_process.hh names its own temporary capture files. Changes: - Add a RandomTag() helper to both contract tests: 64 bits from std::random_device, mixed with the clock so the name stays unique even where random_device is a weak source, formatted as 16 hex chars - Drop the std::remove() before hipGraphDebugDotPrint. With a random name there is nothing to clear, and it was the gap between unlink and open that made the symlink race reachable. The remove after each check stays; that is the cleanup. - Remove the _WIN32/getpid shims, no longer needed now that neither file calls getpid This is unpredictability rather than atomic reservation: mkstemp is POSIX only and hip-tests also runs on Windows, and hipGraphDebugDotPrint takes a path rather than a descriptor, so a reserved fd could not be handed to it. The existing convention in hip_test_process.hh has the same property. Verified: compiles clean under -Wall -Wextra; 100000 draws produced no duplicate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Both files need atomically reserved paths and cleanup on failure paths.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 4
Open (4)
Resolved since last review (1)
Both contract tests had their own copy of the random-suffix helper. Move it to include/hip_test_temp_path.hh so the two cannot drift, and so the one platform shim for getpid is written once rather than per test. Seed with the pid rather than the clock. The mix-in only matters when std::random_device degenerates to a fixed sequence, which the standard permits and at least one toolchain does. In that case live pids within a namespace are distinct by definition, so concurrent processes still get distinct names; two processes can sample the same clock value. Changes: - Add include/hip_test_temp_path.hh with RandomTag() and TempPath() - Route both contract tests through it, dropping their local copies Built ContractGraphDebugTest and ContractLibraryFileTest against a gfx1260 toolchain, clean under -Wall -Wextra, and ran both on a device-free FFM model: 2/2 and 3/3 assertions pass. Confirmed the artifacts really do go through temp_directory_path() by pointing TMPDIR at a read-only directory: ContractGraphDebugTest then fails 2/2 and ContractLibraryFileTest 2/3, the one remaining pass being MissingFile_IsRejected, which is the only case that never writes a file. Not covered: the read-only-cwd case this PR exists for cannot be exercised under the model, because the model itself opens a log file in the working directory and aborts when it cannot. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Rebuilt and re-ran both contract tests: same results as before. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
TheRock Submodule Bump ActivityNewest first
|


Motivation
Two contract test helpers build bare relative paths, so they resolve against the current working directory. An installed test tree run by an unprivileged user has no write permission there (
/opt/rocm/share/hip/catch_testsisroot:root drwxr-xr-x), so the write fails before the API under test is meaningfully exercised.Nothing in the failure names the filesystem, so it reads as a genuine runtime failure of the API under test:
Technical Details
graph_debugandlibrary_filenow write throughtemp_directory_path(), which is writable by definition and honoursTMPDIR. Names are qualified by PID so concurrent test binaries do not collide in the shared temp directory.Paths go through the in-tree
fs::alias fromhip_test_filesystem.hhrather thanstd::filesystemdirectly. That header exists to fall back to<experimental/filesystem>, so usingstd::filesystem::unconditionally would not build on toolchains that only have the experimental one.No CMake change:
catch/CMakeLists.txt:45setsCMAKE_CXX_STANDARD 17, andhip_add_exe_to_target()already linksstdc++fson UNIX (catch/cmake/hip-tests.cmake:91). Cleanup is unchanged — the tests already delete by the returned path.Scope
The IPC socket paths (
mempool_common.hh,hip_vmm_common.hh) have the same root cause — theybind()anAF_UNIXsocket at a bare relativesun_pathand getEACCESfor the same reason — but that fix carries its own design discussion around socket naming and is split out to keep this one reviewable:users/iassiour/hip-tests-ipc-socket-pathsThis PR is the two contract test files only.
Issue Tracking
JIRA ID: ROCM-31233
Test Plan
mode 555), to confirm cwd permissions are the cause.hipcc -fsyntax-only -std=c++17 -Wall -Wextraon the patched files.Test Result
Same binary, only cwd differs:
ContractGraphDebugTestContractLibraryFileTestexit=0exit=0exit=42, 2/2 assertions failedexit=42, 2/3 failedPatched files compile clean, no warnings.
Not covered: the patched binaries were not built and run end to end — CI covers that here.
Submission Checklist