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