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