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