@@ -6,10 +6,10 @@ use std::env;
66use std:: env:: consts:: EXE_SUFFIX ;
77use std:: ffi:: OsStr ;
88use std:: fs;
9- use std:: io;
9+ use std:: io:: { self , Write } ;
1010use std:: path:: { Path , PathBuf } ;
1111use std:: process:: Command ;
12- use std:: sync:: Arc ;
12+ use std:: sync:: { Arc , RwLock , RwLockWriteGuard } ;
1313use std:: time:: Instant ;
1414
1515use enum_map:: { enum_map, Enum , EnumMap } ;
@@ -53,6 +53,8 @@ pub struct Config {
5353 pub rustup_update_root : Option < String > ,
5454 /// This is cwd for the test
5555 pub workdir : RefCell < PathBuf > ,
56+ /// This is the test root for keeping stuff together
57+ pub test_root_dir : PathBuf ,
5658}
5759
5860// Describes all the features of the mock dist server.
@@ -115,6 +117,15 @@ struct ConstState {
115117 const_dist_dir : tempfile:: TempDir ,
116118}
117119
120+ /// The lock to be used when creating test environments.
121+ ///
122+ /// Essentially we use this in `.read()` mode to gate access to `fork()`
123+ /// new subprocesses, and in `.write()` mode to gate creation of new test
124+ /// environments. In doing this we can ensure that new test environment creation
125+ /// does not result in ETXTBSY because the FDs in question happen to be in
126+ /// newly `fork()`d but not yet `exec()`d subprocesses of other tests.
127+ pub static CMD_LOCK : Lazy < RwLock < usize > > = Lazy :: new ( || RwLock :: new ( 0 ) ) ;
128+
118129impl ConstState {
119130 fn new ( const_dist_dir : tempfile:: TempDir ) -> Self {
120131 Self {
@@ -185,9 +196,9 @@ pub fn setup_test_state(test_dist_dir: tempfile::TempDir) -> (tempfile::TempDir,
185196 }
186197
187198 let current_exe_path = env:: current_exe ( ) . unwrap ( ) ;
188- let mut exe_dir = current_exe_path. parent ( ) . unwrap ( ) ;
189- if exe_dir . ends_with ( "deps" ) {
190- exe_dir = exe_dir . parent ( ) . unwrap ( ) ;
199+ let mut built_exe_dir = current_exe_path. parent ( ) . unwrap ( ) ;
200+ if built_exe_dir . ends_with ( "deps" ) {
201+ built_exe_dir = built_exe_dir . parent ( ) . unwrap ( ) ;
191202 }
192203 let test_dir = rustup_test:: test_dir ( ) . unwrap ( ) ;
193204
@@ -221,19 +232,50 @@ pub fn setup_test_state(test_dist_dir: tempfile::TempDir) -> (tempfile::TempDir,
221232 homedir,
222233 rustup_update_root : None ,
223234 workdir : RefCell :: new ( workdir) ,
235+ test_root_dir : test_dir. path ( ) . to_path_buf ( ) ,
224236 } ;
225237
226- let build_path = exe_dir . join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
238+ let build_path = built_exe_dir . join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
227239
228240 let rustup_path = config. exedir . join ( format ! ( "rustup{EXE_SUFFIX}" ) ) ;
229- let setup_path = config. exedir . join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
241+ // Used to create dist servers. Perhaps should only link when needed?
242+ let init_path = config. exedir . join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
230243 let rustc_path = config. exedir . join ( format ! ( "rustc{EXE_SUFFIX}" ) ) ;
231244 let cargo_path = config. exedir . join ( format ! ( "cargo{EXE_SUFFIX}" ) ) ;
232245 let rls_path = config. exedir . join ( format ! ( "rls{EXE_SUFFIX}" ) ) ;
233246 let rust_lldb_path = config. exedir . join ( format ! ( "rust-lldb{EXE_SUFFIX}" ) ) ;
234247
235- copy_binary ( build_path, & rustup_path) . unwrap ( ) ;
236- hard_link ( & rustup_path, setup_path) . unwrap ( ) ;
248+ const ESTIMATED_LINKS_PER_TEST : usize = 6 * 2 ;
249+ // NTFS has a limit of 1023 links per file; test setup creates 6 links, and
250+ // then some tests will link the cached installer to rustup/cargo etc,
251+ // adding more links
252+ const MAX_TESTS_PER_RUSTUP_EXE : usize = 1023 / ESTIMATED_LINKS_PER_TEST ;
253+ // This returning-result inner structure allows failures without poisoning
254+ // cmd_lock.
255+ {
256+ fn link_or_copy (
257+ original : & Path ,
258+ link : & Path ,
259+ lock : & mut RwLockWriteGuard < usize > ,
260+ ) -> io:: Result < ( ) > {
261+ * * lock += 1 ;
262+ if * * lock < MAX_TESTS_PER_RUSTUP_EXE {
263+ hard_link ( original, link)
264+ } else {
265+ // break the *original* so new tests form a new distinct set of
266+ // links. Do this by copying to link, breaking the source,
267+ // linking back.
268+ * * lock = 0 ;
269+ fs:: copy ( original, link) ?;
270+ fs:: remove_file ( original) ?;
271+ hard_link ( link, original)
272+ }
273+ }
274+ let mut lock = CMD_LOCK . write ( ) . unwrap ( ) ;
275+ link_or_copy ( & build_path, & rustup_path, & mut lock)
276+ }
277+ . unwrap ( ) ;
278+ hard_link ( & rustup_path, init_path) . unwrap ( ) ;
237279 hard_link ( & rustup_path, rustc_path) . unwrap ( ) ;
238280 hard_link ( & rustup_path, cargo_path) . unwrap ( ) ;
239281 hard_link ( & rustup_path, rls_path) . unwrap ( ) ;
@@ -282,6 +324,8 @@ fn create_local_update_server(self_dist: &Path, exedir: &Path, version: &str) ->
282324
283325 fs:: create_dir_all ( dist_dir) . unwrap ( ) ;
284326 output_release_file ( self_dist, "1" , version) ;
327+ // TODO: should this hardlink since the modify-codepath presumes it has to
328+ // link break?
285329 fs:: copy ( rustup_bin, dist_exe) . unwrap ( ) ;
286330
287331 let root_url = format ! ( "file://{}" , self_dist. display( ) ) ;
@@ -291,9 +335,10 @@ fn create_local_update_server(self_dist: &Path, exedir: &Path, version: &str) ->
291335pub fn self_update_setup ( f : & dyn Fn ( & mut Config , & Path ) , version : & str ) {
292336 test ( Scenario :: SimpleV2 , & |config| {
293337 // Create a mock self-update server
338+
294339 let self_dist_tmp = tempfile:: Builder :: new ( )
295340 . prefix ( "self_dist" )
296- . tempdir ( )
341+ . tempdir_in ( & config . test_root_dir )
297342 . unwrap ( ) ;
298343 let self_dist = self_dist_tmp. path ( ) ;
299344
@@ -303,9 +348,21 @@ pub fn self_update_setup(f: &dyn Fn(&mut Config, &Path), version: &str) {
303348 let trip = this_host_triple ( ) ;
304349 let dist_dir = self_dist. join ( format ! ( "archive/{version}/{trip}" ) ) ;
305350 let dist_exe = dist_dir. join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
351+ let dist_tmp = dist_dir. join ( "rustup-init-tmp" ) ;
306352
307353 // Modify the exe so it hashes different
308- raw:: append_file ( & dist_exe, "" ) . unwrap ( ) ;
354+ // 1) move out of the way the file
355+ fs:: rename ( & dist_exe, & dist_tmp) . unwrap ( ) ;
356+ // 2) copy it
357+ fs:: copy ( dist_tmp, & dist_exe) . unwrap ( ) ;
358+ // modify it
359+ let mut dest_file = fs:: OpenOptions :: new ( )
360+ . write ( true )
361+ . append ( true )
362+ . create ( true )
363+ . open ( dist_exe)
364+ . unwrap ( ) ;
365+ writeln ! ( dest_file) . unwrap ( ) ;
309366
310367 f ( config, self_dist) ;
311368 } ) ;
@@ -322,8 +379,21 @@ pub fn with_update_server(config: &mut Config, version: &str, f: &dyn Fn(&mut Co
322379 let trip = this_host_triple ( ) ;
323380 let dist_dir = self_dist. join ( format ! ( "archive/{version}/{trip}" ) ) ;
324381 let dist_exe = dist_dir. join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
382+ let dist_tmp = dist_dir. join ( "rustup-init-tmp" ) ;
383+
325384 // Modify the exe so it hashes different
326- raw:: append_file ( & dist_exe, "" ) . unwrap ( ) ;
385+ // 1) move out of the way the file
386+ fs:: rename ( & dist_exe, & dist_tmp) . unwrap ( ) ;
387+ // 2) copy it
388+ fs:: copy ( dist_tmp, & dist_exe) . unwrap ( ) ;
389+ // modify it
390+ let mut dest_file = fs:: OpenOptions :: new ( )
391+ . write ( true )
392+ . append ( true )
393+ . create ( true )
394+ . open ( dist_exe)
395+ . unwrap ( ) ;
396+ writeln ! ( dest_file) . unwrap ( ) ;
327397
328398 config. rustup_update_root = Some ( root_url) ;
329399 f ( config) ;
@@ -663,7 +733,7 @@ impl Config {
663733
664734 let mut retries = 8 ;
665735 let out = loop {
666- let lock = cmd_lock ( ) . read ( ) . unwrap ( ) ;
736+ let lock = CMD_LOCK . read ( ) . unwrap ( ) ;
667737 let out = cmd. output ( ) ;
668738 drop ( lock) ;
669739 match out {
@@ -753,22 +823,6 @@ pub fn env<E: rustup_test::Env>(config: &Config, cmd: &mut E) {
753823 config. env ( cmd)
754824}
755825
756- use std:: sync:: RwLock ;
757-
758- /// Returns the lock to be used when creating test environments.
759- ///
760- /// Essentially we use this in `.read()` mode to gate access to `fork()`
761- /// new subprocesses, and in `.write()` mode to gate creation of new test
762- /// environments. In doing this we can ensure that new test environment creation
763- /// does not result in ETXTBSY because the FDs in question happen to be in
764- /// newly `fork()`d but not yet `exec()`d subprocesses of other tests.
765- pub fn cmd_lock ( ) -> & ' static RwLock < ( ) > {
766- lazy_static ! {
767- static ref LOCK : RwLock <( ) > = RwLock :: new( ( ) ) ;
768- } ;
769- & LOCK
770- }
771-
772826fn allow_inprocess < I , A > ( name : & str , args : I ) -> bool
773827where
774828 I : IntoIterator < Item = A > ,
@@ -1484,7 +1538,7 @@ fn create_custom_toolchains(customdir: &Path) {
14841538 }
14851539}
14861540
1487- pub fn hard_link < A , B > ( a : A , b : B ) -> io:: Result < ( ) >
1541+ pub fn hard_link < A , B > ( original : A , link : B ) -> io:: Result < ( ) >
14881542where
14891543 A : AsRef < Path > ,
14901544 B : AsRef < Path > ,
@@ -1496,21 +1550,5 @@ where
14961550 }
14971551 fs:: hard_link ( a, b) . map ( drop)
14981552 }
1499- inner ( a. as_ref ( ) , b. as_ref ( ) )
1500- }
1501-
1502- pub fn copy_binary < A , B > ( a : A , b : B ) -> io:: Result < ( ) >
1503- where
1504- A : AsRef < Path > ,
1505- B : AsRef < Path > ,
1506- {
1507- fn inner ( a : & Path , b : & Path ) -> io:: Result < ( ) > {
1508- match fs:: remove_file ( b) {
1509- Err ( e) if e. kind ( ) != io:: ErrorKind :: NotFound => return Err ( e) ,
1510- _ => { }
1511- }
1512- fs:: copy ( a, b) . map ( drop)
1513- }
1514- let _lock = cmd_lock ( ) . write ( ) . unwrap ( ) ;
1515- inner ( a. as_ref ( ) , b. as_ref ( ) )
1553+ inner ( original. as_ref ( ) , link. as_ref ( ) )
15161554}
0 commit comments