1use std::cell::RefCell;
30use std::rc::Rc;
31
32#[derive(Default)]
34pub struct TestContext {
35 description: String,
37 before_hooks: Vec<Box<dyn Fn()>>,
39 after_hooks: Vec<Box<dyn Fn()>>,
41 passed: Rc<RefCell<u32>>,
43 failed: Rc<RefCell<u32>>,
45 failures: Rc<RefCell<Vec<String>>>,
47}
48
49impl TestContext {
50 pub fn new(description: impl Into<String>) -> Self {
52 Self {
53 description: description.into(),
54 before_hooks: Vec::new(),
55 after_hooks: Vec::new(),
56 passed: Rc::new(RefCell::new(0)),
57 failed: Rc::new(RefCell::new(0)),
58 failures: Rc::new(RefCell::new(Vec::new())),
59 }
60 }
61
62 pub fn before<F: Fn() + 'static>(&mut self, f: F) {
64 self.before_hooks.push(Box::new(f));
65 }
66
67 pub fn after<F: Fn() + 'static>(&mut self, f: F) {
69 self.after_hooks.push(Box::new(f));
70 }
71
72 pub fn it<F: Fn(&TestContext)>(&self, description: &str, test: F) {
74 for hook in &self.before_hooks {
76 hook();
77 }
78
79 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
81 test(self);
82 }));
83
84 for hook in &self.after_hooks {
86 hook();
87 }
88
89 match result {
90 Ok(()) => {
91 *self.passed.borrow_mut() += 1;
92 }
93 Err(e) => {
94 *self.failed.borrow_mut() += 1;
95 let msg = if let Some(s) = e.downcast_ref::<&str>() {
96 format!("{} - {}: {}", self.description, description, s)
97 } else if let Some(s) = e.downcast_ref::<String>() {
98 format!("{} - {}: {}", self.description, description, s)
99 } else {
100 format!("{} - {}: test panicked", self.description, description)
101 };
102 self.failures.borrow_mut().push(msg);
103 }
104 }
105 }
106
107 pub fn passed(&self) -> u32 {
109 *self.passed.borrow()
110 }
111
112 pub fn failed(&self) -> u32 {
114 *self.failed.borrow()
115 }
116
117 pub fn failures(&self) -> Vec<String> {
119 self.failures.borrow().clone()
120 }
121
122 pub fn all_passed(&self) -> bool {
124 *self.failed.borrow() == 0
125 }
126}
127
128pub fn describe<F: FnOnce(&mut TestContext)>(description: &str, f: F) -> TestContext {
130 let mut ctx = TestContext::new(description);
131 f(&mut ctx);
132 ctx
133}
134
135pub fn describe_and_assert<F: FnOnce(&mut TestContext)>(description: &str, f: F) {
137 let ctx = describe(description, f);
138 if !ctx.all_passed() {
139 panic!(
140 "Test suite '{}' failed: {} passed, {} failed\n{}",
141 description,
142 ctx.passed(),
143 ctx.failed(),
144 ctx.failures().join("\n")
145 );
146 }
147}
148
149pub struct Expectation<T> {
155 value: T,
156 negated: bool,
157}
158
159pub fn expect<T>(value: T) -> Expectation<T> {
161 Expectation {
162 value,
163 negated: false,
164 }
165}
166
167impl<T> Expectation<T> {
168 pub fn not(mut self) -> Self {
170 self.negated = !self.negated;
171 self
172 }
173}
174
175impl<T: PartialEq + std::fmt::Debug> Expectation<T> {
176 pub fn to_equal(self, expected: T) {
178 let matches = self.value == expected;
179 if self.negated {
180 if matches {
181 panic!("Expected {:?} not to equal {:?}", self.value, expected);
182 }
183 } else if !matches {
184 panic!("Expected {:?} to equal {:?}", self.value, expected);
185 }
186 }
187}
188
189impl<T: PartialOrd + std::fmt::Debug> Expectation<T> {
190 pub fn to_be_greater_than(self, other: T) {
192 let matches = self.value > other;
193 if self.negated {
194 if matches {
195 panic!(
196 "Expected {:?} not to be greater than {:?}",
197 self.value, other
198 );
199 }
200 } else if !matches {
201 panic!("Expected {:?} to be greater than {:?}", self.value, other);
202 }
203 }
204
205 pub fn to_be_less_than(self, other: T) {
207 let matches = self.value < other;
208 if self.negated {
209 if matches {
210 panic!("Expected {:?} not to be less than {:?}", self.value, other);
211 }
212 } else if !matches {
213 panic!("Expected {:?} to be less than {:?}", self.value, other);
214 }
215 }
216}
217
218impl Expectation<bool> {
219 pub fn to_be_true(self) {
221 if self.negated {
222 if self.value {
223 panic!("Expected false but got true");
224 }
225 } else if !self.value {
226 panic!("Expected true but got false");
227 }
228 }
229
230 pub fn to_be_false(self) {
232 if self.negated {
233 if !self.value {
234 panic!("Expected true but got false");
235 }
236 } else if self.value {
237 panic!("Expected false but got true");
238 }
239 }
240}
241
242impl<T> Expectation<Option<T>> {
243 pub fn to_be_some(self) {
245 let is_some = self.value.is_some();
246 if self.negated {
247 if is_some {
248 panic!("Expected None but got Some");
249 }
250 } else if !is_some {
251 panic!("Expected Some but got None");
252 }
253 }
254
255 pub fn to_be_none(self) {
257 let is_none = self.value.is_none();
258 if self.negated {
259 if is_none {
260 panic!("Expected Some but got None");
261 }
262 } else if !is_none {
263 panic!("Expected None but got Some");
264 }
265 }
266}
267
268impl<T, E> Expectation<Result<T, E>> {
269 pub fn to_be_ok(self) {
271 let is_ok = self.value.is_ok();
272 if self.negated {
273 if is_ok {
274 panic!("Expected Err but got Ok");
275 }
276 } else if !is_ok {
277 panic!("Expected Ok but got Err");
278 }
279 }
280
281 pub fn to_be_err(self) {
283 let is_err = self.value.is_err();
284 if self.negated {
285 if is_err {
286 panic!("Expected Ok but got Err");
287 }
288 } else if !is_err {
289 panic!("Expected Err but got Ok");
290 }
291 }
292}
293
294impl<T> Expectation<Vec<T>> {
295 pub fn to_be_empty(self) {
297 let is_empty = self.value.is_empty();
298 if self.negated {
299 if is_empty {
300 panic!("Expected non-empty but got empty");
301 }
302 } else if !is_empty {
303 panic!("Expected empty but got {} elements", self.value.len());
304 }
305 }
306
307 pub fn to_have_length(self, expected: usize) {
309 let len = self.value.len();
310 if self.negated {
311 if len == expected {
312 panic!("Expected length not to be {} but it was", expected);
313 }
314 } else if len != expected {
315 panic!("Expected length {} but got {}", expected, len);
316 }
317 }
318}
319
320impl Expectation<&str> {
321 pub fn to_contain(self, needle: &str) {
323 let contains = self.value.contains(needle);
324 if self.negated {
325 if contains {
326 panic!("Expected {:?} not to contain {:?}", self.value, needle);
327 }
328 } else if !contains {
329 panic!("Expected {:?} to contain {:?}", self.value, needle);
330 }
331 }
332
333 pub fn to_start_with(self, prefix: &str) {
335 let starts = self.value.starts_with(prefix);
336 if self.negated {
337 if starts {
338 panic!("Expected {:?} not to start with {:?}", self.value, prefix);
339 }
340 } else if !starts {
341 panic!("Expected {:?} to start with {:?}", self.value, prefix);
342 }
343 }
344
345 pub fn to_end_with(self, suffix: &str) {
347 let ends = self.value.ends_with(suffix);
348 if self.negated {
349 if ends {
350 panic!("Expected {:?} not to end with {:?}", self.value, suffix);
351 }
352 } else if !ends {
353 panic!("Expected {:?} to end with {:?}", self.value, suffix);
354 }
355 }
356}
357
358impl Expectation<String> {
359 pub fn to_contain(self, needle: &str) {
361 let contains = self.value.contains(needle);
362 if self.negated {
363 if contains {
364 panic!("Expected {:?} not to contain {:?}", self.value, needle);
365 }
366 } else if !contains {
367 panic!("Expected {:?} to contain {:?}", self.value, needle);
368 }
369 }
370
371 pub fn to_be_empty(self) {
373 let is_empty = self.value.is_empty();
374 if self.negated {
375 if is_empty {
376 panic!("Expected non-empty string but got empty");
377 }
378 } else if !is_empty {
379 panic!("Expected empty string but got {:?}", self.value);
380 }
381 }
382}
383
384impl Expectation<f32> {
385 pub fn to_be_close_to(self, expected: f32, epsilon: f32) {
387 let diff = (self.value - expected).abs();
388 let close = diff <= epsilon;
389 if self.negated {
390 if close {
391 panic!(
392 "Expected {} not to be close to {} (within {})",
393 self.value, expected, epsilon
394 );
395 }
396 } else if !close {
397 panic!(
398 "Expected {} to be close to {} (within {}), diff was {}",
399 self.value, expected, epsilon, diff
400 );
401 }
402 }
403}
404
405impl Expectation<f64> {
406 pub fn to_be_close_to(self, expected: f64, epsilon: f64) {
408 let diff = (self.value - expected).abs();
409 let close = diff <= epsilon;
410 if self.negated {
411 if close {
412 panic!(
413 "Expected {} not to be close to {} (within {})",
414 self.value, expected, epsilon
415 );
416 }
417 } else if !close {
418 panic!(
419 "Expected {} to be close to {} (within {}), diff was {}",
420 self.value, expected, epsilon, diff
421 );
422 }
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_describe_basic() {
432 let ctx = describe("Math operations", |ctx| {
433 ctx.it("adds numbers", |_| {
434 expect(1 + 1).to_equal(2);
435 });
436
437 ctx.it("subtracts numbers", |_| {
438 expect(5 - 3).to_equal(2);
439 });
440 });
441
442 assert_eq!(ctx.passed(), 2);
443 assert_eq!(ctx.failed(), 0);
444 assert!(ctx.all_passed());
445 }
446
447 #[test]
448 fn test_describe_with_failure() {
449 let ctx = describe("Failing tests", |ctx| {
450 ctx.it("passes", |_| {
451 expect(1).to_equal(1);
452 });
453
454 ctx.it("fails", |_| {
455 expect(1).to_equal(2);
456 });
457 });
458
459 assert_eq!(ctx.passed(), 1);
460 assert_eq!(ctx.failed(), 1);
461 assert!(!ctx.all_passed());
462 }
463
464 #[test]
465 fn test_expect_equality() {
466 expect(42).to_equal(42);
467 expect("hello").to_equal("hello");
468 expect(vec![1, 2, 3]).to_equal(vec![1, 2, 3]);
469 }
470
471 #[test]
472 fn test_expect_not() {
473 expect(1).not().to_equal(2);
474 expect(true).not().to_be_false();
475 }
476
477 #[test]
478 fn test_expect_bool() {
479 expect(true).to_be_true();
480 expect(false).to_be_false();
481 expect(1 > 0).to_be_true();
482 }
483
484 #[test]
485 fn test_expect_comparison() {
486 expect(10).to_be_greater_than(5);
487 expect(3).to_be_less_than(7);
488 }
489
490 #[test]
491 fn test_expect_option() {
492 expect(Some(42)).to_be_some();
493 expect(None::<i32>).to_be_none();
494 }
495
496 #[test]
497 fn test_expect_result() {
498 expect(Ok::<i32, &str>(42)).to_be_ok();
499 expect(Err::<i32, &str>("error")).to_be_err();
500 }
501
502 #[test]
503 fn test_expect_vec() {
504 expect(Vec::<i32>::new()).to_be_empty();
505 expect(vec![1, 2, 3]).to_have_length(3);
506 expect(vec![1, 2]).not().to_be_empty();
507 }
508
509 #[test]
510 fn test_expect_string() {
511 expect("hello world").to_contain("world");
512 expect("hello").to_start_with("hel");
513 expect("hello").to_end_with("llo");
514 expect("hello").not().to_contain("xyz");
515 }
516
517 #[test]
518 fn test_expect_float_close_to() {
519 expect(0.1 + 0.2_f32).to_be_close_to(0.3, 0.001);
520 expect(3.14159_f64).to_be_close_to(3.14, 0.01);
521 }
522
523 #[test]
524 fn test_before_after_hooks() {
525 use std::cell::Cell;
526 use std::rc::Rc;
527
528 let counter = Rc::new(Cell::new(0));
529 let counter_clone = counter.clone();
530 let counter_clone2 = counter.clone();
531
532 let ctx = describe("Hooks", |ctx| {
533 ctx.before(move || {
534 counter_clone.set(counter_clone.get() + 1);
535 });
536
537 ctx.after(move || {
538 counter_clone2.set(counter_clone2.get() + 10);
539 });
540
541 ctx.it("first test", |_| {});
542 ctx.it("second test", |_| {});
543 });
544
545 assert_eq!(counter.get(), 22);
547 assert!(ctx.all_passed());
548 }
549
550 #[test]
551 fn test_nested_describe() {
552 let outer = describe("Outer", |outer_ctx| {
553 outer_ctx.it("outer test", |_| {
554 expect(true).to_be_true();
555 });
556
557 let inner = describe("Inner", |inner_ctx| {
558 inner_ctx.it("inner test", |_| {
559 expect(1 + 1).to_equal(2);
560 });
561 });
562
563 assert!(inner.all_passed());
564 });
565
566 assert!(outer.all_passed());
567 }
568
569 #[test]
574 fn test_expect_string_owned_to_contain() {
575 expect("hello world".to_string()).to_contain("world");
576 }
577
578 #[test]
579 fn test_expect_string_owned_to_be_empty() {
580 expect(String::new()).to_be_empty();
581 }
582
583 #[test]
584 fn test_expect_string_owned_not_empty() {
585 expect("hello".to_string()).not().to_be_empty();
586 }
587
588 #[test]
589 fn test_expect_f32_close_to_negated() {
590 expect(10.0_f32).not().to_be_close_to(1.0, 0.1);
591 }
592
593 #[test]
594 fn test_expect_f64_close_to_negated() {
595 expect(10.0_f64).not().to_be_close_to(1.0, 0.1);
596 }
597
598 #[test]
599 fn test_expect_str_not_start_with() {
600 expect("hello").not().to_start_with("xyz");
601 }
602
603 #[test]
604 fn test_expect_str_not_end_with() {
605 expect("hello").not().to_end_with("xyz");
606 }
607
608 #[test]
609 fn test_expect_option_negated() {
610 expect(Some(42)).not().to_be_none();
611 expect(None::<i32>).not().to_be_some();
612 }
613
614 #[test]
615 fn test_expect_result_negated() {
616 expect(Ok::<i32, &str>(42)).not().to_be_err();
617 expect(Err::<i32, &str>("error")).not().to_be_ok();
618 }
619
620 #[test]
621 fn test_expect_vec_not_length() {
622 expect(vec![1, 2, 3]).not().to_have_length(5);
623 }
624
625 #[test]
626 fn test_expect_bool_negated() {
627 expect(true).not().to_be_false();
628 expect(false).not().to_be_true();
629 }
630
631 #[test]
632 fn test_expect_comparison_negated() {
633 expect(3).not().to_be_greater_than(10);
634 expect(10).not().to_be_less_than(3);
635 }
636
637 #[test]
638 fn test_expect_equality_negated() {
639 expect(1).not().to_equal(2);
640 }
641
642 #[test]
643 fn test_context_passed_plus_failed() {
644 let ctx = describe("Test", |ctx| {
645 ctx.it("pass1", |_| {});
646 ctx.it("pass2", |_| {});
647 ctx.it("fail", |_| {
648 expect(1).to_equal(2);
649 });
650 });
651 assert_eq!(ctx.passed() + ctx.failed(), 3);
652 }
653}