litcheck_lit/suite/
mod.rs1mod error;
2mod registry;
3mod set;
4
5pub use self::error::TestSuiteError;
6pub use self::registry::{DefaultTestSuiteRegistry, TestSuiteRegistry};
7pub use self::set::{TestSuiteKey, TestSuiteSet};
8
9use std::{
10 borrow::Cow,
11 collections::BTreeSet,
12 path::{Path, PathBuf},
13 sync::Arc,
14};
15
16use intrusive_collections::RBTreeAtomicLink;
17use litcheck::{
18 diagnostics::{DiagResult, IntoDiagnostic, Report, SourceFile, SourceSpan, WrapErr},
19 Input, StaticCow,
20};
21use parking_lot::Mutex;
22use serde::Deserialize;
23
24use crate::{Config, test::TestConfig};
25
26#[derive(Deserialize)]
27pub struct TestSuite {
28 #[serde(skip, default)]
29 link: RBTreeAtomicLink,
30 pub name: toml::Spanned<Arc<str>>,
36 #[serde(skip, default)]
38 pub path: Option<Arc<Path>>,
39 #[serde(default = "empty_spanned_path")]
46 pub source_dir: toml::Spanned<StaticCow<Path>>,
47 #[serde(default = "empty_spanned_path")]
54 pub working_dir: toml::Spanned<StaticCow<Path>>,
55 #[serde(skip, default)]
59 pub temp_dir: Option<tempdir::TempDir>,
60 #[serde(flatten)]
65 pub config: Arc<TestConfig>,
66 #[serde(skip)]
70 search_paths: Mutex<BTreeSet<PathBuf>>,
71}
72impl TestSuite {
73 #[inline]
75 pub fn name(&self) -> &str {
76 self.name.as_ref()
78 }
79
80 pub fn span(&self) -> SourceSpan {
81 SourceSpan::from(self.name.span())
82 }
83
84 pub fn id(&self) -> TestSuiteKey {
85 TestSuiteKey::new(self.name.clone(), self.path.clone())
86 }
87
88 #[inline]
89 pub fn source_dir(&self) -> &Path {
90 self.source_dir.as_ref()
91 }
92
93 #[inline]
94 pub fn working_dir(&self) -> &Path {
95 self.working_dir.as_ref()
96 }
97
98 pub fn search_paths(&self) -> parking_lot::MutexGuard<'_, BTreeSet<PathBuf>> {
99 self.search_paths.lock()
100 }
101
102 pub fn filter_by_path<P: Into<PathBuf>>(&self, path: P) {
103 self.search_paths.lock().insert(path.into());
104 }
105
106 pub fn parse<P: AsRef<Path>>(path: P, config: &Config) -> DiagResult<Arc<Self>> {
108 let path = path.as_ref();
109 let path = if path.is_absolute() {
110 path.to_path_buf()
111 } else {
112 path.canonicalize().into_diagnostic()?
113 };
114 let source = Input::from(path.as_path())
115 .into_arc_source(false)
116 .into_diagnostic()?;
117 let toml = source.source();
118 let mut suite = toml::from_str::<Self>(toml).map_err(|error| {
119 let span = error.span();
120 Report::new(TestSuiteError::Syntax {
121 span: span.map(SourceSpan::from),
122 error,
123 })
124 .with_source_code(source.clone())
125 })?;
126
127 if suite.name().is_empty() {
128 return Err(
129 Report::new(TestSuiteError::EmptyName { span: suite.span() })
130 .with_source_code(source.clone()),
131 );
132 }
133 suite.path = Some(path.clone().into());
134
135 let parent_dir = path.parent().unwrap();
136
137 if suite.source_dir() == Path::new("") {
138 *suite.source_dir.get_mut() = Cow::Owned(parent_dir.to_path_buf());
139 }
140
141 if suite.source_dir().is_relative() {
142 let source_dir = parent_dir.join(suite.source_dir.get_ref());
143 *suite.source_dir.get_mut() = Cow::Owned(source_dir);
144 }
145
146 if !suite.source_dir().is_dir() {
147 let span = suite.source_dir.span();
148 return Err(Report::new(TestSuiteError::InvalidSourceDir {
149 span: span.into(),
150 source_dir: suite.source_dir.into_inner().into_owned(),
151 })
152 .with_source_code(source.clone()));
153 }
154
155 if suite.working_dir() == Path::new("") {
156 let temp_dir = tempdir::TempDir::new(suite.name())
157 .into_diagnostic()
158 .wrap_err_with(|| {
159 format!(
160 "failed to create temporary directory for test suite at '{}'",
161 &TestSuiteKey::new(suite.name.clone(), suite.path.clone()),
162 )
163 })?;
164 *suite.working_dir.get_mut() = Cow::Owned(temp_dir.path().to_path_buf());
165 suite.temp_dir = Some(temp_dir);
166 } else if suite.working_dir().is_relative() {
167 let working_dir = parent_dir.join(suite.working_dir.get_ref());
168 *suite.working_dir.get_mut() = Cow::Owned(working_dir);
169 }
170
171 if !suite.working_dir().is_dir() {
172 let span = suite.working_dir.span();
173 let working_dir = suite.working_dir.into_inner();
174 return Err(Report::new(TestSuiteError::InvalidWorkingDir {
175 span: span.into(),
176 working_dir: working_dir.into_owned(),
177 })
178 .with_source_code(source));
179 }
180
181 let mut default_config = TestConfig::default();
183 default_config.set_default_features(config);
184 default_config.set_default_substitutions(config, &suite, path.as_path());
185 let suite_config = Arc::make_mut(&mut suite.config);
186 suite_config.inherit(&default_config);
187
188 Ok(Arc::new(suite))
189 }
190}
191
192fn empty_spanned_path() -> toml::Spanned<StaticCow<Path>> {
193 toml::Spanned::new(0..0, litcheck::fs::empty_path())
194}