1//! Default argument processing when `ui_test` is used
2//! as a test driver.
3
4use std::{borrow::Cow, num::NonZeroUsize};
5
6use color_eyre::eyre::{bail, ensure, Result};
7
8/// Plain arguments if `ui_test` is used as a binary.
9#[derive(Debug, Default)]
10pub struct Args {
11 /// Filters that will be used to match on individual tests
12 pub filters: Vec<String>,
13
14 /// Whether to error on mismatches between `.stderr` files and actual
15 /// output.
16 pub check: bool,
17
18 /// Whether to overwrite `.stderr` files on mismtach with the actual
19 /// output.
20 pub bless: bool,
21
22 /// Only run the test matching the filters exactly.
23 pub exact: bool,
24
25 /// Whether to only run ignored tests.
26 pub ignored: bool,
27
28 /// List the tests that can be run.
29 pub list: bool,
30
31 /// Choose an output format
32 pub format: Format,
33
34 /// The number of threads to use
35 pub threads: Option<NonZeroUsize>,
36
37 /// Skip tests whose names contain any of these entries.
38 pub skip: Vec<String>,
39}
40
41/// Possible choices for styling the output.
42#[derive(Debug, Copy, Clone, Default)]
43pub enum Format {
44 /// Print one line per test
45 #[default]
46 Pretty,
47 /// Remove test lines once the test finishes and show a progress bar.
48 Terse,
49}
50
51impl Args {
52 /// Parse the program arguments.
53 /// This is meant to be used if `ui_test` is used as a `harness=false` test, called from `cargo test`.
54 pub fn test() -> Result<Self> {
55 Self::default().parse_args(std::env::args().skip(1))
56 }
57
58 /// Parse arguments into an existing `Args` struct.
59 pub fn parse_args(mut self, mut iter: impl Iterator<Item = String>) -> Result<Self> {
60 while let Some(arg) = iter.next() {
61 if arg == "--" {
62 continue;
63 }
64 if arg == "--quiet" {
65 self.format = Format::Terse;
66 } else if arg == "--check" {
67 self.check = true;
68 } else if arg == "--bless" {
69 self.bless = true;
70 } else if arg == "--list" {
71 self.list = true;
72 } else if arg == "--exact" {
73 self.exact = true;
74 } else if arg == "--ignored" {
75 self.ignored = true;
76 } else if arg == "--nocapture" {
77 // We ignore this flag for now.
78 } else if let Some(format) = parse_value("--format", &arg, &mut iter)? {
79 self.format = match &*format {
80 "terse" => Format::Terse,
81 "pretty" => Format::Pretty,
82 _ => bail!("unsupported format `{format}`"),
83 };
84 } else if let Some(skip) = parse_value("--skip", &arg, &mut iter)? {
85 self.skip.push(skip.into_owned());
86 } else if arg == "--help" {
87 bail!("available flags: --quiet, --check, --bless, --test-threads=n, --skip")
88 } else if let Some(n) = parse_value("--test-threads", &arg, &mut iter)? {
89 self.threads = Some(n.parse()?);
90 } else if arg.starts_with("--") {
91 bail!(
92 "unknown command line flag `{arg}`: {:?}",
93 iter.collect::<Vec<_>>()
94 );
95 } else {
96 self.filters.push(arg);
97 }
98 }
99 Ok(self)
100 }
101}
102
103fn parse_value<'a>(
104 name: &str,
105 arg: &'a str,
106 iter: &mut impl Iterator<Item = String>,
107) -> Result<Option<Cow<'a, str>>> {
108 let with_eq: &str = match arg.strip_prefix(name) {
109 Some(s: &str) => s,
110 None => return Ok(None),
111 };
112 if let Some(n: &str) = with_eq.strip_prefix('=') {
113 Ok(Some(n.into()))
114 } else {
115 ensure!(with_eq.is_empty(), "`{name}` can only be followed by `=`");
116
117 if let Some(next: String) = iter.next() {
118 Ok(Some(next.into()))
119 } else {
120 bail!("`name` must be followed by a value")
121 }
122 }
123}
124