litcheck_lit/test/
config.rs1use std::{collections::BTreeMap, path::Path};
2
3use litcheck::{
4 diagnostics::{DiagResult, Diagnostic, IntoDiagnostic, Report, SourceSpan},
5 fs::PatternSet,
6 Input, StaticCow,
7};
8use serde::Deserialize;
9
10use crate::{
11 config::{BooleanExpr, FeatureSet, SubstitutionSet},
12 format::SelectedTestFormat,
13 Config,
14};
15
16#[derive(Debug, Diagnostic, thiserror::Error)]
17pub enum TestConfigError {
18 #[error("invalid syntax in configuration file")]
19 #[diagnostic()]
20 Syntax {
21 #[label("{error}")]
22 span: SourceSpan,
23 #[source]
24 error: toml::de::Error,
25 },
26}
27
28#[derive(Default, Clone, Deserialize)]
30pub struct TestConfig {
31 #[serde(default)]
35 pub format: SelectedTestFormat,
36 #[serde(default)]
45 pub patterns: PatternSet,
46 #[serde(default)]
54 pub exclude: PatternSet,
55 #[serde(default)]
57 pub env: BTreeMap<StaticCow<str>, StaticCow<str>>,
58 #[serde(default)]
62 pub substitutions: SubstitutionSet,
63 #[serde(default)]
65 pub available_features: FeatureSet,
66}
67impl TestConfig {
68 pub fn parse<P: AsRef<Path>>(path: P, config: &Config) -> DiagResult<Box<Self>> {
70 let path = path.as_ref();
71 let path = if path.is_absolute() {
72 path.to_path_buf()
73 } else {
74 path.canonicalize().into_diagnostic()?
75 };
76 let source = Input::from(path)
77 .into_source(false, config.source_manager())
78 .into_diagnostic()?;
79 toml::from_str::<Self>(source.as_str())
80 .map(Box::new)
81 .map_err(|error| {
82 let span = error.span().unwrap_or(0..0);
83 Report::new(TestConfigError::Syntax {
84 span: SourceSpan::from_range_unchecked(source.id(), span),
85 error,
86 })
87 .with_source_code(source)
88 })
89 }
90
91 pub fn inherit(&mut self, parent: &Self) {
93 if matches!(self.format, SelectedTestFormat::Default) {
94 self.format = parent.format.clone();
95 }
96
97 if self.patterns.is_empty() {
98 self.patterns = parent.patterns.clone();
99 }
100
101 if self.exclude.is_empty() {
102 self.exclude = parent.exclude.clone();
103 }
104
105 if !parent.env.is_empty() {
106 let env = core::mem::replace(&mut self.env, parent.env.clone());
107 self.env.extend(env);
108 }
109
110 if !parent.substitutions.is_empty() {
111 let subs = core::mem::replace(&mut self.substitutions, parent.substitutions.clone());
112 self.substitutions.extend(subs);
113 }
114
115 if !parent.available_features.is_empty() {
116 let features = core::mem::replace(
117 &mut self.available_features,
118 parent.available_features.clone(),
119 );
120 self.available_features.extend(features);
121 }
122 }
123
124 pub fn set_default_features(&mut self, config: &Config) {
125 use target_lexicon::*;
126
127 let host = config.host();
128 let target = config.target();
129
130 self.available_features
131 .insert(format!("system-{}", &host.operating_system));
132 self.available_features.insert(format!("host={}", &host));
133 self.available_features
134 .insert(format!("target={}", &target));
135 if host == target {
136 self.available_features.insert("native");
137 }
138 match target.architecture {
139 Architecture::X86_64 | Architecture::X86_64h => {
140 self.available_features.insert("target-x86_64");
141 match target.operating_system {
142 OperatingSystem::Darwin(_) => {
143 self.available_features.insert("x86_64-apple");
144 }
145 OperatingSystem::Linux => {
146 self.available_features.insert("x86_64-linux");
147 }
148 _ => (),
149 }
150 }
151 Architecture::X86_32(_) => {
152 self.available_features.insert("target-x86");
153 }
154 Architecture::Aarch64(_) => {
155 self.available_features.insert("target-aarch64");
156 }
157 Architecture::Arm(_) => {
158 self.available_features.insert("target-arm");
159 }
160 arch => {
161 self.available_features.insert(format!("target-{}", arch));
162 }
163 }
164 match target.operating_system {
165 OperatingSystem::Darwin(_) | OperatingSystem::MacOSX { .. } => {
166 self.available_features.insert("system-linker-mach-o");
167 }
168 _ => (),
169 }
170 }
171
172 pub fn set_default_substitutions(
173 &mut self,
174 _config: &Config,
175 suite: &super::TestSuite,
176 source_dir: &Path,
177 ) {
178 let mut exe =
182 std::env::current_exe().expect("unable to detect lit/filecheck executable path");
183 let (filecheck, not) = if exe.ends_with("litcheck") {
184 let filecheck = StaticCow::Owned(format!("{} filecheck", exe.display()));
189 let not = StaticCow::Owned(format!("{} not", exe.display()));
190 (filecheck, not)
191 } else if exe.ends_with("lit") {
192 exe.set_file_name("filecheck");
197 let filecheck = StaticCow::Owned(exe.to_string_lossy().into_owned());
198 exe.set_file_name("not");
199 let not = StaticCow::Owned(exe.to_string_lossy().into_owned());
200 (filecheck, not)
201 } else {
202 (StaticCow::Borrowed("filecheck"), StaticCow::Borrowed("not"))
205 };
206 self.substitutions.insert(
207 "(?<before>^|\\s)[Ff]ile[Cc]heck(?<after>$|\\s)",
208 format!("$before{filecheck}$after"),
209 );
210 self.substitutions.insert(
211 "(?<before>^|\\s)not(?<after>$|\\s)",
212 format!("$before{not}$after"),
213 );
214
215 self.substitutions
216 .insert(r"%\{pathsep\}", if cfg!(windows) { ";" } else { ":" });
217
218 let test_root = source_dir
219 .components()
220 .next()
221 .unwrap()
222 .as_os_str()
223 .to_string_lossy()
224 .into_owned();
225 self.substitutions.insert(r"%\{fs-src-root\}", test_root);
226 let temp_root = suite
227 .working_dir()
228 .components()
229 .next()
230 .unwrap()
231 .as_os_str()
232 .to_string_lossy()
233 .into_owned();
234 self.substitutions.insert(r"%\{fs-tmp-root\}", temp_root);
235 self.substitutions
236 .insert(r"%\{fs-sep\}", std::path::MAIN_SEPARATOR_STR);
237 }
238
239 #[inline]
240 pub fn missing_features<F: AsRef<BooleanExpr>>(&self, required: &[F]) -> Option<String> {
241 self.available_features.missing_features(required)
242 }
243
244 #[inline]
245 pub fn unsupported_features<F: AsRef<BooleanExpr>>(&self, unsupported: &[F]) -> Option<String> {
246 self.available_features.unsupported_features(unsupported)
247 }
248}