Skip to main content

cranpose_render_common/
debug_toggles.rs

1use std::{
2    collections::BTreeMap,
3    ffi::{OsStr, OsString},
4    sync::{Mutex, OnceLock, PoisonError},
5};
6
7fn overrides() -> &'static Mutex<BTreeMap<&'static str, OsString>> {
8    static OVERRIDES: OnceLock<Mutex<BTreeMap<&'static str, OsString>>> = OnceLock::new();
9    OVERRIDES.get_or_init(|| Mutex::new(BTreeMap::new()))
10}
11
12/// The value of a debug toggle: a test override if one is set, else the
13/// process environment. Toggles are read where they act, every time, so a
14/// parity comparison can flip them mid-process.
15#[doc(hidden)]
16pub fn debug_toggle(name: &'static str) -> Option<String> {
17    let map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
18    if let Some(value) = map.get(name) {
19        return value.to_str().map(str::to_owned);
20    }
21    drop(map);
22    std::env::var(name).ok()
23}
24
25/// As [`debug_toggle`], for settings whose values are paths: overrides and
26/// the environment fallback keep their platform encoding instead of
27/// demanding UTF-8.
28#[doc(hidden)]
29pub fn debug_toggle_os(name: &'static str) -> Option<OsString> {
30    let map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
31    if let Some(value) = map.get(name) {
32        return Some(value.clone());
33    }
34    drop(map);
35    std::env::var_os(name)
36}
37
38/// Test-side control of a debug toggle without mutating the process
39/// environment; `None` clears the override.
40#[doc(hidden)]
41pub fn set_debug_toggle(name: &'static str, value: Option<&str>) {
42    set_debug_toggle_os(name, value.map(OsStr::new));
43}
44
45/// As [`set_debug_toggle`], for path-valued settings whose bytes must
46/// survive in their platform encoding.
47#[doc(hidden)]
48pub fn set_debug_toggle_os(name: &'static str, value: Option<&OsStr>) {
49    let mut map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
50    match value {
51        Some(value) => map.insert(name, value.to_owned()),
52        None => map.remove(name),
53    };
54}
55
56#[cfg(all(test, unix))]
57mod tests {
58    use std::os::unix::ffi::OsStrExt;
59
60    use super::*;
61
62    #[test]
63    fn a_non_utf8_path_override_survives_byte_for_byte() {
64        let raw = OsStr::from_bytes(b"/cranpose/\xff\xfe/cache.bin");
65        set_debug_toggle_os("CRANPOSE_TOGGLE_OS_TEST", Some(raw));
66        assert_eq!(
67            debug_toggle_os("CRANPOSE_TOGGLE_OS_TEST").as_deref(),
68            Some(raw)
69        );
70        assert_eq!(debug_toggle("CRANPOSE_TOGGLE_OS_TEST"), None);
71        set_debug_toggle_os("CRANPOSE_TOGGLE_OS_TEST", None);
72        assert_eq!(debug_toggle_os("CRANPOSE_TOGGLE_OS_TEST"), None);
73    }
74}