1use std::collections::{HashMap, VecDeque};
2use std::env;
3use std::fmt::Write;
4use std::fs;
5use std::path::PathBuf;
6use std::result::Result;
7use std::str::FromStr;
8use thiserror::Error;
9
10#[cfg(feature = "async")]
11use futures::future::Future;
12
13#[derive(Error, Debug)]
14pub enum DataDrivenError {
15 #[error("parsing: {0}")]
16 Parse(String),
17 #[error("reading files: {0}")]
18 Io(std::io::Error),
19 #[error("{filename}:{line}: {inner}")]
20 WithContext {
21 line: usize,
22 filename: String,
23 inner: Box<DataDrivenError>,
24 },
25 #[error("argument: {0}")]
26 Argument(String),
27 #[error("didn't use all arguments: {0:?}")]
28 DidntUseAllArguments(Vec<String>),
29}
30
31impl DataDrivenError {
32 fn with_line(self, line: usize) -> Self {
33 match self {
34 DataDrivenError::WithContext {
35 filename, inner, ..
36 } => DataDrivenError::WithContext {
37 line,
38 filename,
39 inner,
40 },
41 e => DataDrivenError::WithContext {
42 line,
43 filename: Default::default(),
44 inner: Box::new(e),
45 },
46 }
47 }
48
49 fn with_filename(self, filename: String) -> Self {
50 match self {
51 DataDrivenError::WithContext { line, inner, .. } => DataDrivenError::WithContext {
52 line,
53 filename,
54 inner,
55 },
56 e => DataDrivenError::WithContext {
57 line: Default::default(),
58 filename,
59 inner: Box::new(e),
60 },
61 }
62 }
63}
64
65pub trait TestCaseResult {
66 type Err: std::fmt::Display + std::fmt::Debug;
67
68 fn result(self) -> Result<String, Self::Err>;
69}
70
71#[derive(Debug)]
72pub enum Never {}
73impl std::fmt::Display for Never {
74 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 unreachable!()
76 }
77}
78
79impl TestCaseResult for String {
80 type Err = Never;
81 fn result(self) -> Result<String, Self::Err> {
82 Ok(self)
83 }
84}
85
86impl<S, E> TestCaseResult for Result<S, E>
87where
88 S: Into<String>,
89 E: std::fmt::Display + std::fmt::Debug,
90{
91 type Err = E;
92 fn result(self) -> Result<String, E> {
93 self.map(|s| s.into())
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct TestCase {
100 pub directive: String,
102 pub args: HashMap<String, Vec<String>>,
104 pub input: String,
106
107 directive_line: String,
108 expected: String,
109 line_number: usize,
110}
111
112impl TestCase {
113 pub fn take_flag(&mut self, arg: &str) -> Result<bool, DataDrivenError> {
116 let contents = self.args.remove(arg);
117 Ok(if let Some(args) = contents {
118 if !args.is_empty() {
119 Err(DataDrivenError::Argument(format!(
120 "must be no arguments to take_flag, {} had {}",
121 arg,
122 args.len(),
123 )))?;
124 }
125 true
126 } else {
127 false
128 })
129 }
130
131 pub fn take_arg<T>(&mut self, arg: &str) -> Result<T, DataDrivenError>
134 where
135 T: FromStr,
136 <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
137 {
138 let result = self.try_take_arg(arg)?;
139 if let Some(result) = result {
140 Ok(result)
141 } else {
142 Err(DataDrivenError::Argument(format!(
143 "no argument named {}",
144 arg
145 )))
146 }
147 }
148
149 pub fn try_take_arg<T>(&mut self, arg: &str) -> Result<Option<T>, DataDrivenError>
151 where
152 T: FromStr,
153 <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
154 {
155 let contents = self.args.remove(arg);
156 Ok(if let Some(args) = contents {
157 match args.len() {
158 0 => None,
159 1 => Some(
160 args[0]
161 .parse()
162 .map_err(|e| DataDrivenError::Argument(format!("couldn't parse: {}", e)))?,
163 ),
164 _ => Err(DataDrivenError::Argument(format!(
165 "must be exactly one argument to take_arg, {} had {}",
166 arg,
167 args.len(),
168 )))?,
169 }
170 } else {
171 None
172 })
173 }
174
175 pub fn take_args<T>(&mut self, arg: &str) -> Result<Vec<T>, DataDrivenError>
178 where
179 T: FromStr,
180 <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
181 {
182 let result = self
183 .try_take_args(arg)
184 .map_err(|e| DataDrivenError::Argument(format!("couldn't parse: {}", e)))?;
185 if let Some(result) = result {
186 Ok(result)
187 } else {
188 Err(DataDrivenError::Argument(format!(
189 "no argument named {}",
190 arg
191 )))
192 }
193 }
194
195 pub fn try_take_args<T>(&mut self, arg: &str) -> Result<Option<Vec<T>>, DataDrivenError>
197 where
198 T: FromStr,
199 <T as std::str::FromStr>::Err: std::error::Error + Send + 'static,
200 {
201 let contents = self.args.remove(arg);
202 Ok(if let Some(args) = contents {
203 Some(
204 args.into_iter()
205 .map(|a| {
206 a.parse()
207 .map_err(|e| DataDrivenError::Parse(format!("couldn't parse: {}", e)))
208 })
209 .collect::<Result<Vec<T>, DataDrivenError>>()?,
210 )
211 } else {
212 None
213 })
214 }
215
216 pub fn expect_empty(&self) -> Result<(), DataDrivenError> {
218 if self.args.is_empty() {
219 Ok(())
220 } else {
221 Err(DataDrivenError::DidntUseAllArguments(
222 self.args.keys().cloned().collect::<Vec<_>>(),
223 ))
224 }
225 }
226}
227
228pub fn walk<F>(dir: &str, f: F)
230where
231 F: FnMut(&mut TestFile),
232{
233 walk_exclusive(dir, f, |_| false);
234}
235
236pub fn walk_exclusive<F, M>(dir: &str, mut f: F, exclusion_matcher: M)
239where
240 F: FnMut(&mut TestFile),
241 M: Fn(&TestFile) -> bool,
242{
243 let mut file_prefix = PathBuf::from(dir);
244 if let Ok(p) = env::var("RUN") {
245 file_prefix = file_prefix.join(p);
246 }
247
248 let mut failures = Vec::new();
250
251 let mut run = |file| {
252 let mut tf = TestFile::new(&file).unwrap();
253 if exclusion_matcher(&tf) {
254 return;
255 }
256 f(&mut tf);
257 if let Some(fail) = tf.failure {
258 failures.push(fail);
259 }
260 };
261
262 if file_prefix.is_dir() {
263 for file in test_files(PathBuf::from(dir)).unwrap() {
264 run(file);
265 }
266 } else if file_prefix.exists() {
267 run(file_prefix);
268 }
269
270 if !failures.is_empty() {
271 let mut msg = String::new();
272 for f in failures {
273 msg.push_str(&f);
274 msg.push('\n');
275 }
276 panic!("{}", msg);
277 }
278}
279
280fn should_ignore_file(name: &str) -> bool {
282 name.starts_with('.') || name.ends_with('~') || name.starts_with('#') && name.ends_with('#')
283}
284
285fn test_files(dir: PathBuf) -> Result<Vec<PathBuf>, DataDrivenError> {
287 let mut q = VecDeque::new();
288 q.push_back(dir);
289 let mut res = vec![];
290 while let Some(hd) = q.pop_front() {
291 for entry in fs::read_dir(hd).map_err(DataDrivenError::Io)? {
292 let path = entry.map_err(DataDrivenError::Io)?.path();
293 if path.is_dir() {
294 q.push_back(path);
295 } else if !should_ignore_file(path.file_name().unwrap().to_str().unwrap()) {
296 res.push(path);
297 }
298 }
299 }
300 Ok(res)
301}
302
303struct DirectiveParser {
311 chars: Vec<char>,
312 idx: usize,
313}
314
315impl DirectiveParser {
316 fn new(s: &str) -> Self {
317 DirectiveParser {
318 chars: s.chars().collect(),
319 idx: 0,
320 }
321 }
322
323 fn munch(&mut self) {
326 while self.idx < self.chars.len() && self.chars[self.idx].is_ascii_whitespace() {
327 self.idx += 1;
328 }
329 }
330
331 fn peek(&mut self) -> Option<char> {
332 if self.idx >= self.chars.len() {
333 None
334 } else {
335 Some(self.chars[self.idx])
336 }
337 }
338
339 fn eat(&mut self, ch: char) -> bool {
341 if self.idx < self.chars.len() && self.chars[self.idx] == ch {
342 self.idx += 1;
343 true
344 } else {
345 false
346 }
347 }
348
349 fn is_wordchar(ch: char) -> bool {
350 ch.is_alphanumeric() || ch == '-' || ch == '_' || ch == '.'
351 }
352
353 fn parse_word(&mut self, context: &str) -> Result<String, DataDrivenError> {
354 let start = self.idx;
355 while self.peek().map_or(false, Self::is_wordchar) {
356 self.idx += 1;
357 }
358 if self.idx == start {
359 match self.peek() {
360 Some(ch) => Err(DataDrivenError::Parse(format!(
361 "expected {}, got {}",
362 context, ch
363 ))),
364 None => Err(DataDrivenError::Parse(format!(
365 "expected {} but directive line ended",
366 context
367 ))),
368 }?
369 }
370 let result = self.chars[start..self.idx].iter().collect();
371 self.munch();
372 Ok(result)
373 }
374
375 fn at_end(&self) -> bool {
376 self.idx >= self.chars.len()
377 }
378
379 fn parse_arg(&mut self) -> Result<(String, Vec<String>), DataDrivenError> {
380 let name = self.parse_word("argument name")?;
381 let vals = self.parse_vals()?;
382 Ok((name, vals))
383 }
384
385 fn parse_vals(&mut self) -> Result<Vec<String>, DataDrivenError> {
387 if !self.eat('=') {
388 return Ok(Vec::new());
389 }
390 self.munch();
391 if !self.eat('(') {
392 return Ok(vec![self.parse_word("argument value")?]);
394 }
395 self.munch();
396 let mut vals = Vec::new();
397 while self.peek() != Some(')') {
398 vals.push(self.parse_word("argument value")?);
399 if !self.eat(',') {
400 break;
401 }
402 self.munch();
403 }
404 match self.peek() {
405 Some(')') => Ok(()),
406 Some(ch) => Err(DataDrivenError::Parse(format!(
407 "expected ',' or ')', got '{}'",
408 ch,
409 ))),
410 None => Err(DataDrivenError::Parse(
411 "expected ',' or '', but directive line ended".into(),
412 )),
413 }?;
414 self.idx += 1;
415 self.munch();
416 Ok(vals)
417 }
418
419 fn parse_directive(
420 &mut self,
421 ) -> Result<(String, HashMap<String, Vec<String>>), DataDrivenError> {
422 self.munch();
423 let directive = self.parse_word("directive")?;
424 let mut args = HashMap::new();
425 while !self.at_end() {
426 let (arg_name, arg_vals) = self.parse_arg()?;
427 if args.contains_key(&arg_name) {
428 Err(DataDrivenError::Parse(format!(
429 "duplicate argument: {}",
430 arg_name
431 )))?;
432 }
433 args.insert(arg_name, arg_vals);
434 }
435 Ok((directive, args))
436 }
437}
438
439#[derive(Debug, Clone)]
442enum Stanza {
443 Test(TestCase),
444 Comment(String),
445}
446
447#[derive(Debug, Clone)]
448pub struct TestFile {
449 stanzas: Vec<Stanza>,
450
451 pub filename: String,
453
454 failure: Option<String>,
458}
459
460fn write_result<W>(w: &mut W, s: String)
461where
462 W: Write,
463{
464 if s.is_empty() || s == "\n" {
466 w.write_str("----\n").unwrap();
467 } else if !s.ends_with('\n') {
468 w.write_str("----\n----\n").unwrap();
469 w.write_str(&s).unwrap();
470 w.write_str("\n----\n---- (no newline)\n").unwrap();
471 } else if s.contains("\n\n") {
472 w.write_str("----\n----\n").unwrap();
473 w.write_str(&s).unwrap();
474 w.write_str("----\n----\n").unwrap();
475 } else {
476 w.write_str("----\n").unwrap();
477 w.write_str(&s).unwrap();
478 }
479}
480
481impl TestFile {
482 fn new(filename: &PathBuf) -> Result<Self, DataDrivenError> {
483 let contents = fs::read_to_string(filename).map_err(DataDrivenError::Io)?;
484 let stanzas =
485 Self::parse(&contents).map_err(|e| e.with_filename(filename.display().to_string()))?;
486 Ok(TestFile {
487 stanzas,
488 filename: filename.to_string_lossy().to_string(),
489 failure: None,
490 })
491 }
492
493 pub fn run<F, R>(&mut self, f: F)
497 where
498 F: FnMut(&mut TestCase) -> R,
499 R: TestCaseResult,
500 {
501 match env::var("REWRITE") {
502 Ok(_) => self.run_rewrite(f),
503 Err(_) => self.run_normal(f),
504 }
505 }
506
507 fn run_normal<F, R>(&mut self, mut f: F)
508 where
509 F: FnMut(&mut TestCase) -> R,
510 R: TestCaseResult,
511 {
512 for stanza in &mut self.stanzas {
513 if let Stanza::Test(case) = stanza {
514 let result = f(case);
515 match result.result() {
516 Ok(result) => {
517 if result != case.expected {
518 self.failure = Some(format!(
519 "failure:\n{}:{}:\n{}\nexpected:\n{}\nactual:\n{}",
520 self.filename, case.line_number, case.input, case.expected, result
521 ));
522 break;
524 }
525 }
526 Err(err) => {
527 self.failure = Some(format!(
528 "failure:\n{}:{}:\n{}\n{}",
529 self.filename, case.line_number, case.input, err
530 ));
531 }
532 }
533 }
534 }
535 }
536
537 fn run_rewrite<F, R>(&mut self, mut f: F)
538 where
539 F: FnMut(&mut TestCase) -> R,
540 R: TestCaseResult,
541 {
542 let mut s = String::new();
543 for stanza in &mut self.stanzas {
544 match stanza {
545 Stanza::Test(case) => {
546 s.push_str(&case.directive_line);
547 s.push('\n');
548 s.push_str(&case.input);
549 write_result(&mut s, f(case).result().unwrap());
550 }
551 Stanza::Comment(c) => {
552 s.push_str(c.as_str());
553 s.push('\n');
554 }
555 }
556 }
557 fs::write(&self.filename, s).unwrap();
559 }
560
561 fn parse(f: &str) -> Result<Vec<Stanza>, DataDrivenError> {
562 let mut stanzas = vec![];
563 let lines: Vec<&str> = f.lines().collect();
564 let mut i = 0;
565 while i < lines.len() {
566 let line = lines[i]
568 .chars()
569 .take_while(|c| *c != '#')
570 .collect::<String>();
571
572 if line.trim() == "" {
573 stanzas.push(Stanza::Comment(lines[i].to_string()));
574 i += 1;
575 continue;
576 }
577
578 let line_number = i + 1;
580
581 let mut parser = DirectiveParser::new(&line);
582 let directive_line = lines[i].to_string();
583 let (directive, args) = parser
584 .parse_directive()
585 .map_err(|e| e.with_line(line_number))?;
586
587 i += 1;
588 let mut input = String::new();
589 while i < lines.len() && lines[i] != "----" {
591 input.push_str(lines[i]);
592 input.push('\n');
593 i += 1;
594 }
595 i += 1;
596 let blank_mode = i < lines.len() && lines[i] == "----";
598 if blank_mode {
599 i += 1;
600 }
601
602 let mut expected = String::new();
604 while i < lines.len() {
605 if blank_mode {
606 if i + 1 >= lines.len() {
607 Err(DataDrivenError::Parse(format!(
608 "unclosed double-separator block for test case starting at line {}",
609 line_number,
610 )))?;
611 }
612 if i + 1 < lines.len() && lines[i] == "----" {
613 if lines[i + 1] == "----" {
614 i += 2;
615 break;
616 } else if lines[i + 1] == "---- (no newline)" {
617 i += 2;
618 if expected.ends_with('\n') {
619 expected.pop().expect("should be nonempty.");
620 }
621 break;
622 }
623 }
624 } else if lines[i].trim() == "" {
625 break;
626 }
627 expected.push_str(lines[i]);
628 expected.push('\n');
629 i += 1;
630 }
631
632 stanzas.push(Stanza::Test(TestCase {
633 directive_line,
634 directive: directive.to_string(),
635 input,
636 args,
637 expected,
638 line_number,
639 }));
640 i += 1;
641 if i < lines.len() {
642 stanzas.push(Stanza::Comment("".to_string()));
643 }
644 }
645
646 Ok(stanzas)
647 }
648}
649
650fn file_list(dir: &str) -> Vec<PathBuf> {
651 let mut file_prefix = PathBuf::from(dir);
652 if let Ok(p) = env::var("RUN") {
653 file_prefix = file_prefix.join(p);
654 }
655
656 if file_prefix.is_dir() {
657 test_files(PathBuf::from(dir)).unwrap()
658 } else if file_prefix.exists() {
659 vec![file_prefix]
660 } else {
661 vec![]
662 }
663}
664
665#[cfg(feature = "async")]
667pub async fn walk_async<F, T>(dir: &str, f: F)
668where
669 F: FnMut(TestFile) -> T,
670 T: Future<Output = TestFile>,
671{
672 walk_async_exclusive(dir, f, |_| false).await;
673}
674
675#[cfg(feature = "async")]
678pub async fn walk_async_exclusive<F, T, M>(dir: &str, mut f: F, exclusion_matcher: M)
679where
680 F: FnMut(TestFile) -> T,
681 T: Future<Output = TestFile>,
682 M: Fn(&TestFile) -> bool,
683{
684 let mut failures = Vec::new();
686 for file in file_list(dir) {
687 let tf = TestFile::new(&file).unwrap();
688 if exclusion_matcher(&tf) {
689 continue;
690 }
691 let tf = f(tf).await;
692 if let Some(fail) = tf.failure {
693 failures.push(fail);
694 }
695 }
696
697 if !failures.is_empty() {
698 let mut msg = String::new();
699 for f in failures {
700 msg.push_str(&f);
701 msg.push('\n');
702 }
703 panic!("{}", msg);
704 }
705}
706
707#[cfg(feature = "async")]
709pub async fn walk_async_concurrent<F, T>(dir: &str, concurrency: usize, f: F)
710where
711 F: FnMut(TestFile) -> T,
712 T: Future<Output = TestFile>,
713{
714 walk_async_concurrent_exclusive(dir, concurrency, f, |_| false).await;
715}
716
717#[cfg(feature = "async")]
719pub async fn walk_async_concurrent_exclusive<F, T, M>(
720 dir: &str,
721 concurrency: usize,
722 mut f: F,
723 exclusion_matcher: M,
724) where
725 F: FnMut(TestFile) -> T,
726 T: Future<Output = TestFile>,
727 M: Fn(&TestFile) -> bool,
728{
729 use futures::StreamExt;
730
731 let mut futures = futures::stream::iter(file_list(dir).into_iter().filter_map(|file| {
733 let tf = TestFile::new(&file).unwrap();
734 if exclusion_matcher(&tf) {
735 return None;
736 }
737 Some(f(tf))
738 }))
739 .buffered(concurrency);
740
741 let mut failures = Vec::new();
743
744 while let Some(tf) = futures.next().await {
745 if let Some(fail) = tf.failure {
746 failures.push(fail);
747 }
748 }
749
750 if !failures.is_empty() {
751 let mut msg = String::new();
752 for f in failures {
753 msg.push_str(&f);
754 msg.push('\n');
755 }
756 panic!("{}", msg);
757 }
758}
759
760#[cfg(feature = "async")]
761impl TestFile {
762 pub async fn run_async<F, T>(&mut self, f: F)
764 where
765 F: FnMut(TestCase) -> T,
766 T: Future<Output = String>,
767 {
768 match env::var("REWRITE") {
769 Ok(_) => self.run_rewrite_async(f).await,
770 Err(_) => self.run_normal_async(f).await,
771 }
772 }
773
774 async fn run_normal_async<F, T>(&mut self, mut f: F)
775 where
776 F: FnMut(TestCase) -> T,
777 T: Future<Output = String>,
778 {
779 for stanza in self.stanzas.drain(..) {
780 if let Stanza::Test(case) = stanza {
781 let original_case = case.clone();
782 let result = f(case).await;
783 if result != original_case.expected {
784 self.failure = Some(format!(
785 "failure:\n{}:{}:\n{}\nexpected:\n{}\nactual:\n{}",
786 self.filename,
787 original_case.line_number,
788 original_case.input,
789 original_case.expected,
790 result
791 ));
792 break;
794 }
795 }
796 }
797 }
798
799 async fn run_rewrite_async<F, T>(&mut self, mut f: F)
800 where
801 F: FnMut(TestCase) -> T,
802 T: Future<Output = String>,
803 {
804 let mut s = String::new();
805 for stanza in self.stanzas.drain(..) {
806 match stanza {
807 Stanza::Test(case) => {
808 s.push_str(&case.directive_line);
809 s.push('\n');
810 s.push_str(&case.input);
811 write_result(&mut s, f(case).await);
812 }
813 Stanza::Comment(c) => {
814 s.push_str(&c);
815 s.push('\n');
816 }
817 }
818 }
819 fs::write(&self.filename, s).unwrap();
821 }
822}
823
824#[cfg(test)]
825mod tests {
826 use super::*;
827
828 #[test]
830 fn parse_directive() {
831 walk("tests/parsing", |f| {
832 f.run(|s| -> String {
833 match DirectiveParser::new(s.input.trim()).parse_directive() {
834 Ok((directive, mut args)) => {
835 let mut sorted_args = args.drain().collect::<Vec<(String, Vec<String>)>>();
836 sorted_args.sort_by(|a, b| a.0.cmp(&b.0));
837 format!("directive: {}\nargs: {:?}\n", directive, sorted_args)
838 }
839 Err(err) => format!("error: {}\n", err),
840 }
841 });
842 });
843 }
844
845 #[cfg(feature = "async")]
847 #[tokio::test]
848 async fn parse_directive_async() {
849 walk_async_concurrent("tests/parsing", 4, |mut f| async {
850 f.run(|s| -> String {
851 match DirectiveParser::new(s.input.trim()).parse_directive() {
852 Ok((directive, mut args)) => {
853 let mut sorted_args = args.drain().collect::<Vec<(String, Vec<String>)>>();
854 sorted_args.sort_by(|a, b| a.0.cmp(&b.0));
855 format!("directive: {}\nargs: {:?}\n", directive, sorted_args)
856 }
857 Err(err) => format!("error: {}\n", err),
858 }
859 });
860 f
861 })
862 .await;
863 }
864}