]> git.proxmox.com Git - cargo.git/blob - tests/testsuite/concurrent.rs
Upgrade to Rust 2018
[cargo.git] / tests / testsuite / concurrent.rs
1 use std::fs::{self, File};
2 use std::io::Write;
3 use std::net::TcpListener;
4 use std::process::Stdio;
5 use std::sync::mpsc::channel;
6 use std::thread;
7 use std::time::Duration;
8 use std::{env, str};
9
10 use git2;
11 use crate::support::cargo_process;
12 use crate::support::git;
13 use crate::support::install::{cargo_home, assert_has_installed_exe};
14 use crate::support::registry::Package;
15 use crate::support::{basic_manifest, execs, project};
16
17 fn pkg(name: &str, vers: &str) {
18 Package::new(name, vers)
19 .file("src/main.rs", "fn main() {{}}")
20 .publish();
21 }
22
23 #[test]
24 fn multiple_installs() {
25 let p = project()
26 .no_manifest()
27 .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0"))
28 .file("a/src/main.rs", "fn main() {}")
29 .file("b/Cargo.toml", &basic_manifest("bar", "0.0.0"))
30 .file("b/src/main.rs", "fn main() {}");
31 let p = p.build();
32
33 let mut a = p.cargo("install").cwd(p.root().join("a")).build_command();
34 let mut b = p.cargo("install").cwd(p.root().join("b")).build_command();
35
36 a.stdout(Stdio::piped()).stderr(Stdio::piped());
37 b.stdout(Stdio::piped()).stderr(Stdio::piped());
38
39 let a = a.spawn().unwrap();
40 let b = b.spawn().unwrap();
41 let a = thread::spawn(move || a.wait_with_output().unwrap());
42 let b = b.wait_with_output().unwrap();
43 let a = a.join().unwrap();
44
45 execs().run_output(&a);
46 execs().run_output(&b);
47
48 assert_has_installed_exe(cargo_home(), "foo");
49 assert_has_installed_exe(cargo_home(), "bar");
50 }
51
52 #[test]
53 fn concurrent_installs() {
54 const LOCKED_BUILD: &str = "waiting for file lock on build directory";
55
56 pkg("foo", "0.0.1");
57 pkg("bar", "0.0.1");
58
59 let mut a = cargo_process("install foo").build_command();
60 let mut b = cargo_process("install bar").build_command();
61
62 a.stdout(Stdio::piped()).stderr(Stdio::piped());
63 b.stdout(Stdio::piped()).stderr(Stdio::piped());
64
65 let a = a.spawn().unwrap();
66 let b = b.spawn().unwrap();
67 let a = thread::spawn(move || a.wait_with_output().unwrap());
68 let b = b.wait_with_output().unwrap();
69 let a = a.join().unwrap();
70
71 assert!(!str::from_utf8(&a.stderr).unwrap().contains(LOCKED_BUILD));
72 assert!(!str::from_utf8(&b.stderr).unwrap().contains(LOCKED_BUILD));
73
74 execs().run_output(&a);
75 execs().run_output(&b);
76
77 assert_has_installed_exe(cargo_home(), "foo");
78 assert_has_installed_exe(cargo_home(), "bar");
79 }
80
81 #[test]
82 fn one_install_should_be_bad() {
83 let p = project()
84 .no_manifest()
85 .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0"))
86 .file("a/src/main.rs", "fn main() {}")
87 .file("b/Cargo.toml", &basic_manifest("foo", "0.0.0"))
88 .file("b/src/main.rs", "fn main() {}");
89 let p = p.build();
90
91 let mut a = p.cargo("install").cwd(p.root().join("a")).build_command();
92 let mut b = p.cargo("install").cwd(p.root().join("b")).build_command();
93
94 a.stdout(Stdio::piped()).stderr(Stdio::piped());
95 b.stdout(Stdio::piped()).stderr(Stdio::piped());
96
97 let a = a.spawn().unwrap();
98 let b = b.spawn().unwrap();
99 let a = thread::spawn(move || a.wait_with_output().unwrap());
100 let b = b.wait_with_output().unwrap();
101 let a = a.join().unwrap();
102
103 let (bad, good) = if a.status.code() == Some(101) {
104 (a, b)
105 } else {
106 (b, a)
107 };
108 execs()
109 .with_status(101)
110 .with_stderr_contains(
111 "[ERROR] binary `foo[..]` already exists in destination as part of `[..]`",
112 ).run_output(&bad);
113 execs()
114 .with_stderr_contains("warning: be sure to add `[..]` to your PATH [..]")
115 .run_output(&good);
116
117 assert_has_installed_exe(cargo_home(), "foo");
118 }
119
120 #[test]
121 fn multiple_registry_fetches() {
122 let mut pkg = Package::new("bar", "1.0.2");
123 for i in 0..10 {
124 let name = format!("foo{}", i);
125 Package::new(&name, "1.0.0").publish();
126 pkg.dep(&name, "*");
127 }
128 pkg.publish();
129
130 let p = project()
131 .no_manifest()
132 .file(
133 "a/Cargo.toml",
134 r#"
135 [package]
136 name = "foo"
137 authors = []
138 version = "0.0.0"
139
140 [dependencies]
141 bar = "*"
142 "#,
143 ).file("a/src/main.rs", "fn main() {}")
144 .file(
145 "b/Cargo.toml",
146 r#"
147 [package]
148 name = "bar"
149 authors = []
150 version = "0.0.0"
151
152 [dependencies]
153 bar = "*"
154 "#,
155 ).file("b/src/main.rs", "fn main() {}");
156 let p = p.build();
157
158 let mut a = p.cargo("build").cwd(p.root().join("a")).build_command();
159 let mut b = p.cargo("build").cwd(p.root().join("b")).build_command();
160
161 a.stdout(Stdio::piped()).stderr(Stdio::piped());
162 b.stdout(Stdio::piped()).stderr(Stdio::piped());
163
164 let a = a.spawn().unwrap();
165 let b = b.spawn().unwrap();
166 let a = thread::spawn(move || a.wait_with_output().unwrap());
167 let b = b.wait_with_output().unwrap();
168 let a = a.join().unwrap();
169
170 execs().run_output(&a);
171 execs().run_output(&b);
172
173 let suffix = env::consts::EXE_SUFFIX;
174 assert!(
175 p.root()
176 .join("a/target/debug")
177 .join(format!("foo{}", suffix))
178 .is_file()
179 );
180 assert!(
181 p.root()
182 .join("b/target/debug")
183 .join(format!("bar{}", suffix))
184 .is_file()
185 );
186 }
187
188 #[test]
189 fn git_same_repo_different_tags() {
190 let a = git::new("dep", |project| {
191 project
192 .file("Cargo.toml", &basic_manifest("dep", "0.5.0"))
193 .file("src/lib.rs", "pub fn tag1() {}")
194 }).unwrap();
195
196 let repo = git2::Repository::open(&a.root()).unwrap();
197 git::tag(&repo, "tag1");
198
199 File::create(a.root().join("src/lib.rs"))
200 .unwrap()
201 .write_all(b"pub fn tag2() {}")
202 .unwrap();
203 git::add(&repo);
204 git::commit(&repo);
205 git::tag(&repo, "tag2");
206
207 let p = project()
208 .no_manifest()
209 .file(
210 "a/Cargo.toml",
211 &format!(
212 r#"
213 [package]
214 name = "foo"
215 authors = []
216 version = "0.0.0"
217
218 [dependencies]
219 dep = {{ git = '{}', tag = 'tag1' }}
220 "#,
221 a.url()
222 ),
223 ).file(
224 "a/src/main.rs",
225 "extern crate dep; fn main() { dep::tag1(); }",
226 ).file(
227 "b/Cargo.toml",
228 &format!(
229 r#"
230 [package]
231 name = "bar"
232 authors = []
233 version = "0.0.0"
234
235 [dependencies]
236 dep = {{ git = '{}', tag = 'tag2' }}
237 "#,
238 a.url()
239 ),
240 ).file(
241 "b/src/main.rs",
242 "extern crate dep; fn main() { dep::tag2(); }",
243 );
244 let p = p.build();
245
246 let mut a = p.cargo("build -v").cwd(p.root().join("a")).build_command();
247 let mut b = p.cargo("build -v").cwd(p.root().join("b")).build_command();
248
249 a.stdout(Stdio::piped()).stderr(Stdio::piped());
250 b.stdout(Stdio::piped()).stderr(Stdio::piped());
251
252 let a = a.spawn().unwrap();
253 let b = b.spawn().unwrap();
254 let a = thread::spawn(move || a.wait_with_output().unwrap());
255 let b = b.wait_with_output().unwrap();
256 let a = a.join().unwrap();
257
258 execs().run_output(&a);
259 execs().run_output(&b);
260 }
261
262 #[test]
263 fn git_same_branch_different_revs() {
264 let a = git::new("dep", |project| {
265 project
266 .file("Cargo.toml", &basic_manifest("dep", "0.5.0"))
267 .file("src/lib.rs", "pub fn f1() {}")
268 }).unwrap();
269
270 let p = project()
271 .no_manifest()
272 .file(
273 "a/Cargo.toml",
274 &format!(
275 r#"
276 [package]
277 name = "foo"
278 authors = []
279 version = "0.0.0"
280
281 [dependencies]
282 dep = {{ git = '{}' }}
283 "#,
284 a.url()
285 ),
286 ).file(
287 "a/src/main.rs",
288 "extern crate dep; fn main() { dep::f1(); }",
289 ).file(
290 "b/Cargo.toml",
291 &format!(
292 r#"
293 [package]
294 name = "bar"
295 authors = []
296 version = "0.0.0"
297
298 [dependencies]
299 dep = {{ git = '{}' }}
300 "#,
301 a.url()
302 ),
303 ).file(
304 "b/src/main.rs",
305 "extern crate dep; fn main() { dep::f2(); }",
306 );
307 let p = p.build();
308
309 // Generate a Cargo.lock pointing at the current rev, then clear out the
310 // target directory
311 p.cargo("build").cwd(p.root().join("a")).run();
312 fs::remove_dir_all(p.root().join("a/target")).unwrap();
313
314 // Make a new commit on the master branch
315 let repo = git2::Repository::open(&a.root()).unwrap();
316 File::create(a.root().join("src/lib.rs"))
317 .unwrap()
318 .write_all(b"pub fn f2() {}")
319 .unwrap();
320 git::add(&repo);
321 git::commit(&repo);
322
323 // Now run both builds in parallel. The build of `b` should pick up the
324 // newest commit while the build of `a` should use the locked old commit.
325 let mut a = p.cargo("build").cwd(p.root().join("a")).build_command();
326 let mut b = p.cargo("build").cwd(p.root().join("b")).build_command();
327
328 a.stdout(Stdio::piped()).stderr(Stdio::piped());
329 b.stdout(Stdio::piped()).stderr(Stdio::piped());
330
331 let a = a.spawn().unwrap();
332 let b = b.spawn().unwrap();
333 let a = thread::spawn(move || a.wait_with_output().unwrap());
334 let b = b.wait_with_output().unwrap();
335 let a = a.join().unwrap();
336
337 execs().run_output(&a);
338 execs().run_output(&b);
339 }
340
341 #[test]
342 fn same_project() {
343 let p = project()
344 .file("src/main.rs", "fn main() {}")
345 .file("src/lib.rs", "");
346 let p = p.build();
347
348 let mut a = p.cargo("build").build_command();
349 let mut b = p.cargo("build").build_command();
350
351 a.stdout(Stdio::piped()).stderr(Stdio::piped());
352 b.stdout(Stdio::piped()).stderr(Stdio::piped());
353
354 let a = a.spawn().unwrap();
355 let b = b.spawn().unwrap();
356 let a = thread::spawn(move || a.wait_with_output().unwrap());
357 let b = b.wait_with_output().unwrap();
358 let a = a.join().unwrap();
359
360 execs().run_output(&a);
361 execs().run_output(&b);
362 }
363
364 // Make sure that if Cargo dies while holding a lock that it's released and the
365 // next Cargo to come in will take over cleanly.
366 // older win versions don't support job objects, so skip test there
367 #[test]
368 #[cfg_attr(target_os = "windows", ignore)]
369 fn killing_cargo_releases_the_lock() {
370 let p = project()
371 .file(
372 "Cargo.toml",
373 r#"
374 [package]
375 name = "foo"
376 authors = []
377 version = "0.0.0"
378 build = "build.rs"
379 "#,
380 ).file("src/main.rs", "fn main() {}")
381 .file(
382 "build.rs",
383 r#"
384 use std::net::TcpStream;
385
386 fn main() {
387 if std::env::var("A").is_ok() {
388 TcpStream::connect(&std::env::var("ADDR").unwrap()[..])
389 .unwrap();
390 std::thread::sleep(std::time::Duration::new(10, 0));
391 }
392 }
393 "#,
394 );
395 let p = p.build();
396
397 // Our build script will connect to our local TCP socket to inform us that
398 // it's started and that's how we know that `a` will have the lock
399 // when we kill it.
400 let l = TcpListener::bind("127.0.0.1:0").unwrap();
401 let mut a = p.cargo("build").build_command();
402 let mut b = p.cargo("build").build_command();
403 a.stdout(Stdio::piped()).stderr(Stdio::piped());
404 b.stdout(Stdio::piped()).stderr(Stdio::piped());
405 a.env("ADDR", l.local_addr().unwrap().to_string())
406 .env("A", "a");
407 b.env("ADDR", l.local_addr().unwrap().to_string())
408 .env_remove("A");
409
410 // Spawn `a`, wait for it to get to the build script (at which point the
411 // lock is held), then kill it.
412 let mut a = a.spawn().unwrap();
413 l.accept().unwrap();
414 a.kill().unwrap();
415
416 // Spawn `b`, then just finish the output of a/b the same way the above
417 // tests does.
418 let b = b.spawn().unwrap();
419 let a = thread::spawn(move || a.wait_with_output().unwrap());
420 let b = b.wait_with_output().unwrap();
421 let a = a.join().unwrap();
422
423 // We killed `a`, so it shouldn't succeed, but `b` should have succeeded.
424 assert!(!a.status.success());
425 execs().run_output(&b);
426 }
427
428 #[test]
429 fn debug_release_ok() {
430 let p = project().file("src/main.rs", "fn main() {}");
431 let p = p.build();
432
433 p.cargo("build").run();
434 fs::remove_dir_all(p.root().join("target")).unwrap();
435
436 let mut a = p.cargo("build").build_command();
437 let mut b = p.cargo("build --release").build_command();
438 a.stdout(Stdio::piped()).stderr(Stdio::piped());
439 b.stdout(Stdio::piped()).stderr(Stdio::piped());
440 let a = a.spawn().unwrap();
441 let b = b.spawn().unwrap();
442 let a = thread::spawn(move || a.wait_with_output().unwrap());
443 let b = b.wait_with_output().unwrap();
444 let a = a.join().unwrap();
445
446 execs()
447 .with_stderr(
448 "\
449 [COMPILING] foo v0.0.1 [..]
450 [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
451 ",
452 ).run_output(&a);
453 execs()
454 .with_stderr(
455 "\
456 [COMPILING] foo v0.0.1 [..]
457 [FINISHED] release [optimized] target(s) in [..]
458 ",
459 ).run_output(&b);
460 }
461
462 #[test]
463 fn no_deadlock_with_git_dependencies() {
464 let dep1 = git::new("dep1", |project| {
465 project
466 .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
467 .file("src/lib.rs", "")
468 }).unwrap();
469
470 let dep2 = git::new("dep2", |project| {
471 project
472 .file("Cargo.toml", &basic_manifest("dep2", "0.5.0"))
473 .file("src/lib.rs", "")
474 }).unwrap();
475
476 let p = project()
477 .file(
478 "Cargo.toml",
479 &format!(
480 r#"
481 [package]
482 name = "foo"
483 authors = []
484 version = "0.0.0"
485
486 [dependencies]
487 dep1 = {{ git = '{}' }}
488 dep2 = {{ git = '{}' }}
489 "#,
490 dep1.url(),
491 dep2.url()
492 ),
493 ).file("src/main.rs", "fn main() { }");
494 let p = p.build();
495
496 let n_concurrent_builds = 5;
497
498 let (tx, rx) = channel();
499 for _ in 0..n_concurrent_builds {
500 let cmd = p
501 .cargo("build")
502 .build_command()
503 .stdout(Stdio::piped())
504 .stderr(Stdio::piped())
505 .spawn();
506 let tx = tx.clone();
507 thread::spawn(move || {
508 let result = cmd.unwrap().wait_with_output().unwrap();
509 tx.send(result).unwrap()
510 });
511 }
512
513 for _ in 0..n_concurrent_builds {
514 let result = rx.recv_timeout(Duration::from_secs(30)).expect("Deadlock!");
515 execs().run_output(&result);
516 }
517 }