skipif 0.1.0

Turn test cases into no-ops with _SKIPPED appended to their name based on compile time conditions.
Documentation
// Copyright 2024 Steve Wayne Warren
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::parse::Arg;
use super::parse::CallExpr;
use super::parse::SkipIfArgs;

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum RequiredElement {
    EnvVar(String),
}

impl RequiredElement {
    pub(crate) fn is_missing(&self) -> bool {
        match self {
            Self::EnvVar(s) => std::env::var(s).is_err(),
        }
    }
}

const MISSING_ENV: &str = "missing_env";

impl CallExpr {
    fn is_missing_env(&self) -> bool {
        self.method == MISSING_ENV
    }
}

impl SkipIfArgs {
    fn get_required_elements(&self) -> Vec<RequiredElement> {
        self.elements
            .iter()
            .filter_map(|arg| match arg {
                Arg::CallExpr(expr) if expr.is_missing_env() => Some(
                    expr.args
                        .iter()
                        .map(ToString::to_string)
                        .map(RequiredElement::EnvVar),
                ),
                _ => None,
            })
            .flatten()
            .collect()
    }
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) struct SkipIf {
    pub(crate) required_elements: Vec<RequiredElement>,
}

impl SkipIf {
    pub(crate) fn analyze(skipif: SkipIfArgs) -> Self {
        let mut required_elements = skipif.get_required_elements();
        required_elements.dedup();
        Self { required_elements }
    }
}

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

    use super::super::parse::*;
    use super::*;
    use crate::test::*;

    #[rstest]
    #[case::single_call_arg(
        skip_if_args(vec![call_expr("missing_env", vec!["ENV_VAR"])]),
        skipif(vec![required_env_vars(vec!["ENV_VAR"])]),
    )]
    #[case::multiple_call_args(
        skip_if_args(vec![call_expr(
            "missing_env",
            vec!["ENV_VAR1", "ENV_VAR2", "ENV_VAR3"],
        )]),
        skipif(vec![required_env_vars(vec![
            "ENV_VAR1", "ENV_VAR2", "ENV_VAR3",
        ])]),
    )]
    #[case::multiple_instances_of_same_arg(
        skip_if_args(vec![
            call_expr(
            "missing_env",
            vec!["ENV_VAR1", "ENV_VAR2", "ENV_VAR3"],
        ),
            call_expr(
            "missing_env",
            vec!["ENV_VAR3"],
        ),
        ]),
        skipif(vec![required_env_vars(vec![
            "ENV_VAR1", "ENV_VAR2", "ENV_VAR3",
        ])]),
    )]
    fn analyze_skipif_args(#[case] input: SkipIfArgs, #[case] expected: SkipIf) {
        let actual = SkipIf::analyze(input);
        assert_eq!(expected, actual);

        for element in actual.required_elements {
            assert!(element.is_missing())
        }
    }
}