litcheck_lit/test/
config.rs1use std::{collections::BTreeMap, path::Path};
2
3use litcheck::{
4 diagnostics::{DiagResult, Diagnostic, IntoDiagnostic, Report, SourceFile, 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)]
48 pub env: BTreeMap<StaticCow<str>, StaticCow<str>>,
49 #[serde(default)]
53 pub substitutions: SubstitutionSet,
54 #[serde(default)]
56 pub available_features: FeatureSet,
57}
58impl TestConfig {
59 pub fn parse<P: AsRef<Path>>(path: P) -> DiagResult<Box<Self>> {
61 let path = path.as_ref();
62 let path = if path.is_absolute() {
63 path.to_path_buf()
64 } else {
65 path.canonicalize().into_diagnostic()?
66 };
67 let source = Input::from(path).into_source(false).into_diagnostic()?;
68 toml::from_str::<Self>(source.source())
69 .map(Box::new)
70 .map_err(|error| {
71 let span = error.span().unwrap_or(0..0);
72 Report::new(TestConfigError::Syntax {
73 span: SourceSpan::from(span),
74 error,
75 })
76 .with_source_code(source)
77 })
78 }
79
80 pub fn inherit(&mut self, parent: &Self) {
82 if matches!(self.format, SelectedTestFormat::Default) {
83 self.format = parent.format.clone();
84 }
85
86 if self.patterns.is_empty() {
87 self.patterns = parent.patterns.clone();
88 }
89
90 if !parent.env.is_empty() {
91 let env = core::mem::replace(&mut self.env, parent.env.clone());
92 self.env.extend(env);
93 }
94
95 if !parent.substitutions.is_empty() {
96 let subs = core::mem::replace(&mut self.substitutions, parent.substitutions.clone());
97 self.substitutions.extend(subs);
98 }
99
100 if !parent.available_features.is_empty() {
101 let features = core::mem::replace(
102 &mut self.available_features,
103 parent.available_features.clone(),
104 );
105 self.available_features.extend(features);
106 }
107 }
108
109 pub fn set_default_features(&mut self, config: &Config) {
110 use target_lexicon::*;
111
112 let host = config.host();
113 let target = config.target();
114
115 self.available_features
116 .insert(format!("system-{}", &host.operating_system));
117 self.available_features.insert(format!("host={}", &host));
118 self.available_features
119 .insert(format!("target={}", &target));
120 if host == target {
121 self.available_features.insert("native");
122 }
123 match target.architecture {
124 Architecture::X86_64 | Architecture::X86_64h => {
125 self.available_features.insert("target-x86_64");
126 match target.operating_system {
127 OperatingSystem::Darwin => {
128 self.available_features.insert("x86_64-apple");
129 }
130 OperatingSystem::Linux => {
131 self.available_features.insert("x86_64-linux");
132 }
133 _ => (),
134 }
135 }
136 Architecture::X86_32(_) => {
137 self.available_features.insert("target-x86");
138 }
139 Architecture::Aarch64(_) => {
140 self.available_features.insert("target-aarch64");
141 }
142 Architecture::Arm(_) => {
143 self.available_features.insert("target-arm");
144 }
145 arch => {
146 self.available_features.insert(format!("target-{}", arch));
147 }
148 }
149 match target.operating_system {
150 OperatingSystem::Darwin | OperatingSystem::MacOSX { .. } => {
151 self.available_features.insert("system-linker-mach-o");
152 }
153 _ => (),
154 }
155 }
156
157 pub fn set_default_substitutions(
158 &mut self,
159 _config: &Config,
160 suite: &super::TestSuite,
161 source_dir: &Path,
162 ) {
163 let mut exe =
167 std::env::current_exe().expect("unable to detect lit/filecheck executable path");
168 let filecheck = if exe.ends_with("litcheck") {
169 StaticCow::Owned(format!("{} filecheck", exe.display()))
174 } else if exe.ends_with("lit") {
175 exe.set_file_name("filecheck");
180 StaticCow::Owned(exe.to_string_lossy().into_owned())
181 } else {
182 StaticCow::Borrowed("filecheck")
185 };
186 self.substitutions.insert("[Ff]ile[Cc]heck", filecheck);
187
188 self.substitutions
189 .insert(r"%\{pathsep\}", if cfg!(windows) { ";" } else { ":" });
190
191 let test_root = source_dir
192 .components()
193 .next()
194 .unwrap()
195 .as_os_str()
196 .to_string_lossy()
197 .into_owned();
198 self.substitutions.insert(r"%\{fs-src-root\}", test_root);
199 let temp_root = suite
200 .working_dir()
201 .components()
202 .next()
203 .unwrap()
204 .as_os_str()
205 .to_string_lossy()
206 .into_owned();
207 self.substitutions.insert(r"%\{fs-tmp-root\}", temp_root);
208 self.substitutions
209 .insert(r"%\{fs-sep\}", std::path::MAIN_SEPARATOR_STR);
210 }
211
212 #[inline]
213 pub fn missing_features<F: AsRef<BooleanExpr>>(&self, required: &[F]) -> Option<String> {
214 self.available_features.missing_features(required)
215 }
216
217 #[inline]
218 pub fn unsupported_features<F: AsRef<BooleanExpr>>(&self, unsupported: &[F]) -> Option<String> {
219 self.available_features.unsupported_features(unsupported)
220 }
221}