]> git.proxmox.com Git - cargo.git/blob - tests/testsuite/old_cargos.rs
Stabilize namespaced and weak dependency features.
[cargo.git] / tests / testsuite / old_cargos.rs
1 //! Tests for checking behavior of old cargos.
2 //!
3 //! These tests are ignored because it is intended to be run on a developer
4 //! system with a bunch of toolchains installed. This requires `rustup` to be
5 //! installed. It will iterate over installed toolchains, and run some tests
6 //! over each one, producing a report at the end. As of this writing, I have
7 //! tested 1.0 to 1.51. Run this with:
8 //!
9 //! ```console
10 //! cargo test --test testsuite -- old_cargos --nocapture --ignored
11 //! ```
12
13 use cargo::CargoResult;
14 use cargo_test_support::paths::CargoPathExt;
15 use cargo_test_support::registry::{self, Dependency, Package};
16 use cargo_test_support::{cargo_exe, execs, paths, process, project, rustc_host};
17 use cargo_util::{ProcessBuilder, ProcessError};
18 use semver::Version;
19 use std::fs;
20
21 fn tc_process(cmd: &str, toolchain: &str) -> ProcessBuilder {
22 let mut p = if toolchain == "this" {
23 if cmd == "cargo" {
24 process(&cargo_exe())
25 } else {
26 process(cmd)
27 }
28 } else {
29 let mut cmd = process(cmd);
30 cmd.arg(format!("+{}", toolchain));
31 cmd
32 };
33 // Reset PATH since `process` modifies it to remove rustup.
34 p.env("PATH", std::env::var_os("PATH").unwrap());
35 p
36 }
37
38 /// Returns a sorted list of all toolchains.
39 ///
40 /// The returned value includes the parsed version, and the rustup toolchain
41 /// name as a string.
42 fn collect_all_toolchains() -> Vec<(Version, String)> {
43 let rustc_version = |tc| {
44 let mut cmd = tc_process("rustc", tc);
45 cmd.arg("-V");
46 let output = cmd.exec_with_output().expect("rustc installed");
47 let version = std::str::from_utf8(&output.stdout).unwrap();
48 let parts: Vec<_> = version.split_whitespace().collect();
49 assert_eq!(parts[0], "rustc");
50 assert!(parts[1].starts_with("1."));
51 Version::parse(parts[1]).expect("valid version")
52 };
53
54 // Provide a way to override the list.
55 if let Ok(tcs) = std::env::var("OLD_CARGO") {
56 return tcs
57 .split(',')
58 .map(|tc| (rustc_version(tc), tc.to_string()))
59 .collect();
60 }
61
62 let host = rustc_host();
63 // I tend to have lots of toolchains installed, but I don't want to test
64 // all of them (like dated nightlies, or toolchains for non-host targets).
65 let valid_names = &[
66 format!("stable-{}", host),
67 format!("beta-{}", host),
68 format!("nightly-{}", host),
69 ];
70
71 let output = ProcessBuilder::new("rustup")
72 .args(&["toolchain", "list"])
73 .exec_with_output()
74 .expect("rustup should be installed");
75 let stdout = std::str::from_utf8(&output.stdout).unwrap();
76 let mut toolchains: Vec<_> = stdout
77 .lines()
78 .map(|line| {
79 // Some lines say things like (default), just get the version.
80 line.split_whitespace().next().expect("non-empty line")
81 })
82 .filter(|line| {
83 line.ends_with(&host)
84 && (line.starts_with("1.") || valid_names.iter().any(|name| name == line))
85 })
86 .map(|line| (rustc_version(line), line.to_string()))
87 .collect();
88
89 // Also include *this* cargo.
90 toolchains.push((rustc_version("this"), "this".to_string()));
91 toolchains.sort_by(|a, b| a.0.cmp(&b.0));
92 toolchains
93 }
94
95 // This is a test for exercising the behavior of older versions of cargo with
96 // the new feature syntax.
97 //
98 // The test involves a few dependencies with different feature requirements:
99 //
100 // * `bar` 1.0.0 is the base version that does not use the new syntax.
101 // * `bar` 1.0.1 has a feature with the new syntax, but the feature is unused.
102 // The optional dependency `new-baz-dep` should not be activated.
103 // * `bar` 1.0.2 has a dependency on `baz` that *requires* the new feature
104 // syntax.
105 #[ignore]
106 #[cargo_test]
107 fn new_features() {
108 if std::process::Command::new("rustup").output().is_err() {
109 panic!("old_cargos requires rustup to be installed");
110 }
111 Package::new("new-baz-dep", "1.0.0").publish();
112
113 Package::new("baz", "1.0.0").publish();
114 let baz101_cksum = Package::new("baz", "1.0.1")
115 .add_dep(Dependency::new("new-baz-dep", "1.0").optional(true))
116 .feature("new-feat", &["dep:new-baz-dep"])
117 .publish();
118
119 let bar100_cksum = Package::new("bar", "1.0.0")
120 .add_dep(Dependency::new("baz", "1.0").optional(true))
121 .feature("feat", &["baz"])
122 .publish();
123 let bar101_cksum = Package::new("bar", "1.0.1")
124 .add_dep(Dependency::new("baz", "1.0").optional(true))
125 .feature("feat", &["dep:baz"])
126 .publish();
127 let bar102_cksum = Package::new("bar", "1.0.2")
128 .add_dep(Dependency::new("baz", "1.0").enable_features(&["new-feat"]))
129 .publish();
130
131 let p = project()
132 .file(
133 "Cargo.toml",
134 r#"
135 [package]
136 name = "foo"
137 version = "0.1.0"
138
139 [dependencies]
140 bar = "1.0"
141 "#,
142 )
143 .file("src/lib.rs", "")
144 .build();
145
146 let lock_bar_to = |toolchain_version: &Version, bar_version| {
147 let lock = if toolchain_version < &Version::new(1, 12, 0) {
148 let url = registry::registry_url();
149 match bar_version {
150 100 => format!(
151 r#"
152 [root]
153 name = "foo"
154 version = "0.1.0"
155 dependencies = [
156 "bar 1.0.0 (registry+{url})",
157 ]
158
159 [[package]]
160 name = "bar"
161 version = "1.0.0"
162 source = "registry+{url}"
163 "#,
164 url = url
165 ),
166 101 => format!(
167 r#"
168 [root]
169 name = "foo"
170 version = "0.1.0"
171 dependencies = [
172 "bar 1.0.1 (registry+{url})",
173 ]
174
175 [[package]]
176 name = "bar"
177 version = "1.0.1"
178 source = "registry+{url}"
179 "#,
180 url = url
181 ),
182 102 => format!(
183 r#"
184 [root]
185 name = "foo"
186 version = "0.1.0"
187 dependencies = [
188 "bar 1.0.2 (registry+{url})",
189 ]
190
191 [[package]]
192 name = "bar"
193 version = "1.0.2"
194 source = "registry+{url}"
195 dependencies = [
196 "baz 1.0.1 (registry+{url})",
197 ]
198
199 [[package]]
200 name = "baz"
201 version = "1.0.1"
202 source = "registry+{url}"
203 "#,
204 url = url
205 ),
206 _ => panic!("unexpected version"),
207 }
208 } else {
209 match bar_version {
210 100 => format!(
211 r#"
212 [root]
213 name = "foo"
214 version = "0.1.0"
215 dependencies = [
216 "bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
217 ]
218
219 [[package]]
220 name = "bar"
221 version = "1.0.0"
222 source = "registry+https://github.com/rust-lang/crates.io-index"
223
224 [metadata]
225 "checksum bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
226 "#,
227 bar100_cksum
228 ),
229 101 => format!(
230 r#"
231 [root]
232 name = "foo"
233 version = "0.1.0"
234 dependencies = [
235 "bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
236 ]
237
238 [[package]]
239 name = "bar"
240 version = "1.0.1"
241 source = "registry+https://github.com/rust-lang/crates.io-index"
242
243 [metadata]
244 "checksum bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
245 "#,
246 bar101_cksum
247 ),
248 102 => format!(
249 r#"
250 [root]
251 name = "foo"
252 version = "0.1.0"
253 dependencies = [
254 "bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
255 ]
256
257 [[package]]
258 name = "bar"
259 version = "1.0.2"
260 source = "registry+https://github.com/rust-lang/crates.io-index"
261 dependencies = [
262 "baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
263 ]
264
265 [[package]]
266 name = "baz"
267 version = "1.0.1"
268 source = "registry+https://github.com/rust-lang/crates.io-index"
269
270 [metadata]
271 "checksum bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "{bar102_cksum}"
272 "checksum baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{baz101_cksum}"
273 "#,
274 bar102_cksum = bar102_cksum,
275 baz101_cksum = baz101_cksum
276 ),
277 _ => panic!("unexpected version"),
278 }
279 };
280 p.change_file("Cargo.lock", &lock);
281 };
282
283 let toolchains = collect_all_toolchains();
284
285 let config_path = paths::home().join(".cargo/config");
286 let lock_path = p.root().join("Cargo.lock");
287
288 struct ToolchainBehavior {
289 bar: Option<Version>,
290 baz: Option<Version>,
291 new_baz_dep: Option<Version>,
292 }
293
294 // Collect errors to print at the end. One entry per toolchain, a list of
295 // strings to print.
296 let mut unexpected_results: Vec<Vec<String>> = Vec::new();
297
298 for (version, toolchain) in &toolchains {
299 let mut tc_result = Vec::new();
300 // Write a config appropriate for this version.
301 if version < &Version::new(1, 12, 0) {
302 fs::write(
303 &config_path,
304 format!(
305 r#"
306 [registry]
307 index = "{}"
308 "#,
309 registry::registry_url()
310 ),
311 )
312 .unwrap();
313 } else {
314 fs::write(
315 &config_path,
316 format!(
317 "
318 [source.crates-io]
319 registry = 'https://wut' # only needed by 1.12
320 replace-with = 'dummy-registry'
321
322 [source.dummy-registry]
323 registry = '{}'
324 ",
325 registry::registry_url()
326 ),
327 )
328 .unwrap();
329 }
330
331 // Fetches the version of a package in the lock file.
332 let pkg_version = |pkg| -> Option<Version> {
333 let output = tc_process("cargo", toolchain)
334 .args(&["pkgid", pkg])
335 .cwd(p.root())
336 .exec_with_output()
337 .ok()?;
338 let stdout = std::str::from_utf8(&output.stdout).unwrap();
339 let version = stdout
340 .trim()
341 .rsplitn(2, ':')
342 .next()
343 .expect("version after colon");
344 Some(Version::parse(version).expect("parseable version"))
345 };
346
347 // Runs `cargo build` and returns the versions selected in the lock.
348 let run_cargo = || -> CargoResult<ToolchainBehavior> {
349 match tc_process("cargo", toolchain)
350 .args(&["build", "--verbose"])
351 .cwd(p.root())
352 .exec_with_output()
353 {
354 Ok(_output) => {
355 eprintln!("{} ok", toolchain);
356 let bar = pkg_version("bar");
357 let baz = pkg_version("baz");
358 let new_baz_dep = pkg_version("new-baz-dep");
359 Ok(ToolchainBehavior {
360 bar,
361 baz,
362 new_baz_dep,
363 })
364 }
365 Err(e) => {
366 eprintln!("{} err {}", toolchain, e);
367 Err(e)
368 }
369 }
370 };
371
372 macro_rules! check_lock {
373 ($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, None) => {
374 check_lock!(= $tc_result, $pkg, $which, $actual, None);
375 };
376 ($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
377 check_lock!(= $tc_result, $pkg, $which, $actual, Some(Version::parse($expected).unwrap()));
378 };
379 (= $tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
380 let exp: Option<Version> = $expected;
381 if $actual != $expected {
382 $tc_result.push(format!(
383 "{} for {} saw {:?} but expected {:?}",
384 $which, $pkg, $actual, exp
385 ));
386 }
387 };
388 }
389
390 let check_err_contains = |tc_result: &mut Vec<_>, err: anyhow::Error, contents| {
391 if let Some(ProcessError {
392 stderr: Some(stderr),
393 ..
394 }) = err.downcast_ref::<ProcessError>()
395 {
396 let stderr = std::str::from_utf8(stderr).unwrap();
397 if !stderr.contains(contents) {
398 tc_result.push(format!(
399 "{} expected to see error contents:\n{}\nbut saw:\n{}",
400 toolchain, contents, stderr
401 ));
402 }
403 } else {
404 panic!("{} unexpected error {}", toolchain, err);
405 }
406 };
407
408 // Unlocked behavior.
409 let which = "unlocked";
410 lock_path.rm_rf();
411 p.build_dir().rm_rf();
412 match run_cargo() {
413 Ok(behavior) => {
414 // TODO: Switch to 51 after backport.
415 if version < &Version::new(1, 52, 0) && toolchain != "this" {
416 check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
417 check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
418 check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
419 } else {
420 check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
421 check_lock!(tc_result, "baz", which, behavior.baz, None);
422 check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
423 }
424 }
425 Err(e) => {
426 tc_result.push(format!("unlocked build failed: {}", e));
427 }
428 }
429
430 let which = "locked bar 1.0.0";
431 lock_bar_to(version, 100);
432 match run_cargo() {
433 Ok(behavior) => {
434 check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
435 check_lock!(tc_result, "baz", which, behavior.baz, None);
436 check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
437 }
438 Err(e) => {
439 tc_result.push(format!("bar 1.0.0 locked build failed: {}", e));
440 }
441 }
442
443 let which = "locked bar 1.0.1";
444 lock_bar_to(version, 101);
445 match run_cargo() {
446 Ok(behavior) => {
447 check_lock!(tc_result, "bar", which, behavior.bar, "1.0.1");
448 check_lock!(tc_result, "baz", which, behavior.baz, None);
449 check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
450 }
451 Err(e) => {
452 if toolchain == "this" {
453 // 1.0.1 can't be used without -Znamespaced-features
454 // It gets filtered out of the index.
455 check_err_contains(&mut tc_result, e,
456 "error: failed to select a version for the requirement `bar = \"=1.0.1\"`\n\
457 candidate versions found which didn't match: 1.0.2, 1.0.0"
458 );
459 } else {
460 tc_result.push(format!("bar 1.0.1 locked build failed: {}", e));
461 }
462 }
463 }
464
465 let which = "locked bar 1.0.2";
466 lock_bar_to(version, 102);
467 match run_cargo() {
468 Ok(behavior) => {
469 check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
470 check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
471 check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
472 }
473 Err(e) => {
474 if toolchain == "this" {
475 // baz can't lock to 1.0.1, it requires -Znamespaced-features
476 check_err_contains(&mut tc_result, e,
477 "error: failed to select a version for the requirement `baz = \"=1.0.1\"`\n\
478 candidate versions found which didn't match: 1.0.0"
479 );
480 } else {
481 tc_result.push(format!("bar 1.0.2 locked build failed: {}", e));
482 }
483 }
484 }
485
486 unexpected_results.push(tc_result);
487 }
488
489 // Generate a report.
490 let mut has_err = false;
491 for ((tc_vers, tc_name), errs) in toolchains.iter().zip(unexpected_results) {
492 if errs.is_empty() {
493 continue;
494 }
495 eprintln!("error: toolchain {} (version {}):", tc_name, tc_vers);
496 for err in errs {
497 eprintln!(" {}", err);
498 }
499 has_err = true;
500 }
501 if has_err {
502 panic!("at least one toolchain did not run as expected");
503 }
504 }
505
506 #[cargo_test]
507 #[ignore]
508 fn index_cache_rebuild() {
509 // Checks that the index cache gets rebuilt.
510 //
511 // 1.48 will not cache entries with features with the same name as a
512 // dependency. If the cache does not get rebuilt, then running with
513 // `-Znamespaced-features` would prevent the new cargo from seeing those
514 // entries. The index cache version was changed to prevent this from
515 // happening, and switching between versions should work correctly
516 // (although it will thrash the cash, that's better than not working
517 // correctly.
518 Package::new("baz", "1.0.0").publish();
519 Package::new("bar", "1.0.0").publish();
520 Package::new("bar", "1.0.1")
521 .add_dep(Dependency::new("baz", "1.0").optional(true))
522 .feature("baz", &["dep:baz"])
523 .publish();
524
525 let p = project()
526 .file(
527 "Cargo.toml",
528 r#"
529 [package]
530 name = "foo"
531 version = "0.1.0"
532
533 [dependencies]
534 bar = "1.0"
535 "#,
536 )
537 .file("src/lib.rs", "")
538 .build();
539
540 // This version of Cargo errors on index entries that have overlapping
541 // feature names, so 1.0.1 will be missing.
542 execs()
543 .with_process_builder(tc_process("cargo", "1.48.0"))
544 .arg("check")
545 .cwd(p.root())
546 .with_stderr(
547 "\
548 [UPDATING] [..]
549 [DOWNLOADING] crates ...
550 [DOWNLOADED] bar v1.0.0 [..]
551 [CHECKING] bar v1.0.0
552 [CHECKING] foo v0.1.0 [..]
553 [FINISHED] [..]
554 ",
555 )
556 .run();
557
558 fs::remove_file(p.root().join("Cargo.lock")).unwrap();
559
560 // This should rebuild the cache and use 1.0.1.
561 p.cargo("check")
562 .with_stderr(
563 "\
564 [UPDATING] [..]
565 [DOWNLOADING] crates ...
566 [DOWNLOADED] bar v1.0.1 [..]
567 [CHECKING] bar v1.0.1
568 [CHECKING] foo v0.1.0 [..]
569 [FINISHED] [..]
570 ",
571 )
572 .run();
573
574 fs::remove_file(p.root().join("Cargo.lock")).unwrap();
575
576 // Verify 1.48 can still resolve, and is at 1.0.0.
577 execs()
578 .with_process_builder(tc_process("cargo", "1.48.0"))
579 .arg("tree")
580 .cwd(p.root())
581 .with_stdout(
582 "\
583 foo v0.1.0 [..]
584 └── bar v1.0.0
585 ",
586 )
587 .run();
588 }
589
590 #[cargo_test]
591 #[ignore]
592 fn avoids_split_debuginfo_collision() {
593 // Checks for a bug where .o files were being incorrectly shared between
594 // different toolchains using incremental and split-debuginfo on macOS.
595 let p = project()
596 .file(
597 "Cargo.toml",
598 r#"
599 [package]
600 name = "foo"
601 version = "0.1.0"
602
603 [profile.dev]
604 split-debuginfo = "unpacked"
605 "#,
606 )
607 .file("src/main.rs", "fn main() {}")
608 .build();
609
610 execs()
611 .with_process_builder(tc_process("cargo", "stable"))
612 .arg("build")
613 .env("CARGO_INCREMENTAL", "1")
614 .cwd(p.root())
615 .with_stderr(
616 "\
617 [COMPILING] foo v0.1.0 [..]
618 [FINISHED] [..]
619 ",
620 )
621 .run();
622
623 p.cargo("build")
624 .env("CARGO_INCREMENTAL", "1")
625 .with_stderr(
626 "\
627 [COMPILING] foo v0.1.0 [..]
628 [FINISHED] [..]
629 ",
630 )
631 .run();
632
633 execs()
634 .with_process_builder(tc_process("cargo", "stable"))
635 .arg("build")
636 .env("CARGO_INCREMENTAL", "1")
637 .cwd(p.root())
638 .with_stderr(
639 "\
640 [COMPILING] foo v0.1.0 [..]
641 [FINISHED] [..]
642 ",
643 )
644 .run();
645 }