cargo_flake/
lib.rs

1#![deny(clippy::all)]
2#![deny(clippy::cargo)]
3#![deny(clippy::pedantic)]
4#![allow(clippy::default_trait_access)]
5
6use argh::FromArgs;
7use lazy_static::lazy_static;
8use regex::Regex;
9use std::str;
10
11#[derive(FromArgs, Debug)]
12/// see `flake` subcommand
13pub struct Config {
14    #[argh(subcommand)]
15    nested: SubCommand,
16}
17
18impl Config {
19    #[must_use]
20    pub fn flake_config(&self) -> Option<&FlakeConfig> {
21        match self.nested {
22            SubCommand::Flake(ref fc) => Some(fc),
23        }
24    }
25}
26
27#[derive(FromArgs, Debug)]
28#[argh(subcommand)]
29enum SubCommand {
30    Flake(FlakeConfig),
31}
32
33#[derive(FromArgs, Debug)]
34#[argh(subcommand, name = "flake")]
35/// `cargo flake` -- a tool to detect flakey tests
36pub struct FlakeConfig {
37    /// total global thread count
38    #[argh(option)]
39    pub threads: Option<usize>,
40
41    /// space separated feature list
42    #[argh(option)]
43    pub features: Option<String>,
44
45    /// only run tests with this prefix
46    #[argh(option)]
47    pub prefix: Option<String>,
48
49    /// how many times to run individual tests
50    #[argh(option)]
51    pub iterations: Option<u16>,
52
53    /// how many tests are allowed to fail
54    #[argh(option)]
55    pub tolerable_failures: Option<u16>,
56}
57
58lazy_static! {
59    static ref TEST_NAME_RE: Regex =
60        Regex::new("((?:[a-zA-Z0-9_]+[:]{2})*[a-zA-Z0-9_]+): test").unwrap();
61}
62
63#[must_use]
64pub fn parse_test_names(input: &str) -> Vec<String> {
65    let mut output = Vec::new();
66    for cap in TEST_NAME_RE.captures_iter(input) {
67        output.push(cap[1].into());
68    }
69    output
70}
71
72#[derive(Debug)]
73pub struct TestSetup {
74    pub name: String,
75    pub command: String,
76    pub iterations: u16,
77}
78
79#[derive(Debug, Clone)]
80pub struct TestResult {
81    pub name: String,
82    pub iterations: u16,
83    pub successes: u16,
84    pub failures: u16,
85}
86
87impl TestResult {
88    #[must_use]
89    pub fn new(name: String) -> Self {
90        TestResult {
91            name,
92            iterations: 0,
93            successes: 0,
94            failures: 0,
95        }
96    }
97}
98
99#[cfg(test)]
100mod test {
101    use super::parse_test_names;
102
103    #[test]
104    fn test_name_match() {
105        let text = "tests::a_test: test\n\nA_test: test\nnonsense text\na_test: test\ntls::settings::test::from_config_not_enabled: test\n: test";
106        let names = parse_test_names(text);
107
108        assert_eq!("tests::a_test", &names[0]);
109        assert_eq!("A_test", &names[1]);
110        assert_eq!("a_test", &names[2]);
111        assert_eq!("tls::settings::test::from_config_not_enabled", &names[3]);
112        assert_eq!(4, names.len());
113    }
114
115    #[test]
116    fn test_long() {
117        let mut sum: u128 = 1;
118        for _ in 0..1_000_000_000 {
119            sum = sum.saturating_add(sum);
120            assert!(true)
121        }
122        assert!(sum > 2);
123    }
124
125    #[ignore]
126    #[test]
127    fn born_to_fail() {
128        for _ in 0..1_000_000 {
129            assert!(true)
130        }
131        assert!(false)
132    }
133}