1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use condition::TestCondition;
8use verb::TestVerb;
9
10pub mod arguments;
11pub mod condition;
12pub mod error;
13pub mod test_case;
14pub mod verb;
15pub use kdl;
16pub use miette;
17
18pub struct TestDsl<H> {
23 verbs: HashMap<String, Box<dyn TestVerb<H>>>,
24 conditions: HashMap<String, Box<dyn condition::TestCondition<H>>>,
25}
26
27impl<H> std::fmt::Debug for TestDsl<H> {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("TestDsl").finish_non_exhaustive()
30 }
31}
32
33impl<H: 'static> Default for TestDsl<H> {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl<H: 'static> TestDsl<H> {
40 pub fn new() -> Self {
42 TestDsl {
43 verbs: HashMap::default(),
44 conditions: HashMap::default(),
45 }
46 }
47
48 pub fn add_verb(&mut self, name: impl AsRef<str>, verb: impl TestVerb<H>) {
54 let existing = self.verbs.insert(name.as_ref().to_string(), Box::new(verb));
55 assert!(existing.is_none());
56 }
57
58 pub fn add_condition(
65 &mut self,
66 name: impl AsRef<str>,
67 condition: impl condition::TestCondition<H>,
68 ) {
69 let existing = self
70 .conditions
71 .insert(name.as_ref().to_string(), Box::new(condition));
72
73 assert!(existing.is_none());
74 }
75
76 pub fn parse_document(
79 &self,
80 input: miette::NamedSource<Arc<str>>,
81 ) -> Result<Vec<test_case::TestCase<H>>, error::TestParseError> {
82 let document = kdl::KdlDocument::parse(input.inner())?;
83
84 let mut cases = vec![];
85
86 let mut errors = vec![];
87
88 for testcase_node in document.nodes() {
89 if testcase_node.name().value() != "testcase" {
90 errors.push(error::TestErrorCase::NotTestcase {
91 span: testcase_node.name().span(),
92 });
93
94 continue;
95 }
96
97 let mut testcase = test_case::TestCase::new(input.clone());
98
99 for verb_node in testcase_node.iter_children() {
100 match self.parse_verb(verb_node) {
101 Ok(verb) => testcase.creators.push(verb),
102 Err(e) => errors.push(e),
103 }
104 }
105
106 cases.push(testcase);
107 }
108
109 if !errors.is_empty() {
110 return Err(error::TestParseError {
111 errors,
112 source_code: Some(input.clone()),
113 });
114 }
115
116 Ok(cases)
117 }
118
119 fn parse_condition(
120 &self,
121 condition_node: &kdl::KdlNode,
122 ) -> Result<Box<dyn TestConditionCreator<H>>, error::TestErrorCase> {
123 self.conditions
124 .get(condition_node.name().value())
125 .ok_or_else(|| error::TestErrorCase::UnknownCondition {
126 condition: condition_node.name().span(),
127 })
128 .map(|cond| {
129 Box::new(DirectCondition {
130 condition: cond.clone(),
131 node: condition_node.clone(),
132 }) as Box<_>
133 })
134 }
135
136 fn parse_verb(
137 &self,
138 verb_node: &kdl::KdlNode,
139 ) -> Result<Box<dyn TestVerbCreator<H>>, error::TestErrorCase> {
140 match verb_node.name().value() {
141 "repeat" => {
142 let times = verb_node
143 .get(0)
144 .ok_or_else(|| error::TestErrorCase::MissingArgument {
145 parent: verb_node.name().span(),
146 missing: String::from("`repeat` takes one argument, the repetition count"),
147 })?
148 .as_integer()
149 .ok_or_else(|| error::TestErrorCase::WrongArgumentType {
150 parent: verb_node.name().span(),
151 argument: verb_node.iter().next().unwrap().span(),
152 expected: String::from("Expected an integer"),
153 })? as usize;
154
155 let block = verb_node
156 .iter_children()
157 .map(|node| self.parse_verb(node))
158 .collect::<Result<_, _>>()?;
159
160 Ok(Box::new(Repeat { times, block }))
161 }
162 "group" => Ok(Box::new(Group {
163 block: verb_node
164 .iter_children()
165 .map(|n| self.parse_verb(n))
166 .collect::<Result<_, _>>()?,
167 })),
168 "assert" => Ok(Box::new(AssertConditions {
169 conditions: verb_node
170 .iter_children()
171 .map(|node| self.parse_condition(node))
172 .collect::<Result<_, _>>()?,
173 })),
174 name => {
175 let verb = self
176 .verbs
177 .get(name)
178 .ok_or_else(|| error::TestErrorCase::UnknownVerb {
179 verb: verb_node.name().span(),
180 })?
181 .clone();
182
183 Ok(Box::new(DirectVerb {
184 verb,
185 node: verb_node.clone(),
186 }))
187 }
188 }
189 }
190}
191
192trait TestVerbCreator<H> {
193 fn get_test_verbs(&self) -> Box<dyn Iterator<Item = TestVerbInstance<'_, H>> + '_>;
194}
195
196trait TestConditionCreator<H> {
197 fn get_test_conditions(&self) -> Box<dyn Iterator<Item = TestConditionInstance<'_, H>> + '_>;
198}
199
200struct Group<H> {
201 block: Vec<Box<dyn TestVerbCreator<H>>>,
202}
203
204impl<H: 'static> TestVerbCreator<H> for Group<H> {
205 fn get_test_verbs(&self) -> Box<dyn Iterator<Item = TestVerbInstance<'_, H>> + '_> {
206 Box::new(self.block.iter().flat_map(|c| c.get_test_verbs()))
207 }
208}
209
210struct Repeat<H> {
211 times: usize,
212 block: Vec<Box<dyn TestVerbCreator<H>>>,
213}
214
215impl<H: 'static> TestVerbCreator<H> for Repeat<H> {
216 fn get_test_verbs(&self) -> Box<dyn Iterator<Item = TestVerbInstance<'_, H>> + '_> {
217 Box::new(
218 std::iter::repeat_with(|| self.block.iter().flat_map(|c| c.get_test_verbs()))
219 .take(self.times)
220 .flatten(),
221 )
222 }
223}
224
225struct DirectVerb<H> {
226 verb: Box<dyn TestVerb<H>>,
227 node: kdl::KdlNode,
228}
229
230impl<H: 'static> TestVerbCreator<H> for DirectVerb<H> {
231 fn get_test_verbs(&self) -> Box<dyn Iterator<Item = TestVerbInstance<'_, H>> + '_> {
232 Box::new(std::iter::once(TestVerbInstance {
233 verb: self.verb.clone(),
234 node: &self.node,
235 }))
236 }
237}
238
239struct DirectCondition<H> {
240 condition: Box<dyn TestCondition<H>>,
241 node: kdl::KdlNode,
242}
243
244impl<H: 'static> TestConditionCreator<H> for DirectCondition<H> {
245 fn get_test_conditions(&self) -> Box<dyn Iterator<Item = TestConditionInstance<'_, H>> + '_> {
246 Box::new(std::iter::once(TestConditionInstance {
247 condition: self.condition.clone(),
248 node: &self.node,
249 }))
250 }
251}
252
253struct AssertConditions<H> {
254 conditions: Vec<Box<dyn TestConditionCreator<H>>>,
255}
256
257impl<H: 'static> TestVerbCreator<H> for AssertConditions<H> {
258 fn get_test_verbs(&self) -> Box<dyn Iterator<Item = TestVerbInstance<'_, H>> + '_> {
259 Box::new(
260 self.conditions
261 .iter()
262 .flat_map(|cond| cond.get_test_conditions())
263 .map(|cond| TestVerbInstance {
264 node: cond.node,
265 verb: Box::new(AssertVerb {
266 condition: cond.condition,
267 }),
268 }),
269 )
270 }
271}
272
273struct AssertVerb<H> {
274 condition: Box<dyn TestCondition<H>>,
275}
276
277impl<H: 'static> TestVerb<H> for AssertVerb<H> {
278 fn run(&self, harness: &mut H, node: &kdl::KdlNode) -> Result<(), error::TestErrorCase> {
279 self.condition.check_now(harness, node).and_then(|res| {
280 res.then_some(())
281 .ok_or_else(|| error::TestErrorCase::Error {
282 error: miette::miette!("Assert failed"),
283 label: node.span(),
284 })
285 })
286 }
287
288 fn clone_box(&self) -> Box<dyn TestVerb<H>> {
289 Box::new(self.clone())
290 }
291}
292
293impl<H: 'static> Clone for AssertVerb<H> {
294 fn clone(&self) -> Self {
295 AssertVerb {
296 condition: self.condition.clone(),
297 }
298 }
299}
300
301struct TestConditionInstance<'p, H> {
302 condition: Box<dyn TestCondition<H>>,
303 node: &'p kdl::KdlNode,
304}
305
306struct TestVerbInstance<'p, H> {
307 verb: Box<dyn TestVerb<H>>,
308 node: &'p kdl::KdlNode,
309}
310
311impl<'p, H: 'static> TestVerbInstance<'p, H> {
312 fn run<'h>(&'p self, harness: &'h mut H) -> Result<(), error::TestErrorCase> {
313 self.verb.run(harness, self.node)
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use std::sync::Arc;
320 use std::sync::atomic::AtomicUsize;
321
322 use miette::NamedSource;
323
324 use crate::TestDsl;
325 use crate::verb::FunctionVerb;
326
327 struct ArithmeticHarness {
328 value: AtomicUsize,
329 }
330
331 #[test]
332 fn simple_test() {
333 let mut ts = TestDsl::<ArithmeticHarness>::new();
334 ts.add_verb(
335 "add_one",
336 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
337 ah.value.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
338
339 Ok(())
340 }),
341 );
342
343 ts.add_verb(
344 "mul_two",
345 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
346 let value = ah.value.load(std::sync::atomic::Ordering::SeqCst);
347 ah.value
348 .store(value * 2, std::sync::atomic::Ordering::SeqCst);
349 Ok(())
350 }),
351 );
352
353 let tc = ts
354 .parse_document(NamedSource::new(
355 "test.kdl",
356 Arc::from(
357 r#"
358 testcase {
359 add_one
360 add_one
361 mul_two
362 }
363 "#,
364 ),
365 ))
366 .unwrap();
367
368 let mut ah = ArithmeticHarness {
369 value: AtomicUsize::new(0),
370 };
371
372 tc[0].run(&mut ah).unwrap();
373
374 assert_eq!(ah.value.load(std::sync::atomic::Ordering::SeqCst), 4);
375 }
376
377 #[test]
378 fn repeat_test() {
379 let mut ts = TestDsl::<ArithmeticHarness>::new();
380 ts.add_verb(
381 "add_one",
382 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
383 ah.value.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
384
385 Ok(())
386 }),
387 );
388
389 ts.add_verb(
390 "mul_two",
391 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
392 let value = ah.value.load(std::sync::atomic::Ordering::SeqCst);
393 ah.value
394 .store(value * 2, std::sync::atomic::Ordering::SeqCst);
395
396 Ok(())
397 }),
398 );
399
400 let tc = ts
401 .parse_document(NamedSource::new(
402 "test.kdl",
403 Arc::from(
404 r#"
405 testcase {
406 repeat 2 {
407 repeat 2 {
408 add_one
409 mul_two
410 }
411 }
412 }
413 "#,
414 ),
415 ))
416 .unwrap();
417
418 let mut ah = ArithmeticHarness {
419 value: AtomicUsize::new(0),
420 };
421
422 tc[0].run(&mut ah).unwrap();
423
424 assert_eq!(ah.value.load(std::sync::atomic::Ordering::SeqCst), 30);
425 }
426
427 #[test]
428 fn check_arguments_work() {
429 let mut ts = TestDsl::<ArithmeticHarness>::new();
430 ts.add_verb(
431 "add_one",
432 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
433 ah.value.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
434
435 Ok(())
436 }),
437 );
438
439 ts.add_verb(
440 "add",
441 FunctionVerb::new(|ah: &mut ArithmeticHarness, num: usize| {
442 ah.value.fetch_add(num, std::sync::atomic::Ordering::SeqCst);
443 Ok(())
444 }),
445 );
446
447 ts.add_verb(
448 "mul_two",
449 FunctionVerb::new(|ah: &mut ArithmeticHarness| {
450 let value = ah.value.load(std::sync::atomic::Ordering::SeqCst);
451 ah.value
452 .store(value * 2, std::sync::atomic::Ordering::SeqCst);
453 Ok(())
454 }),
455 );
456
457 let tc = ts
458 .parse_document(NamedSource::new(
459 "test.kdl",
460 Arc::from(
461 r#"
462 testcase {
463 repeat 2 {
464 repeat 2 {
465 group {
466 add 2
467 mul_two
468 }
469 }
470 }
471 }
472 "#,
473 ),
474 ))
475 .unwrap();
476
477 let mut ah = ArithmeticHarness {
478 value: AtomicUsize::new(0),
479 };
480
481 tc[0].run(&mut ah).unwrap();
482
483 assert_eq!(ah.value.load(std::sync::atomic::Ordering::SeqCst), 60);
484 }
485}