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