1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use std::str::FromStr;

use clap::Parser;

/// Command line arguments.
///
/// This type represents everything the user can specify via CLI args. The main
/// method is [`from_args`][Arguments::from_args] which reads the global
/// `std::env::args()` and parses them into this type.
///
/// The CLI is very similar to the one from the native test harness. However,
/// there are minor differences:
/// - Most notable: the `--help` message is slightly different. This comes from
///   the fact that this crate (right now) uses structopt (which uses `clap`)
///   while the original `libtest` uses `docopt`.
/// - `--skip` only accepts one value per occurence (but can occur multiple
///   times). This solves ambiguity with the `filter` value at the very end.
///   Consider "`--skip foo bar`": should this be parsed as `skip: vec!["foo",
///   "bar"], filter: None` or `skip: vec!["foo"], filter: Some("bar")`? Here,
///   it's clearly the latter version. If you need multiple values for `skip`,
///   do it like this: `--skip foo --skip bar`.
/// - `--bench` and `--test` cannot be both set at the same time. It doesn't
///   make sense, but it's allowed in `libtest` for some reason.
///
/// **Note**: just because all CLI args can be parsed, doesn't mean that they
/// are all automatically used. Check [`run_tests`][::run_tests] for information on which
/// arguments are automatically used and require special care.
#[derive(Parser, Debug, Clone)]
#[structopt(
    help_template = "USAGE: [FLAGS] [OPTIONS] [FILTER]\n\n{all-args}\n\n\n{after-help}",
    disable_version_flag = true,
    after_help = "By default, all tests are run in parallel. This can be altered with the \n\
        --test-threads flag or the RUST_TEST_THREADS environment variable when running \n\
        tests (set it to 1).\n\
        \n\
        All tests have their standard output and standard error captured by default. \n\
        This can be overridden with the --nocapture flag or setting RUST_TEST_NOCAPTURE \n\
        environment variable to a value other than \"0\". Logging is not captured by default.",
)]
pub struct Arguments {
    // ============== FLAGS ===================================================
    /// Determines if ignored tests should be run.
    #[structopt(long = "--ignored", help = "Run ignored tests")]
    pub ignored: bool,

    /// Run tests, but not benchmarks.
    #[structopt(
        long = "--test",
        conflicts_with = "bench",
        help = "Run tests and not benchmarks",
    )]
    pub test: bool,

    /// Run benchmarks, but not tests.
    #[structopt(long = "--bench", help = "Run benchmarks instead of tests")]
    pub bench: bool,

    /// Only list all tests and benchmarks.
    #[structopt(long = "--list", help = "List all tests and benchmarks")]
    pub list: bool,

    /// If set, stdout/stderr are not captured during the test but are instead
    /// printed directly.
    #[structopt(
        long = "--nocapture",
        help = "don't capture stdout/stderr of each task, allow printing directly",
    )]
    pub nocapture: bool,

    /// If set, filters are matched exactly rather than by substring.
    #[structopt(
        long = "--exact",
        help = "Exactly match filters rather than by substring",
    )]
    pub exact: bool,

    /// If set, display only one character per test instead of one line.
    /// Especially useful for huge test suites.
    ///
    /// This is an alias for `--format=terse`. If this is set, `format` is
    /// `None`.
    #[structopt(
        short = 'q',
        long = "--quiet",
        conflicts_with = "format",
        help = "Display one character per test instead of one line. Alias to --format=terse",
    )]
    pub quiet: bool,

    // ============== OPTIONS =================================================
    /// Number of threads used for parallel testing.
    #[structopt(
        long = "--test-threads",
        help = "Number of threads used for running tests in parallel",
    )]
    pub num_threads: Option<usize>,

    /// Path of the logfile. If specified, everything will be written into the
    /// file instead of stdout.
    #[structopt(
        long = "--logfile",
        value_name = "PATH",
        help = "Write logs to the specified file instead of stdout",
    )]
    pub logfile: Option<String>,

    /// A list of filters. Tests whose names contain parts of any of these
    /// filters are skipped.
    #[structopt(
        long = "--skip",
        value_name = "FILTER",
        number_of_values = 1,
        help = "Skip tests whose names contain FILTER (this flag can be used multiple times)",
    )]
    pub skip: Vec<String>,

    /// Specifies whether or not to color the output.
    #[structopt(
        long = "--color",
        possible_values = &["auto", "always", "never"],
        value_name = "auto|always|never",
        help = "Configure coloring of output: \n\
            - auto = colorize if stdout is a tty and tests are run on serially (default)\n\
            - always = always colorize output\n\
            - never = never colorize output\n",
    )]
    pub color: Option<ColorSetting>,

    /// Specifies the format of the output.
    #[structopt(
        long = "--format",
        possible_values = &["pretty", "terse", "json"],
        value_name = "pretty|terse|json",
        help = "Configure formatting of output: \n\
            - pretty = Print verbose output\n\
            - terse = Display one character per test\n\
            - json = Output a json document\n",
    )]
    pub format: Option<FormatSetting>,

    // ============== POSITIONAL VALUES =======================================
    /// Filter string. Only tests which contain this string are run.
    #[structopt(
        name = "FILTER",
        help = "The FILTER string is tested against the name of all tests, and only those tests \
                whose names contain the filter are run.",
    )]
    pub filter_string: Option<String>,
}

impl Arguments {
    /// Parses the global CLI arguments given to the application.
    ///
    /// If the parsing fails (due to incorrect CLI args), an error is shown and
    /// the application exits. If help is requested (`-h` or `--help`), a help
    /// message is shown and the application exits, too.
    pub fn from_args() -> Self {
        Parser::parse()
    }

    /// Like `from_args()`, but operates on an explicit iterator and not the global arguments.
    pub fn from_iter<I>(iter: I) -> Self
    where
        Self: Sized,
        I: IntoIterator,
        I::Item: Into<std::ffi::OsString> + Clone,
    {
        Parser::parse_from(iter)
    }
}

/// Possible values for the `--color` option.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSetting {
    /// Colorize output if stdout is a tty and tests are run on serially
    /// (default).
    Auto,

    /// Always colorize output.
    Always,

    /// Never colorize output.
    Never,
}

impl Default for ColorSetting {
    fn default() -> Self {
        ColorSetting::Auto
    }
}

impl FromStr for ColorSetting {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "auto" => Ok(ColorSetting::Auto),
            "always" => Ok(ColorSetting::Always),
            "never" => Ok(ColorSetting::Never),
            _ => Err("foo"),
        }
    }
}

/// Possible values for the `--format` option.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatSetting {
    /// One line per test. Output for humans. (default)
    Pretty,

    /// One character per test. Usefull for test suites with many tests.
    Terse,

    /// Output as JSON.
    Json,
}

impl Default for FormatSetting {
    fn default() -> Self {
        FormatSetting::Pretty
    }
}

impl FromStr for FormatSetting {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "pretty" => Ok(FormatSetting::Pretty),
            "terse" => Ok(FormatSetting::Terse),
            "json" => Ok(FormatSetting::Json),
            _ => Err("foo"),
        }
    }
}