use crate::Termination;
use core::{mem::MaybeUninit, str};
#[derive(Clone, Copy, Debug)]
pub enum Ignore {
No,
Yes,
YesWithMessage(&'static str),
}
#[derive(Clone, Copy, Debug)]
pub enum ShouldPanic {
No,
Yes,
YesWithMessage(&'static str),
}
pub trait TestCase {
fn name(&self) -> &str;
fn modules(&self) -> &[&str];
fn run(&self);
fn ignore(&self) -> Ignore;
fn should_panic(&self) -> ShouldPanic;
fn message(&self) -> Option<&'static str>;
}
#[doc(hidden)]
pub const fn split_module_path_len(module_path: &'static str) -> usize {
let mut len = 1;
let mut i = 1;
while i < module_path.len() {
if module_path.as_bytes()[i - 1] == b':' && module_path.as_bytes()[i] == b':' {
len += 1;
i += 1;
}
i += 1;
}
len
}
#[doc(hidden)]
pub const fn split_module_path<const LEN: usize>(module_path: &'static str) -> [&'static str; LEN] {
let mut result: MaybeUninit<[&'static str; LEN]> = MaybeUninit::uninit();
let mut result_index = 0;
let mut module_path_start = 0;
let mut module_path_index = 1;
while module_path_index < module_path.len() {
if module_path.as_bytes()[module_path_index - 1] == b':'
&& module_path.as_bytes()[module_path_index] == b':'
{
let module = unsafe {
str::from_utf8_unchecked(core::slice::from_raw_parts(
module_path.as_ptr().add(module_path_start),
module_path_index - 1 - module_path_start,
))
};
if result_index >= LEN {
panic!("module path was split into too many parts")
}
unsafe {
(result.as_mut_ptr() as *mut &str)
.add(result_index)
.write(module);
}
result_index += 1;
module_path_index += 1;
module_path_start = module_path_index;
}
module_path_index += 1;
}
let module = unsafe {
str::from_utf8_unchecked(core::slice::from_raw_parts(
module_path.as_ptr().add(module_path_start),
module_path.len() - module_path_start,
))
};
if result_index >= LEN {
panic!("module path was split into too many parts")
}
unsafe {
(result.as_mut_ptr() as *mut &str)
.add(result_index)
.write(module);
}
result_index += 1;
if result_index < LEN {
panic!("unable to split module path into enough separate parts")
}
unsafe { result.assume_init() }
}
#[doc(hidden)]
pub struct Test<T> {
pub name: &'static str,
pub modules: &'static [&'static str],
pub test: fn() -> T,
pub ignore: Ignore,
pub should_panic: ShouldPanic,
}
impl<T> TestCase for Test<T>
where
T: Termination,
{
fn name(&self) -> &str {
self.name
}
fn modules(&self) -> &[&str] {
if self.modules.len() <= 1 {
self.modules
} else {
&self.modules[1..]
}
}
fn run(&self) {
(self.test)().terminate()
}
fn ignore(&self) -> Ignore {
self.ignore
}
fn should_panic(&self) -> ShouldPanic {
self.should_panic
}
fn message(&self) -> Option<&'static str> {
if let Ignore::YesWithMessage(message) = self.ignore {
Some(message)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::{Ignore, ShouldPanic, Test, TestCase, split_module_path, split_module_path_len};
use claims::{assert_matches, assert_none, assert_some_eq};
use gba_test_macros::test;
#[test]
fn test_name() {
let test = Test {
name: "foo",
modules: &[""],
test: || {},
ignore: Ignore::No,
should_panic: ShouldPanic::No,
};
assert_eq!(test.name(), "foo")
}
#[test]
fn test_module_split() {
let test = Test {
name: "",
modules: &["foo", "bar"],
test: || {},
ignore: Ignore::No,
should_panic: ShouldPanic::No,
};
assert_eq!(test.modules(), &["bar"]);
}
#[test]
fn test_module_no_split() {
let test = Test {
name: "",
modules: &["foo"],
test: || {},
ignore: Ignore::No,
should_panic: ShouldPanic::No,
};
assert_eq!(test.modules(), &["foo"]);
}
#[test]
fn test_run_no_panic() {
let test = Test {
name: "",
modules: &[""],
test: || {
assert!(true);
},
ignore: Ignore::No,
should_panic: ShouldPanic::No,
};
test.run();
}
#[test]
#[should_panic(expected = "assertion failed: false")]
fn test_run_panic() {
let test = Test {
name: "",
modules: &[""],
test: || {
assert!(false);
},
ignore: Ignore::No,
should_panic: ShouldPanic::No,
};
test.run();
}
#[test]
fn test_ignore() {
let test = Test {
name: "",
modules: &[""],
test: || {},
ignore: Ignore::Yes,
should_panic: ShouldPanic::No,
};
assert_matches!(test.ignore(), Ignore::Yes);
}
#[test]
fn test_should_panic() {
let test = Test {
name: "",
modules: &[""],
test: || {},
ignore: Ignore::No,
should_panic: ShouldPanic::Yes,
};
assert_matches!(test.should_panic(), ShouldPanic::Yes);
}
#[test]
fn test_message() {
let test = Test {
name: "",
modules: &[""],
test: || {},
ignore: Ignore::YesWithMessage("foo"),
should_panic: ShouldPanic::No,
};
assert_some_eq!(test.message(), "foo");
}
#[test]
fn test_no_message() {
let test = Test {
name: "",
modules: &[""],
test: || {},
ignore: Ignore::Yes,
should_panic: ShouldPanic::No,
};
assert_none!(test.message());
}
#[test]
fn split_module_path_len_empty() {
assert_eq!(split_module_path_len(""), 1);
}
#[test]
fn split_module_path_len_single() {
assert_eq!(split_module_path_len("foo"), 1);
}
#[test]
fn split_module_path_len_single_colon() {
assert_eq!(split_module_path_len(":"), 1);
}
#[test]
fn split_module_path_len_empty_with_separator() {
assert_eq!(split_module_path_len("::"), 2);
}
#[test]
fn split_module_path_len_separator_with_extra_colon() {
assert_eq!(split_module_path_len(":::"), 2);
}
#[test]
fn split_module_path_len_modules_split_by_separator() {
assert_eq!(split_module_path_len("foo::bar"), 2);
}
#[test]
fn split_module_path_len_many_modules_split_by_separators() {
assert_eq!(split_module_path_len("foo::bar::baz::quux"), 4);
}
#[test]
fn split_module_path_len_modules_leading_separator() {
assert_eq!(split_module_path_len("::foo::bar"), 3);
}
#[test]
fn split_module_path_len_modules_trailing_separator() {
assert_eq!(split_module_path_len("foo::bar::"), 3);
}
#[test]
fn split_module_path_empty() {
assert_eq!(split_module_path::<1>(""), [""]);
}
#[test]
fn split_module_path_single() {
assert_eq!(split_module_path::<1>("foo"), ["foo"]);
}
#[test]
fn split_module_path_single_colon() {
assert_eq!(split_module_path::<1>(":"), [":"]);
}
#[test]
fn split_module_path_empty_with_separator() {
assert_eq!(split_module_path::<2>("::"), ["", ""]);
}
#[test]
fn split_module_path_separator_with_extra_colon() {
assert_eq!(split_module_path::<2>(":::"), ["", ":"]);
}
#[test]
fn split_module_path_modules_split_by_separator() {
assert_eq!(split_module_path::<2>("foo::bar"), ["foo", "bar"]);
}
#[test]
fn split_module_path_many_modules_split_by_separators() {
assert_eq!(
split_module_path::<4>("foo::bar::baz::quux"),
["foo", "bar", "baz", "quux"]
);
}
#[test]
fn split_module_path_modules_leading_separator() {
assert_eq!(split_module_path::<3>("::foo::bar"), ["", "foo", "bar"]);
}
#[test]
fn split_module_path_modules_trailing_separator() {
assert_eq!(split_module_path::<3>("foo::bar::"), ["foo", "bar", ""]);
}
#[test]
#[should_panic(expected = "module path was split into too many parts")]
fn split_module_path_size_too_small() {
split_module_path::<0>("foo");
}
#[test]
#[should_panic(expected = "module path was split into too many parts")]
fn split_module_path_size_too_small_multiple_parts() {
split_module_path::<2>("foo::bar::baz");
}
#[test]
#[should_panic(expected = "unable to split module path into enough separate parts")]
fn split_module_path_size_too_large() {
split_module_path::<2>("foo");
}
}