Skip to main content

nextest_runner/user_config/
experimental.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! User-level experimental features.
5//!
6//! These features are configured in the user config file (`~/.config/nextest/config.toml`)
7//! or via environment variables. They are separate from the repository-level experimental
8//! features in [`ConfigExperimental`](crate::config::core::ConfigExperimental).
9
10use serde::Deserialize;
11use std::{collections::BTreeSet, env, fmt, str::FromStr};
12
13/// User-level experimental features.
14///
15/// Configured in the `[experimental]` table of the user config file:
16///
17/// ```toml
18/// [experimental]
19/// record = true
20/// ```
21///
22/// Unknown features in this table produce a warning rather than an error, so
23/// older nextest binaries can load configs written for newer ones.
24#[derive(Clone, Copy, Debug, Default, Deserialize)]
25#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
26#[serde(rename_all = "kebab-case")]
27pub struct ExperimentalConfig {
28    /// Enables the record-replay-rerun feature, which stores test run results
29    /// on disk so they can be replayed or selectively rerun later.
30    #[serde(default)]
31    pub record: bool,
32}
33
34impl ExperimentalConfig {
35    /// Converts to a set of enabled experimental features.
36    pub fn to_set(self) -> BTreeSet<UserConfigExperimental> {
37        let Self { record } = self;
38        let mut set = BTreeSet::new();
39        if record {
40            set.insert(UserConfigExperimental::Record);
41        }
42        set
43    }
44}
45
46/// User-level experimental features.
47///
48/// These features can be enabled in the user config file or via environment variables.
49/// Unlike repository-level experimental features, these are personal preferences that
50/// aren't version-controlled with the project.
51#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
52#[non_exhaustive]
53pub enum UserConfigExperimental {
54    /// Enable recording of test runs.
55    Record,
56}
57
58impl UserConfigExperimental {
59    /// Returns the environment variable name that enables this feature.
60    pub fn env_var(&self) -> &'static str {
61        match self {
62            Self::Record => "NEXTEST_EXPERIMENTAL_RECORD",
63        }
64    }
65
66    /// Returns the feature name as used in configuration.
67    pub fn name(&self) -> &'static str {
68        match self {
69            Self::Record => "record",
70        }
71    }
72
73    /// Returns all known experimental features.
74    pub fn all() -> &'static [Self] {
75        &[Self::Record]
76    }
77
78    /// Returns the set of experimental features enabled via environment variables.
79    ///
80    /// A feature is enabled if its corresponding environment variable is set to "1".
81    pub fn from_env() -> BTreeSet<Self> {
82        Self::all()
83            .iter()
84            .filter(|feature| env::var(feature.env_var()).is_ok_and(|v| v == "1"))
85            .copied()
86            .collect()
87    }
88}
89
90impl fmt::Display for UserConfigExperimental {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        f.write_str(self.name())
93    }
94}
95
96impl FromStr for UserConfigExperimental {
97    type Err = UnknownUserExperimentalError;
98
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        match s {
101            "record" => Ok(Self::Record),
102            _ => Err(UnknownUserExperimentalError {
103                feature: s.to_owned(),
104            }),
105        }
106    }
107}
108
109/// Error returned when parsing an unknown experimental feature name.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct UnknownUserExperimentalError {
112    /// The unknown feature name.
113    pub feature: String,
114}
115
116impl fmt::Display for UnknownUserExperimentalError {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(
119            f,
120            "unknown experimental feature `{}`; known features: {}",
121            self.feature,
122            UserConfigExperimental::all()
123                .iter()
124                .map(|f| f.name())
125                .collect::<Vec<_>>()
126                .join(", ")
127        )
128    }
129}
130
131impl std::error::Error for UnknownUserExperimentalError {}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_from_str() {
139        assert_eq!(
140            "record".parse::<UserConfigExperimental>().unwrap(),
141            UserConfigExperimental::Record
142        );
143
144        assert!("unknown".parse::<UserConfigExperimental>().is_err());
145    }
146
147    #[test]
148    fn test_display() {
149        assert_eq!(UserConfigExperimental::Record.to_string(), "record");
150    }
151
152    #[test]
153    fn test_env_var() {
154        assert_eq!(
155            UserConfigExperimental::Record.env_var(),
156            "NEXTEST_EXPERIMENTAL_RECORD"
157        );
158    }
159}