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