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