diff options
Diffstat (limited to 'tests/testsuite/cache_messages.rs')
-rw-r--r-- | tests/testsuite/cache_messages.rs | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/tests/testsuite/cache_messages.rs b/tests/testsuite/cache_messages.rs new file mode 100644 index 0000000..b856ed1 --- /dev/null +++ b/tests/testsuite/cache_messages.rs @@ -0,0 +1,488 @@ +//! Tests for caching compiler diagnostics. + +use super::messages::raw_rustc_output; +use cargo_test_support::tools; +use cargo_test_support::{basic_manifest, is_coarse_mtime, project, registry::Package, sleep_ms}; + +fn as_str(bytes: &[u8]) -> &str { + std::str::from_utf8(bytes).expect("valid utf-8") +} + +#[cargo_test] +fn simple() { + // A simple example that generates two warnings (unused functions). + let p = project() + .file( + "src/lib.rs", + " + fn a() {} + fn b() {} + ", + ) + .build(); + + // Capture what rustc actually emits. This is done to avoid relying on the + // exact message formatting in rustc. + let rustc_output = raw_rustc_output(&p, "src/lib.rs", &[]); + + // -q so the output is the same as rustc (no "Compiling" or "Finished"). + let cargo_output1 = p + .cargo("check -q --color=never") + .exec_with_output() + .expect("cargo to run"); + assert_eq!(rustc_output, as_str(&cargo_output1.stderr)); + assert!(cargo_output1.stdout.is_empty()); + // Check that the cached version is exactly the same. + let cargo_output2 = p + .cargo("check -q") + .exec_with_output() + .expect("cargo to run"); + assert_eq!(rustc_output, as_str(&cargo_output2.stderr)); + assert!(cargo_output2.stdout.is_empty()); +} + +// same as `simple`, except everything is using the short format +#[cargo_test] +fn simple_short() { + let p = project() + .file( + "src/lib.rs", + " + fn a() {} + fn b() {} + ", + ) + .build(); + + let rustc_output = raw_rustc_output(&p, "src/lib.rs", &["--error-format=short"]); + + let cargo_output1 = p + .cargo("check -q --color=never --message-format=short") + .exec_with_output() + .expect("cargo to run"); + assert_eq!(rustc_output, as_str(&cargo_output1.stderr)); + // assert!(cargo_output1.stdout.is_empty()); + let cargo_output2 = p + .cargo("check -q --message-format=short") + .exec_with_output() + .expect("cargo to run"); + println!("{}", String::from_utf8_lossy(&cargo_output2.stdout)); + assert_eq!(rustc_output, as_str(&cargo_output2.stderr)); + assert!(cargo_output2.stdout.is_empty()); +} + +#[cargo_test] +fn color() { + // Check enabling/disabling color. + let p = project().file("src/lib.rs", "fn a() {}").build(); + + // Hack for issue in fwdansi 1.1. It is squashing multiple resets + // into a single reset. + // https://github.com/kennytm/fwdansi/issues/2 + fn normalize(s: &str) -> String { + #[cfg(windows)] + return s.replace("\x1b[0m\x1b[0m", "\x1b[0m"); + #[cfg(not(windows))] + return s.to_string(); + } + + let compare = |a, b| { + assert_eq!(normalize(a), normalize(b)); + }; + + // Capture the original color output. + let rustc_color = raw_rustc_output(&p, "src/lib.rs", &["--color=always"]); + assert!(rustc_color.contains("\x1b[")); + + // Capture the original non-color output. + let rustc_nocolor = raw_rustc_output(&p, "src/lib.rs", &[]); + assert!(!rustc_nocolor.contains("\x1b[")); + + // First pass, non-cached, with color, should be the same. + let cargo_output1 = p + .cargo("check -q --color=always") + .exec_with_output() + .expect("cargo to run"); + compare(&rustc_color, as_str(&cargo_output1.stderr)); + + // Replay cached, with color. + let cargo_output2 = p + .cargo("check -q --color=always") + .exec_with_output() + .expect("cargo to run"); + compare(&rustc_color, as_str(&cargo_output2.stderr)); + + // Replay cached, no color. + let cargo_output_nocolor = p + .cargo("check -q --color=never") + .exec_with_output() + .expect("cargo to run"); + compare(&rustc_nocolor, as_str(&cargo_output_nocolor.stderr)); +} + +#[cargo_test] +fn cached_as_json() { + // Check that cached JSON output is the same. + let p = project().file("src/lib.rs", "fn a() {}").build(); + + // Grab the non-cached output, feature disabled. + // NOTE: When stabilizing, this will need to be redone. + let cargo_output = p + .cargo("check --message-format=json") + .exec_with_output() + .expect("cargo to run"); + assert!(cargo_output.status.success()); + let orig_cargo_out = as_str(&cargo_output.stdout); + assert!(orig_cargo_out.contains("compiler-message")); + p.cargo("clean").run(); + + // Check JSON output, not fresh. + let cargo_output1 = p + .cargo("check --message-format=json") + .exec_with_output() + .expect("cargo to run"); + assert_eq!(as_str(&cargo_output1.stdout), orig_cargo_out); + + // Check JSON output, fresh. + let cargo_output2 = p + .cargo("check --message-format=json") + .exec_with_output() + .expect("cargo to run"); + // The only difference should be this field. + let fix_fresh = as_str(&cargo_output2.stdout).replace("\"fresh\":true", "\"fresh\":false"); + assert_eq!(fix_fresh, orig_cargo_out); +} + +#[cargo_test] +fn clears_cache_after_fix() { + // Make sure the cache is invalidated when there is no output. + let p = project().file("src/lib.rs", "fn asdf() {}").build(); + // Fill the cache. + p.cargo("check").with_stderr_contains("[..]asdf[..]").run(); + let cpath = p + .glob("target/debug/.fingerprint/foo-*/output-*") + .next() + .unwrap() + .unwrap(); + assert!(std::fs::read_to_string(cpath).unwrap().contains("asdf")); + + // Fix it. + if is_coarse_mtime() { + sleep_ms(1000); + } + p.change_file("src/lib.rs", ""); + + p.cargo("check") + .with_stdout("") + .with_stderr( + "\ +[CHECKING] foo [..] +[FINISHED] [..] +", + ) + .run(); + assert_eq!( + p.glob("target/debug/.fingerprint/foo-*/output-*").count(), + 0 + ); + + // And again, check the cache is correct. + p.cargo("check") + .with_stdout("") + .with_stderr( + "\ +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn rustdoc() { + // Create a warning in rustdoc. + let p = project() + .file( + "src/lib.rs", + " + #![warn(missing_docs)] + pub fn f() {} + ", + ) + .build(); + + let rustdoc_output = p + .cargo("doc -q --color=always") + .exec_with_output() + .expect("rustdoc to run"); + assert!(rustdoc_output.status.success()); + let rustdoc_stderr = as_str(&rustdoc_output.stderr); + assert!(rustdoc_stderr.contains("missing")); + assert!(rustdoc_stderr.contains("\x1b[")); + assert_eq!( + p.glob("target/debug/.fingerprint/foo-*/output-*").count(), + 1 + ); + + // Check the cached output. + let rustdoc_output = p + .cargo("doc -q --color=always") + .exec_with_output() + .expect("rustdoc to run"); + assert_eq!(as_str(&rustdoc_output.stderr), rustdoc_stderr); +} + +#[cargo_test] +fn fix() { + // Make sure `fix` is not broken by caching. + let p = project().file("src/lib.rs", "pub fn try() {}").build(); + + p.cargo("fix --edition --allow-no-vcs").run(); + + assert_eq!(p.read_file("src/lib.rs"), "pub fn r#try() {}"); +} + +#[cargo_test] +fn very_verbose() { + // Handle cap-lints in dependencies. + Package::new("bar", "1.0.0") + .file("src/lib.rs", "fn not_used() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check -vv") + .with_stderr_contains("[..]not_used[..]") + .run(); + + p.cargo("check").with_stderr("[FINISHED] [..]").run(); + + p.cargo("check -vv") + .with_stderr_contains("[..]not_used[..]") + .run(); +} + +#[cargo_test] +fn doesnt_create_extra_files() { + // Ensure it doesn't create `output` files when not needed. + Package::new("dep", "1.0.0") + .file("src/lib.rs", "fn unused() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + dep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").run(); + + assert_eq!( + p.glob("target/debug/.fingerprint/foo-*/output-*").count(), + 0 + ); + assert_eq!( + p.glob("target/debug/.fingerprint/dep-*/output-*").count(), + 0 + ); + if is_coarse_mtime() { + sleep_ms(1000); + } + p.change_file("src/lib.rs", "fn unused() {}"); + p.cargo("check").run(); + assert_eq!( + p.glob("target/debug/.fingerprint/foo-*/output-*").count(), + 1 + ); +} + +#[cargo_test] +fn replay_non_json() { + // Handles non-json output. + let rustc = project() + .at("rustc") + .file("Cargo.toml", &basic_manifest("rustc_alt", "1.0.0")) + .file( + "src/main.rs", + r#" + fn main() { + eprintln!("line 1"); + eprintln!("line 2"); + let r = std::process::Command::new("rustc") + .args(std::env::args_os().skip(1)) + .status(); + std::process::exit(r.unwrap().code().unwrap_or(2)); + } + "#, + ) + .build(); + rustc.cargo("build").run(); + let p = project().file("src/lib.rs", "").build(); + p.cargo("check") + .env("RUSTC", rustc.bin("rustc_alt")) + .with_stderr( + "\ +[CHECKING] foo [..] +line 1 +line 2 +[FINISHED] dev [..] +", + ) + .run(); + + p.cargo("check") + .env("RUSTC", rustc.bin("rustc_alt")) + .with_stderr( + "\ +line 1 +line 2 +[FINISHED] dev [..] +", + ) + .run(); +} + +#[cargo_test] +fn caching_large_output() { + // Handles large number of messages. + // This is an arbitrary amount that is greater than the 100 used in + // job_queue. This is here to check for deadlocks or any other problems. + const COUNT: usize = 250; + let rustc = project() + .at("rustc") + .file("Cargo.toml", &basic_manifest("rustc_alt", "1.0.0")) + .file( + "src/main.rs", + &format!( + r#" + fn main() {{ + for i in 0..{} {{ + eprintln!("{{{{\"message\": \"test message {{}}\", \"level\": \"warning\", \ + \"spans\": [], \"children\": [], \"rendered\": \"test message {{}}\"}}}}", + i, i); + }} + let r = std::process::Command::new("rustc") + .args(std::env::args_os().skip(1)) + .status(); + std::process::exit(r.unwrap().code().unwrap_or(2)); + }} + "#, + COUNT + ), + ) + .build(); + + let mut expected = String::new(); + for i in 0..COUNT { + expected.push_str(&format!("test message {}\n", i)); + } + + rustc.cargo("build").run(); + let p = project().file("src/lib.rs", "").build(); + p.cargo("check") + .env("RUSTC", rustc.bin("rustc_alt")) + .with_stderr(&format!( + "\ +[CHECKING] foo [..] +{}warning: `foo` (lib) generated 250 warnings +[FINISHED] dev [..] +", + expected + )) + .run(); + + p.cargo("check") + .env("RUSTC", rustc.bin("rustc_alt")) + .with_stderr(&format!( + "\ +{}warning: `foo` (lib) generated 250 warnings +[FINISHED] dev [..] +", + expected + )) + .run(); +} + +#[cargo_test] +fn rustc_workspace_wrapper() { + let p = project() + .file( + "src/lib.rs", + "pub fn f() { assert!(true); }\n\ + fn unused_func() {}", + ) + .build(); + + p.cargo("check -v") + .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper()) + .with_stderr_contains("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]") + .run(); + + // Check without a wrapper should rebuild + p.cargo("check -v") + .with_stderr_contains( + "\ +[CHECKING] foo [..] +[RUNNING] `rustc[..] +[WARNING] [..]unused_func[..] +", + ) + .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]") + .run(); + + // Again, reading from the cache. + p.cargo("check -v") + .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper()) + .with_stderr_contains("[FRESH] foo [..]") + .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]") + .run(); + + // And `check` should also be fresh, reading from cache. + p.cargo("check -v") + .with_stderr_contains("[FRESH] foo [..]") + .with_stderr_contains("[WARNING] [..]unused_func[..]") + .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]") + .run(); +} + +#[cargo_test] +fn wacky_hashless_fingerprint() { + // On Windows, executables don't have hashes. This checks for a bad + // assumption that caused bad caching. + let p = project() + .file("src/bin/a.rs", "fn main() { let unused = 1; }") + .file("src/bin/b.rs", "fn main() {}") + .build(); + p.cargo("check --bin b") + .with_stderr_does_not_contain("[..]unused[..]") + .run(); + p.cargo("check --bin a") + .with_stderr_contains("[..]unused[..]") + .run(); + // This should not pick up the cache from `a`. + p.cargo("check --bin b") + .with_stderr_does_not_contain("[..]unused[..]") + .run(); +} |