1use crate::Termination;
12use core::{mem::MaybeUninit, str};
13
14#[derive(Clone, Copy, Debug)]
32pub enum Ignore {
33 No,
35 Yes,
37 YesWithMessage(&'static str),
39}
40
41#[derive(Clone, Copy, Debug)]
59pub enum ShouldPanic {
60 No,
62 Yes,
64 YesWithMessage(&'static str),
66}
67
68pub trait TestCase {
76 fn name(&self) -> &str;
78
79 fn modules(&self) -> &[&str];
81
82 fn run(&self);
87
88 fn ignore(&self) -> Ignore;
94
95 fn should_panic(&self) -> ShouldPanic;
97
98 fn message(&self) -> Option<&'static str>;
100}
101
102#[doc(hidden)]
106pub const fn split_module_path_len(module_path: &'static str) -> usize {
107 let mut len = 1;
108
109 let mut i = 1;
110 while i < module_path.len() {
111 if module_path.as_bytes()[i - 1] == b':' && module_path.as_bytes()[i] == b':' {
112 len += 1;
113 i += 1;
114 }
115 i += 1;
116 }
117
118 len
119}
120
121#[doc(hidden)]
125pub const fn split_module_path<const LEN: usize>(module_path: &'static str) -> [&'static str; LEN] {
126 let mut result: MaybeUninit<[&'static str; LEN]> = MaybeUninit::uninit();
127 let mut result_index = 0;
128 let mut module_path_start = 0;
129 let mut module_path_index = 1;
131 while module_path_index < module_path.len() {
132 if module_path.as_bytes()[module_path_index - 1] == b':'
133 && module_path.as_bytes()[module_path_index] == b':'
134 {
135 let module = unsafe {
136 str::from_utf8_unchecked(core::slice::from_raw_parts(
137 module_path.as_ptr().add(module_path_start),
138 module_path_index - 1 - module_path_start,
139 ))
140 };
141 if result_index >= LEN {
143 panic!("module path was split into too many parts")
144 }
145 unsafe {
146 (result.as_mut_ptr() as *mut &str)
147 .add(result_index)
148 .write(module);
149 }
150 result_index += 1;
151 module_path_index += 1;
152 module_path_start = module_path_index;
153 }
154 module_path_index += 1;
155 }
156 let module = unsafe {
158 str::from_utf8_unchecked(core::slice::from_raw_parts(
159 module_path.as_ptr().add(module_path_start),
160 module_path.len() - module_path_start,
161 ))
162 };
163 if result_index >= LEN {
165 panic!("module path was split into too many parts")
166 }
167 unsafe {
168 (result.as_mut_ptr() as *mut &str)
169 .add(result_index)
170 .write(module);
171 }
172 result_index += 1;
173
174 if result_index < LEN {
176 panic!("unable to split module path into enough separate parts")
177 }
178
179 unsafe { result.assume_init() }
180}
181
182#[doc(hidden)]
188pub struct Test<T> {
189 pub name: &'static str,
191 pub modules: &'static [&'static str],
193 pub test: fn() -> T,
195 pub ignore: Ignore,
199 pub should_panic: ShouldPanic,
203}
204
205impl<T> TestCase for Test<T>
206where
207 T: Termination,
208{
209 fn name(&self) -> &str {
210 self.name
211 }
212
213 fn modules(&self) -> &[&str] {
214 if self.modules.len() <= 1 {
215 self.modules
216 } else {
217 &self.modules[1..]
218 }
219 }
220
221 fn run(&self) {
222 (self.test)().terminate()
223 }
224
225 fn ignore(&self) -> Ignore {
226 self.ignore
227 }
228
229 fn should_panic(&self) -> ShouldPanic {
230 self.should_panic
231 }
232
233 fn message(&self) -> Option<&'static str> {
234 if let Ignore::YesWithMessage(message) = self.ignore {
235 Some(message)
236 } else {
237 None
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::{Ignore, ShouldPanic, Test, TestCase, split_module_path, split_module_path_len};
245
246 use claims::{assert_matches, assert_none, assert_some_eq};
247 use gba_test_macros::test;
248
249 #[test]
250 fn test_name() {
251 let test = Test {
252 name: "foo",
253 modules: &[""],
254 test: || {},
255 ignore: Ignore::No,
256 should_panic: ShouldPanic::No,
257 };
258
259 assert_eq!(test.name(), "foo")
260 }
261
262 #[test]
263 fn test_module_split() {
264 let test = Test {
265 name: "",
266 modules: &["foo", "bar"],
267 test: || {},
268 ignore: Ignore::No,
269 should_panic: ShouldPanic::No,
270 };
271
272 assert_eq!(test.modules(), &["bar"]);
273 }
274
275 #[test]
276 fn test_module_no_split() {
277 let test = Test {
278 name: "",
279 modules: &["foo"],
280 test: || {},
281 ignore: Ignore::No,
282 should_panic: ShouldPanic::No,
283 };
284
285 assert_eq!(test.modules(), &["foo"]);
286 }
287
288 #[test]
289 fn test_run_no_panic() {
290 let test = Test {
291 name: "",
292 modules: &[""],
293 test: || {
294 assert!(true);
295 },
296 ignore: Ignore::No,
297 should_panic: ShouldPanic::No,
298 };
299
300 test.run();
301 }
302
303 #[test]
304 #[should_panic(expected = "assertion failed: false")]
305 fn test_run_panic() {
306 let test = Test {
307 name: "",
308 modules: &[""],
309 test: || {
310 assert!(false);
311 },
312 ignore: Ignore::No,
313 should_panic: ShouldPanic::No,
314 };
315
316 test.run();
317 }
318
319 #[test]
320 fn test_ignore() {
321 let test = Test {
322 name: "",
323 modules: &[""],
324 test: || {},
325 ignore: Ignore::Yes,
326 should_panic: ShouldPanic::No,
327 };
328
329 assert_matches!(test.ignore(), Ignore::Yes);
330 }
331
332 #[test]
333 fn test_should_panic() {
334 let test = Test {
335 name: "",
336 modules: &[""],
337 test: || {},
338 ignore: Ignore::No,
339 should_panic: ShouldPanic::Yes,
340 };
341
342 assert_matches!(test.should_panic(), ShouldPanic::Yes);
343 }
344
345 #[test]
346 fn test_message() {
347 let test = Test {
348 name: "",
349 modules: &[""],
350 test: || {},
351 ignore: Ignore::YesWithMessage("foo"),
352 should_panic: ShouldPanic::No,
353 };
354
355 assert_some_eq!(test.message(), "foo");
356 }
357
358 #[test]
359 fn test_no_message() {
360 let test = Test {
361 name: "",
362 modules: &[""],
363 test: || {},
364 ignore: Ignore::Yes,
365 should_panic: ShouldPanic::No,
366 };
367
368 assert_none!(test.message());
369 }
370
371 #[test]
372 fn split_module_path_len_empty() {
373 assert_eq!(split_module_path_len(""), 1);
374 }
375
376 #[test]
377 fn split_module_path_len_single() {
378 assert_eq!(split_module_path_len("foo"), 1);
379 }
380
381 #[test]
382 fn split_module_path_len_single_colon() {
383 assert_eq!(split_module_path_len(":"), 1);
384 }
385
386 #[test]
387 fn split_module_path_len_empty_with_separator() {
388 assert_eq!(split_module_path_len("::"), 2);
389 }
390
391 #[test]
392 fn split_module_path_len_separator_with_extra_colon() {
393 assert_eq!(split_module_path_len(":::"), 2);
394 }
395
396 #[test]
397 fn split_module_path_len_modules_split_by_separator() {
398 assert_eq!(split_module_path_len("foo::bar"), 2);
399 }
400
401 #[test]
402 fn split_module_path_len_many_modules_split_by_separators() {
403 assert_eq!(split_module_path_len("foo::bar::baz::quux"), 4);
404 }
405
406 #[test]
407 fn split_module_path_len_modules_leading_separator() {
408 assert_eq!(split_module_path_len("::foo::bar"), 3);
409 }
410
411 #[test]
412 fn split_module_path_len_modules_trailing_separator() {
413 assert_eq!(split_module_path_len("foo::bar::"), 3);
414 }
415
416 #[test]
417 fn split_module_path_empty() {
418 assert_eq!(split_module_path::<1>(""), [""]);
419 }
420
421 #[test]
422 fn split_module_path_single() {
423 assert_eq!(split_module_path::<1>("foo"), ["foo"]);
424 }
425
426 #[test]
427 fn split_module_path_single_colon() {
428 assert_eq!(split_module_path::<1>(":"), [":"]);
429 }
430
431 #[test]
432 fn split_module_path_empty_with_separator() {
433 assert_eq!(split_module_path::<2>("::"), ["", ""]);
434 }
435
436 #[test]
437 fn split_module_path_separator_with_extra_colon() {
438 assert_eq!(split_module_path::<2>(":::"), ["", ":"]);
439 }
440
441 #[test]
442 fn split_module_path_modules_split_by_separator() {
443 assert_eq!(split_module_path::<2>("foo::bar"), ["foo", "bar"]);
444 }
445
446 #[test]
447 fn split_module_path_many_modules_split_by_separators() {
448 assert_eq!(
449 split_module_path::<4>("foo::bar::baz::quux"),
450 ["foo", "bar", "baz", "quux"]
451 );
452 }
453
454 #[test]
455 fn split_module_path_modules_leading_separator() {
456 assert_eq!(split_module_path::<3>("::foo::bar"), ["", "foo", "bar"]);
457 }
458
459 #[test]
460 fn split_module_path_modules_trailing_separator() {
461 assert_eq!(split_module_path::<3>("foo::bar::"), ["foo", "bar", ""]);
462 }
463
464 #[test]
465 #[should_panic(expected = "module path was split into too many parts")]
466 fn split_module_path_size_too_small() {
467 split_module_path::<0>("foo");
468 }
469
470 #[test]
471 #[should_panic(expected = "module path was split into too many parts")]
472 fn split_module_path_size_too_small_multiple_parts() {
473 split_module_path::<2>("foo::bar::baz");
474 }
475
476 #[test]
477 #[should_panic(expected = "unable to split module path into enough separate parts")]
478 fn split_module_path_size_too_large() {
479 split_module_path::<2>("foo");
480 }
481}