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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use std::{collections::BTreeMap, path::Path};

use litcheck::{
    diagnostics::{DiagResult, Diagnostic, IntoDiagnostic, Report, SourceFile, SourceSpan},
    fs::PatternSet,
    Input, StaticCow,
};
use serde::Deserialize;

use crate::{
    config::{BooleanExpr, FeatureSet, SubstitutionSet},
    format::SelectedTestFormat,
    Config,
};

#[derive(Debug, Diagnostic, thiserror::Error)]
pub enum TestConfigError {
    #[error("invalid syntax in configuration file")]
    #[diagnostic()]
    Syntax {
        #[label("{error}")]
        span: SourceSpan,
        #[source]
        error: toml::de::Error,
    },
}

/// This is the type represents a local lit test configuration file
#[derive(Default, Clone, Deserialize)]
pub struct TestConfig {
    /// The test format which will be used to discover and run tests in the suite
    ///
    /// Defaults to the `ShTest` format, see [crate::formats::ShTest] for details.
    #[serde(default)]
    pub format: SelectedTestFormat,
    /// For test formats which scan directories for tests, this is a list of glob
    /// patterns used to find test files, e.g. `*.rs` would search for any test files
    /// ending in `.rs` in any of the search paths.
    ///
    /// See [glob::Pattern] docs for the full syntax available for glob patterns.
    ///
    /// By default all text files found in the search path for tests are considered
    /// valid tests, unless they are prefixed with `.`, e.g. `.gitignore`.
    #[serde(default)]
    pub patterns: PatternSet,
    /// Environment variables to set when executing tests
    #[serde(default)]
    pub env: BTreeMap<StaticCow<str>, StaticCow<str>>,
    /// The set of substitutions which can be used in a test script.
    ///
    /// The substitutions will be replaced prior to running the test.
    #[serde(default)]
    pub substitutions: SubstitutionSet,
    /// The set of feature strings available for these tests
    #[serde(default)]
    pub available_features: FeatureSet,
}
impl TestConfig {
    /// Parse local test suite configuration from `input`, inheriting from `parent` where applicable
    pub fn parse<P: AsRef<Path>>(path: P) -> DiagResult<Box<Self>> {
        let path = path.as_ref();
        let path = if path.is_absolute() {
            path.to_path_buf()
        } else {
            path.canonicalize().into_diagnostic()?
        };
        let source = Input::from(path).into_source(false).into_diagnostic()?;
        toml::from_str::<Self>(source.source())
            .map(Box::new)
            .map_err(|error| {
                let span = error.span().unwrap_or(0..0);
                Report::new(TestConfigError::Syntax {
                    span: SourceSpan::from(span),
                    error,
                })
                .with_source_code(source)
            })
    }

    /// Inherits values from `parent` that are empty/default in `self`.
    pub fn inherit(&mut self, parent: &Self) {
        if matches!(self.format, SelectedTestFormat::Default) {
            self.format = parent.format.clone();
        }

        if self.patterns.is_empty() {
            self.patterns = parent.patterns.clone();
        }

        if !parent.env.is_empty() {
            let env = core::mem::replace(&mut self.env, parent.env.clone());
            self.env.extend(env);
        }

        if !parent.substitutions.is_empty() {
            let subs = core::mem::replace(&mut self.substitutions, parent.substitutions.clone());
            self.substitutions.extend(subs);
        }

        if !parent.available_features.is_empty() {
            let features = core::mem::replace(
                &mut self.available_features,
                parent.available_features.clone(),
            );
            self.available_features.extend(features);
        }
    }

    pub fn set_default_features(&mut self, config: &Config) {
        use target_lexicon::*;

        let host = config.host();
        let target = config.target();

        self.available_features
            .insert(format!("system-{}", &host.operating_system));
        self.available_features.insert(format!("host={}", &host));
        self.available_features
            .insert(format!("target={}", &target));
        if host == target {
            self.available_features.insert("native");
        }
        match target.architecture {
            Architecture::X86_64 | Architecture::X86_64h => {
                self.available_features.insert("target-x86_64");
                match target.operating_system {
                    OperatingSystem::Darwin => {
                        self.available_features.insert("x86_64-apple");
                    }
                    OperatingSystem::Linux => {
                        self.available_features.insert("x86_64-linux");
                    }
                    _ => (),
                }
            }
            Architecture::X86_32(_) => {
                self.available_features.insert("target-x86");
            }
            Architecture::Aarch64(_) => {
                self.available_features.insert("target-aarch64");
            }
            Architecture::Arm(_) => {
                self.available_features.insert("target-arm");
            }
            arch => {
                self.available_features.insert(format!("target-{}", arch));
            }
        }
        match target.operating_system {
            OperatingSystem::Darwin | OperatingSystem::MacOSX { .. } => {
                self.available_features.insert("system-linker-mach-o");
            }
            _ => (),
        }
    }

    pub fn set_default_substitutions(
        &mut self,
        _config: &Config,
        suite: &super::TestSuite,
        source_dir: &Path,
    ) {
        // This binary is a multi-call executable containing both lit and filecheck,
        // so get the path that we were invoked with (or that the OS chose to give us),
        // and depending on how it was invoked,
        let mut exe =
            std::env::current_exe().expect("unable to detect lit/filecheck executable path");
        let filecheck = if exe.ends_with("filecheck") {
            // We are running lit, so a filename of 'filecheck'
            // means that lit was invoked explicitly, thus we
            // should use a substitution that invokes filecheck
            // explicitly
            StaticCow::Owned(format!("{} check", exe.display()))
        } else if exe.ends_with("lit") {
            // We must have been invoked as a symlink, in
            // which case the filecheck symlink is in the
            // same directory, we just need to update the
            // executable name
            exe.set_file_name("filecheck");
            StaticCow::Owned(exe.to_string_lossy().into_owned())
        } else {
            // We're probably running as a test executable right now,
            // so just use 'filecheck' as the substitution
            StaticCow::Borrowed("filecheck")
        };
        self.substitutions.insert("[Ff]ile[Cc]heck", filecheck);

        self.substitutions
            .insert(r"%\{pathsep\}", if cfg!(windows) { ";" } else { ":" });

        let test_root = source_dir
            .components()
            .next()
            .unwrap()
            .as_os_str()
            .to_string_lossy()
            .into_owned();
        self.substitutions.insert(r"%\{fs-src-root\}", test_root);
        let temp_root = suite
            .working_dir()
            .components()
            .next()
            .unwrap()
            .as_os_str()
            .to_string_lossy()
            .into_owned();
        self.substitutions.insert(r"%\{fs-tmp-root\}", temp_root);
        self.substitutions
            .insert(r"%\{fs-sep\}", std::path::MAIN_SEPARATOR_STR);
    }

    #[inline]
    pub fn missing_features<F: AsRef<BooleanExpr>>(&self, required: &[F]) -> Option<String> {
        self.available_features.missing_features(required)
    }

    #[inline]
    pub fn unsupported_features<F: AsRef<BooleanExpr>>(&self, unsupported: &[F]) -> Option<String> {
        self.available_features.unsupported_features(unsupported)
    }
}