use crate::{
cargo_integration::linking_flags,
env::{self, VarGuard, is_env_truthy},
ui,
};
use anyhow::{Context, Result, anyhow};
use cargo_metadata::{Metadata, Package, Target};
use log::debug;
use std::{ffi::OsString, fs::copy, path::Path, sync::Mutex};
use ui_test::diagnostics::Diagnostics;
static MUTEX: Mutex<()> = Mutex::new(());
fn normalize_codes(path: &Path, bytes: &[u8]) -> Diagnostics {
let mut out = ui_test::diagnostics::rustc::rustc_diagnostics_extractor(path, bytes);
for line in &mut out.messages {
for d in line {
if let Some(code) = &d.code
&& code.contains("::")
&& let Some(unprefixed) = code.split("::").last()
{
println!("normalize_codes: REMOVE PREFIX: {code} -> {unprefixed}");
d.code = Some(unprefixed.to_string());
} else if let Some(code) = &d.code {
println!("normalize_codes: NO PREFIX: {code}");
} else {
println!("normalize_codes: NO CODE");
}
}
}
out
}
pub(crate) fn run_tests(driver: &Path, src_base: &Path, config: &ui::Config) -> Result<()> {
let _lock = MUTEX.lock().unwrap();
let _var = config
.dylint_toml
.as_ref()
.map(|value| VarGuard::set(env::DYLINT_TOML, value));
let mut cfg = ui_test::Config::rustc(src_base);
cfg.program.program = driver.to_path_buf();
for arg in ["-Dwarnings", "--emit=metadata"] {
cfg.program.args.push(OsString::from(arg));
}
for arg in &config.rustc_flags {
cfg.program.args.push(OsString::from(arg));
}
cfg.filter_files.push(src_base.display().to_string());
for key in [
env::DYLINT_LIBS,
env::CLIPPY_DISABLE_DOCS_LINKS,
env::DYLINT_TOML,
env::RUST_BACKTRACE,
env::RUST_LOG,
] {
let val = std::env::var_os(key);
cfg.program
.envs
.push((OsString::from(key), val.map(Into::into)));
}
let bless = is_env_truthy(env::BLESS);
let is_dylint_driver = driver
.file_name()
.and_then(|s| s.to_str())
.map(|s| s.contains("dylint-driver"))
.unwrap_or(false);
let expected_exit: i32 = if is_dylint_driver {
config.expected_exit_status
} else {
1
};
cfg.comment_defaults.base().exit_status =
ui_test::spanned::Spanned::<i32>::dummy(expected_exit).into();
debug!(
"run_tests: BLESS environment variable = {}",
std::env::var("BLESS").unwrap_or_else(|_| "unset".to_string())
);
debug!("run_tests: is_env_truthy(BLESS) = {}", bless);
debug!("run_tests: src_base = {}", src_base.display());
debug!("run_tests: driver = {}", driver.display());
cfg.stderr_filter(r"(?m)^\[[^\]]+\s+DEBUG\s+dylint_driver\].*\n", b"");
if config.normalize_codes {
cfg.diagnostic_extractor = normalize_codes;
}
if bless {
debug!("run_tests: Running two-pass blessing approach");
debug!("run_tests: Pass 1 - Verification (ignore_output_conflict)");
cfg.output_conflict_handling = ui_test::ignore_output_conflict;
cfg.bless_command = Some(format!("{}=1 cargo test", env::BLESS));
let verify_result = ui_test::run_tests(cfg.clone());
debug!("run_tests: Pass 1 result = {:?}", verify_result);
match &verify_result {
Ok(_) => debug!("run_tests: Pass 1 SUCCEEDED - continuing to blessing"),
Err(e) => debug!("run_tests: Pass 1 FAILED - {}", e),
}
verify_result.map_err(|err| anyhow!("verification failed: {err}"))?;
debug!("run_tests: Pass 2 - Blessing (bless_output_files)");
cfg.output_conflict_handling = ui_test::bless_output_files;
let bless_result = ui_test::run_tests(cfg);
debug!("run_tests: Pass 2 result = {:?}", bless_result);
bless_result.map_err(|err| anyhow!("blessing failed: {err}"))
} else {
debug!("run_tests: Running non-blessing mode (error_on_output_conflict)");
cfg.bless_command = Some(format!("{}=1 cargo test", env::BLESS));
cfg.output_conflict_handling = ui_test::error_on_output_conflict;
let result = ui_test::run_tests(cfg);
debug!("run_tests: Non-blessing result = {:?}", result);
result.map_err(|err| anyhow!("run tests failed: {err}"))
}
}
pub fn run_example_test(
driver: &Path,
metadata: &Metadata,
package: &Package,
target: &Target,
config: &ui::Config,
) -> Result<()> {
let linking_flags = linking_flags(metadata, package, target)?;
let file_name = target
.src_path
.file_name()
.ok_or_else(|| anyhow!("Could not get file name"))?;
let tempdir = tempfile::tempdir().with_context(|| "`tempdir` failed")?;
let src_base = tempdir.path();
let to = src_base.join(file_name);
copy(&target.src_path, &to).with_context(|| {
format!(
"Could not copy `{}` to `{}`",
target.src_path,
to.to_string_lossy()
)
})?;
for extension in ["fixed", "stderr", "stdout"] {
copy_with_extension(&target.src_path, &to, extension)
.map(|_| ())
.unwrap_or_default();
}
let mut config = config.clone();
config.rustc_flags.extend(linking_flags.iter().cloned());
run_tests(driver, src_base, &config)
}
fn copy_with_extension<P: AsRef<Path>, Q: AsRef<Path>>(
from: P,
to: Q,
extension: &str,
) -> Result<u64> {
let from = from.as_ref().with_extension(extension);
let to = to.as_ref().with_extension(extension);
copy(from, to).map_err(Into::into)
}
#[cfg(test)]
mod gating_tests {
use super::*;
use std::path::PathBuf;
fn write_file(dir: &Path, name: &str, contents: &str) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, contents).unwrap();
path
}
#[test]
fn no_annotations_does_not_bless_even_with_env() {
debug!("🧪 Starting no_annotations_does_not_bless_even_with_env");
let tmp = tempfile::tempdir().unwrap();
debug!("🧪 Created temp dir: {}", tmp.path().display());
let file = write_file(
tmp.path(),
"no_annot.rs",
"//@edition: 2021\nfn main(){ let _ = undefined; }\n",
);
debug!("🧪 Created test file: {}", file.display());
debug!(
"🧪 File contents:\n{}",
std::fs::read_to_string(&file).unwrap()
);
let config = ui::Config::default();
unsafe { std::env::set_var("BLESS", "1") };
debug!("🧪 Set BLESS=1");
debug!("🧪 About to call run_tests with rustc...");
let rustc_path = std::path::Path::new("rustc");
let result = run_tests(rustc_path, tmp.path(), &config);
debug!("🧪 run_tests returned: {:?}", result);
let stderr_path = file.with_extension("stderr");
let stderr_exists = stderr_path.exists();
debug!(
"🧪 .stderr file exists? {} (path: {})",
stderr_exists,
stderr_path.display()
);
if stderr_exists {
debug!(
"🧪 .stderr file contents:\n{}",
std::fs::read_to_string(&stderr_path)
.unwrap_or_else(|_| "Could not read stderr file".to_string())
);
}
assert!(result.is_err(), "verify should fail without annotations");
assert!(
!file.with_extension("stderr").exists(),
".stderr must not be created when annotations are missing"
);
debug!("🧪 All assertions passed!");
}
}