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
31pub 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}