litcheck_lit/config/
mod.rs

1mod expr;
2mod features;
3pub(crate) mod substitutions;
4
5pub use self::expr::*;
6pub use self::features::FeatureSet;
7pub use self::substitutions::{
8    InvalidSubstitutionPatternError, ScopedSubstitutionSet, SubstitutionSet,
9};
10
11use std::path::PathBuf;
12
13use clap::Args;
14use regex::Regex;
15
16pub type Variable = litcheck::variables::Variable<String, String>;
17
18#[derive(Debug, Args)]
19pub struct Config {
20    /// The test files or directories to search for tests
21    #[arg(value_name = "TESTS", required(true), trailing_var_arg(true))]
22    pub tests: Vec<String>,
23    /// Run `N` tests in parallel
24    ///
25    /// By default, this is automatically chosen to match the number of available CPUs
26    #[arg(long, short = 'j', value_name = "N")]
27    pub workers: Option<usize>,
28    /// Add a user-defined parameter NAME with the given optional VALUE.
29    #[arg(long = "param", short = 'D', value_name = "NAME=[VALUE]")]
30    pub params: Vec<Variable>,
31    /// Suppress any output except for test failures.
32    #[arg(long, short = 'q', default_value_t = false, help_heading = "Output")]
33    pub quiet: bool,
34    /// Show more information on test failures, for example, the entire test output instead
35    /// of just the result.
36    ///
37    /// Each command is printed before it is executed, along with where the command was derived from.
38    #[arg(long, short = 'v', default_value_t = false, help_heading = "Output")]
39    pub verbose: bool,
40    /// Enable `-v`, but for all tests, not just failed tests
41    #[arg(
42        long = "show-all",
43        short = 'a',
44        default_value_t = false,
45        help_heading = "Output"
46    )]
47    pub all: bool,
48    /// Don't execute any tests (assume PASS).
49    ///
50    /// For example, this will still parse test cases for RUN commands, but will not
51    /// actually execute those commands to run the tests.
52    #[arg(
53        long = "no-execute",
54        default_value_t = false,
55        help_heading = "Execution Options"
56    )]
57    pub no_execute: bool,
58    /// Specify an additional PATH to use when searching for executables in tests.
59    #[arg(
60        long = "path",
61        value_name = "PATH",
62        value_parser(canonical_path_parser()),
63        help_heading = "Execution Options"
64    )]
65    pub search_paths: Vec<PathBuf>,
66    /// Spend at most N seconds running each individual test.
67    ///
68    /// 0 means no time limit, and is the default value.
69    #[arg(
70        long,
71        value_name = "N",
72        default_value = "0",
73        help_heading = "Execution Options"
74    )]
75    pub timeout: Option<u64>,
76    /// Run only those tests whose name matches the given regular expression.
77    #[arg(long, env = "LIT_FILTER", help_heading = "Selection Options")]
78    pub filter: Option<Regex>,
79    /// Exclude those tests whose name matches the given regular expression.
80    #[arg(long, env = "LIT_FILTER_OUT", help_heading = "Selection Options")]
81    pub filter_out: Option<Regex>,
82    /// If specified, will set available features based on the provided target triple.
83    ///
84    /// Expects a string like 'x86_64-apple-darwin'.
85    ///
86    /// The default target is the host
87    #[arg(
88        long,
89        value_parser(target_triple_parser()),
90        value_name = "TRIPLE",
91        help_heading = "Selection Options"
92    )]
93    pub target: Option<target_lexicon::Triple>,
94}
95impl Config {
96    pub fn new(tests: Vec<String>) -> Self {
97        Self {
98            tests,
99            workers: None,
100            params: vec![],
101            quiet: false,
102            verbose: false,
103            all: false,
104            no_execute: false,
105            search_paths: vec![],
106            timeout: None,
107            filter: None,
108            filter_out: None,
109            target: None,
110        }
111    }
112
113    /// Returns true if a test with `name` should be selected for execution
114    pub fn is_selected(&self, name: &str) -> bool {
115        let is_selected = self
116            .filter
117            .as_ref()
118            .map(|filter| filter.is_match(name))
119            .unwrap_or(true);
120        let is_excluded = self
121            .filter_out
122            .as_ref()
123            .map(|filter_out| filter_out.is_match(name))
124            .unwrap_or(false);
125        is_selected && !is_excluded
126    }
127
128    pub fn host(&self) -> target_lexicon::Triple {
129        target_lexicon::Triple::host()
130    }
131
132    pub fn target(&self) -> target_lexicon::Triple {
133        self.target
134            .clone()
135            .unwrap_or_else(target_lexicon::Triple::host)
136    }
137}
138
139fn target_triple_parser() -> clap::builder::ValueParser {
140    use clap::{builder::ValueParser, error::ErrorKind, Error};
141    ValueParser::from(|s: &str| -> Result<target_lexicon::Triple, clap::Error> {
142        s.parse::<target_lexicon::Triple>().map_err(|err| {
143            Error::raw(
144                ErrorKind::ValueValidation,
145                format!("invalid target triple '{s}': {err}"),
146            )
147        })
148    })
149}
150
151fn canonical_path_parser() -> clap::builder::ValueParser {
152    use clap::{builder::ValueParser, error::ErrorKind, Error};
153    ValueParser::from(|s: &str| -> Result<PathBuf, clap::Error> {
154        let path = std::path::Path::new(s);
155        path.canonicalize().map_err(|err| {
156            Error::raw(
157                ErrorKind::ValueValidation,
158                format!("invalid path '{s}': {err}"),
159            )
160        })
161    })
162}