2121//! * `lockless_other_dependencies` — the same project with no committed
2222//! `Cargo.lock`: with no resolved graph to read, a crate declared beside
2323//! any other dependency is refused just as loudly.
24+ //! * `user_patch_same_crate` / `config_patch_same_crate` — the user
25+ //! overrides the patched crate with a `[patch]` path entry (root manifest
26+ //! or `.cargo/config.toml`): hosted mode refuses it rather than dropping
27+ //! the override and breaking `--locked` (#480).
2428//!
2529//! Every shape runs the same chain against the real cargo: a baseline build
2630//! with a private CARGO_HOME (network to crates.io for fixture setup only),
@@ -109,6 +113,11 @@ struct Shape {
109113 /// A shape hosted mode must REFUSE: the rewriter warning code every
110114 /// patch is skipped with. The scan must leave every file untouched.
111115 refused : Option < & ' static str > ,
116+ /// Extra crates.io dependencies fetched into the CARGO_HOME by a helper
117+ /// project before the scan: a patched crate the project itself resolves
118+ /// elsewhere (a `[patch]` override) still needs its registry copy for
119+ /// the served `.crate`, exactly as on a machine that built it before.
120+ prefetch : & ' static str ,
112121}
113122
114123fn run_socket ( cwd : & Path , args : & [ & str ] , cargo_home : & Path ) -> ( i32 , String , String ) {
@@ -477,6 +486,19 @@ async fn run_shape(shape: Shape) -> Option<()> {
477486 if shape. lockless {
478487 std:: fs:: remove_file ( proj. join ( "Cargo.lock" ) ) . unwrap ( ) ;
479488 }
489+ if !shape. prefetch . is_empty ( ) {
490+ let helper = tmp. path ( ) . join ( "prefetch" ) ;
491+ std:: fs:: create_dir_all ( helper. join ( "src" ) ) . unwrap ( ) ;
492+ std:: fs:: write ( helper. join ( "Cargo.toml" ) , helper_manifest ( shape. prefetch ) ) . unwrap ( ) ;
493+ std:: fs:: write ( helper. join ( "src/main.rs" ) , "fn main() {}\n " ) . unwrap ( ) ;
494+ let fetched = cargo ( & helper, & [ "build" , "-q" ] , & home) ;
495+ assert ! (
496+ fetched. status. success( ) ,
497+ "{}: the prefetch helper must build:\n {}" ,
498+ shape. tag,
499+ stderr( & fetched)
500+ ) ;
501+ }
480502 let before = snapshot ( & proj) ;
481503
482504 let mut served = Vec :: new ( ) ;
@@ -787,6 +809,10 @@ fn pin_patched_versions(proj: &Path, home: &Path, patches: &[Patch]) {
787809 }
788810}
789811
812+ fn helper_manifest ( deps : & str ) -> String {
813+ format ! ( "[package]\n name = \" prefetch\" \n version = \" 0.1.0\" \n edition = \" 2018\" \n \n [dependencies]\n {deps}" )
814+ }
815+
790816fn consumer_manifest ( deps : & str ) -> String {
791817 format ! ( "[package]\n name = \" consumer\" \n version = \" 0.1.0\" \n edition = \" 2018\" \n \n [dependencies]\n {deps}" )
792818}
@@ -814,6 +840,7 @@ async fn cargo_hosted_multi_version_pins_each_declaration_and_removes_cleanly()
814840 crlf : false ,
815841 lockless : false ,
816842 refused : None ,
843+ prefetch : "" ,
817844 } ;
818845 let _ = run_shape ( shape) . await ;
819846}
@@ -837,6 +864,7 @@ async fn cargo_hosted_legacy_config_is_restored_byte_for_byte() {
837864 crlf : false ,
838865 lockless : false ,
839866 refused : None ,
867+ prefetch : "" ,
840868 } ;
841869 let _ = run_shape ( shape) . await ;
842870}
@@ -875,6 +903,7 @@ async fn cargo_hosted_config_trailing_bytes_are_restored() {
875903 crlf : false ,
876904 lockless : false ,
877905 refused : None ,
906+ prefetch : "" ,
878907 } ;
879908 if run_shape ( shape) . await . is_none ( ) {
880909 return ;
@@ -908,6 +937,7 @@ async fn cargo_hosted_same_line_in_two_sections_removes_cleanly() {
908937 crlf : false ,
909938 lockless : false ,
910939 refused : None ,
940+ prefetch : "" ,
911941 } ;
912942 let _ = run_shape ( shape) . await ;
913943}
@@ -947,6 +977,7 @@ async fn cargo_hosted_workspace_member_declaration_is_pinned() {
947977 crlf : false ,
948978 lockless : false ,
949979 refused : None ,
980+ prefetch : "" ,
950981 } ;
951982 let _ = run_shape ( shape) . await ;
952983}
@@ -969,6 +1000,7 @@ async fn cargo_hosted_crlf_project_keeps_its_line_endings() {
9691000 crlf : true ,
9701001 lockless : false ,
9711002 refused : None ,
1003+ prefetch : "" ,
9721004 } ;
9731005 let _ = run_shape ( shape) . await ;
9741006}
@@ -994,6 +1026,7 @@ async fn cargo_hosted_refuses_a_crate_another_crate_depends_on() {
9941026 crlf : false ,
9951027 lockless : false ,
9961028 refused : Some ( "redirect_cargo_transitive_dependents" ) ,
1029+ prefetch : "" ,
9971030 } ;
9981031 let _ = run_shape ( shape) . await ;
9991032}
@@ -1020,6 +1053,82 @@ async fn cargo_hosted_refuses_a_lockless_project_with_other_dependencies() {
10201053 crlf : false ,
10211054 lockless : true ,
10221055 refused : Some ( "redirect_cargo_lockless_dependents" ) ,
1056+ prefetch : "" ,
1057+ } ;
1058+ let _ = run_shape ( shape) . await ;
1059+ }
1060+
1061+ fn local_crate ( name : & str , version : & str ) -> String {
1062+ format ! ( "[package]\n name = \" {name}\" \n version = \" {version}\" \n edition = \" 2018\" \n " )
1063+ }
1064+
1065+ /// #480: the user overrides the patched crate with a root
1066+ /// `[patch.crates-io]` path entry. Pinning the declaration to the Socket
1067+ /// registry would silently drop the override (cargo: "patch … was not used
1068+ /// in the crate graph") and, with a lock, break `cargo fetch --locked`, so
1069+ /// hosted mode refuses it and rewrites nothing. The project has no
1070+ /// committed `Cargo.lock` (a library): the crawl then walks the whole
1071+ /// registry cache, finds the crates.io copy another project fetched, and
1072+ /// hands the crate to the rewriter — which must read the override table.
1073+ /// (With a lock the rewriter refuses from the sourceless lock block; the
1074+ /// unit tests cover that, as the lock-scoped crawl never discovers it.)
1075+ #[ tokio:: test( flavor = "multi_thread" ) ]
1076+ async fn cargo_hosted_refuses_a_crate_the_root_manifest_patch_overrides ( ) {
1077+ let shape = Shape {
1078+ tag : "user-patch-same-crate" ,
1079+ files : vec ! [
1080+ (
1081+ "Cargo.toml" ,
1082+ format!(
1083+ "{}\n [patch.crates-io]\n cfg-if = {{ path = \" cfg-if-local\" }}\n " ,
1084+ consumer_manifest( "cfg-if = \" 1.0.4\" \n " )
1085+ ) ,
1086+ ) ,
1087+ ( "src/main.rs" , "fn main() {}\n " . to_string( ) ) ,
1088+ ( "cfg-if-local/Cargo.toml" , local_crate( "cfg-if" , "1.0.4" ) ) ,
1089+ (
1090+ "cfg-if-local/src/lib.rs" ,
1091+ "pub fn user_fork() {}\n " . to_string( ) ,
1092+ ) ,
1093+ ] ,
1094+ patches : vec ! [ CFG_IF_1 ] ,
1095+ oracle : Vec :: new ( ) ,
1096+ crlf : false ,
1097+ lockless : true ,
1098+ refused : Some ( "redirect_cargo_dep_overridden" ) ,
1099+ prefetch : "cfg-if = \" =1.0.4\" \n " ,
1100+ } ;
1101+ let _ = run_shape ( shape) . await ;
1102+ }
1103+
1104+ /// #480: the same override in the project `.cargo/config.toml` under the
1105+ /// index-URL spelling (cargo merges `[patch]` from the config chain): the
1106+ /// manifest says nothing, and the redirect is still refused.
1107+ #[ tokio:: test( flavor = "multi_thread" ) ]
1108+ async fn cargo_hosted_refuses_a_crate_a_config_patch_overrides ( ) {
1109+ let shape = Shape {
1110+ tag : "config-patch-same-crate" ,
1111+ files : vec ! [
1112+ ( "Cargo.toml" , consumer_manifest( "cfg-if = \" 1.0.4\" \n " ) ) ,
1113+ (
1114+ ".cargo/config.toml" ,
1115+ "[patch.\" https://github.com/rust-lang/crates.io-index\" ]\n \
1116+ cfg-if = { path = \" local/cfg-if\" }\n "
1117+ . to_string( ) ,
1118+ ) ,
1119+ ( "src/main.rs" , "fn main() {}\n " . to_string( ) ) ,
1120+ ( "local/cfg-if/Cargo.toml" , local_crate( "cfg-if" , "1.0.4" ) ) ,
1121+ (
1122+ "local/cfg-if/src/lib.rs" ,
1123+ "pub fn user_fork() {}\n " . to_string( ) ,
1124+ ) ,
1125+ ] ,
1126+ patches : vec ! [ CFG_IF_1 ] ,
1127+ oracle : Vec :: new ( ) ,
1128+ crlf : false ,
1129+ lockless : true ,
1130+ refused : Some ( "redirect_cargo_dep_overridden" ) ,
1131+ prefetch : "cfg-if = \" =1.0.4\" \n " ,
10231132 } ;
10241133 let _ = run_shape ( shape) . await ;
10251134}
0 commit comments