@@ -444,6 +444,11 @@ fn test_add_numbers_runtime_args(config: &TestConfig) -> Result<(), String> {
444444fn test_runfiles_source_precedence ( config : & TestConfig ) -> Result < ( ) , String > {
445445 println ! ( " Running test: runfiles_source_precedence" ) ;
446446
447+ // The launcher prefers a directory at equal precedence where Bazel
448+ // materializes the runfiles tree; on Windows the tree is sparse, so the
449+ // manifest wins. Mirror that choice when asserting the selected source.
450+ let prefer_directory = !cfg ! ( windows) ;
451+
447452 let test_dir = config. work_dir . join ( "test_runfiles_source_precedence" ) ;
448453 fs:: create_dir_all ( & test_dir) . map_err ( |e| format ! ( "Failed to create test dir: {}" , e) ) ?;
449454
@@ -475,8 +480,9 @@ fn test_runfiles_source_precedence(config: &TestConfig) -> Result<(), String> {
475480 & [ 0 ] ,
476481 ) ?;
477482
478- // Both environment sources have the same precedence, so the directory owns
479- // both resolution and the environment exported to the child.
483+ // Both environment sources have the same precedence. Where the runfiles tree
484+ // is materialized the directory owns both resolution and the exported
485+ // environment; on Windows the tree is sparse, so the manifest wins.
480486 let mut command = Command :: new ( & stub_path) ;
481487 command
482488 . env ( "RUNFILES_DIR" , & runfiles. runfiles_dir )
@@ -487,14 +493,21 @@ fn test_runfiles_source_precedence(config: &TestConfig) -> Result<(), String> {
487493 . map_err ( |e| format ! ( "Failed to run stub with both runfiles variables: {}" , e) ) ?;
488494 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
489495 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
490- if !output. status . success ( )
491- || !stdout. contains ( "ARGC:3" )
492- || environment_path ( & stdout, "RUNFILES_DIR" ) != Some ( runfiles. runfiles_dir . as_path ( ) )
493- || environment_path ( & stdout, "JAVA_RUNFILES" ) != Some ( runfiles. runfiles_dir . as_path ( ) )
494- || !stdout. contains ( "ENV:RUNFILES_MANIFEST_FILE=<unset>" )
495- {
496+ // The directory maps `tool` to print-env (prints ARGC/ENV), the manifest to
497+ // add-numbers (prints SUM), so the output identifies which source was chosen.
498+ let selected_expected = if prefer_directory {
499+ output. status . success ( )
500+ && stdout. contains ( "ARGC:3" )
501+ && environment_path ( & stdout, "RUNFILES_DIR" ) == Some ( runfiles. runfiles_dir . as_path ( ) )
502+ && environment_path ( & stdout, "JAVA_RUNFILES" ) == Some ( runfiles. runfiles_dir . as_path ( ) )
503+ && stdout. contains ( "ENV:RUNFILES_MANIFEST_FILE=<unset>" )
504+ } else {
505+ output. status . success ( ) && stdout. contains ( "SUM:15" ) && !stdout. contains ( "ARGC:3" )
506+ } ;
507+ if !selected_expected {
496508 return Err ( format ! (
497- "Directory environment source did not win the tie.\n stdout: {}\n stderr: {}" ,
509+ "Same-precedence environment sources did not select the {} source.\n stdout: {}\n stderr: {}" ,
510+ if prefer_directory { "directory" } else { "manifest" } ,
498511 stdout, stderr
499512 ) ) ;
500513 }
@@ -561,7 +574,8 @@ fn test_runfiles_source_precedence(config: &TestConfig) -> Result<(), String> {
561574 }
562575
563576 // With no environment source, the adjacent directory and manifest have equal
564- // precedence, so the directory wins and is exported consistently.
577+ // precedence: the directory wins where the tree is materialized, the manifest
578+ // on Windows, and the winner is exported consistently.
565579 let mut command = Command :: new ( & stub_path) ;
566580 command
567581 . env_remove ( "RUNFILES_DIR" )
@@ -572,65 +586,112 @@ fn test_runfiles_source_precedence(config: &TestConfig) -> Result<(), String> {
572586 . map_err ( |e| format ! ( "Failed to run stub with adjacent sources: {}" , e) ) ?;
573587 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
574588 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
575- if !output. status . success ( )
576- || !stdout. contains ( "ARGC:3" )
577- || environment_path ( & stdout, "RUNFILES_DIR" ) != Some ( runfiles. runfiles_dir . as_path ( ) )
578- || environment_path ( & stdout, "JAVA_RUNFILES" ) != Some ( runfiles. runfiles_dir . as_path ( ) )
579- || !stdout. contains ( "ENV:RUNFILES_MANIFEST_FILE=<unset>" )
580- {
589+ let selected_expected = if prefer_directory {
590+ output. status . success ( )
591+ && stdout. contains ( "ARGC:3" )
592+ && environment_path ( & stdout, "RUNFILES_DIR" ) == Some ( runfiles. runfiles_dir . as_path ( ) )
593+ && environment_path ( & stdout, "JAVA_RUNFILES" ) == Some ( runfiles. runfiles_dir . as_path ( ) )
594+ && stdout. contains ( "ENV:RUNFILES_MANIFEST_FILE=<unset>" )
595+ } else {
596+ output. status . success ( ) && stdout. contains ( "SUM:15" ) && !stdout. contains ( "ARGC:3" )
597+ } ;
598+ if !selected_expected {
581599 return Err ( format ! (
582- "Adjacent directory did not win the tie.\n stdout: {}\n stderr: {}" ,
600+ "Adjacent {} source did not win the tie.\n stdout: {}\n stderr: {}" ,
601+ if prefer_directory { "directory" } else { "manifest" } ,
583602 stdout, stderr,
584603 ) ) ;
585604 }
586605
587- // Source selection is global, not per key. A missing directory entry must
588- // not fall through to either an environment or adjacent manifest.
589- let missing_rlocation = format ! ( "{}/bin/missing{}" , WORKSPACE_NAME , EXE_EXT ) ;
590- let missing_stub_name = format ! ( "missing_stub{}" , EXE_EXT ) ;
591- let missing_stub = test_dir. join ( & missing_stub_name) ;
592- let missing_manifest = test_dir. join ( format ! ( "{}.runfiles_manifest" , missing_stub_name) ) ;
593- let add_target = add_binary. to_string_lossy ( ) ;
594- #[ cfg( windows) ]
595- let add_target = add_target. replace ( '\\' , "/" ) ;
596- fs:: write (
597- & missing_manifest,
598- format ! ( "{} {}\n " , missing_rlocation, add_target) ,
599- )
600- . map_err ( |e| format ! ( "Failed to write no-fallback fixture: {}" , e) ) ?;
601- finalize_stub (
602- config,
603- & missing_stub,
604- & [ & missing_rlocation, "7" , "8" ] ,
605- & [ 0 ] ,
606- ) ?;
607- for ( case, manifest_env) in [
608- ( "environment manifest" , Some ( & missing_manifest) ) ,
609- ( "adjacent manifest" , None ) ,
610- ] {
611- let mut command = Command :: new ( & missing_stub) ;
606+ // Source selection is global, not per key: a key that only the NON-selected
607+ // source can resolve must not fall through to it.
608+ //
609+ // Case A: both sources come from the environment, so the platform preference
610+ // picks the winner. Embed a key that lives ONLY in the loser — any
611+ // fall-through would resolve it and betray the mixing.
612+ {
613+ let a_stub_name = format ! ( "no_fallthrough_env_stub{}" , EXE_EXT ) ;
614+ let a_stub = test_dir. join ( & a_stub_name) ;
615+ let a_manifest = test_dir. join ( "no_fallthrough_env.runfiles_manifest" ) ;
616+ // Manifest RHS values use forward slashes (Bazel's Windows convention);
617+ // harmless on Unix, where paths carry no backslashes.
618+ let add_target = add_binary. to_string_lossy ( ) . replace ( '\\' , "/" ) ;
619+
620+ let embedded_key = if prefer_directory {
621+ // Directory wins; put the embedded key only in the manifest.
622+ let key = format ! ( "{}/bin/only_in_manifest{}" , WORKSPACE_NAME , EXE_EXT ) ;
623+ fs:: write ( & a_manifest, format ! ( "{} {}\n " , key, add_target) )
624+ . map_err ( |e| format ! ( "Failed to write no-fallthrough manifest: {}" , e) ) ?;
625+ key
626+ } else {
627+ // Manifest wins; reuse `tool` (present only in the directory, as
628+ // print-env) and give the manifest an unrelated entry so it loads but
629+ // cannot resolve the key.
630+ let decoy = format ! ( "{}/bin/decoy{}" , WORKSPACE_NAME , EXE_EXT ) ;
631+ fs:: write ( & a_manifest, format ! ( "{} {}\n " , decoy, add_target) )
632+ . map_err ( |e| format ! ( "Failed to write no-fallthrough manifest: {}" , e) ) ?;
633+ executable_rlocation. clone ( )
634+ } ;
635+ finalize_stub ( config, & a_stub, & [ & embedded_key, "7" , "8" ] , & [ 0 ] ) ?;
636+
637+ let mut command = Command :: new ( & a_stub) ;
612638 command
613639 . env ( "RUNFILES_DIR" , & runfiles. runfiles_dir )
640+ . env ( "RUNFILES_MANIFEST_FILE" , & a_manifest)
614641 . env_remove ( "JAVA_RUNFILES" ) ;
615- if let Some ( manifest) = manifest_env {
616- command. env ( "RUNFILES_MANIFEST_FILE" , manifest) ;
642+ let output = command
643+ . output ( )
644+ . map_err ( |e| format ! ( "Failed to test no-fallthrough environment sources: {}" , e) ) ?;
645+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
646+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
647+ // The loser's happy-path signature must never appear: SUM from the
648+ // manifest's add-numbers, or ARGC from the directory's print-env.
649+ let loser_ran = if prefer_directory {
650+ stdout. contains ( "SUM:15" )
617651 } else {
618- command. env_remove ( "RUNFILES_MANIFEST_FILE" ) ;
652+ stdout. contains ( "ARGC" )
653+ } ;
654+ if output. status . success ( ) || loser_ran {
655+ return Err ( format ! (
656+ "Selected source fell through to the other (environment sources).\n stdout: {}\n stderr: {}" ,
657+ stdout, stderr
658+ ) ) ;
619659 }
660+ }
661+
662+ // Case B: RUNFILES_DIR is the only environment source (the manifest is merely
663+ // adjacent), so the environment directory wins on every platform; a key only
664+ // the adjacent manifest holds must not fall through to it.
665+ {
666+ let missing_rlocation = format ! ( "{}/bin/missing{}" , WORKSPACE_NAME , EXE_EXT ) ;
667+ let missing_stub_name = format ! ( "missing_stub{}" , EXE_EXT ) ;
668+ let missing_stub = test_dir. join ( & missing_stub_name) ;
669+ let missing_manifest =
670+ test_dir. join ( format ! ( "{}.runfiles_manifest" , missing_stub_name) ) ;
671+ let add_target = add_binary. to_string_lossy ( ) . replace ( '\\' , "/" ) ;
672+ fs:: write ( & missing_manifest, format ! ( "{} {}\n " , missing_rlocation, add_target) )
673+ . map_err ( |e| format ! ( "Failed to write no-fallthrough adjacent fixture: {}" , e) ) ?;
674+ finalize_stub ( config, & missing_stub, & [ & missing_rlocation, "7" , "8" ] , & [ 0 ] ) ?;
675+
676+ let mut command = Command :: new ( & missing_stub) ;
677+ command
678+ . env ( "RUNFILES_DIR" , & runfiles. runfiles_dir )
679+ . env_remove ( "RUNFILES_MANIFEST_FILE" )
680+ . env_remove ( "JAVA_RUNFILES" ) ;
620681 let output = command
621682 . output ( )
622- . map_err ( |e| format ! ( "Failed to test no-fallback {} : {}" , case , e) ) ?;
683+ . map_err ( |e| format ! ( "Failed to test no-fallthrough adjacent manifest : {}" , e) ) ?;
623684 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
624685 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
625686 if output. status . success ( ) || stdout. contains ( "SUM:15" ) {
626687 return Err ( format ! (
627- "Directory selection fell through to {} .\n stdout: {}\n stderr: {}" ,
628- case , stdout, stderr
688+ "Environment directory fell through to the adjacent manifest .\n stdout: {}\n stderr: {}" ,
689+ stdout, stderr
629690 ) ) ;
630691 }
631692 }
632693
633- println ! ( " PASS (environment precedence, then directory preference)" ) ;
694+ println ! ( " PASS (environment precedence, then platform source preference)" ) ;
634695 Ok ( ( ) )
635696}
636697
0 commit comments