1#![doc = include_str!("../README.md")]
2use serde::{Deserialize, Serialize};
3use tarantool::test::test_cases;
4use tester::{TestDescAndFn, TestFn, TestOpts};
5
6use anyhow::bail;
7pub use tarantool::proc as test_entrypoint;
8pub use tarantool::test;
9
10pub const DEFAULT_TEST_ENTRYPOINT_NAME: &str = "default_test_entrypoint";
11
12pub trait TestSuite {
14 fn before_all() {}
15 fn after_all() {}
16
17 fn before_each() {}
18 fn after_each() {}
19}
20
21impl TestSuite for () {}
23
24#[derive(Clone, Deserialize, Serialize, Debug, Default)]
25pub struct TestsConfig {
26 #[serde(default)]
27 pub filter: Option<String>,
28}
29
30#[macro_export]
34macro_rules! bind_test_suite {
35 () => {
36 bind_test_suite!(@default_entrypoint, ());
37 };
38 ($suite:ty) => {
39 bind_test_suite!(@default_entrypoint, $suite);
40 };
41 ($entrypoint:ident) => {
42 bind_test_suite($entrypoint, ());
43 };
44 (@default_entrypoint, $suite:ty) => {
46 bind_test_suite!(default_test_entrypoint, $suite);
47 };
48 ($entrypoint:ident, $suite:ty) => {
49 #[$crate::test_entrypoint]
50 fn $entrypoint(input: String) -> Result<(), Box<dyn std::error::Error>> {
51 $crate::handle_entrypoint::<$suite>(input)?;
52 Ok(())
53 }
54 };
55}
56
57pub fn collect_tests<S: TestSuite>(cfg: TestsConfig) -> Vec<TestDescAndFn> {
59 let tests = test_cases();
60
61 tests
62 .iter()
63 .filter(|case| {
64 cfg.filter
65 .as_ref()
66 .map(|must_contains| case.name().contains(must_contains))
67 .unwrap_or(true)
68 })
69 .map(|case| {
70 let test_fn = move || {
71 S::before_each();
72 case.run();
73 S::after_each();
74 };
75 let mut tester = case.to_tester();
76 tester.testfn = TestFn::DynTestFn(Box::new(test_fn));
77 tester
78 })
79 .collect()
80}
81
82pub fn execute_tests<S: TestSuite>(mut cfg: TestsConfig) -> anyhow::Result<()> {
84 if cfg.filter == Some("".to_string()) {
85 cfg.filter = None
86 }
87
88 let opts = &TestOpts {
89 list: false,
90 filter: None,
91 filter_exact: false,
92 force_run_in_process: false,
93 exclude_should_panic: false,
94 run_ignored: tester::RunIgnored::No,
95 run_tests: true,
96 bench_benchmarks: false,
97 logfile: None,
98 nocapture: false,
99 color: tester::ColorConfig::AutoColor,
100 format: tester::OutputFormat::Pretty,
101 test_threads: Some(1),
102 skip: vec![],
103 time_options: None,
104 options: tester::Options::new(),
105 };
106
107 S::before_all();
108 let result = tester::run_tests_console(opts, collect_tests::<S>(cfg));
109 S::after_all();
110
111 match result {
112 Ok(true) => Ok(()),
113 failure => {
114 bail!("failed to successfully pass the tests due to failure: {failure:?}");
115 }
116 }
117}
118
119pub fn handle_entrypoint<S: TestSuite>(input: String) -> anyhow::Result<()> {
123 let input: TestsConfig = serde_json::from_str(&input)?;
124 execute_tests::<S>(input)?;
125 Ok(())
126}