@@ -201,7 +201,8 @@ fn run_test(test: &Test, force_rewrite: bool, port: u16) -> Option<String> {
201201 fs:: write ( & stdout_path, filter_stdout ( stdout_utf8) . as_bytes ( ) ) . expect ( "Failed to write STDOUT" ) ;
202202 let stderr_path = test_context_directory. path ( ) . join ( "STDERR" ) ;
203203 let stderr_utf8 = std:: str:: from_utf8 ( & output. stderr ) . expect ( "stderr should be utf8" ) ;
204- fs:: write ( & stderr_path, filter_stderr ( stderr_utf8) . as_bytes ( ) ) . expect ( "Failed to write STDERR" ) ;
204+ fs:: write ( & stderr_path, filter_stderr ( stderr_utf8, test_context_directory. path ( ) ) . as_bytes ( ) )
205+ . expect ( "Failed to write STDERR" ) ;
205206
206207 let exitcode_path = test_context_directory. path ( ) . join ( "EXITCODE" ) ;
207208 if let Some ( code) = output. status . code ( ) {
@@ -291,26 +292,22 @@ fn filter_stdout(data: &str) -> String {
291292}
292293
293294/// Replace strings in the stderr of a Leo execution that we don't need to match exactly.
294- fn filter_stderr ( data : & str ) -> String {
295+ fn filter_stderr ( data : & str , temp_dir : & Path ) -> String {
295296 use regex:: Regex ;
296297 use std:: borrow:: Cow ;
297298
299+ // Rewrite the test's temp directory to a stable `TMPDIR` placeholder. The
300+ // harness created this directory, so substitute its exact path rather than
301+ // guessing the shape with a regex - `$TMPDIR` varies by environment.
302+ let data = redact_temp_dir ( data, temp_dir) ;
303+
298304 let regexes = [
299305 // Strip ANSI color codes so downstream regexes match cleanly.
300306 ( Regex :: new ( r"\x1b\[[0-9;]*m" ) . unwrap ( ) , "" ) ,
301307 // Match `-->` followed by any path, capture only the filename with line/col
302308 ( Regex :: new ( r"-->\s+.*?/([^/]+\.leo:\d+:\d+)" ) . unwrap ( ) , "--> SOURCE_DIRECTORY/$1" ) ,
303309 // Match ariadne's `╭─[ path:line:col ]` header, normalize the path portion.
304310 ( Regex :: new ( r"╭─\[\s*.*?/([^/]+\.leo:\d+:\d+)\s*\]" ) . unwrap ( ) , "╭─[ SOURCE_DIRECTORY/$1 ]" ) ,
305- // Normalize tempdir prefixes written by `tempfile::TempDir::new()` so expectations
306- // are stable across machines and OSes, regardless of surrounding quoting:
307- // /tmp/.tmpXXX/contents/ (Linux)
308- // /private/var/folders/XX/XXXX/T/.tmpXXX/contents/ (macOS)
309- // → TMPDIR/contents/
310- (
311- Regex :: new ( r"(?:/tmp|/private/var/folders/[^/]+/[^/]+/T)/\.tmp[A-Za-z0-9]+/contents/" ) . unwrap ( ) ,
312- "TMPDIR/contents/" ,
313- ) ,
314311 // Normalize dynamic devnode ports back to 3030 for stable expectations.
315312 ( Regex :: new ( r"http://localhost:\d+" ) . unwrap ( ) , "http://localhost:3030" ) ,
316313 // snarkVM prints parameter download warnings to stderr on fresh runners.
@@ -323,7 +320,7 @@ fn filter_stderr(data: &str) -> String {
323320 ( Regex :: new ( r"⚠️ Network request failed, retrying in \d+s \(attempt \d+/\d+\)\.\.\.\n" ) . unwrap ( ) , "" ) ,
324321 ] ;
325322
326- let mut cow = Cow :: Borrowed ( data) ;
323+ let mut cow = Cow :: Borrowed ( data. as_str ( ) ) ;
327324 for ( regex, replacement) in regexes {
328325 if let Cow :: Owned ( s) = regex. replace_all ( & cow, replacement) {
329326 cow = Cow :: Owned ( s) ;
@@ -333,6 +330,23 @@ fn filter_stderr(data: &str) -> String {
333330 cow. into_owned ( )
334331}
335332
333+ /// Rewrite every occurrence of the test's temporary directory in `data` to a
334+ /// stable `TMPDIR` placeholder, so expectations don't depend on where `$TMPDIR`
335+ /// points (it varies by environment - e.g. nix-shell uses `/tmp/nix-shell.XXX`).
336+ ///
337+ /// Both the directory as created and its canonicalized form are rewritten: Leo
338+ /// canonicalizes paths before printing them, and the canonical form differs from
339+ /// the created path under a symlinked root (e.g. macOS, where `/var/folders/...`
340+ /// resolves to `/private/var/folders/...`). The canonical form is rewritten
341+ /// first since it can contain the created path as a substring.
342+ fn redact_temp_dir ( data : & str , temp_dir : & Path ) -> String {
343+ let mut out = data. to_owned ( ) ;
344+ if let Ok ( canonical) = temp_dir. canonicalize ( ) {
345+ out = out. replace ( & * canonical. to_string_lossy ( ) , "TMPDIR" ) ;
346+ }
347+ out. replace ( & * temp_dir. to_string_lossy ( ) , "TMPDIR" )
348+ }
349+
336350/// Filter dynamic values in JSON output files to allow comparison across runs.
337351fn filter_json_file ( data : & str ) -> String {
338352 use regex:: Regex ;
0 commit comments