1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
mod expr;
mod features;
pub(crate) mod substitutions;

pub use self::expr::*;
pub use self::features::FeatureSet;
pub use self::substitutions::{
    InvalidSubstitutionPatternError, ScopedSubstitutionSet, SubstitutionSet,
};

use std::path::PathBuf;

use clap::Args;
use regex::Regex;

pub type Variable = litcheck::variables::Variable<String, String>;

#[derive(Debug, Args)]
pub struct Config {
    /// The test files or directories to search for tests
    #[arg(value_name = "TESTS", required(true), trailing_var_arg(true))]
    pub tests: Vec<String>,
    /// Run `N` tests in parallel
    ///
    /// By default, this is automatically chosen to match the number of available CPUs
    #[arg(long, short = 'j', value_name = "N")]
    pub workers: Option<usize>,
    /// Add a user-defined parameter NAME with the given optional VALUE.
    #[arg(long = "param", short = 'D', value_name = "NAME=[VALUE]")]
    pub params: Vec<Variable>,
    /// Suppress any output except for test failures.
    #[arg(long, short = 'q', default_value_t = false, help_heading = "Output")]
    pub quiet: bool,
    /// Show more information on test failures, for example, the entire test output instead
    /// of just the result.
    ///
    /// Each command is printed before it is executed, along with where the command was derived from.
    #[arg(long, short = 'v', default_value_t = false, help_heading = "Output")]
    pub verbose: bool,
    /// Enable `-v`, but for all tests, not just failed tests
    #[arg(
        long = "show-all",
        short = 'a',
        default_value_t = false,
        help_heading = "Output"
    )]
    pub all: bool,
    /// Don't execute any tests (assume PASS).
    ///
    /// For example, this will still parse test cases for RUN commands, but will not
    /// actually execute those commands to run the tests.
    #[arg(
        long = "no-execute",
        default_value_t = false,
        help_heading = "Execution Options"
    )]
    pub no_execute: bool,
    /// Specify an additional PATH to use when searching for executables in tests.
    #[arg(
        long = "path",
        value_name = "PATH",
        value_parser(canonical_path_parser()),
        help_heading = "Execution Options"
    )]
    pub search_paths: Vec<PathBuf>,
    /// Spend at most N seconds running each individual test.
    ///
    /// 0 means no time limit, and is the default value.
    #[arg(
        long,
        value_name = "N",
        default_value = "0",
        help_heading = "Execution Options"
    )]
    pub timeout: Option<u64>,
    /// Run only those tests whose name matches the given regular expression.
    #[arg(long, env = "LIT_FILTER", help_heading = "Selection Options")]
    pub filter: Option<Regex>,
    /// Exclude those tests whose name matches the given regular expression.
    #[arg(long, env = "LIT_FILTER_OUT", help_heading = "Selection Options")]
    pub filter_out: Option<Regex>,
    /// If specified, will set available features based on the provided target triple.
    ///
    /// Expects a string like 'x86_64-apple-darwin'.
    ///
    /// The default target is the host
    #[arg(
        long,
        value_parser(target_triple_parser()),
        value_name = "TRIPLE",
        help_heading = "Selection Options"
    )]
    pub target: Option<target_lexicon::Triple>,
}
impl Config {
    pub fn new(tests: Vec<String>) -> Self {
        Self {
            tests,
            workers: None,
            params: vec![],
            quiet: false,
            verbose: false,
            all: false,
            no_execute: false,
            search_paths: vec![],
            timeout: None,
            filter: None,
            filter_out: None,
            target: None,
        }
    }

    /// Returns true if a test with `name` should be selected for execution
    pub fn is_selected(&self, name: &str) -> bool {
        let is_selected = self
            .filter
            .as_ref()
            .map(|filter| filter.is_match(name))
            .unwrap_or(true);
        let is_excluded = self
            .filter_out
            .as_ref()
            .map(|filter_out| filter_out.is_match(name))
            .unwrap_or(false);
        is_selected && !is_excluded
    }

    pub fn host(&self) -> target_lexicon::Triple {
        target_lexicon::Triple::host()
    }

    pub fn target(&self) -> target_lexicon::Triple {
        self.target
            .clone()
            .unwrap_or_else(target_lexicon::Triple::host)
    }
}

fn target_triple_parser() -> clap::builder::ValueParser {
    use clap::{builder::ValueParser, error::ErrorKind, Error};
    ValueParser::from(|s: &str| -> Result<target_lexicon::Triple, clap::Error> {
        s.parse::<target_lexicon::Triple>().map_err(|err| {
            Error::raw(
                ErrorKind::ValueValidation,
                format!("invalid target triple '{s}': {err}"),
            )
        })
    })
}

fn canonical_path_parser() -> clap::builder::ValueParser {
    use clap::{builder::ValueParser, error::ErrorKind, Error};
    ValueParser::from(|s: &str| -> Result<PathBuf, clap::Error> {
        let path = std::path::Path::new(s);
        path.canonicalize().map_err(|err| {
            Error::raw(
                ErrorKind::ValueValidation,
                format!("invalid path '{s}': {err}"),
            )
        })
    })
}