1 | //! Default argument processing when `ui_test` is used |
2 | //! as a test driver. |
3 | |
4 | use color_eyre::eyre::{bail, ensure, Result}; |
5 | use std::{borrow::Cow, num::NonZeroUsize}; |
6 | |
7 | /// Plain arguments if `ui_test` is used as a binary. |
8 | #[derive (Debug, Default)] |
9 | pub 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)] |
42 | pub 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 | |
50 | impl 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 | |
102 | fn 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 | |