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| {
80                env::var(feature.env_var())
81                    .map(|v| v == "1")
82                    .unwrap_or(false)
83            })
84            .copied()
85            .collect()
86    }
87}
88
89impl fmt::Display for UserConfigExperimental {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.write_str(self.name())
92    }
93}
94
95impl FromStr for UserConfigExperimental {
96    type Err = UnknownUserExperimentalError;
97
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        match s {
100            "record" => Ok(Self::Record),
101            _ => Err(UnknownUserExperimentalError {
102                feature: s.to_owned(),
103            }),
104        }
105    }
106}
107
108/// Error returned when parsing an unknown experimental feature name.
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct UnknownUserExperimentalError {
111    /// The unknown feature name.
112    pub feature: String,
113}
114
115impl fmt::Display for UnknownUserExperimentalError {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        write!(
118            f,
119            "unknown experimental feature `{}`; known features: {}",
120            self.feature,
121            UserConfigExperimental::all()
122                .iter()
123                .map(|f| f.name())
124                .collect::<Vec<_>>()
125                .join(", ")
126        )
127    }
128}
129
130impl std::error::Error for UnknownUserExperimentalError {}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_from_str() {
138        assert_eq!(
139            "record".parse::<UserConfigExperimental>().unwrap(),
140            UserConfigExperimental::Record
141        );
142
143        assert!("unknown".parse::<UserConfigExperimental>().is_err());
144    }
145
146    #[test]
147    fn test_display() {
148        assert_eq!(UserConfigExperimental::Record.to_string(), "record");
149    }
150
151    #[test]
152    fn test_env_var() {
153        assert_eq!(
154            UserConfigExperimental::Record.env_var(),
155            "NEXTEST_EXPERIMENTAL_RECORD"
156        );
157    }
158}