//! Tests for future-incompat-report messages //! //! Note that these tests use the -Zfuture-incompat-test for rustc. //! This causes rustc to treat *every* lint as future-incompatible. //! This is done because future-incompatible lints are inherently //! ephemeral, but we don't want to continually update these tests. //! So we pick some random lint that will likely always be the same //! over time. use cargo_test_support::prelude::*; use cargo_test_support::registry::Package; use cargo_test_support::{basic_manifest, project, str, Project}; use super::config::write_config_toml; // An arbitrary lint (unused_variables) that triggers a lint. // We use a special flag to force it to generate a report. const FUTURE_EXAMPLE: &'static str = "fn main() { let x = 1; }"; // Some text that will be displayed when the lint fires. const FUTURE_OUTPUT: &'static str = "[..]unused variable[..]"; fn simple_project() -> Project { project() .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) .file("src/main.rs", FUTURE_EXAMPLE) .build() } #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn output_on_stable() { let p = simple_project(); p.cargo("check") .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data(str![[r#" ... [WARNING] unused variable: `x` ... [NOTE] to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1` "#]]) .run(); } // This feature is stable, and should not be gated #[cargo_test] fn no_gate_future_incompat_report() { let p = simple_project(); p.cargo("check --future-incompat-report") .with_status(0) .run(); p.cargo("report future-incompatibilities --id foo") .with_stderr_data(str![[r#" [ERROR] no reports are currently available "#]]) .with_status(101) .run(); } #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn test_zero_future_incompat() { let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) .file("src/main.rs", "fn main() {}") .build(); // No note if --future-incompat-report is not specified. p.cargo("check") .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data(str![[r#" [CHECKING] foo v0.0.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); p.cargo("check --future-incompat-report") .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data(str![[r#" [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [NOTE] 0 dependencies had future-incompatible warnings "#]]) .run(); } #[allow(deprecated)] #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn test_single_crate() { let p = simple_project(); for command in &["build", "check", "rustc", "test"] { let check_has_future_compat = || { p.cargo(command) .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data("\ ... [WARNING] unused variable: `x` ... [WARNING] the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 ([ROOT]/foo) ... ") .with_stderr_does_not_contain("[..]incompatibility[..]") .run(); }; // Check that we show a message with no [future-incompat-report] config section write_config_toml(""); check_has_future_compat(); // Check that we show a message with `frequency = "always"` write_config_toml( "\ [future-incompat-report] frequency = 'always' ", ); check_has_future_compat(); // Check that we do not show a message with `frequency = "never"` write_config_toml( "\ [future-incompat-report] frequency = 'never' ", ); p.cargo(command) .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data( "\ [WARNING] unused variable: `x` ... ", ) .with_stderr_does_not_contain("[..]rejected[..]") .with_stderr_does_not_contain("[..]incompatibility[..]") .run(); // Check that passing `--future-incompat-report` overrides `frequency = 'never'` p.cargo(command).arg("--future-incompat-report") .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data("\ [WARNING] unused variable: `x` ... [WARNING] the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 ([ROOT]/foo) ... - foo@0.0.0 ... ") .run(); } } #[allow(deprecated)] #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn test_multi_crate() { Package::new("first-dep", "0.0.1") .file("src/lib.rs", FUTURE_EXAMPLE) .publish(); Package::new("second-dep", "0.0.2") .file("src/lib.rs", FUTURE_EXAMPLE) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.0" [dependencies] first-dep = "*" second-dep = "*" "#, ) .file("src/lib.rs", "") .build(); for command in &["build", "check", "rustc", "test"] { p.cargo(command) .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_does_not_contain(FUTURE_OUTPUT) .with_stderr_data("\ ... [WARNING] the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2 ... ") // Check that we don't have the 'triggers' message shown at the bottom of this loop, // and that we don't explain how to show a per-package report .with_stderr_does_not_contain("[..]triggers[..]") .with_stderr_does_not_contain("[..]--package[..]") .with_stderr_does_not_contain("[..]-p[..]") .run(); p.cargo(command).arg("--future-incompat-report") .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data("\ ... [WARNING] the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2 ... - first-dep@0.0.1 ... - second-dep@0.0.2 ... ") .run(); p.cargo("report future-incompatibilities") .arg("--package") .arg("first-dep@0.0.1") .with_stdout_data( "\ ... The package `first-dep v0.0.1` currently triggers the following future incompatibility lints: > [WARNING] unused variable: `x` ... ", ) .with_stdout_does_not_contain("[..]second-dep-0.0.2/src[..]") .run(); p.cargo("report future-incompatibilities") .arg("--package") .arg("second-dep@0.0.2") .with_stdout_data( "\ ... The package `second-dep v0.0.2` currently triggers the following future incompatibility lints: > [WARNING] unused variable: `x` ... ", ) .with_stdout_does_not_contain("[..]first-dep-0.0.1/src[..]") .run(); } // Test that passing the correct id via '--id' doesn't generate a warning message let output = p .cargo("check") .env("RUSTFLAGS", "-Zfuture-incompat-test") .exec_with_output() .unwrap(); // Extract the 'id' from the stdout. We are looking // for the id in a line of the form "run `cargo report future-incompatibilities --id yZ7S`" // which is generated by Cargo to tell the user what command to run // This is just to test that passing the id suppresses the warning mesasge. Any users needing // access to the report from a shell script should use the `--future-incompat-report` flag let stderr = std::str::from_utf8(&output.stderr).unwrap(); // Find '--id ' in the output let mut iter = stderr.split(' '); iter.find(|w| *w == "--id").unwrap(); let id = iter .next() .unwrap_or_else(|| panic!("Unexpected output:\n{}", stderr)); // Strip off the trailing '`' included in the output let id: String = id.chars().take_while(|c| *c != '`').collect(); p.cargo(&format!("report future-incompatibilities --id {}", id)) .with_stdout_data(str![[r#" ... The package `first-dep v0.0.1` currently triggers the following future incompatibility lints: ... The package `second-dep v0.0.2` currently triggers the following future incompatibility lints: ... "#]]) .run(); // Test without --id, and also the full output of the report. let output = p .cargo("report future-incompat") .exec_with_output() .unwrap(); let output = std::str::from_utf8(&output.stdout).unwrap(); assert!(output.starts_with("The following warnings were discovered")); let mut lines = output .lines() // Skip the beginning of the per-package information. .skip_while(|line| !line.starts_with("The package")); for expected in &["first-dep v0.0.1", "second-dep v0.0.2"] { assert_eq!( &format!( "The package `{}` currently triggers the following future incompatibility lints:", expected ), lines.next().unwrap(), "Bad output:\n{}", output ); let mut count = 0; while let Some(line) = lines.next() { if line.is_empty() { break; } count += 1; } assert!(count > 0); } assert_eq!(lines.next(), None); } #[allow(deprecated)] #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn color() { let p = simple_project(); p.cargo("check") .env("RUSTFLAGS", "-Zfuture-incompat-test") .masquerade_as_nightly_cargo(&["future-incompat-test"]) .run(); p.cargo("report future-incompatibilities") .with_stdout_does_not_contain("[..]\x1b[[..]") .run(); p.cargo("report future-incompatibilities") .env("CARGO_TERM_COLOR", "always") .with_stdout_contains("[..]\x1b[[..]") .run(); } #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn bad_ids() { let p = simple_project(); p.cargo("report future-incompatibilities --id 1") .with_status(101) .with_stderr_data(str![[r#" [ERROR] no reports are currently available "#]]) .run(); p.cargo("check") .env("RUSTFLAGS", "-Zfuture-incompat-test") .masquerade_as_nightly_cargo(&["future-incompat-test"]) .run(); p.cargo("report future-incompatibilities --id foo") .with_status(1) .with_stderr_data(str![ "[ERROR] Invalid value: could not parse `foo` as a number" ]) .run(); p.cargo("report future-incompatibilities --id 7") .with_status(101) .with_stderr_data(str![[r#" [ERROR] could not find report with ID 7 Available IDs are: 1 "#]]) .run(); } #[cargo_test( nightly, reason = "-Zfuture-incompat-test requires nightly (permanently)" )] fn suggestions_for_updates() { Package::new("with_updates", "1.0.0") .file("src/lib.rs", FUTURE_EXAMPLE) .publish(); Package::new("big_update", "1.0.0") .file("src/lib.rs", FUTURE_EXAMPLE) .publish(); Package::new("without_updates", "1.0.0") .file("src/lib.rs", FUTURE_EXAMPLE) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] with_updates = "1" big_update = "1" without_updates = "1" "#, ) .file("src/lib.rs", "") .build(); p.cargo("generate-lockfile").run(); Package::new("with_updates", "1.0.1") .file("src/lib.rs", "") .publish(); Package::new("with_updates", "1.0.2") .file("src/lib.rs", "") .publish(); Package::new("with_updates", "3.0.1") .file("src/lib.rs", "") .publish(); Package::new("big_update", "2.0.0") .file("src/lib.rs", "") .publish(); // This is a hack to force cargo to update the index. Cargo can't do this // automatically because doing a network update on every build would be a // bad idea. Under normal circumstances, we'll hope the user has done // something else along the way to trigger an update (building some other // project or something). This could use some more consideration of how to // handle this better (maybe only trigger an update if it hasn't updated // in a long while?). p.cargo("update without_updates").run(); p.cargo("check --future-incompat-report") .masquerade_as_nightly_cargo(&["future-incompat-test"]) .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_data(str![[r#" ... - Some affected dependencies have newer versions available. You may want to consider updating them to a newer version to see if the issue has been fixed. big_update v1.0.0 has the following newer versions available: 2.0.0 with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1 ... "#]]) .run(); p.cargo("report future-incompatibilities") .with_stdout_data(str![[r#" ... - Some affected dependencies have newer versions available. You may want to consider updating them to a newer version to see if the issue has been fixed. big_update v1.0.0 has the following newer versions available: 2.0.0 with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1 ... "#]]) .run() }