#![doc = include_str!("../README.md")]
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
process::Command,
};
use semver::Version;
pub use ui_test;
#[derive(Debug)]
struct TestSetup {
rustc_path: String,
env_vars: HashMap<String, String>,
toolchain: String,
marker_api: String,
}
#[macro_export]
macro_rules! simple_ui_test_config {
() => {
$crate::simple_ui_test_config!("tests/ui");
};
($ui_dir:expr) => {
$crate::simple_ui_test_config!(
$ui_dir,
&std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "./target".into())
);
};
($ui_dir:expr, $target_dir:expr) => {
$crate::create_ui_test_config(
std::path::PathBuf::from_iter(std::path::Path::new($ui_dir)),
std::path::Path::new($target_dir),
env!("CARGO_PKG_NAME"),
std::path::Path::new(env!("CARGO_MANIFEST_DIR")),
marker_api::MARKER_API_VERSION,
);
};
}
pub fn create_ui_test_config(
ui_dir: PathBuf,
target_dir: &Path,
crate_name: &str,
crate_dir: &Path,
marker_api_version: &str,
) -> ui_test::color_eyre::Result<ui_test::Config> {
let setup = retrieve_test_setup(crate_name, &std::fs::canonicalize(crate_dir)?);
verify_driver(&setup, marker_api_version);
for (key, val) in setup.env_vars {
env::set_var(key, val);
}
let output_conflict_handling = if env::var_os("RUST_BLESS").is_some() || env::args().any(|arg| arg == "--bless") {
ui_test::OutputConflictHandling::Bless
} else {
ui_test::OutputConflictHandling::Error("cargo test -- -- --bless".into())
};
let ui_target_dir = target_dir.join("ui_test");
std::fs::create_dir_all(&ui_target_dir)?;
let ui_target_dir = std::fs::canonicalize(ui_target_dir)?;
let mut config = ui_test::Config {
mode: ui_test::Mode::Yolo {
rustfix: ui_test::RustfixMode::MachineApplicable,
},
filter_files: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(),
out_dir: ui_target_dir,
output_conflict_handling,
..ui_test::Config::rustc(ui_dir)
};
config.program.program = PathBuf::from(setup.rustc_path);
config.program.args.push("-Aunused".into());
Ok(config)
}
fn retrieve_test_setup(crate_name: &str, pkg_dir: &Path) -> TestSetup {
#[cfg(not(feature = "dev-build"))]
const CARGO_MARKER_INVOCATION: &[&str] = &["marker"];
#[cfg(feature = "dev-build")]
const CARGO_MARKER_INVOCATION: &[&str] = &["run", "--bin", "cargo-marker", "--", "marker"];
#[cfg(not(feature = "dev-build"))]
let command_dir = pkg_dir;
#[cfg(feature = "dev-build")]
let command_dir = pkg_dir.parent().unwrap();
let lint_spec = format!(r#"{} = {{ path = '{}' }}"#, crate_name, pkg_dir.display());
let mut cmd = Command::new("cargo");
let output = cmd
.current_dir(command_dir)
.args(CARGO_MARKER_INVOCATION)
.arg("test-setup")
.arg("-l")
.arg(lint_spec)
.output()
.expect("Unable to run the test setup using `cargo-marker`");
let stdout = String::from_utf8(output.stdout).unwrap();
if !output.status.success() {
let stderr = String::from_utf8(output.stderr).unwrap();
if stderr.starts_with("error: no such command") {
panic!("{NO_SUCH_COMMENT_ADVICE}");
}
panic!("Test setup failed:\n\n===STDOUT===\n{stdout}\n\n===STDERR===\n{stderr}\n");
}
let info_vars: HashMap<_, _> = stdout
.lines()
.filter_map(|line| line.strip_prefix("info:"))
.filter_map(|line| line.split_once('='))
.map(|(var, value)| (var.to_string(), value.to_string()))
.collect();
let mut env_vars: HashMap<_, _> = stdout
.lines()
.filter_map(|line| line.strip_prefix("env:"))
.filter_map(|line| line.split_once('='))
.map(|(var, value)| (var.to_string(), value.to_string()))
.collect();
let toolchain = info_vars
.get("toolchain")
.expect("missing info field 'toolchain'")
.clone();
let marker_api = info_vars
.get("marker-api")
.expect("missing info field 'marker-api'")
.clone();
TestSetup {
rustc_path: env_vars.remove("RUSTC_WORKSPACE_WRAPPER").unwrap(),
env_vars,
toolchain,
marker_api,
}
}
const NO_SUCH_COMMENT_ADVICE: &str = r#"
===========================================================
Error: Command `marker` was not found
UI tests require `cargo-marker` to be installed
* Try installing `cargo-marker`
```
# Update `cargo-marker` first
cargo install cargo_marker
# Now update the driver
cargo marker setup --auto-install-toolchain
```
===========================================================
"#;
const DRIVER_FAIL_ADVICE: &str = r#"
===========================================================
Error: Unable to start Marker's driver
UI tests need to be executed with the nightly version of the driver
* Try setting the version in a `rust-toolchain.toml` file, like this:
```
[toolchain]
channel = "{toolchain}"
```
* Try setting the channel when invoking the tests, like this:
```
cargo +{toolchain} test"
```
===========================================================
"#;
const VERSION_LESS_ADVICE: &str = r#"
===========================================================
Error: API versions mismatch, the lint crate is behind the driver.
* Try updating the used api version to: `{marker_api}`
===========================================================
"#;
const VERSION_GREATER_ADVICE: &str = r#"
===========================================================
Error: API versions mismatch, the lint crate uses a newer version
* Try updating the driver.
```
# Update `cargo-marker` first
cargo install cargo_marker
# Now update the driver
cargo marker setup --auto-install-toolchain
```
You might also need to update the used toolchain. In that case a new error
will be emitted.
===========================================================
"#;
fn verify_driver(setup: &TestSetup, marker_api_version: &str) {
let test = Command::new(&setup.rustc_path)
.arg("-V")
.spawn()
.expect("failed to start marker's driver")
.wait()
.expect("failed to wait for marker's driver");
if !test.success() {
panic!("{}", DRIVER_FAIL_ADVICE.replace("{toolchain}", &setup.toolchain));
}
let this_version = Version::parse(marker_api_version).unwrap();
let driver_version = Version::parse(&setup.marker_api).unwrap();
match this_version.cmp(&driver_version) {
std::cmp::Ordering::Less => {
panic!("{}", VERSION_LESS_ADVICE.replace("{marker_api}", &setup.marker_api));
},
std::cmp::Ordering::Equal => {
},
std::cmp::Ordering::Greater => {
panic!("{VERSION_GREATER_ADVICE}");
},
}
}