sccache/tests/sccache_cargo.rs

408 строки
12 KiB
Rust

//! System tests for compiling Rust code with cargo.
//!
//! Any copyright is dedicated to the Public Domain.
//! http://creativecommons.org/publicdomain/zero/1.0/
pub mod helpers;
use anyhow::{Context, Result};
use helpers::{cargo_clean, stop_sccache, CARGO, CRATE_DIR};
use assert_cmd::prelude::*;
use fs_err as fs;
use helpers::{SccacheTest, SCCACHE_BIN};
use predicates::prelude::*;
use serial_test::serial;
use std::path::Path;
use std::process::Command;
#[macro_use]
extern crate log;
#[test]
#[serial]
fn test_rust_cargo_check() -> Result<()> {
test_rust_cargo_cmd("check", SccacheTest::new(None)?)
}
#[test]
#[serial]
fn test_rust_cargo_check_readonly() -> Result<()> {
test_rust_cargo_cmd_readonly("check", SccacheTest::new(None)?)
}
#[test]
#[serial]
fn test_rust_cargo_build() -> Result<()> {
test_rust_cargo_cmd("build", SccacheTest::new(None)?)
}
#[test]
#[serial]
fn test_rust_cargo_build_readonly() -> Result<()> {
test_rust_cargo_cmd_readonly("build", SccacheTest::new(None)?)
}
#[test]
#[serial]
#[cfg(unix)]
fn test_run_log_no_perm() -> Result<()> {
trace!("sccache with log");
stop_sccache()?;
let mut cmd = Command::new(SCCACHE_BIN.as_os_str());
cmd.arg("gcc")
.env("SCCACHE_ERROR_LOG", "/no-perm.log") // Should not work
.env("SCCACHE_LOG", "debug");
cmd.assert().failure().stderr(predicate::str::contains(
"Cannot open/write log file '/no-perm.log'",
));
Ok(())
}
#[test]
#[serial]
fn test_run_log() -> Result<()> {
trace!("sccache with log");
stop_sccache()?;
let tempdir = tempfile::Builder::new()
.prefix("sccache_test_rust_cargo")
.tempdir()
.context("Failed to create tempdir")?;
let tmppath = tempdir.path().join("perm.log");
let mut cmd = Command::new(SCCACHE_BIN.as_os_str());
cmd.arg("--start-server")
.env("SCCACHE_ERROR_LOG", &tmppath) // Should not work
.env("SCCACHE_LOG", "debug");
cmd.assert().success();
stop_sccache()?;
assert!(Path::new(&tmppath).is_file());
Ok(())
}
/// This test checks that changing an environment variable reference by env! is detected by
/// sccache, causes a rebuild and is correctly printed to stdout.
#[test]
#[serial]
fn test_rust_cargo_run_with_env_dep_parsing() -> Result<()> {
test_rust_cargo_env_dep(SccacheTest::new(None)?)
}
#[cfg(feature = "unstable")]
#[test]
#[serial]
fn test_rust_cargo_check_nightly() -> Result<()> {
use std::ffi::OsString;
test_rust_cargo_cmd(
"check",
SccacheTest::new(Some(&[(
"RUSTFLAGS",
OsString::from("-Cprofile-generate=."),
)]))?,
)
}
#[cfg(feature = "unstable")]
#[test]
#[serial]
fn test_rust_cargo_check_nightly_readonly() -> Result<()> {
use std::ffi::OsString;
test_rust_cargo_cmd_readonly(
"check",
SccacheTest::new(Some(&[(
"RUSTFLAGS",
OsString::from("-Cprofile-generate=."),
)]))?,
)
}
#[cfg(feature = "unstable")]
#[test]
#[serial]
fn test_rust_cargo_build_nightly() -> Result<()> {
use std::ffi::OsString;
test_rust_cargo_cmd(
"build",
SccacheTest::new(Some(&[(
"RUSTFLAGS",
OsString::from("-Cprofile-generate=."),
)]))?,
)
}
#[cfg(feature = "unstable")]
#[test]
#[serial]
fn test_rust_cargo_build_nightly_readonly() -> Result<()> {
use std::ffi::OsString;
test_rust_cargo_cmd_readonly(
"build",
SccacheTest::new(Some(&[(
"RUSTFLAGS",
OsString::from("-Cprofile-generate=."),
)]))?,
)
}
/// Test that building a simple Rust crate with cargo using sccache results in a cache hit
/// when built a second time and a cache miss, when the environment variable referenced via
/// env! is changed.
fn test_rust_cargo_cmd(cmd: &str, test_info: SccacheTest) -> Result<()> {
// `cargo clean` first, just to be sure there's no leftover build objects.
cargo_clean(&test_info)?;
// Now build the crate with cargo.
Command::new(CARGO.as_os_str())
.args([cmd, "--color=never"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8().not())?
.try_success()?;
// Clean it so we can build it again.
cargo_clean(&test_info)?;
Command::new(CARGO.as_os_str())
.args([cmd, "--color=always"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8())?
.try_success()?;
test_info
.show_stats()?
.try_stdout(
predicates::str::contains(
r#""cache_hits":{"counts":{"Rust":2},"adv_counts":{"rust":2}}"#,
)
.from_utf8(),
)?
.try_success()?;
Ok(())
}
fn restart_sccache(
test_info: &SccacheTest,
additional_envs: Option<Vec<(String, String)>>,
) -> Result<()> {
let cache_dir = test_info.tempdir.path().join("cache");
stop_sccache()?;
trace!("sccache --start-server");
let mut cmd = Command::new(SCCACHE_BIN.as_os_str());
cmd.arg("--start-server");
cmd.env("SCCACHE_DIR", &cache_dir);
if let Some(additional_envs) = additional_envs {
cmd.envs(additional_envs);
}
cmd.assert()
.try_success()
.context("Failed to start sccache server")?;
Ok(())
}
/// Test that building a simple Rust crate with cargo using sccache results in the following behaviors (for three different runs):
/// - In read-only mode, a cache miss.
/// - In read-write mode, a cache miss.
/// - In read-only mode, a cache hit.
///
/// The environment variable for read/write mode is added by this function.
fn test_rust_cargo_cmd_readonly(cmd: &str, test_info: SccacheTest) -> Result<()> {
// `cargo clean` first, just to be sure there's no leftover build objects.
cargo_clean(&test_info)?;
// The cache must be put into read-only mode, and that can only be configured
// when the server starts up, so we need to restart it.
restart_sccache(
&test_info,
Some(vec![("SCCACHE_LOCAL_RW_MODE".into(), "READ_ONLY".into())]),
)?;
// Now build the crate with cargo.
Command::new(CARGO.as_os_str())
.args([cmd, "--color=never"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8().not())?
.try_success()?;
// Stats reset on server restart, so this needs to be run for each build.
test_info
.show_stats()?
.try_stdout(
predicates::str::contains(r#""cache_hits":{"counts":{},"adv_counts":{}}"#).from_utf8(),
)?
.try_stdout(
predicates::str::contains(
r#""cache_misses":{"counts":{"Rust":2},"adv_counts":{"rust":2}}"#,
)
.from_utf8(),
)?
.try_success()?;
cargo_clean(&test_info)?;
restart_sccache(
&test_info,
Some(vec![("SCCACHE_LOCAL_RW_MODE".into(), "READ_WRITE".into())]),
)?;
Command::new(CARGO.as_os_str())
.args([cmd, "--color=always"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8())?
.try_success()?;
test_info
.show_stats()?
.try_stdout(
predicates::str::contains(r#""cache_hits":{"counts":{},"adv_counts":{}}"#).from_utf8(),
)?
.try_stdout(
predicates::str::contains(
r#""cache_misses":{"counts":{"Rust":2},"adv_counts":{"rust":2}}"#,
)
.from_utf8(),
)?
.try_success()?;
cargo_clean(&test_info)?;
restart_sccache(
&test_info,
Some(vec![("SCCACHE_LOCAL_RW_MODE".into(), "READ_ONLY".into())]),
)?;
Command::new(CARGO.as_os_str())
.args([cmd, "--color=always"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8())?
.try_success()?;
test_info
.show_stats()?
.try_stdout(
predicates::str::contains(
r#""cache_hits":{"counts":{"Rust":2},"adv_counts":{"rust":2}}"#,
)
.from_utf8(),
)?
.try_stdout(
predicates::str::contains(r#""cache_misses":{"counts":{},"adv_counts":{}}"#)
.from_utf8(),
)?
.try_success()?;
Ok(())
}
fn test_rust_cargo_env_dep(test_info: SccacheTest) -> Result<()> {
cargo_clean(&test_info)?;
// Now build the crate with cargo.
Command::new(CARGO.as_os_str())
.args(["run", "--color=never"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8().not())?
.try_stdout(predicates::str::contains("Env var: 1"))?
.try_success()?;
// Clean it so we can build it again.
cargo_clean(&test_info)?;
Command::new(CARGO.as_os_str())
.args(["run", "--color=always"])
.envs(test_info.env.iter().cloned())
.env("TEST_ENV_VAR", "OTHER_VALUE")
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8())?
.try_stdout(predicates::str::contains("Env var: OTHER_VALUE"))?
.try_success()?;
// Now get the stats and ensure that we had one cache hit for the second build.
// The test crate has one dependency (itoa) so there are two separate compilations, but only
// itoa should be cached (due to the changed environment variable).
test_info
.show_stats()?
.try_stdout(predicates::str::contains(r#""cache_hits":{"counts":{"Rust":1}"#).from_utf8())?
.try_success()?;
drop(test_info);
Ok(())
}
/// Test that building a simple Rust crate with cargo using sccache in read-only mode with an empty cache results in
/// a cache miss that is produced by the readonly storage wrapper (and does not attempt to write to the underlying cache).
#[test]
#[serial]
fn test_rust_cargo_cmd_readonly_preemtive_block() -> Result<()> {
let test_info = SccacheTest::new(None)?;
// `cargo clean` first, just to be sure there's no leftover build objects.
cargo_clean(&test_info)?;
let sccache_log = test_info.tempdir.path().join("sccache.log");
stop_sccache()?;
restart_sccache(
&test_info,
Some(vec![
("SCCACHE_LOCAL_RW_MODE".into(), "READ_ONLY".into()),
("SCCACHE_LOG".into(), "trace".into()),
(
"SCCACHE_ERROR_LOG".into(),
sccache_log.to_str().unwrap().into(),
),
]),
)?;
// Now build the crate with cargo.
// Assert that our cache miss is due to the readonly storage wrapper, not due to the underlying disk cache.
Command::new(CARGO.as_os_str())
.args(["build", "--color=never"])
.envs(test_info.env.iter().cloned())
.current_dir(CRATE_DIR.as_os_str())
.assert()
.try_stderr(predicates::str::contains("\x1b[").from_utf8().not())?
.try_success()?;
let log_contents = fs::read_to_string(sccache_log)?;
assert!(predicates::str::contains("server has setup with ReadOnly").eval(log_contents.as_str()));
assert!(predicates::str::contains(
"Error executing cache write: Cannot write to read-only storage"
)
.eval(log_contents.as_str()));
assert!(predicates::str::contains("DiskCache::finish_put")
.not()
.eval(log_contents.as_str()));
// Stats reset on server restart, so this needs to be run for each build.
test_info
.show_stats()?
.try_stdout(
predicates::str::contains(r#""cache_hits":{"counts":{},"adv_counts":{}}"#).from_utf8(),
)?
.try_stdout(
predicates::str::contains(
r#""cache_misses":{"counts":{"Rust":2},"adv_counts":{"rust":2}}"#,
)
.from_utf8(),
)?
.try_success()?;
Ok(())
}