dylint_uitesting 5.0.0

Better UI testing for dylint libraries with ui_test
Documentation
use std::{
    env::current_dir,
    path::{Path, PathBuf},
};

use log::debug;

use crate::{
    cargo_integration::{example_target, example_targets},
    runtime::initialize,
    test_runner::run_example_test,
};
enum Target {
    SrcBase(PathBuf),
    Example(String),
    Examples,
}

/// Expected exit status for dylint driver (101 instead of 1 for some reason, ask upstream);
const DEFAULT_EXPECTED_EXIT_STATUS: i32 = 101;

#[derive(Clone)]
pub(super) struct Config {
    pub(super) rustc_flags: Vec<String>,
    pub(super) dylint_toml: Option<String>,
    pub(super) expected_exit_status: i32,
    pub(super) normalize_codes: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            rustc_flags: Vec::new(),
            dylint_toml: None,
            expected_exit_status: DEFAULT_EXPECTED_EXIT_STATUS,
            normalize_codes: false,
        }
    }
}

/// Test builder
pub struct Test {
    name: String,
    target: Target,
    config: Config,
}

impl Test {
    /// Test a library on all source files in a directory (similar to [`ui_test`]).
    ///
    /// [`ui_test`]: crate::ui_test
    #[must_use]
    pub fn src_base(name: &str, src_base: impl AsRef<Path>) -> Self {
        Self::new(name, Target::SrcBase(src_base.as_ref().to_owned()))
    }

    /// Test a library on one example target (similar to [`ui_test_example`]).
    ///
    /// [`ui_test_example`]: crate::ui_test_example
    #[must_use]
    pub fn example(name: &str, example: &str) -> Self {
        Self::new(name, Target::Example(example.to_owned()))
    }

    /// Test a library on all example targets (similar to [`ui_test_examples`]).
    ///
    /// [`ui_test_examples`]: crate::ui_test_examples
    #[must_use]
    pub fn examples(name: &str) -> Self {
        Self::new(name, Target::Examples)
    }

    /// Pass flags to the compiler when running the test.
    pub fn rustc_flags(
        &mut self,
        rustc_flags: impl IntoIterator<Item = impl AsRef<str>>,
    ) -> &mut Self {
        self.config
            .rustc_flags
            .extend(rustc_flags.into_iter().map(|s| s.as_ref().to_owned()));
        self
    }

    /// Set the `dylint.toml` file's contents (for testing configurable libraries).
    pub fn dylint_toml(&mut self, dylint_toml: impl AsRef<str>) -> &mut Self {
        self.config.dylint_toml = Some(dylint_toml.as_ref().to_owned());
        self
    }

    /// Set the expected exit status for the dylint driver.
    pub fn expected_exit_status(&mut self, code: i32) -> &mut Self {
        self.config.expected_exit_status = code;
        self
    }

    /// Whether to strip prefixes from diagnostic codes, e.g. clippy::xxx -> xxx
    pub fn normalize_codes(&mut self, normalize_codes: bool) -> &mut Self {
        self.config.normalize_codes = normalize_codes;
        self
    }

    /// Run the test.
    #[allow(clippy::needless_pass_by_ref_mut)]
    pub fn run(&mut self) {
        self.run_immutable();
    }

    fn new(name: &str, target: Target) -> Self {
        Self {
            name: name.to_owned(),
            target,
            config: Config::default(),
        }
    }

    fn run_immutable(&self) {
        debug!(
            "run_immutable: Starting run_immutable for library '{}'",
            self.name
        );
        let driver = initialize(&self.name).unwrap();
        debug!("run_immutable: Got driver: {}", driver.display());

        match &self.target {
            Target::SrcBase(src_base) => {
                debug!(
                    "run_immutable: Running SrcBase target with src_base: {}",
                    src_base.display()
                );
                crate::test_runner::run_tests(driver, src_base, &self.config)
                    .expect("run tests failed");
            }
            Target::Example(example) => {
                debug!("run_immutable: Running Example target: {}", example);
                let metadata = dylint_internal::cargo::current_metadata().unwrap();
                let current_dir = current_dir().unwrap();
                let package =
                    dylint_internal::cargo::package_with_root(&metadata, &current_dir).unwrap();
                let target = example_target(&package, example).unwrap();

                crate::test_runner::run_example_test(
                    driver,
                    &metadata,
                    &package,
                    &target,
                    &self.config,
                )
                .unwrap();
            }
            Target::Examples => {
                let metadata = dylint_internal::cargo::current_metadata().unwrap();
                let current_dir = current_dir().unwrap();
                let package =
                    dylint_internal::cargo::package_with_root(&metadata, &current_dir).unwrap();
                let targets = example_targets(&package).unwrap();

                for target in targets {
                    run_example_test(driver, &metadata, &package, &target, &self.config).unwrap();
                }
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    // smoelius: Verify that `rustc_flags` compiles when used as intended.
    #[allow(dead_code)]
    fn rustc_flags() {
        let _ = Test::src_base("name", PathBuf::new()).rustc_flags(["--test"]);
    }
}