1use std::fmt::Debug;
17use std::ops::Drop;
18
19pub trait TestFixture<'param, P, R>: Drop
20where
21 P: Debug + 'static,
22{
23 fn new(curried_params: &'param P) -> Self;
24
25 fn parameters() -> Option<Box<Iterator<Item = P>>>;
26
27 fn setup(&mut self) -> FixtureBinding<Self, R>
28 where
29 Self: std::marker::Sized;
30
31 fn tear_down(&self) {}
32}
33
34pub struct FixtureBinding<'fixture, F: 'fixture, R> {
35 pub val: R,
36 pub params: &'fixture F,
37}
38
39impl<'fixture, F: 'fixture, R> FixtureBinding<'fixture, F, R> {
40 pub fn decompose(self) -> (R, &'fixture F) {
41 (self.val, self.params)
42 }
43
44 pub fn into_val(self) -> R {
45 self.val
46 }
47
48 pub fn into_params(self) -> &'fixture F {
49 self.params
50 }
51}
52
53#[macro_export(local_inner_macros)]
57macro_rules! fixture {
58 ( @impl_drop $name:ident ) => {
59 impl<'param> ::std::ops::Drop for $name<'param> {
60 fn drop(&mut self) {
61 use ::galvanic_test::TestFixture;
62 self.tear_down();
63 }
64 }
65 };
66
67 ( @impl_struct $name:ident Params[$($param:ident : $param_ty:ty),*] Members[$($member:ident : $member_ty:ty),*] ) => {
68 #[allow(non_camel_case_types)]
69 #[derive(Debug)]
70 pub struct $name<'param> {
71 $(pub $param : &'param $param_ty,)*
72 $($member : Option<$member_ty>,)*
73 }
74 };
75
76 ( @new_method Params[$param:ident : $param_ty:ty] Members[$($member:ident),*] ) => {
77 fn new($param : &'param $param_ty) -> Self {
78 Self {
79 $param,
80 $($member: None,)*
81 }
82 }
83 };
84 ( @new_method Params[$($param:ident : $param_ty:ty),+] Members[$($member:ident),*] ) => {
85 fn new(&($(ref $param),*) : &'param ($($param_ty),*)) -> Self {
86 Self {
87 $($param,)*
88 $($member: None,)*
89 }
90 }
91 };
92
93 ( $name:ident ( ) -> $ret_ty:ty {
94 $(members { $($member:ident : Option<$member_ty:ty>),* })*
95 setup(& mut $self_setup:ident) $setup_body:block
96 $(tear_down(&$self_td:ident) $tear_down_body:block)*
97 }
98 ) => {
99 fixture!(@impl_struct $name Params[_phantom : ()] Members[$($($member : $member_ty),*),*]);
100
101 impl<'param> ::galvanic_test::TestFixture<'param, (), $ret_ty> for $name<'param> {
102 fn new(_phantom: &'param ()) -> Self {
103 Self {
104 _phantom,
105 $($($member: None),*),*
106 }
107 }
108 fn parameters() -> Option<Box<Iterator<Item=()>>> {
109 Some(Box::new(Some(()).into_iter()))
110 }
111 fn setup(&mut $self_setup) -> ::galvanic_test::FixtureBinding<Self, $ret_ty> {
112 let value = $setup_body;
113 ::galvanic_test::FixtureBinding {
114 val: value,
115 params: $self_setup
116 }
117 }
118 $(fn tear_down(&$self_td) $tear_down_body)*
119 }
120
121 fixture!(@impl_drop $name);
122 };
123
124 ( $name:ident ($($param:ident : $param_ty:ty),+) -> $ret_ty:ty {
125 $(members { $($member:ident : Option<$member_ty:ty>),* })*
126 $(params $params_body:block)*
127 setup(& mut $self_setup:ident) $setup_body:block
128 $(tear_down(&$self_td:ident) $tear_down_body:block)*
129 }
130 ) => {
131 fixture!(@impl_struct $name Params[$($param : $param_ty),*] Members[$($($member : $member_ty),*),*]);
132
133 impl<'param> ::galvanic_test::TestFixture<'param, ($($param_ty),*), $ret_ty> for $name<'param> {
134 fixture!(@new_method Params[$($param : $param_ty),*] Members[$($($member),*),*]);
135 fn parameters() -> Option<Box<Iterator<Item=($($param_ty),*)>>> {
136 (None as Option<Box<Iterator<Item=($($param_ty),*)>>>)
137 $(; Some(Box::new($params_body)))*
138 }
139 fn setup(&mut $self_setup) -> ::galvanic_test::FixtureBinding<Self, $ret_ty> {
140 let value = $setup_body;
141 ::galvanic_test::FixtureBinding {
142 val: value,
143 params: $self_setup
144 }
145 }
146 $(fn tear_down(&$self_td) $tear_down_body)*
147 }
148
149 fixture!(@impl_drop $name);
150 };
151}
152
153#[macro_export(local_inner_macros)]
154macro_rules! test {
155 ( @parameters | $body:block $test_case_failed:ident ) => { $body };
156
157 ( @parameters | $body:block $test_case_failed:ident $(($fixture_obj:ident, $params:expr, $fixture:ident))+) => {
158 let mut described_parameters = String::from("Test panicked before all fixtures have been assigned.");
159 let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
160 let mut described_params = Vec::new();
161 $(
162 let params = &$params;
163 let mut $fixture_obj = $fixture::new(params);
164 described_params.push(_galvanic__format!("{:?}", $fixture_obj));
165 let mut $fixture = $fixture_obj.setup();
166 noop(&$fixture);
167 )*
168 described_parameters = described_params.join(", ");
169 $body
170 }));
171 if result.is_err() {
172 _galvanic__println!("The above error occured with the following parameterisation of the test case:\n {}\n",
173 described_parameters);
174 $test_case_failed.set(true);
175 }
176 };
177
178 ( @parameters , $($remainder:tt)+ ) => {
179 test!(@parameters $($remainder)*);
180 };
181
182 ( @parameters $fixture:ident ( $($expr:expr),* ) $($remainder:tt)+ ) => {
183 test!(@parameters $($remainder)* (fixture_obj, ($($expr),*), $fixture));
184 };
185
186 ( @parameters $fixture:ident $($remainder:tt)+ ) => {
187 match $fixture::parameters() {
188 Some(iterator) => {
189 for params in iterator {
190 test!(@parameters $($remainder)* (fixture_obj, params, $fixture));
191 }
192 },
193 None => _galvanic__panic!(_galvanic__concat!(
194 "If a test fixture should be injected without supplying parameters, ",
195 "it either needs to have no arguments ",
196 "or a `params` block returning an iterator of parameter tuples ",
197 "must be given for the fixture."))
198 }
199 };
200
201 ( $(#[$attr:meta])* $name:ident | $($args_and_body:tt)* ) => {
202 #[test]
203 $(#[$attr])*
204 fn $name() {
205 #[allow(dead_code)]
206 fn noop<F, R>(_: &::galvanic_test::FixtureBinding<F,R>) { }
207 let test_case_failed = ::std::cell::Cell::new(false);
209 test!(@parameters $($args_and_body)* test_case_failed);
210 if test_case_failed.get() {
211 _galvanic__panic!("Some parameterised test cases failed");
212 }
213 }
214 };
215
216 ( $(#[$attr:meta])* $name:ident $body:block ) => {
217 #[test]
218 $(#[$attr])*
219 fn $name() {
220 $body
221 }
222 };
223}
224
225#[macro_export(local_inner_macros)]
226#[cfg(not(feature = "galvanic_mock_integration"))]
227macro_rules! test_suite {
228 ( name $name:ident ; $($remainder:tt)* ) => {
230 #[cfg(test)]
231 mod $name {
232 #[allow(unused_imports)] use ::galvanic_test::TestFixture;
233 galvanic_test::__test_suite_int!(@int $($remainder)*);
234 }
235 };
236
237 ( $($remainder:tt)* ) => {
239 #[cfg(test)]
240 mod __galvanic_test {
241 #[allow(unused_imports)] use ::galvanic_test::TestFixture;
242 galvanic_test::__test_suite_int!(@int $($remainder)*);
243 }
244 };
245}
246
247#[macro_export(local_inner_macros)]
248#[cfg(feature = "galvanic_mock_integration")]
249macro_rules! test_suite {
250 ( name $name:ident ; $($remainder:tt)* ) => {
252 #[cfg(test)]
253 mod $name {
254 #[allow(unused_imports)] use ::galvanic_mock::use_mocks;
255 #[allow(unused_imports)] use super::*;
256 #[use_mocks]
257 mod with_mocks {
258 #[allow(unused_imports)] use ::galvanic_test::TestFixture;
259 galvanic_test::__test_suite_int!(@int $($remainder)*);
260 }
261 }
262 };
263
264 ( $($remainder:tt)* ) => {
266 #[cfg(test)]
267 mod __galvanic_test {
268 #[allow(unused_imports)] use ::galvanic_mock::use_mocks;
269 #[allow(unused_imports)] use super::*;
270 #[use_mocks]
271 mod with_mocks {
272 #[allow(unused_imports)] use ::galvanic_test::TestFixture;
273 galvanic_test::__test_suite_int!(@int $($remainder)*);
274 }
275 }
276 };
277}
278
279#[macro_export(local_inner_macros)]
280macro_rules! __test_suite_int {
281 ( @int $(#[$attr:meta])* fixture $name:ident ($($param:ident : $param_ty:ty),*) -> $ret_ty:ty {
283 $(members { $($member:ident : Option<$member_ty:ty>),* })*
284 $(params $params_body:block)*
285 setup(& mut $self_setup:ident) $setup_body:block
286 $(tear_down(&$self_td:ident) $tear_down_body:block)*
287 } $($remainder:tt)*
288 ) => {
289 fixture!( $(#[$attr])* $name ($($param : $param_ty),*) -> $ret_ty {
290 $(members { $($member : Option<$member_ty>),* })*
291 $(params $params_body)*
292 setup(& mut $self_setup) $setup_body
293 $(tear_down(&$self_td) $tear_down_body)*
294 });
295 galvanic_test::__test_suite_int!(@int $($remainder)*);
296 };
297
298 ( @int $(#[$attr:meta])* test $name:ident ( $($fixture:ident $(($($expr:expr),*))*),* )
300 $body:block
301 $($remainder:tt)*
302 ) => {
303 test!( $(#[$attr])* $name | $($fixture $(($($expr),*))* ),* | $body);
304 galvanic_test::__test_suite_int!(@int $($remainder)*);
305 };
306
307 ( @int $item:item
309 $($remainder:tt)*
310 ) => {
311 $item
312 galvanic_test::__test_suite_int!(@int $($remainder)*);
313 };
314
315 ( @int ) => { };
317}
318
319#[doc(hidden)]
320#[macro_export]
321macro_rules! _galvanic__panic {
322 ($($inner:tt)*) => {
323 panic!($($inner)*)
324 };
325}
326
327#[doc(hidden)]
328#[macro_export]
329macro_rules! _galvanic__format {
330 ($($inner:tt)*) => {
331 format!($($inner)*)
332 };
333}
334
335#[doc(hidden)]
336#[macro_export]
337macro_rules! _galvanic__println {
338 ($($inner:tt)*) => {
339 println!($($inner)*)
340 };
341}
342
343#[doc(hidden)]
344#[macro_export]
345macro_rules! _galvanic__concat {
346 ($($inner:tt)*) => {
347 concat!($($inner)*)
348 };
349}