| 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 | |