1use std::collections::BTreeMap;
2
3#[derive(Debug)]
4pub(crate) struct RunnerSpec {
5 cases: Vec<CaseSpec>,
6 include: Option<Vec<String>>,
7 default_bin: Option<crate::schema::Bin>,
8 timeout: Option<std::time::Duration>,
9 env: crate::schema::Env,
10}
11
12impl RunnerSpec {
13 pub(crate) fn new() -> Self {
14 Self {
15 cases: Default::default(),
16 include: None,
17 default_bin: None,
18 timeout: Default::default(),
19 env: Default::default(),
20 }
21 }
22
23 pub(crate) fn case(
24 &mut self,
25 glob: &std::path::Path,
26 #[cfg_attr(miri, allow(unused_variables))] expected: Option<crate::schema::CommandStatus>,
27 ) {
28 self.cases.push(CaseSpec {
29 glob: glob.into(),
30 #[cfg(not(miri))]
31 expected,
32 #[cfg(miri)]
33 expected: Some(crate::schema::CommandStatus::Skipped),
34 });
35 }
36
37 pub(crate) fn include(&mut self, include: Option<Vec<String>>) {
38 self.include = include;
39 }
40
41 pub(crate) fn default_bin(&mut self, bin: Option<crate::schema::Bin>) {
42 self.default_bin = bin;
43 }
44
45 pub(crate) fn timeout(&mut self, time: Option<std::time::Duration>) {
46 self.timeout = time;
47 }
48
49 pub(crate) fn env(&mut self, key: impl Into<String>, value: impl Into<String>) {
50 self.env.add.insert(key.into(), value.into());
51 }
52
53 pub(crate) fn prepare(&mut self) -> crate::Runner {
54 let mut runner = crate::Runner::new();
55
56 let mut cases: BTreeMap<std::path::PathBuf, crate::Case> = BTreeMap::new();
58
59 for spec in &self.cases {
60 if let Some(glob) = get_glob(&spec.glob) {
61 match ::glob::glob(glob) {
62 Ok(paths) => {
63 for path in paths {
64 match path {
65 Ok(path) => {
66 cases.insert(
67 path.clone(),
68 crate::Case {
69 path,
70 expected: spec.expected,
71 default_bin: self.default_bin.clone(),
72 timeout: self.timeout,
73 env: self.env.clone(),
74 error: None,
75 },
76 );
77 }
78 Err(err) => {
79 let path = err.path().to_owned();
80 let err = crate::Error::new(err.into_error().to_string());
81 cases.insert(path.clone(), crate::Case::with_error(path, err));
82 }
83 }
84 }
85 }
86 Err(err) => {
87 let err = crate::Error::new(err.to_string());
88 cases.insert(
89 spec.glob.clone(),
90 crate::Case::with_error(spec.glob.clone(), err),
91 );
92 }
93 }
94 } else {
95 let path = spec.glob.as_path();
96 cases.insert(
97 path.into(),
98 crate::Case {
99 path: path.into(),
100 expected: spec.expected,
101 default_bin: self.default_bin.clone(),
102 timeout: self.timeout,
103 env: self.env.clone(),
104 error: None,
105 },
106 );
107 }
108 }
109
110 for case in cases.into_values() {
111 if self.is_included(&case) {
112 runner.case(case);
113 }
114 }
115
116 runner
117 }
118
119 fn is_included(&self, case: &crate::Case) -> bool {
120 if let Some(include) = self.include.as_deref() {
121 include
122 .iter()
123 .any(|i| case.path.to_string_lossy().contains(i))
124 } else {
125 true
126 }
127 }
128}
129
130impl Default for RunnerSpec {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136#[derive(Debug)]
137struct CaseSpec {
138 glob: std::path::PathBuf,
139 expected: Option<crate::schema::CommandStatus>,
140}
141
142fn get_glob(path: &std::path::Path) -> Option<&str> {
143 if let Some(utf8) = path.to_str() {
144 if utf8.contains('*') {
145 return Some(utf8);
146 }
147 }
148
149 None
150}