asmov_common_testing/
test.rs1use std::path::{Path, PathBuf};
2use crate::*;
3
4pub struct Test<'module,'func> {
8 pub(crate) module: &'module TestModule,
9 pub(crate) namepath: Namepath,
10 pub(crate) temp_dir: Option<PathBuf>,
11 pub(crate) fixture_dir: Option<PathBuf>,
12 pub(crate) teardown_func: Option<Box<dyn FnOnce(&mut Test) + 'func>>,
13}
14
15impl<'module,'func> Test<'module,'func> {
16 pub fn module(&self) -> &'module TestModule {
18 &self.module
19 }
20
21 fn teardown(&mut self) {
22 if let Some(teardown_fn) = self.teardown_func.take() {
23 teardown_fn(self);
24 }
25 }
26}
27
28impl<'module,'func> Testing for Test<'module,'func> {
29 fn namepath(&self) -> &Namepath {
30 &self.namepath
31 }
32
33 fn use_case(&self)-> UseCase {
34 self.module.use_case
35 }
36
37 fn fixture_dir(&self) -> &Path {
38 &self.fixture_dir.as_ref()
39 .context("Test `fixture dir` is not configured").unwrap()
40 }
41
42 fn temp_dir(&self) -> &Path {
43 self.temp_dir.as_ref()
44 .context("Test `temp dir` is not configured").unwrap()
45 }
46}
47
48impl<'module,'func> Drop for Test<'module,'func> {
49 fn drop(&mut self) {
50 self.teardown();
51 }
52}
53
54pub struct TestBuilder<'module,'func> {
56 pub(crate) name: &'static str,
57 pub(crate) module: &'module TestModule,
58 pub(crate) using_temp_dir: bool,
59 pub(crate) inherit_temp_dir: bool,
60 pub(crate) using_fixture_dir: bool,
61 pub(crate) inherit_fixture_dir: bool,
62 pub(crate) setup_func: Option<Box<dyn FnOnce(&mut Test) + 'func>>,
63 pub(crate) teardown_func: Option<Box<dyn FnOnce(&mut Test) + 'func>>,
64}
65
66impl<'module,'func> TestBuilder<'module,'func> {
67 pub fn new(module: &'module TestModule, name: &'static str) -> Self{
68 debug_assert!(!name.contains("::") && !name.contains('/') && !name.contains('.'),
69 "Test name should be a single non-delimited token.");
70
71 Self {
72 name,
73 module,
74 using_temp_dir: false,
75 inherit_temp_dir: false,
76 using_fixture_dir: false,
77 inherit_fixture_dir: false,
78 setup_func: None,
79 teardown_func: None,
80 }
81 }
82
83 pub fn build(self) -> Test<'module,'func> {
85 let namepath = Namepath::new_test(
86 self.module.namepath.raw.package_name,
87 self.module.use_case,
88 self.module.namepath.raw().path,
89 self.name)
90 .expect("Invalid namepath for Test");
91
92 let temp_dir = if self.using_temp_dir {
93 Some(build_temp_dir(&namepath, &self.module.base_temp_dir()))
94 } else if self.inherit_temp_dir {
95 Some(self.module.temp_dir().to_owned())
96 } else {
97 None
98 };
99
100 let fixture_dir = if self.using_fixture_dir {
101 Some(build_fixture_dir(&namepath))
102 } else if self.inherit_fixture_dir {
103 Some(self.module.fixture_dir().to_owned())
104 } else {
105 None
106 };
107
108 let mut test = Test {
109 module: self.module,
110 namepath,
111 temp_dir,
112 fixture_dir,
113 teardown_func: self.teardown_func,
114 };
115
116 if let Some(setup_fn) = self.setup_func {
117 setup_fn(&mut test);
118 }
119
120 test
121 }
122
123 pub fn using_fixture_dir(mut self) -> Self {
126 assert!(!self.inherit_fixture_dir, "Configuring both `inherit` and `using` for `fixture_dir` is ambiguous");
127 self.using_fixture_dir = true;
128 self
129 }
130
131 pub fn using_temp_dir(mut self) -> Self {
134 assert!(!self.inherit_temp_dir);
135 if self.module.temp_dir.is_none() {
136 panic!("Test cannot use a temporary directory unless its parent Module uses one");
137 }
138
139 self.using_temp_dir = true;
140 self
141 }
142
143 pub fn inherit_temp_dir(mut self) -> Self {
146 assert!(!self.using_temp_dir);
147 if self.module.temp_dir.is_none() {
148 panic!("Test cannot use a temporary directory unless its parent Module uses one");
149 }
150
151 self.inherit_temp_dir = true;
152 self
153 }
154
155 pub fn inherit_fixture_dir(mut self) -> Self {
158 assert!(!self.using_fixture_dir);
159 self.inherit_fixture_dir = true;
160 self
161 }
162
163 pub fn setup(mut self, func: impl FnOnce(&mut Test) + 'func) -> Self {
165 self.setup_func = Some(Box::new(func));
166 self
167 }
168
169 pub fn teardown(mut self, func: impl FnOnce(&mut Test) + 'func) -> Self {
171 self.teardown_func = Some(Box::new(func));
172 self
173 }
174}
175
176#[macro_export]
178macro_rules! test {
179 ({$($b:tt)+}) => {
180 $crate::TestBuilder::new(&TESTING, function_name!())
181 $($b)+
182 .build()
183 };
184 () => {
185 $crate::TestBuilder::new(&TESTING, function_name!()).build()
186 };
187}
188
189#[macro_export]
191macro_rules! test_with {
192 ($m:ident, {$($b:tt)+}) => {
193 let builder = $crate::TestBuilder::new(&$m, function_name!());
194 builder$($b)+
195 .build()
196 };
197 ($m:ident) => {
198 $crate::TestBuilder::new(&$m, function_name!()).build()
199 };
200}
201
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::prelude::*;
207
208 static MODULE_BASIC: Module = module!(Unit);
209
210 static MODULE_WITH_DIRS: Module = module!(Unit, {
211 .using_fixture_dir()
212 .using_temp_dir()
213 });
214
215 #[test] #[named]
217 fn test_module() {
218 let test = MODULE_BASIC.test_builder(function_name!()).build();
219 assert_eq!(&*MODULE_BASIC.namepath(), test.module().namepath(),
220 "Test parent Module should be bound.");
221 }
222
223 #[test] #[named]
225 fn test_name() {
226 let test = MODULE_BASIC.test_builder(function_name!()).build();
227 assert_eq!("test-name", test.namepath().name(),
228 "Test name should be set.");
229 }
230
231 #[test] #[should_panic]
233 fn test_name_invalid() {
234 MODULE_BASIC.test_builder("foo.bar").build(); }
236
237 #[test] #[named]
240 fn test_namepath() {
241 const EXPECTED_TEST_NAMEPATH: &'static str = "unit/test/test-namepath";
242 let test = MODULE_BASIC.test_builder(function_name!()).build();
243 assert_eq!(EXPECTED_TEST_NAMEPATH, test.namepath().to_string());
244 }
245
246 #[test] #[should_panic] #[named]
248 fn test_temp_dir_unconfigured_access() {
249 MODULE_BASIC.test_builder(function_name!())
250 .build()
251 .temp_dir(); }
253
254 #[test] #[should_panic] #[named]
256 fn test_temp_dir_using_unconfigured_module() {
257 MODULE_BASIC.test_builder(function_name!())
258 .using_temp_dir() .build();
260 }
261
262 #[test] #[should_panic] #[named]
264 fn test_temp_dir_inherited_unconfigured_module() {
265 MODULE_BASIC.test_builder(function_name!())
266 .inherit_temp_dir() .build();
268 }
269
270 #[test] #[named]
273 fn test_temp_dir_using() {
274 let test = MODULE_WITH_DIRS.test_builder(function_name!())
275 .using_temp_dir()
276 .build();
277
278 assert!(test.temp_dir().exists());
279 assert_eq!(MODULE_WITH_DIRS.temp_dir().join("test-temp-dir-using"), test.temp_dir());
280 }
281
282 #[test] #[named]
284 fn test_temp_dir_inherited() {
285 let test = MODULE_WITH_DIRS.test_builder(function_name!())
286 .inherit_temp_dir()
287 .build();
288
289 assert_eq!(MODULE_WITH_DIRS.temp_dir(), test.temp_dir(),
290 "Test configured to `inherit_temp_dir()` should have the same temp path as its parent.");
291 }
292
293 #[test] #[should_panic] #[named]
295 fn test_fixture_dir_unconfigured_access() {
296 MODULE_WITH_DIRS.test_builder(function_name!())
297 .build()
298 .fixture_dir(); }
300
301 #[test] #[should_panic] #[named]
303 fn test_fixture_dir_using_unconfigured_module() {
304 MODULE_BASIC.test_builder(function_name!())
305 .using_fixture_dir() .build();
307 }
308
309 #[test] #[should_panic] #[named]
311 fn test_fixture_dir_inherited_unconfigured_module() {
312 MODULE_BASIC.test_builder(function_name!())
313 .inherit_fixture_dir() .build();
315 }
316
317 #[test] #[named]
322 fn test_fixture_dir_using() {
323 let test = MODULE_WITH_DIRS.test_builder(function_name!())
324 .using_fixture_dir()
325 .build();
326
327 assert_eq!(MODULE_WITH_DIRS.fixture_dir().join("test-fixture-dir-using"), test.fixture_dir(),
328 "Test configured with `using_fixture_dir()` should have a path of: `Module::fixture_dir()` + `Test::name()`");
329 assert!(test.fixture_dir().exists(),
330 "Fixture path should exist for Test configured as `using_fixture_dir()`");
331 }
332
333 #[test] #[named]
338 fn test_fixture_dir_inherited() {
339 let test = MODULE_WITH_DIRS.test_builder(function_name!())
340 .inherit_fixture_dir()
341 .build();
342
343 assert_eq!(MODULE_WITH_DIRS.fixture_dir(), test.fixture_dir(),
344 "Test configured to `inherit_fixture_dir()` should have a fixture path that is the same as its Module.");
345 assert!(test.fixture_dir().exists(),
346 "Fixture path should exist for Test configured to `inherit_fixture_dir()` from Module");
347 }
348
349 static mut SETUP_FUNC_CALLED: bool = false;
351 fn setup_func(_test: &mut Test) {
352 unsafe {
353 SETUP_FUNC_CALLED = true;
354 }
355 }
356
357 #[test] #[named]
359 fn test_setup_function() {
360 let _testgroup = MODULE_BASIC.test_builder(function_name!())
361 .setup(setup_func)
362 .build();
363
364 unsafe {
365 assert!(SETUP_FUNC_CALLED,
366 "Test setup function should be ran on construction.");
367 }
368 }
369
370 #[test] #[named]
372 fn test_setup_closure() {
373 let mut setup_closure_called = false;
374 MODULE_BASIC.test_builder(function_name!())
375 .setup(|_| {
376 setup_closure_called = true;
377 })
378 .build();
379
380 assert!(setup_closure_called,
381 "Test setup closure should be ran on construction.");
382 }
383
384 static mut TEARDOWN_FUNC_CALLED: bool = false;
386 fn teardown_func(_group: &mut Test) {
387 unsafe {
388 TEARDOWN_FUNC_CALLED = true;
389 }
390 }
391
392 #[test] #[named]
394 fn test_teardown_function() {
395 {
396 MODULE_BASIC.test_builder(function_name!())
397 .teardown(teardown_func)
398 .build();
399 }
400
401 unsafe {
402 assert!(TEARDOWN_FUNC_CALLED,
403 "Test teardown function should be ran on destruction.");
404 }
405 }
406
407 #[test] #[named]
409 fn test_teardown_closure() {
410 let mut teardown_closure_called = false;
411 {
412 MODULE_BASIC.test_builder(function_name!())
413 .teardown(|_| {
414 teardown_closure_called = true;
415 })
416 .build();
417 }
418
419 assert!(teardown_closure_called,
420 "Test teardown closure should be ran on destruction.");
421 }
422}