lit/
config.rs

1//! The type that stores testing configuration.
2//!
3//! Use the code in this module to tune testing behaviour.
4
5#[cfg(feature = "clap")] pub mod clap;
6
7use std::path::{Path, PathBuf};
8use std::collections::HashMap;
9use std::fmt;
10use tempfile::NamedTempFile;
11
12const DEFAULT_MAX_OUTPUT_CONTEXT_LINE_COUNT: usize = 10;
13
14/// The configuration of the test runner.
15#[derive(Clone, Debug)]
16pub struct Config
17{
18    /// A list of file extensions which contain tests.
19    pub supported_file_extensions: Vec<String>,
20    /// Paths to tests or folders containing tests.
21    pub test_paths: Vec<PathBuf>,
22    /// Constants that tests can refer to via `@<name>` syntax.
23    pub constants: HashMap<String, String>,
24    /// A function which used to dynamically lookup variables.
25    ///
26    /// The default variable lookup can be found at `Config::DEFAULT_VARIABLE_LOOKUP`.
27    ///
28    /// In your own custom variable lookups, most of the time you will want to
29    /// include a fallback call to `Config::DEFAULT_VARIABLE_LOOKUP`.
30    pub variable_lookup: VariableLookup,
31    /// Whether temporary files generated by the tests should be
32    /// cleaned up, where possible.
33    ///
34    /// This includes temporary files created by using `@tempfile`
35    /// variables.
36    pub cleanup_temporary_files: bool,
37    /// Export all generated test artifacts to the specified directory.
38    pub save_artifacts_to_directory: Option<PathBuf>,
39    /// Whether verbose information about resolved variables should be printed to stderr.
40    pub dump_variable_resolution: bool,
41    /// If set, debug output should be truncated to this many number of
42    /// context lines.
43    pub truncate_output_context_to_number_of_lines: Option<usize>,
44    /// A list of extra directory paths that should be included in the `$PATH` when
45    /// executing processes specified inside the tests.
46    pub extra_executable_search_paths: Vec<PathBuf>,
47    /// Whether messages on the standard error streams emitted during test runs
48    /// should always be shown.
49    pub always_show_stderr: bool,
50    /// Which shell to use (defaults to 'bash').
51    pub shell: String,
52}
53
54/// A function which can dynamically define newly used variables in a test.
55#[derive(Clone)]
56pub struct VariableLookup(fn(&str) -> Option<String>);
57
58impl Config
59{
60    /// The default variable lookup function.
61    ///
62    /// The supported variables are:
63    ///
64    /// * Any variable containing the string `"tempfile"`
65    ///   * Each distinct variable will be resolved to a distinct temporary file path.
66    pub const DEFAULT_VARIABLE_LOOKUP: VariableLookup = VariableLookup(|v| {
67        if v.contains("tempfile") {
68            let temp_file = NamedTempFile::new().expect("failed to create a temporary file");
69            Some(temp_file.into_temp_path().to_str().expect("temp file path is not utf-8").to_owned())
70        } else {
71            None
72        }
73    });
74
75    /// Marks a file extension as supported by the runner.
76    ///
77    /// We only attempt to run tests for files within the extension
78    /// whitelist.
79    pub fn add_extension<S>(&mut self, ext: S) where S: AsRef<str> {
80        self.supported_file_extensions.push(ext.as_ref().to_owned())
81    }
82
83    /// Marks multiple file extensions as supported by the running.
84    pub fn add_extensions(&mut self, extensions: &[&str]) {
85        self.supported_file_extensions.extend(extensions.iter().map(|s| s.to_string()));
86    }
87
88    /// Adds a search path to the test runner.
89    ///
90    /// We will recurse through the path to find tests.
91    pub fn add_search_path<P>(&mut self, path: P) where P: Into<String> {
92        self.test_paths.push(PathBuf::from(path.into()).canonicalize().unwrap());
93    }
94
95    /// Adds an extra executable directory to the OS `$PATH` when executing tests.
96    pub fn add_executable_search_path<P>(&mut self, path: P) where P: AsRef<Path> {
97        self.extra_executable_search_paths.push(path.as_ref().to_owned())
98    }
99
100    /// Gets an iterator over all test search directories.
101    pub fn test_search_directories(&self) -> impl Iterator<Item=&Path> {
102        self.test_paths.iter().filter(|p| {
103            println!("test path file name: {:?}", p.file_name());
104            p.is_dir()
105        }).map(PathBuf::as_ref)
106    }
107
108    /// Checks if a given extension will have tests run on it
109    pub fn is_extension_supported(&self, extension: &str) -> bool {
110        self.supported_file_extensions.iter().
111            find(|ext| &ext[..] == extension).is_some()
112    }
113
114    /// Looks up a variable.
115    pub fn lookup_variable<'a>(&self,
116                           name: &str,
117                           variables: &'a mut HashMap<String, String>)
118        -> &'a str {
119        if !variables.contains_key(name) {
120            match self.variable_lookup.0(name) {
121                Some(initial_value) => {
122                    variables.insert(name.to_owned(), initial_value.clone());
123                },
124                None => (),
125            }
126        }
127
128        variables.get(name).expect(&format!("no variable with the name '{}' exists", name))
129    }
130}
131
132impl Default for Config
133{
134    fn default() -> Self {
135        let mut extra_executable_search_paths = Vec::new();
136
137        // Always inject the current directory of the executable into the PATH so
138        // that lit can be used manually inside the test if desired.
139        if let Ok(current_exe) = std::env::current_exe() {
140            if let Some(parent) = current_exe.parent() {
141                extra_executable_search_paths.push(parent.to_owned());
142            }
143        }
144
145        Config {
146            supported_file_extensions: Vec::new(),
147            test_paths: Vec::new(),
148            constants: HashMap::new(),
149            variable_lookup: Config::DEFAULT_VARIABLE_LOOKUP,
150            cleanup_temporary_files: true,
151            save_artifacts_to_directory: None,
152            dump_variable_resolution: false,
153            always_show_stderr: false,
154            truncate_output_context_to_number_of_lines: Some(DEFAULT_MAX_OUTPUT_CONTEXT_LINE_COUNT),
155            extra_executable_search_paths,
156            shell: "bash".to_string(),
157        }
158    }
159}
160
161impl fmt::Debug for VariableLookup {
162    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
163        "<function>".fmt(fmt)
164    }
165}
166
167#[cfg(test)]
168mod test {
169    use super::*;
170
171    #[test]
172    fn lookup_variable_works_correctly() {
173        let config = Config {
174            variable_lookup: VariableLookup(|v| {
175                if v.contains("tempfile") { Some(format!("/tmp/temp-{}", v.as_bytes().as_ptr() as usize)) } else { None }
176            }),
177            constants: vec![("name".to_owned(), "bob".to_owned())].into_iter().collect(),
178            ..Config::default()
179        };
180        let mut variables = config.constants.clone();
181
182        // Can lookup constants
183        assert_eq!("bob", config.lookup_variable("name", &mut variables),
184                   "cannot lookup constants by name");
185        let first_temp = config.lookup_variable("first_tempfile", &mut variables).to_owned();
186        let second_temp = config.lookup_variable("second_tempfile", &mut variables).to_owned();
187
188        assert!(first_temp != second_temp,
189                "different temporary paths should be different");
190
191        assert_eq!(first_temp,
192                   config.lookup_variable("first_tempfile", &mut variables),
193                   "first temp has changed its value");
194
195        assert_eq!(second_temp,
196                   config.lookup_variable("second_tempfile", &mut variables),
197                   "second temp has changed its value");
198    }
199}
200