unit_testing/assertions/
mod.rs

1use colored_truecolor::Colorize;
2use is_executable::IsExecutable;
3use progress_bar::{
4    finalize_progress_bar, inc_progress_bar, init_progress_bar, print_progress_bar_final_info,
5    print_progress_bar_info, set_progress_bar_action, Color, Style,
6};
7use regex::Regex;
8use std::cell::Cell;
9use std::collections::{HashMap, HashSet};
10use std::path::Path;
11use std::process::ExitStatus;
12use std::thread::sleep;
13use std::time::{Duration, Instant};
14use std::{fs, io};
15
16use crate::objects::{Failure, Success, Take, Testable, Theory};
17use crate::output::{
18    ASSERT_BEGIN, ASSERT_BETWEEN, ASSERT_CONTAINS, ASSERT_EQUALS, ASSERT_EXISTS, ASSERT_FAIL,
19    ASSERT_FINNISH, ASSERT_IS_EXECUTABLE, ASSERT_KO, ASSERT_MATCH, ASSERT_NOT_CONTAINS,
20    ASSERT_NOT_EXISTS, ASSERT_OK, ASSERT_SHOULD_BE_BEGIN, ASSERT_SHOULD_BE_BETWEEN,
21    ASSERT_SHOULD_BE_EQUALS, ASSERT_SHOULD_BE_EXECUTABLE, ASSERT_SHOULD_BE_EXISTS,
22    ASSERT_SHOULD_BE_FAIL, ASSERT_SHOULD_BE_FINNISH, ASSERT_SHOULD_BE_KO,
23    ASSERT_SHOULD_BE_NOT_CONTAINS, ASSERT_SHOULD_BE_NOT_EXISTS, ASSERT_SHOULD_BE_OK,
24    ASSERT_SHOULD_BE_SUCCESS, ASSERT_SHOULD_BE_SUPERIOR, ASSERT_SHOULD_BE_UNEQUALS,
25    ASSERT_SHOULD_CONTAINS, ASSERT_SHOULD_MATCH, ASSERT_SUCCESS, ASSERT_SUPERIOR,
26    ASSERT_THEORY_IS_FALSE, ASSERT_THEORY_IS_TRUE, ASSERT_THEORY_SHOULD_BE_FALSE,
27    ASSERT_THEORY_SHOULD_BE_TRUE, ASSERT_UNEQUALS, IS_FAIL, IS_SUCCESS, THEORY_IS_FALSE,
28    THEORY_IS_TRUE,
29};
30
31///
32/// # To run assertions tests
33///
34pub struct Assert {
35    c: Cell<usize>,
36    sleep: u64,
37    messages: HashMap<usize, String>,
38    take: HashMap<usize, u128>,
39}
40
41impl Success for Assert {
42    fn run(&mut self, callbacks: Vec<&dyn Fn() -> Result<ExitStatus, io::Error>>) -> &mut Self {
43        for &c in &callbacks {
44            self.check(
45                c().unwrap().success(),
46                ASSERT_SUCCESS,
47                ASSERT_SHOULD_BE_SUCCESS,
48            );
49        }
50        self
51    }
52
53    fn success(&mut self, callbacks: Vec<&dyn Fn() -> bool>) -> &mut Self {
54        for &c in &callbacks {
55            self.check(c(), IS_SUCCESS, IS_FAIL);
56        }
57        self
58    }
59}
60
61impl Theory for Assert {
62    fn chaos(&mut self, callback: &dyn Fn() -> bool) -> &mut Self {
63        self.take(
64            !callback(),
65            ASSERT_THEORY_IS_FALSE,
66            ASSERT_THEORY_SHOULD_BE_FALSE,
67        )
68    }
69    fn theorem<T: PartialEq>(&mut self, expected: T, actual: &dyn Fn() -> T) -> &mut Self {
70        self.take(expected.eq(&actual()), THEORY_IS_TRUE, THEORY_IS_FALSE)
71    }
72    fn theory<T: PartialEq>(&mut self, expected: T, callback: &dyn Fn() -> T) -> &mut Self {
73        self.take(
74            expected == callback(),
75            ASSERT_THEORY_IS_TRUE,
76            ASSERT_THEORY_SHOULD_BE_TRUE,
77        )
78    }
79}
80
81impl Failure for Assert {
82    fn command_fail(
83        &mut self,
84        callbacks: Vec<&dyn Fn() -> Result<ExitStatus, io::Error>>,
85    ) -> &mut Self {
86        for &c in &callbacks {
87            let status = c().unwrap();
88            self.check(!status.success(), ASSERT_FAIL, ASSERT_SHOULD_BE_FAIL);
89        }
90        self
91    }
92
93    fn fail(&mut self, callbacks: Vec<&dyn Fn() -> bool>) -> &mut Self {
94        for &c in &callbacks {
95            self.check(!c(), ASSERT_FAIL, ASSERT_SHOULD_BE_FAIL);
96        }
97        self
98    }
99}
100
101impl Take for Assert {
102    fn assert_that(&mut self, t: bool) -> bool {
103        self.assert(t)
104    }
105
106    fn take(&mut self, t: bool, s: &str, e: &str) -> &mut Self {
107        let i: Instant = Instant::now();
108
109        if self.assert_that(t) {
110            assert_eq!(self.messages.insert(self.c.get(), s.to_string()), None);
111            assert_eq!(self.take.insert(self.c.get(), i.elapsed().as_nanos()), None);
112        } else {
113            panic!("{}", format_args!("{s} match {e}"))
114        }
115        self
116    }
117
118    fn check(&mut self, t: bool, s: &str, e: &str) {
119        let i: Instant = Instant::now();
120
121        if self.assert_that(t) {
122            assert!(self.messages.insert(self.c.get(), s.to_string()).is_some());
123            assert!(self
124                .take
125                .insert(self.c.get(), i.elapsed().as_nanos())
126                .is_some());
127        } else {
128            panic!("{}", format_args!("{s} match {e}"))
129        }
130    }
131}
132
133impl Testable for Assert {
134    fn matches(&mut self, pattern: &str, values: Vec<String>) -> &mut Self {
135        let r = Regex::new(pattern).unwrap();
136
137        for x in &values {
138            self.check(r.is_match(x.as_str()), ASSERT_MATCH, ASSERT_SHOULD_MATCH);
139        }
140        self
141    }
142
143    fn capture(&mut self, pattern: &str, x: &str, key: usize, values: Vec<String>) -> &mut Self {
144        let r = Regex::new(pattern).unwrap();
145        let caps = r.captures(x).unwrap();
146        for v in &values {
147            self.check(
148                caps.get(key)
149                    .expect("failed to get key")
150                    .as_str()
151                    .eq(v.as_str()),
152                ASSERT_MATCH,
153                ASSERT_SHOULD_MATCH,
154            );
155        }
156        self
157    }
158
159    fn it(
160        title: &str,
161        description: &str,
162        sleep_time: u64,
163        callbacks: Vec<&dyn Fn(&mut Self) -> &mut Self>,
164    ) {
165        println!("\n{}\n", description.white().bold());
166        println!(
167            "     {}",
168            format_args!("{} {}", "[ + ]".green().bold(), title.blue().bold())
169        );
170        let mut x = Self::new(sleep_time);
171
172        let mut j = &mut x;
173        for &c in &callbacks {
174            j = c(j);
175        }
176        assert!(j.end());
177    }
178
179    fn ok(&mut self, f: bool) -> &mut Self {
180        self.take(f, ASSERT_OK, ASSERT_SHOULD_BE_OK)
181    }
182
183    fn ko(&mut self, f: bool) -> &mut Self {
184        self.take(!f, ASSERT_KO, ASSERT_SHOULD_BE_KO)
185    }
186
187    fn assert(&mut self, test: bool) -> bool {
188        assert!(test);
189        self.c.set(self.c.get() + 1);
190        true
191    }
192
193    fn eq<T: PartialEq>(&mut self, a: T, b: T) -> &mut Self {
194        self.take(a.eq(&b), ASSERT_EQUALS, ASSERT_SHOULD_BE_EQUALS)
195    }
196
197    fn ne<T: PartialEq>(&mut self, a: T, b: T) -> &mut Self {
198        self.take(a.ne(&b), ASSERT_UNEQUALS, ASSERT_SHOULD_BE_UNEQUALS)
199    }
200    fn gt<T: PartialOrd>(&mut self, a: T, min: T) -> &mut Self {
201        self.take(a.gt(&min), ASSERT_SUPERIOR, ASSERT_SHOULD_BE_SUPERIOR)
202    }
203    fn ge<T: PartialOrd>(&mut self, a: T, min: T) -> &mut Self {
204        self.take(a.ge(&min), ASSERT_SUPERIOR, ASSERT_SHOULD_BE_SUPERIOR)
205    }
206    fn le<T: PartialOrd>(&mut self, a: T, min: T) -> &mut Self {
207        self.take(a.le(&min), ASSERT_SUPERIOR, ASSERT_SHOULD_BE_SUPERIOR)
208    }
209    fn lt<T: PartialOrd>(&mut self, a: T, min: T) -> &mut Self {
210        self.take(a.lt(&min), ASSERT_SUPERIOR, ASSERT_SHOULD_BE_SUPERIOR)
211    }
212
213    fn between<T: PartialOrd>(&mut self, a: T, min: T, max: T) -> &mut Self {
214        self.take(a > min && a < max, ASSERT_BETWEEN, ASSERT_SHOULD_BE_BETWEEN)
215    }
216
217    fn vec_contains<T: PartialEq>(&mut self, a: Vec<T>, b: T) -> &mut Self {
218        self.take(a.contains(&b), ASSERT_CONTAINS, ASSERT_SHOULD_CONTAINS)
219    }
220
221    fn exe(&mut self, p: &str) -> &mut Self {
222        self.take(
223            Path::new(p).is_executable(),
224            ASSERT_IS_EXECUTABLE,
225            ASSERT_SHOULD_BE_EXECUTABLE,
226        )
227    }
228
229    fn vec_no_contains<T: PartialEq>(&mut self, a: Vec<T>, b: T) -> &mut Self {
230        self.take(
231            !a.contains(&b),
232            ASSERT_NOT_CONTAINS,
233            ASSERT_SHOULD_BE_NOT_CONTAINS,
234        )
235    }
236
237    fn option_contains<T: PartialEq>(&mut self, a: Option<T>, b: T) -> &mut Self {
238        self.take(a.expect("") == b, ASSERT_CONTAINS, ASSERT_SHOULD_CONTAINS)
239    }
240
241    fn hash_contains(&mut self, a: &mut HashSet<String>, b: String) -> &mut Self {
242        self.take(a.contains(&b), ASSERT_CONTAINS, ASSERT_SHOULD_CONTAINS)
243    }
244
245    fn str_contains(&mut self, a: &str, b: &str) -> &mut Self {
246        self.take(a.contains(b), ASSERT_CONTAINS, ASSERT_SHOULD_CONTAINS)
247    }
248
249    fn file_contains(&mut self, f: &str, v: &str) -> &mut Self {
250        self.take(
251            fs::read_to_string(f)
252                .unwrap_or_else(|_| panic!("The filename {f} has not been founded"))
253                .contains(v),
254            ASSERT_CONTAINS,
255            ASSERT_SHOULD_CONTAINS,
256        )
257    }
258
259    fn exists(&mut self, p: &str) -> &mut Self {
260        self.take(
261            Path::new(p).exists(),
262            ASSERT_EXISTS,
263            ASSERT_SHOULD_BE_EXISTS,
264        )
265    }
266
267    fn not_exists(&mut self, p: &str) -> &mut Self {
268        self.take(
269            !Path::new(p).exists(),
270            ASSERT_NOT_EXISTS,
271            ASSERT_SHOULD_BE_NOT_EXISTS,
272        )
273    }
274
275    fn start_with(&mut self, actual: &str, expected: &str) -> &mut Self {
276        self.take(
277            actual.starts_with(expected),
278            ASSERT_BEGIN,
279            ASSERT_SHOULD_BE_BEGIN,
280        )
281    }
282
283    fn end_with(&mut self, actual: &str, expected: &str) -> &mut Self {
284        self.take(
285            actual.ends_with(expected),
286            ASSERT_FINNISH,
287            ASSERT_SHOULD_BE_FINNISH,
288        )
289    }
290
291    fn end(&mut self) -> bool {
292        let total: usize = self.c.get();
293        init_progress_bar(total);
294        set_progress_bar_action("[ ✓ ]", Color::Green, Style::Bold);
295
296        let mut take = self.take.values();
297        let mut messages = self.messages.values();
298        for _i in 0..total {
299            sleep(Duration::from_millis(self.sleep));
300            print_progress_bar_info(
301                "[ ✓ ]",
302                format!(
303                    "{} {} {} {}",
304                    messages.next().unwrap().to_string().blue().bold(),
305                    "take".white().bold(),
306                    take.next().unwrap().to_string().cyan().bold(),
307                    "ns".blue().bold()
308                )
309                .as_str(),
310                Color::Green,
311                Style::Bold,
312            );
313            inc_progress_bar();
314        }
315
316        print_progress_bar_final_info(
317            "[ ✓ ]",
318            format!(
319                "{} {}",
320                total.to_string().blue().bold(),
321                "assertions".blue().bold()
322            )
323            .as_str(),
324            Color::Green,
325            Style::Bold,
326        );
327        finalize_progress_bar();
328        true
329    }
330
331    fn new(sleep_time: u64) -> Self {
332        Self {
333            c: Cell::new(0),
334            sleep: sleep_time,
335            messages: HashMap::new(),
336            take: HashMap::new(),
337        }
338    }
339}
340
341#[cfg(test)]
342mod test {
343    use crate::assert_that;
344    use crate::assertions::Assert;
345    use crate::objects::{Testable, Theory};
346    use crate::output::DISABLE_PROGRESS_TIME;
347    use std::collections::HashSet;
348
349    fn ok() -> bool {
350        true
351    }
352
353    fn ko() -> bool {
354        false
355    }
356
357    fn must_pass(u: &mut Assert) -> &mut Assert {
358        u.ok(ok()).ko(ko())
359    }
360
361    fn must_exists(u: &mut Assert) -> &mut Assert {
362        u.exists(".").exists("README.md")
363    }
364
365    fn must_linux(u: &mut Assert) -> &mut Assert {
366        u
367    }
368
369    fn must_equals(u: &mut Assert) -> &mut Assert {
370        u.eq("README.md", "README.md")
371            .eq(4, 4)
372            .eq(4.4, 4.4)
373            .eq(true, true)
374            .eq(false, false)
375    }
376
377    fn must_contains(u: &mut Assert) -> &mut Assert {
378        let mut v: Vec<String> = Vec::new();
379        let o = Some("a".to_string());
380        v.push("value".to_string());
381        v.push("h".to_string());
382        u.vec_contains(v, "h".to_string())
383            .option_contains(o, "a".to_string())
384            .str_contains("linux", "linux")
385            .file_contains("README.md", "Installation")
386            .hash_contains(&mut HashSet::from(["a".to_string()]), "a".to_string())
387    }
388
389    fn must_unequals(u: &mut Assert) -> &mut Assert {
390        u.ne("README.md", ".")
391            .ne(4, 6)
392            .ne(5.6, 4.4)
393            .ne(false, true)
394            .ne(false, true)
395    }
396
397    fn must_superior(u: &mut Assert) -> &mut Assert {
398        u.gt(1, 0).gt(5, 2)
399    }
400
401    fn programs(u: &mut Assert) -> &mut Assert {
402        u
403    }
404
405    fn no_programs(u: &mut Assert) -> &mut Assert {
406        u
407    }
408
409    fn must_inferior(u: &mut Assert) -> &mut Assert {
410        u.lt(10, 50).lt(50, 200)
411    }
412
413    fn must_between(u: &mut Assert) -> &mut Assert {
414        u.between(10, 5, 50).between(50, 10, 200)
415    }
416
417    fn pythagore() -> f32 {
418        3.0_f32.hypot(4.0)
419    }
420
421    fn pythagore_not_work() -> bool {
422        4.0_f32.hypot(4.0) == 5.0
423    }
424
425    fn must_theory(u: &mut Assert) -> &mut Assert {
426        u.theory(5.0, &pythagore).chaos(&pythagore_not_work)
427    }
428
429    #[test]
430    pub fn all() {
431        assert_that!(
432            "Test the assert framework",
433            "Check if all values passes on success, can't be have failures.",
434            DISABLE_PROGRESS_TIME,
435            vec![
436                &must_between,
437                &programs,
438                &must_theory,
439                &no_programs,
440                &must_unequals,
441                &must_linux,
442                &must_equals,
443                &must_exists,
444                &must_pass,
445                &must_contains,
446                &must_superior,
447                &must_inferior,
448            ]
449        );
450    }
451}