Skip to main content

uv_preview/
lib.rs

1use std::sync::{Mutex, OnceLock};
2use std::{
3    fmt::{Debug, Display, Formatter},
4    ops::BitOr,
5    str::FromStr,
6};
7
8use enumflags2::{BitFlags, bitflags};
9use thiserror::Error;
10use uv_warnings::warn_user_once;
11
12/// Indicates if the preview state has been finalized yet or not.
13enum PreviewState {
14    Provisional(Preview),
15    Final(Preview),
16}
17
18/// Indicates how the preview was initialised, to distinguish between normal
19/// code and unit tests.
20enum PreviewMode {
21    /// Initialised by a call to [`init`].
22    Normal(Mutex<PreviewState>),
23    /// Initialised by a call to [`test::with_features`].
24    #[cfg(feature = "testing")]
25    Test(std::sync::RwLock<Option<Preview>>),
26}
27
28static PREVIEW: OnceLock<PreviewMode> = OnceLock::new();
29
30/// Error type for global preview state initialization related errors
31#[derive(Debug, Error)]
32pub enum PreviewError {
33    /// Returned when [`set`] or [`finalize`] are called on a finalized state.
34    #[error("The preview configuration has already been finalized")]
35    AlreadyFinalized,
36
37    /// Returned when [`finalize`] is called on an uninitialized state.
38    #[error("The preview configuration has not been initialized yet")]
39    NotInitialized,
40
41    /// Returned when [`set`] or [`finalize`] are called on a test state.
42    #[cfg(feature = "testing")]
43    #[error("The preview configuration is in test mode and {}::{} cannot be used", module_path!(), .0)]
44    InTest(&'static str),
45}
46
47/// Initialize the global preview configuration.
48///
49/// This should be called once at startup with the resolved preview settings.
50pub fn set(preview: Preview) -> Result<(), PreviewError> {
51    let mode = PREVIEW.get_or_init(|| {
52        PreviewMode::Normal(Mutex::new(PreviewState::Provisional(Preview::default())))
53    });
54    match mode {
55        PreviewMode::Normal(mutex) => {
56            // Calling `set` in a test context is already disallowed, so a panic if
57            // the mutex is poisoned is fine.
58            let mut state = mutex.lock().unwrap();
59            match &*state {
60                PreviewState::Provisional(_) => {
61                    *state = PreviewState::Provisional(preview);
62                    Ok(())
63                }
64                PreviewState::Final(_) => Err(PreviewError::AlreadyFinalized),
65            }
66        }
67        #[cfg(feature = "testing")]
68        PreviewMode::Test(_) => Err(PreviewError::InTest("set")),
69    }
70}
71
72pub fn finalize() -> Result<(), PreviewError> {
73    match PREVIEW.get().ok_or(PreviewError::NotInitialized)? {
74        PreviewMode::Normal(mutex) => {
75            // Calling `set` in a test context is already disallowed, so a panic if
76            // the mutex is poisoned is fine.
77            let mut state = mutex.lock().unwrap();
78            match &*state {
79                PreviewState::Provisional(preview) => {
80                    *state = PreviewState::Final(*preview);
81                    Ok(())
82                }
83                PreviewState::Final(_) => Err(PreviewError::AlreadyFinalized),
84            }
85        }
86        #[cfg(feature = "testing")]
87        PreviewMode::Test(_) => Err(PreviewError::InTest("finalize")),
88    }
89}
90
91/// Get the current global preview configuration.
92///
93/// # Panics
94///
95/// When called before [`init`] or (with the `testing` feature) when the
96/// current thread does not hold a [`test::with_features`] guard.
97pub fn get() -> Preview {
98    match PREVIEW.get() {
99        Some(PreviewMode::Normal(mutex)) => match *mutex.lock().unwrap() {
100            PreviewState::Provisional(preview) => preview,
101            PreviewState::Final(preview) => preview,
102        },
103        #[cfg(feature = "testing")]
104        Some(PreviewMode::Test(rwlock)) => {
105            assert!(
106                test::HELD.get(),
107                "The preview configuration is in test mode but the current thread does not hold a `FeaturesGuard`\nHint: Use `{}::test::with_features` to get a `FeaturesGuard` and hold it when testing functions which rely on the global preview state",
108                module_path!()
109            );
110            // The unwrap may panic only if the current thread had panicked
111            // while attempting to write the value and then recovered with
112            // `catch_unwind`. This seems unlikely.
113            rwlock
114                .read()
115                .unwrap()
116                .expect("FeaturesGuard is held but preview value is not set")
117        }
118        #[cfg(feature = "testing")]
119        None => panic!(
120            "The preview configuration has not been initialized\nHint: Use `{}::init` or `{}::test::with_features` to initialize it",
121            module_path!(),
122            module_path!()
123        ),
124        #[cfg(not(feature = "testing"))]
125        None => panic!("The preview configuration has not been initialized"),
126    }
127}
128
129/// Check if a specific preview feature is enabled globally.
130pub fn is_enabled(flag: PreviewFeature) -> bool {
131    get().is_enabled(flag)
132}
133
134/// Functions for unit tests, do not use from normal code!
135#[cfg(feature = "testing")]
136pub mod test {
137    use super::{PREVIEW, Preview, PreviewMode};
138    use std::cell::Cell;
139    use std::sync::{Mutex, MutexGuard, RwLock};
140
141    /// The global preview state test mutex. It does not guard any data but is
142    /// simply used to ensure tests which rely on the global preview state are
143    /// ran serially.
144    static MUTEX: Mutex<()> = Mutex::new(());
145
146    thread_local! {
147        /// Whether the current thread holds the global mutex.
148        ///
149        /// This is used to catch situations where a test forgets to set the
150        /// global test state but happens to work anyway because of another test
151        /// setting the state.
152        pub(crate) static HELD: Cell<bool> = const { Cell::new(false) };
153    }
154
155    /// A scope guard which ensures that the global preview state is configured
156    /// and consistent for the duration of its lifetime.
157    #[derive(Debug)]
158    #[expect(unused)]
159    pub struct FeaturesGuard(MutexGuard<'static, ()>);
160
161    /// Temporarily set the state of preview features for the duration of the
162    /// lifetime of the returned guard.
163    ///
164    /// Calls cannot be nested, and this function must be used to set the global
165    /// preview features when testing functionality which uses it, otherwise
166    /// that functionality will panic.
167    ///
168    /// The preview state will only be valid for the thread which calls this
169    /// function, it will not be valid for any other thread. This is a
170    /// consequence of how `HELD` is used to check for tests which are missing
171    /// the guard.
172    pub fn with_features(features: &[super::PreviewFeature]) -> FeaturesGuard {
173        assert!(
174            !HELD.get(),
175            "Additional calls to `{}::with_features` are not allowed while holding a `FeaturesGuard`",
176            module_path!()
177        );
178
179        let guard = match MUTEX.lock() {
180            Ok(guard) => guard,
181            // This is okay because the mutex isn't guarding any data, so when
182            // it gets poisoned, it just means a test thread died while holding
183            // it, so it's safe to just re-grab it from the PoisonError, there's
184            // no chance of any corruption.
185            Err(err) => err.into_inner(),
186        };
187
188        HELD.set(true);
189
190        let state = PREVIEW.get_or_init(|| PreviewMode::Test(RwLock::new(None)));
191        match state {
192            PreviewMode::Test(rwlock) => {
193                *rwlock.write().unwrap() = Some(Preview::new(features));
194            }
195            PreviewMode::Normal(_) => {
196                panic!(
197                    "Cannot use `{}::with_features` after `uv_preview::init` has been called",
198                    module_path!()
199                );
200            }
201        }
202        FeaturesGuard(guard)
203    }
204
205    impl Drop for FeaturesGuard {
206        fn drop(&mut self) {
207            HELD.set(false);
208
209            match PREVIEW.get().unwrap() {
210                PreviewMode::Test(rwlock) => {
211                    *rwlock.write().unwrap() = None;
212                }
213                PreviewMode::Normal(_) => {
214                    unreachable!("FeaturesGuard should not exist when in Normal mode");
215                }
216            }
217        }
218    }
219}
220
221#[bitflags]
222#[repr(u64)]
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub enum PreviewFeature {
225    PythonInstallDefault = 1 << 0,
226    PythonUpgrade = 1 << 1,
227    JsonOutput = 1 << 2,
228    Pylock = 1 << 3,
229    AddBounds = 1 << 4,
230    PackageConflicts = 1 << 5,
231    ExtraBuildDependencies = 1 << 6,
232    DetectModuleConflicts = 1 << 7,
233    Format = 1 << 8,
234    NativeAuth = 1 << 9,
235    S3Endpoint = 1 << 10,
236    CacheSize = 1 << 11,
237    InitProjectFlag = 1 << 12,
238    WorkspaceMetadata = 1 << 13,
239    WorkspaceDir = 1 << 14,
240    WorkspaceList = 1 << 15,
241    SbomExport = 1 << 16,
242    AuthHelper = 1 << 17,
243    DirectPublish = 1 << 18,
244    TargetWorkspaceDiscovery = 1 << 19,
245    MetadataJson = 1 << 20,
246    GcsEndpoint = 1 << 21,
247    AdjustUlimit = 1 << 22,
248    SpecialCondaEnvNames = 1 << 23,
249    RelocatableEnvsDefault = 1 << 24,
250    PublishRequireNormalized = 1 << 25,
251    Audit = 1 << 26,
252    ProjectDirectoryMustExist = 1 << 27,
253    IndexExcludeNewer = 1 << 28,
254    AzureEndpoint = 1 << 29,
255    TomlBackwardsCompatibility = 1 << 30,
256    MalwareCheck = 1 << 31,
257    VenvSafeClear = 1 << 32,
258}
259
260impl PreviewFeature {
261    /// Returns the string representation of a single preview feature flag.
262    fn as_str(self) -> &'static str {
263        match self {
264            Self::PythonInstallDefault => "python-install-default",
265            Self::PythonUpgrade => "python-upgrade",
266            Self::JsonOutput => "json-output",
267            Self::Pylock => "pylock",
268            Self::AddBounds => "add-bounds",
269            Self::PackageConflicts => "package-conflicts",
270            Self::ExtraBuildDependencies => "extra-build-dependencies",
271            Self::DetectModuleConflicts => "detect-module-conflicts",
272            Self::Format => "format",
273            Self::NativeAuth => "native-auth",
274            Self::S3Endpoint => "s3-endpoint",
275            Self::CacheSize => "cache-size",
276            Self::InitProjectFlag => "init-project-flag",
277            Self::WorkspaceMetadata => "workspace-metadata",
278            Self::WorkspaceDir => "workspace-dir",
279            Self::WorkspaceList => "workspace-list",
280            Self::SbomExport => "sbom-export",
281            Self::AuthHelper => "auth-helper",
282            Self::DirectPublish => "direct-publish",
283            Self::TargetWorkspaceDiscovery => "target-workspace-discovery",
284            Self::MetadataJson => "metadata-json",
285            Self::GcsEndpoint => "gcs-endpoint",
286            Self::AdjustUlimit => "adjust-ulimit",
287            Self::SpecialCondaEnvNames => "special-conda-env-names",
288            Self::RelocatableEnvsDefault => "relocatable-envs-default",
289            Self::PublishRequireNormalized => "publish-require-normalized",
290            Self::Audit => "audit",
291            Self::ProjectDirectoryMustExist => "project-directory-must-exist",
292            Self::IndexExcludeNewer => "index-exclude-newer",
293            Self::AzureEndpoint => "azure-endpoint",
294            Self::TomlBackwardsCompatibility => "toml-backwards-compatibility",
295            Self::MalwareCheck => "malware-check",
296            Self::VenvSafeClear => "venv-safe-clear",
297        }
298    }
299}
300
301impl Display for PreviewFeature {
302    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
303        write!(f, "{}", self.as_str())
304    }
305}
306
307#[derive(Debug, Error, Clone)]
308#[error("Unknown feature flag")]
309pub struct PreviewFeatureParseError;
310
311impl FromStr for PreviewFeature {
312    type Err = PreviewFeatureParseError;
313
314    fn from_str(s: &str) -> Result<Self, Self::Err> {
315        Ok(match s {
316            "python-install-default" => Self::PythonInstallDefault,
317            "python-upgrade" => Self::PythonUpgrade,
318            "json-output" => Self::JsonOutput,
319            "pylock" => Self::Pylock,
320            "add-bounds" => Self::AddBounds,
321            "package-conflicts" => Self::PackageConflicts,
322            "extra-build-dependencies" => Self::ExtraBuildDependencies,
323            "detect-module-conflicts" => Self::DetectModuleConflicts,
324            "format" => Self::Format,
325            "native-auth" => Self::NativeAuth,
326            "s3-endpoint" => Self::S3Endpoint,
327            "gcs-endpoint" => Self::GcsEndpoint,
328            "cache-size" => Self::CacheSize,
329            "init-project-flag" => Self::InitProjectFlag,
330            "workspace-metadata" => Self::WorkspaceMetadata,
331            "workspace-dir" => Self::WorkspaceDir,
332            "workspace-list" => Self::WorkspaceList,
333            "sbom-export" => Self::SbomExport,
334            "auth-helper" => Self::AuthHelper,
335            "direct-publish" => Self::DirectPublish,
336            "target-workspace-discovery" => Self::TargetWorkspaceDiscovery,
337            "metadata-json" => Self::MetadataJson,
338            "adjust-ulimit" => Self::AdjustUlimit,
339            "special-conda-env-names" => Self::SpecialCondaEnvNames,
340            "relocatable-envs-default" => Self::RelocatableEnvsDefault,
341            "publish-require-normalized" => Self::PublishRequireNormalized,
342            "audit" => Self::Audit,
343            "project-directory-must-exist" => Self::ProjectDirectoryMustExist,
344            "index-exclude-newer" => Self::IndexExcludeNewer,
345            "azure-endpoint" => Self::AzureEndpoint,
346            "toml-backwards-compatibility" => Self::TomlBackwardsCompatibility,
347            "malware-check" => Self::MalwareCheck,
348            "venv-safe-clear" => Self::VenvSafeClear,
349            _ => return Err(PreviewFeatureParseError),
350        })
351    }
352}
353
354#[derive(Clone, Copy, PartialEq, Eq, Default)]
355pub struct Preview {
356    flags: BitFlags<PreviewFeature>,
357}
358
359impl Debug for Preview {
360    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
361        let flags: Vec<_> = self.flags.iter().collect();
362        f.debug_struct("Preview").field("flags", &flags).finish()
363    }
364}
365
366impl Preview {
367    pub fn new(flags: &[PreviewFeature]) -> Self {
368        Self {
369            flags: flags.iter().copied().fold(BitFlags::empty(), BitOr::bitor),
370        }
371    }
372
373    pub fn all() -> Self {
374        Self {
375            flags: BitFlags::all(),
376        }
377    }
378
379    pub fn from_args(preview: bool, no_preview: bool, preview_features: &[PreviewFeature]) -> Self {
380        if no_preview {
381            return Self::default();
382        }
383
384        if preview {
385            return Self::all();
386        }
387
388        Self::new(preview_features)
389    }
390
391    /// Check if a single feature is enabled.
392    pub fn is_enabled(&self, flag: PreviewFeature) -> bool {
393        self.flags.contains(flag)
394    }
395
396    /// Check if all preview feature rae enabled.
397    pub fn all_enabled(&self) -> bool {
398        self.flags.is_all()
399    }
400
401    /// Check if any preview feature is enabled.
402    pub fn any_enabled(&self) -> bool {
403        !self.flags.is_empty()
404    }
405}
406
407impl Display for Preview {
408    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
409        if self.flags.is_empty() {
410            write!(f, "disabled")
411        } else if self.flags.is_all() {
412            write!(f, "enabled")
413        } else {
414            write!(
415                f,
416                "{}",
417                itertools::join(self.flags.iter().map(PreviewFeature::as_str), ",")
418            )
419        }
420    }
421}
422
423#[derive(Debug, Error, Clone)]
424pub enum PreviewParseError {
425    #[error("Empty string in preview features: {0}")]
426    Empty(String),
427}
428
429impl FromStr for Preview {
430    type Err = PreviewParseError;
431
432    fn from_str(s: &str) -> Result<Self, Self::Err> {
433        let mut flags = BitFlags::empty();
434
435        for part in s.split(',') {
436            let part = part.trim();
437            if part.is_empty() {
438                return Err(PreviewParseError::Empty(
439                    "Empty string in preview features".to_string(),
440                ));
441            }
442
443            match PreviewFeature::from_str(part) {
444                Ok(flag) => flags |= flag,
445                Err(_) => {
446                    warn_user_once!("Unknown preview feature: `{part}`");
447                }
448            }
449        }
450
451        Ok(Self { flags })
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_preview_feature_from_str() {
461        let features = PreviewFeature::from_str("python-install-default").unwrap();
462        assert_eq!(features, PreviewFeature::PythonInstallDefault);
463    }
464
465    #[test]
466    fn test_preview_from_str() {
467        // Test single feature
468        let preview = Preview::from_str("python-install-default").unwrap();
469        assert_eq!(preview.flags, PreviewFeature::PythonInstallDefault);
470
471        // Test multiple features
472        let preview = Preview::from_str("python-upgrade,json-output").unwrap();
473        assert!(preview.is_enabled(PreviewFeature::PythonUpgrade));
474        assert!(preview.is_enabled(PreviewFeature::JsonOutput));
475        assert_eq!(preview.flags.bits().count_ones(), 2);
476
477        // Test with whitespace
478        let preview = Preview::from_str("pylock , add-bounds").unwrap();
479        assert!(preview.is_enabled(PreviewFeature::Pylock));
480        assert!(preview.is_enabled(PreviewFeature::AddBounds));
481
482        // Test empty string error
483        assert!(Preview::from_str("").is_err());
484        assert!(Preview::from_str("pylock,").is_err());
485        assert!(Preview::from_str(",pylock").is_err());
486
487        // Test unknown feature (should be ignored with warning)
488        let preview = Preview::from_str("unknown-feature,pylock").unwrap();
489        assert!(preview.is_enabled(PreviewFeature::Pylock));
490        assert_eq!(preview.flags.bits().count_ones(), 1);
491    }
492
493    #[test]
494    fn test_preview_display() {
495        // Test disabled
496        let preview = Preview::default();
497        assert_eq!(preview.to_string(), "disabled");
498        let preview = Preview::new(&[]);
499        assert_eq!(preview.to_string(), "disabled");
500
501        // Test enabled (all features)
502        let preview = Preview::all();
503        assert_eq!(preview.to_string(), "enabled");
504
505        // Test single feature
506        let preview = Preview::new(&[PreviewFeature::PythonInstallDefault]);
507        assert_eq!(preview.to_string(), "python-install-default");
508
509        // Test multiple features
510        let preview = Preview::new(&[PreviewFeature::PythonUpgrade, PreviewFeature::Pylock]);
511        assert_eq!(preview.to_string(), "python-upgrade,pylock");
512    }
513
514    #[test]
515    fn test_preview_from_args() {
516        // Test no preview and no no_preview, and no features
517        let preview = Preview::from_args(false, false, &[]);
518        assert_eq!(preview.to_string(), "disabled");
519
520        // Test no_preview
521        let preview = Preview::from_args(true, true, &[]);
522        assert_eq!(preview.to_string(), "disabled");
523
524        // Test preview (all features)
525        let preview = Preview::from_args(true, false, &[]);
526        assert_eq!(preview.to_string(), "enabled");
527
528        // Test specific features
529        let features = vec![PreviewFeature::PythonUpgrade, PreviewFeature::JsonOutput];
530        let preview = Preview::from_args(false, false, &features);
531        assert!(preview.is_enabled(PreviewFeature::PythonUpgrade));
532        assert!(preview.is_enabled(PreviewFeature::JsonOutput));
533        assert!(!preview.is_enabled(PreviewFeature::Pylock));
534    }
535
536    #[test]
537    fn test_preview_feature_as_str() {
538        assert_eq!(
539            PreviewFeature::PythonInstallDefault.as_str(),
540            "python-install-default"
541        );
542        assert_eq!(PreviewFeature::PythonUpgrade.as_str(), "python-upgrade");
543        assert_eq!(PreviewFeature::JsonOutput.as_str(), "json-output");
544        assert_eq!(PreviewFeature::Pylock.as_str(), "pylock");
545        assert_eq!(PreviewFeature::AddBounds.as_str(), "add-bounds");
546        assert_eq!(
547            PreviewFeature::PackageConflicts.as_str(),
548            "package-conflicts"
549        );
550        assert_eq!(
551            PreviewFeature::ExtraBuildDependencies.as_str(),
552            "extra-build-dependencies"
553        );
554        assert_eq!(
555            PreviewFeature::DetectModuleConflicts.as_str(),
556            "detect-module-conflicts"
557        );
558        assert_eq!(PreviewFeature::Format.as_str(), "format");
559        assert_eq!(PreviewFeature::NativeAuth.as_str(), "native-auth");
560        assert_eq!(PreviewFeature::S3Endpoint.as_str(), "s3-endpoint");
561        assert_eq!(PreviewFeature::CacheSize.as_str(), "cache-size");
562        assert_eq!(
563            PreviewFeature::InitProjectFlag.as_str(),
564            "init-project-flag"
565        );
566        assert_eq!(
567            PreviewFeature::WorkspaceMetadata.as_str(),
568            "workspace-metadata"
569        );
570        assert_eq!(PreviewFeature::WorkspaceDir.as_str(), "workspace-dir");
571        assert_eq!(PreviewFeature::WorkspaceList.as_str(), "workspace-list");
572        assert_eq!(PreviewFeature::SbomExport.as_str(), "sbom-export");
573        assert_eq!(PreviewFeature::AuthHelper.as_str(), "auth-helper");
574        assert_eq!(PreviewFeature::DirectPublish.as_str(), "direct-publish");
575        assert_eq!(
576            PreviewFeature::TargetWorkspaceDiscovery.as_str(),
577            "target-workspace-discovery"
578        );
579        assert_eq!(PreviewFeature::MetadataJson.as_str(), "metadata-json");
580        assert_eq!(PreviewFeature::GcsEndpoint.as_str(), "gcs-endpoint");
581        assert_eq!(PreviewFeature::AdjustUlimit.as_str(), "adjust-ulimit");
582        assert_eq!(
583            PreviewFeature::SpecialCondaEnvNames.as_str(),
584            "special-conda-env-names"
585        );
586        assert_eq!(
587            PreviewFeature::RelocatableEnvsDefault.as_str(),
588            "relocatable-envs-default"
589        );
590        assert_eq!(
591            PreviewFeature::PublishRequireNormalized.as_str(),
592            "publish-require-normalized"
593        );
594        assert_eq!(
595            PreviewFeature::ProjectDirectoryMustExist.as_str(),
596            "project-directory-must-exist"
597        );
598        assert_eq!(
599            PreviewFeature::IndexExcludeNewer.as_str(),
600            "index-exclude-newer"
601        );
602        assert_eq!(PreviewFeature::AzureEndpoint.as_str(), "azure-endpoint");
603        assert_eq!(
604            PreviewFeature::TomlBackwardsCompatibility.as_str(),
605            "toml-backwards-compatibility"
606        );
607        assert_eq!(PreviewFeature::MalwareCheck.as_str(), "malware-check");
608        assert_eq!(PreviewFeature::VenvSafeClear.as_str(), "venv-safe-clear");
609    }
610
611    #[test]
612    fn test_global_preview() {
613        {
614            let _guard =
615                test::with_features(&[PreviewFeature::Pylock, PreviewFeature::WorkspaceMetadata]);
616            assert!(!is_enabled(PreviewFeature::InitProjectFlag));
617            assert!(is_enabled(PreviewFeature::Pylock));
618            assert!(is_enabled(PreviewFeature::WorkspaceMetadata));
619            assert!(!is_enabled(PreviewFeature::AuthHelper));
620        }
621        {
622            let _guard =
623                test::with_features(&[PreviewFeature::InitProjectFlag, PreviewFeature::AuthHelper]);
624            assert!(is_enabled(PreviewFeature::InitProjectFlag));
625            assert!(!is_enabled(PreviewFeature::Pylock));
626            assert!(!is_enabled(PreviewFeature::WorkspaceMetadata));
627            assert!(is_enabled(PreviewFeature::AuthHelper));
628        }
629    }
630
631    #[test]
632    #[should_panic(
633        expected = "Additional calls to `uv_preview::test::with_features` are not allowed while holding a `FeaturesGuard`"
634    )]
635    fn test_global_preview_panic_nested() {
636        let _guard =
637            test::with_features(&[PreviewFeature::Pylock, PreviewFeature::WorkspaceMetadata]);
638        let _guard2 =
639            test::with_features(&[PreviewFeature::InitProjectFlag, PreviewFeature::AuthHelper]);
640    }
641
642    #[test]
643    #[should_panic(expected = "uv_preview::test::with_features")]
644    fn test_global_preview_panic_uninitialized() {
645        let _preview = get();
646    }
647}