@@ -6,10 +6,10 @@ use std::env;
6
6
use std:: env:: consts:: EXE_SUFFIX ;
7
7
use std:: ffi:: OsStr ;
8
8
use std:: fs;
9
- use std:: io;
9
+ use std:: io:: { self , Write } ;
10
10
use std:: path:: { Path , PathBuf } ;
11
11
use std:: process:: Command ;
12
- use std:: sync:: Arc ;
12
+ use std:: sync:: { Arc , RwLock , RwLockWriteGuard } ;
13
13
use std:: time:: Instant ;
14
14
15
15
use enum_map:: { enum_map, Enum , EnumMap } ;
@@ -53,6 +53,8 @@ pub struct Config {
53
53
pub rustup_update_root : Option < String > ,
54
54
/// This is cwd for the test
55
55
pub workdir : RefCell < PathBuf > ,
56
+ /// This is the test root for keeping stuff together
57
+ pub test_root_dir : PathBuf ,
56
58
}
57
59
58
60
// Describes all the features of the mock dist server.
@@ -115,6 +117,15 @@ struct ConstState {
115
117
const_dist_dir : tempfile:: TempDir ,
116
118
}
117
119
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
+
118
129
impl ConstState {
119
130
fn new ( const_dist_dir : tempfile:: TempDir ) -> Self {
120
131
Self {
@@ -185,9 +196,9 @@ pub fn setup_test_state(test_dist_dir: tempfile::TempDir) -> (tempfile::TempDir,
185
196
}
186
197
187
198
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 ( ) ;
191
202
}
192
203
let test_dir = rustup_test:: test_dir ( ) . unwrap ( ) ;
193
204
@@ -221,19 +232,50 @@ pub fn setup_test_state(test_dist_dir: tempfile::TempDir) -> (tempfile::TempDir,
221
232
homedir,
222
233
rustup_update_root : None ,
223
234
workdir : RefCell :: new ( workdir) ,
235
+ test_root_dir : test_dir. path ( ) . to_path_buf ( ) ,
224
236
} ;
225
237
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}" ) ) ;
227
239
228
240
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}" ) ) ;
230
243
let rustc_path = config. exedir . join ( format ! ( "rustc{EXE_SUFFIX}" ) ) ;
231
244
let cargo_path = config. exedir . join ( format ! ( "cargo{EXE_SUFFIX}" ) ) ;
232
245
let rls_path = config. exedir . join ( format ! ( "rls{EXE_SUFFIX}" ) ) ;
233
246
let rust_lldb_path = config. exedir . join ( format ! ( "rust-lldb{EXE_SUFFIX}" ) ) ;
234
247
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 ( ) ;
237
279
hard_link ( & rustup_path, rustc_path) . unwrap ( ) ;
238
280
hard_link ( & rustup_path, cargo_path) . unwrap ( ) ;
239
281
hard_link ( & rustup_path, rls_path) . unwrap ( ) ;
@@ -282,6 +324,8 @@ fn create_local_update_server(self_dist: &Path, exedir: &Path, version: &str) ->
282
324
283
325
fs:: create_dir_all ( dist_dir) . unwrap ( ) ;
284
326
output_release_file ( self_dist, "1" , version) ;
327
+ // TODO: should this hardlink since the modify-codepath presumes it has to
328
+ // link break?
285
329
fs:: copy ( rustup_bin, dist_exe) . unwrap ( ) ;
286
330
287
331
let root_url = format ! ( "file://{}" , self_dist. display( ) ) ;
@@ -291,9 +335,10 @@ fn create_local_update_server(self_dist: &Path, exedir: &Path, version: &str) ->
291
335
pub fn self_update_setup ( f : & dyn Fn ( & mut Config , & Path ) , version : & str ) {
292
336
test ( Scenario :: SimpleV2 , & |config| {
293
337
// Create a mock self-update server
338
+
294
339
let self_dist_tmp = tempfile:: Builder :: new ( )
295
340
. prefix ( "self_dist" )
296
- . tempdir ( )
341
+ . tempdir_in ( & config . test_root_dir )
297
342
. unwrap ( ) ;
298
343
let self_dist = self_dist_tmp. path ( ) ;
299
344
@@ -303,9 +348,21 @@ pub fn self_update_setup(f: &dyn Fn(&mut Config, &Path), version: &str) {
303
348
let trip = this_host_triple ( ) ;
304
349
let dist_dir = self_dist. join ( format ! ( "archive/{version}/{trip}" ) ) ;
305
350
let dist_exe = dist_dir. join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
351
+ let dist_tmp = dist_dir. join ( "rustup-init-tmp" ) ;
306
352
307
353
// 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 ( ) ;
309
366
310
367
f ( config, self_dist) ;
311
368
} ) ;
@@ -322,8 +379,21 @@ pub fn with_update_server(config: &mut Config, version: &str, f: &dyn Fn(&mut Co
322
379
let trip = this_host_triple ( ) ;
323
380
let dist_dir = self_dist. join ( format ! ( "archive/{version}/{trip}" ) ) ;
324
381
let dist_exe = dist_dir. join ( format ! ( "rustup-init{EXE_SUFFIX}" ) ) ;
382
+ let dist_tmp = dist_dir. join ( "rustup-init-tmp" ) ;
383
+
325
384
// 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 ( ) ;
327
397
328
398
config. rustup_update_root = Some ( root_url) ;
329
399
f ( config) ;
@@ -663,7 +733,7 @@ impl Config {
663
733
664
734
let mut retries = 8 ;
665
735
let out = loop {
666
- let lock = cmd_lock ( ) . read ( ) . unwrap ( ) ;
736
+ let lock = CMD_LOCK . read ( ) . unwrap ( ) ;
667
737
let out = cmd. output ( ) ;
668
738
drop ( lock) ;
669
739
match out {
@@ -753,22 +823,6 @@ pub fn env<E: rustup_test::Env>(config: &Config, cmd: &mut E) {
753
823
config. env ( cmd)
754
824
}
755
825
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
-
772
826
fn allow_inprocess < I , A > ( name : & str , args : I ) -> bool
773
827
where
774
828
I : IntoIterator < Item = A > ,
@@ -1484,7 +1538,7 @@ fn create_custom_toolchains(customdir: &Path) {
1484
1538
}
1485
1539
}
1486
1540
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 < ( ) >
1488
1542
where
1489
1543
A : AsRef < Path > ,
1490
1544
B : AsRef < Path > ,
@@ -1496,21 +1550,5 @@ where
1496
1550
}
1497
1551
fs:: hard_link ( a, b) . map ( drop)
1498
1552
}
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 ( ) )
1516
1554
}
0 commit comments