tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_split.rs (6093B)


      1 //! Functionality for splitting CTS tests at certain paths into more tests and files in
      2 //! [`crate::main`].
      3 
      4 use std::collections::BTreeSet;
      5 
      6 /// The value portion of a key-value pair used in [`crate::main`] to split tests into more tests or
      7 /// files.
      8 #[derive(Debug)]
      9 pub(crate) struct Config<O> {
     10    /// The new base name used for the sibling directory that this entry will split off from the
     11    /// original CTS test.
     12    ///
     13    /// Chunking WPT tests happens at a directory level in Firefox's CI. If we split into a child
     14    /// directory, instead of a sibling directory, then we would actually cause the child test to
     15    /// be run in multiple chunks. Therefore, it's required to split tests into siblings.
     16    pub new_sibling_basename: &'static str,
     17    /// How to split the test this entry refers to.
     18    pub split_by: SplitBy<O>,
     19 }
     20 
     21 impl<O> Config<O> {
     22    pub fn map_observed_values<T>(self, f: impl FnOnce(O) -> T) -> Config<T> {
     23        let Self {
     24            new_sibling_basename,
     25            split_by,
     26        } = self;
     27        Config {
     28            new_sibling_basename,
     29            split_by: split_by.map_observed_values(f),
     30        }
     31    }
     32 }
     33 
     34 /// A [`Config::split_by`] value.
     35 #[derive(Debug)]
     36 pub(crate) enum SplitBy<O> {
     37    FirstParam {
     38        /// The name of the first parameter in the test, to be used as validation that we are
     39        /// actually pointing to the correct test.
     40        expected_name: &'static str,
     41        /// The method by which a test should be divided.
     42        split_to: SplitParamsTo,
     43        /// Values collected during [`Entry::process`], corresponding to a new test entry each.
     44        observed_values: O,
     45    },
     46 }
     47 
     48 impl SplitBy<()> {
     49    /// Convenience for constructing [`SplitBy::FirstParam`].
     50    pub fn first_param(name: &'static str, split_to: SplitParamsTo) -> Self {
     51        Self::FirstParam {
     52            expected_name: name,
     53            split_to,
     54            observed_values: (),
     55        }
     56    }
     57 }
     58 
     59 impl<O> SplitBy<O> {
     60    pub fn map_observed_values<T>(self, f: impl FnOnce(O) -> T) -> SplitBy<T> {
     61        match self {
     62            Self::FirstParam {
     63                expected_name,
     64                split_to,
     65                observed_values,
     66            } => {
     67                let observed_values = f(observed_values);
     68                SplitBy::FirstParam {
     69                    expected_name,
     70                    split_to,
     71                    observed_values,
     72                }
     73            }
     74        }
     75    }
     76 }
     77 
     78 /// A [SplitBy::FirstParam::split_to].
     79 #[derive(Debug)]
     80 pub(crate) enum SplitParamsTo {
     81    /// Place new test entries as siblings in the same file.
     82    SeparateTestsInSameFile,
     83 }
     84 
     85 #[derive(Debug)]
     86 pub(crate) struct Entry<'a> {
     87    /// Whether this
     88    pub seen: SeenIn,
     89    pub config: Config<BTreeSet<&'a str>>,
     90 }
     91 
     92 impl Entry<'_> {
     93    pub fn from_config(config: Config<()>) -> Self {
     94        Self {
     95            seen: SeenIn::nowhere(),
     96            config: config.map_observed_values(|()| BTreeSet::new()),
     97        }
     98    }
     99 }
    100 
    101 /// An [`Entry::seen`].
    102 #[derive(Debug, Default)]
    103 pub(crate) struct SeenIn {
    104    pub listing: bool,
    105    pub wpt_files: bool,
    106 }
    107 
    108 impl SeenIn {
    109    /// Default value: all seen locations set to `false`.
    110    pub fn nowhere() -> Self {
    111        Self::default()
    112    }
    113 }
    114 
    115 impl<'a> Entry<'a> {
    116    /// Accumulates a line from the test listing script in upstream CTS.
    117    ///
    118    /// Line is expected to have a full CTS test path, including at least one case parameter, i.e.:
    119    ///
    120    /// ```
    121    /// webgpu:path,to,test,group:test:param1="value1";…
    122    /// ```
    123    ///
    124    /// Note that `;…` is not strictly necessary, when there is only a single case parameter for
    125    /// the test.
    126    ///
    127    /// See [`crate::main`] for more details on how upstream CTS' listing script is invoked.
    128    pub(crate) fn process_listing_line(
    129        &mut self,
    130        test_group_and_later_path: &'a str,
    131    ) -> miette::Result<()> {
    132        let rest = test_group_and_later_path;
    133 
    134        let Self { seen, config } = self;
    135 
    136        let Config {
    137            new_sibling_basename: _,
    138            split_by,
    139        } = config;
    140 
    141        match split_by {
    142            SplitBy::FirstParam {
    143                ref expected_name,
    144                split_to: _,
    145                observed_values,
    146            } => {
    147                // NOTE: This only parses strings with no escaped characters. We may need different
    148                // values later, at which point we'll have to consider what to do here.
    149                let (ident, rest) = rest.split_once("=").ok_or_else(|| {
    150                    miette::diagnostic!("failed to get start of value of first arg")
    151                })?;
    152 
    153                if ident != *expected_name {
    154                    return Err(miette::diagnostic!(
    155                        "expected {:?}, got {:?}",
    156                        expected_name,
    157                        ident
    158                    )
    159                    .into());
    160                }
    161 
    162                let value = rest
    163                    .split_once(';')
    164                    .map(|(value, _rest)| value)
    165                    .unwrap_or(rest);
    166 
    167                // TODO: parse as JSON?
    168 
    169                observed_values.insert(value);
    170            }
    171        }
    172        seen.listing = true;
    173 
    174        Ok(())
    175    }
    176 }
    177 
    178 /// Iterate over `entries`' [`seen` members](Entry::seen), accumulating one of their fields and
    179 /// panicking if any are `false`
    180 ///
    181 /// Used in [`crate::main`] to check that a specific [`Entry::seen`] fields have been set to `true`
    182 /// for each entry configured.
    183 pub(crate) fn assert_seen<'a>(
    184    what: &'static str,
    185    entries: impl Iterator<Item = (&'a &'a str, &'a Entry<'a>)>,
    186    mut in_: impl FnMut(&'a SeenIn) -> &'a bool,
    187 ) {
    188    let mut unseen = Vec::new();
    189    entries.for_each(|(test_path, entry)| {
    190        if !*in_(&entry.seen) {
    191            unseen.push(test_path);
    192        }
    193    });
    194    if !unseen.is_empty() {
    195        panic!(
    196            concat!(
    197                "did not find the following test split config. entries ",
    198                "in {}: {:#?}",
    199            ),
    200            what, unseen
    201        );
    202    }
    203 }