use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(feature = "std")]
pub(super) static BACKTRACE_ENABLED: TraceConfig = TraceConfig::new(
&["OCKAM_BACKTRACE", "RUST_LIB_BACKTRACE", "RUST_BACKTRACE"],
false,
);
#[cfg(feature = "std")]
pub(super) static SPANTRACE_ENABLED: TraceConfig = TraceConfig::new(
&[
"OCKAM_SPANTRACE",
"OCKAM_BACKTRACE",
"RUST_LIB_BACKTRACE",
"RUST_BACKTRACE",
],
true,
);
pub(super) struct TraceConfig {
state: AtomicU8,
vars: &'static [&'static str],
default: bool,
}
impl TraceConfig {
pub const fn new(vars: &'static [&'static str], default: bool) -> Self {
Self {
state: AtomicU8::new(encode_opt_bool(None)),
vars,
default,
}
}
pub(super) fn get(&self) -> bool {
are_traces_enabled(&self.state, || {
check_env_vars(self.vars).unwrap_or(self.default)
})
}
}
fn check_env_vars(vars: &[&str]) -> Option<bool> {
vars.iter()
.copied()
.find_map(std::env::var_os)
.map(|val| val != "0")
}
fn are_traces_enabled(state: &AtomicU8, read_env: impl FnOnce() -> bool) -> bool {
match decode_opt_bool(state.load(Ordering::Relaxed)) {
Some(b) => b,
None => {
let enabled = read_env();
state.store(encode_opt_bool(Some(enabled)), Ordering::Relaxed);
enabled
}
}
}
fn decode_opt_bool(n: u8) -> Option<bool> {
match n {
0 => None,
n => Some((n - 1) != 0),
}
}
const fn encode_opt_bool(u: Option<bool>) -> u8 {
match u {
None => 0,
Some(b) => b as u8 + 1,
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ffi::{OsStr, OsString};
#[test]
fn test_bool_enc() {
assert_eq!(decode_opt_bool(encode_opt_bool(None)), None);
assert_eq!(decode_opt_bool(encode_opt_bool(Some(false))), Some(false));
assert_eq!(decode_opt_bool(encode_opt_bool(Some(true))), Some(true));
assert_eq!(encode_opt_bool(decode_opt_bool(0)), 0);
assert_eq!(encode_opt_bool(decode_opt_bool(1)), 1);
assert_eq!(encode_opt_bool(decode_opt_bool(2)), 2);
}
struct AutoVar {
name: String,
val: Option<OsString>,
}
impl AutoVar {
fn new(name: impl Into<String>) -> Self {
let name = name.into();
let val = std::env::var_os(&name);
Self { name, val }
}
}
impl Drop for AutoVar {
fn drop(&mut self) {
force_var(&self.name, self.val.as_deref());
}
}
fn force_var<V: AsRef<OsStr>>(name: &str, value: Option<V>) {
match value {
None => std::env::remove_var(name),
Some(v) => std::env::set_var(name, v),
}
}
#[test]
fn test_env() {
#[track_caller]
fn testcase(
ockam_bt: Option<&'static str>,
rust_lib_bt: Option<&'static str>,
rust_bt: Option<&'static str>,
want: bool,
) {
let _guards = [
AutoVar::new("OCKAM_BACKTRACE__FAKE_VAR_FOR_TESTS"),
AutoVar::new("RUST_LIB_BACKTRACE__FAKE_VAR_FOR_TESTS"),
AutoVar::new("RUST_BACKTRACE__FAKE_VAR_FOR_TESTS"),
];
force_var("OCKAM_BACKTRACE__FAKE_VAR_FOR_TESTS", ockam_bt);
force_var("RUST_LIB_BACKTRACE__FAKE_VAR_FOR_TESTS", rust_lib_bt);
force_var("RUST_BACKTRACE__FAKE_VAR_FOR_TESTS", rust_bt);
let got = check_env_vars(&[
"OCKAM_BACKTRACE__FAKE_VAR_FOR_TESTS",
"RUST_LIB_BACKTRACE__FAKE_VAR_FOR_TESTS",
"RUST_BACKTRACE__FAKE_VAR_FOR_TESTS",
]);
assert_eq!(
got.unwrap_or(false),
want,
"ockam: {:?}, rust_lib: {:?}, rust: {:?}, interpreted as {:?}",
ockam_bt,
rust_lib_bt,
rust_bt,
got,
);
}
testcase(None, None, None, false);
for filler in [None, Some(""), Some("1"), Some("0")] {
testcase(Some(""), filler, filler, true);
testcase(None, Some(""), filler, true);
testcase(None, None, Some(""), true);
testcase(Some("0"), filler, filler, false);
testcase(None, Some("0"), filler, false);
testcase(None, None, Some("0"), false);
}
}
}