hyperactor/config/
global.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Global layered configuration for Hyperactor.
10//!
11//! This module provides the process-wide configuration store and APIs
12//! to access it. Configuration values are resolved via a **layered
13//! model**: `TestOverride → Runtime → Env → File → Default`.
14//!
15//! - Reads (`get`, `get_cloned`) consult layers in that order, falling
16//!   back to defaults if no explicit value is set.
17//! - `attrs()` returns a complete snapshot of all CONFIG-marked keys at
18//!   call time: it materializes defaults for keys not set in any layer.
19//!   Keys without @meta(CONFIG = …) are excluded.
20//! - In tests, `lock()` and `override_key` allow temporary overrides
21//!   that are removed automatically when the guard drops.
22//! - In normal operation, a parent process can capture its effective
23//!   config via `attrs()` and pass that snapshot to a child during
24//!   bootstrap. The child installs it as a `Runtime` layer so the
25//!   parent's values take precedence over Env/File/Defaults.
26//!
27//! This design provides flexibility (easy test overrides, runtime
28//! updates, YAML/Env baselines) while ensuring type safety and
29//! predictable resolution order.
30//!
31//!
32//! # Testing
33//!
34//! Tests can override global configuration using [`lock`]. This
35//! ensures such tests are serialized (and cannot clobber each other's
36//! overrides).
37//!
38//! ```ignore
39//! #[test]
40//! fn test_my_feature() {
41//!     let config = hyperactor::config::global::lock();
42//!     let _guard = config.override_key(SOME_CONFIG_KEY, test_value);
43//!     // ... test logic here ...
44//! }
45//! ```
46use std::collections::HashMap;
47use std::marker::PhantomData;
48use std::sync::atomic::AtomicU64;
49use std::sync::atomic::Ordering;
50
51use super::*;
52use crate::attrs::AttrValue;
53use crate::attrs::Key;
54use crate::config::CONFIG;
55
56/// Configuration source layers in priority order.
57///
58/// Resolution order is always: **TestOverride -> Runtime -> Env
59/// -> File -> ClientOverride -> Default**.
60///
61/// Smaller `priority()` number = higher precedence.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63pub enum Source {
64    /// Values set by the config snapshot sent from the client
65    /// during proc bootstrap.
66    ClientOverride,
67    /// Values loaded from configuration files (e.g., YAML).
68    File,
69    /// Values read from environment variables at process startup.
70    /// Higher priority than File and ClientOverride, but lower than
71    /// Runtime/TestOverride.
72    Env,
73    /// Values set programmatically at runtime. Highest stable
74    /// priority layer; only overridden by TestOverride.
75    Runtime,
76    /// Ephemeral values inserted by tests via
77    /// `ConfigLock::override_key`. Always wins over all other
78    /// sources; removed when the guard drops.
79    TestOverride,
80}
81
82/// Return the numeric priority for a source.
83///
84/// Smaller number = higher precedence. Matches the documented
85/// order: TestOverride (0) -> Runtime (1) -> Env (2) -> File (3) -> ClientOverride (4).
86fn priority(s: Source) -> u8 {
87    match s {
88        Source::TestOverride => 0,
89        Source::Runtime => 1,
90        Source::Env => 2,
91        Source::File => 3,
92        Source::ClientOverride => 4,
93    }
94}
95
96/// The full set of configuration layers in priority order.
97///
98/// `Layers` wraps a vector of [`Layer`]s, always kept sorted by
99/// [`priority`] (lowest number = highest precedence).
100///
101/// Resolution (`get`, `get_cloned`, `attrs`) consults `ordered`
102/// from front to back, returning the first value found for each
103/// key and falling back to defaults if none are set in any layer.
104struct Layers {
105    /// Kept sorted by `priority` (lowest number first = highest
106    /// priority).
107    ordered: Vec<Layer>,
108}
109
110/// A single configuration layer in the global configuration model.
111///
112/// Layers are consulted in priority order (`TestOverride → Runtime →
113/// Env → File → Default`) when resolving configuration values. Each
114/// variant holds an [`Attrs`] map of key/value pairs.
115///
116/// The `TestOverride` variant additionally maintains per-key override
117/// stacks to support nested and out-of-order test overrides. These
118/// stacks are currently placeholders for a future refactor; for now,
119/// only the `attrs` field is used.
120///
121/// Variants:
122/// - [`Layer::ClientOverride`] - Values set by the config snapshot sent from the client
123///   during proc bootstrap.
124/// - [`Layer::File`] — Values loaded from configuration files.
125/// - [`Layer::Env`] — Values sourced from process environment
126///   variables.
127/// - [`Layer::Runtime`] — Programmatically set runtime overrides.
128/// - [`Layer::TestOverride`] — Temporary in-test overrides applied
129///   under [`ConfigLock`].
130///
131/// Layers are stored in [`Layers::ordered`], kept sorted by their
132/// effective [`Source`] priority (`TestOverride` first, `Default` last).
133enum Layer {
134    /// Values set by the config snapshot sent from the client
135    /// during proc bootstrap.
136    ClientOverride(Attrs),
137
138    /// Values loaded from configuration files.
139    File(Attrs),
140
141    /// Values read from process environment variables. Typically
142    /// installed at startup via [`init_from_env`].
143    Env(Attrs),
144
145    /// Values set programmatically at runtime. Stable high-priority
146    /// layer used by parent/child bootstrap and dynamic updates.
147    Runtime(Attrs),
148
149    /// Ephemeral values inserted during tests via
150    /// [`ConfigLock::override_key`]. Always takes precedence over all
151    /// other layers. Currently holds both the active `attrs` map and
152    /// a per-key `stacks` table (used to support nested or
153    /// out-of-order test overrides in future refactors).
154    TestOverride {
155        attrs: Attrs,
156        stacks: HashMap<&'static str, OverrideStack>,
157    },
158}
159
160/// A per-key stack of test overrides used by the
161/// [`Layer::TestOverride`] layer.
162///
163/// Each stack tracks the sequence of active overrides applied to a
164/// single configuration key. The topmost frame represents the
165/// currently effective override; earlier frames represent older
166/// (still live) guards that may drop out of order.
167///
168/// Fields:
169/// - `env_var`: The associated process environment variable name, if
170///   any.
171/// - `saved_env`: The original environment variable value before the
172///   first override was applied (or `None` if it did not exist).
173/// - `frames`: The stack of active override frames, with the top
174///   being the last element in the vector.
175///
176/// The full stack mechanism is not yet active; it is introduced
177/// incrementally to prepare for robust out-of-order test override
178/// restoration.
179struct OverrideStack {
180    /// The name of the process environment variable associated with
181    /// this configuration key, if any.
182    ///
183    /// Used to mirror changes to the environment when overrides are
184    /// applied or removed. `None` if the key has no
185    /// `CONFIG.env_name`.
186    env_var: Option<String>,
187
188    /// The original value of the environment variable before the
189    /// first override was applied.
190    ///
191    /// Stored so it can be restored once the last frame is dropped.
192    /// `None` means the variable did not exist prior to overriding.
193    saved_env: Option<String>,
194
195    /// The sequence of active override frames for this key.
196    ///
197    /// Each frame represents one active test override; the last
198    /// element (`frames.last()`) is the current top-of-stack and
199    /// defines the effective value seen in the configuration and
200    /// environment.
201    frames: Vec<OverrideFrame>,
202}
203
204/// A single entry in a per-key override stack.
205///
206/// Each `OverrideFrame` represents one active test override applied
207/// via [`ConfigLock::override_key`]. Frames are uniquely identified
208/// by a monotonically increasing token and record both the value
209/// being overridden and its string form for environment mirroring.
210///
211/// When a guard drops, its frame is removed from the stack; if it was
212/// the top, the next frame (if any) becomes active, or the original
213/// environment is restored when the stack becomes empty.
214struct OverrideFrame {
215    /// A unique, monotonically increasing identifier for this
216    /// override frame.
217    ///
218    /// Used to associate a dropping [`ConfigValueGuard`] with its
219    /// corresponding entry in the stack, even if drops occur out of
220    /// order.
221    token: u64,
222
223    /// The serialized configuration value active while this frame is
224    /// on top of its stack.
225    ///
226    /// Stored as a boxed [`SerializableValue`] to match how values
227    /// are kept within [`Attrs`].
228    value: Box<dyn crate::attrs::SerializableValue>,
229
230    /// Pre-rendered string form of the value, used for environment
231    /// variable updates when this frame becomes active.
232    ///
233    /// Avoids recomputing `value.display()` on every push or pop.
234    env_str: String,
235}
236
237/// Return the [`Source`] corresponding to a given [`Layer`].
238///
239/// This provides a uniform way to retrieve a layer's logical source
240/// (File, Env, Runtime, or TestOverride) regardless of its internal
241/// representation. Used for sorting layers by priority and for
242/// source-based lookups or removals.
243fn layer_source(l: &Layer) -> Source {
244    match l {
245        Layer::File(_) => Source::File,
246        Layer::Env(_) => Source::Env,
247        Layer::Runtime(_) => Source::Runtime,
248        Layer::TestOverride { .. } => Source::TestOverride,
249        Layer::ClientOverride(_) => Source::ClientOverride,
250    }
251}
252
253/// Return an immutable reference to the [`Attrs`] contained in a
254/// [`Layer`].
255///
256/// This abstracts over the specific layer variant so callers can read
257/// configuration values uniformly without needing to pattern-match on
258/// the layer type. For `TestOverride`, this returns the current
259/// top-level attributes reflecting the active overrides.
260fn layer_attrs(l: &Layer) -> &Attrs {
261    match l {
262        Layer::File(a) | Layer::Env(a) | Layer::Runtime(a) | Layer::ClientOverride(a) => a,
263        Layer::TestOverride { attrs, .. } => attrs,
264    }
265}
266
267/// Return a mutable reference to the [`Attrs`] contained in a
268/// [`Layer`].
269///
270/// This allows callers to modify configuration values within any
271/// layer without needing to pattern-match on its variant. For
272/// `TestOverride`, the returned [`Attrs`] always reflect the current
273/// top-of-stack overrides for each key.
274fn layer_attrs_mut(l: &mut Layer) -> &mut Attrs {
275    match l {
276        Layer::File(a) | Layer::Env(a) | Layer::Runtime(a) | Layer::ClientOverride(a) => a,
277        Layer::TestOverride { attrs, .. } => attrs,
278    }
279}
280
281/// Return the index of the [`Layer::TestOverride`] within the
282/// [`Layers`] vector.
283///
284/// If a TestOverride layer is present, its position in the ordered
285/// list is returned; otherwise, `None` is returned. This is used to
286/// locate the active test override layer for inserting or restoring
287/// temporary configuration values.
288fn test_override_index(layers: &Layers) -> Option<usize> {
289    layers
290        .ordered
291        .iter()
292        .position(|l| matches!(l, Layer::TestOverride { .. }))
293}
294
295/// Global layered configuration store.
296///
297/// This is the single authoritative store for configuration in
298/// the process. It is always present, protected by an `RwLock`,
299/// and holds a [`Layers`] struct containing all active sources.
300///
301/// On startup it is seeded with a single [`Source::Env`] layer
302/// (values loaded from process environment variables). Additional
303/// layers can be installed later via [`set`] or cleared with
304/// [`clear`]. Reads (`get`, `get_cloned`, `attrs`) consult the
305/// layers in priority order.
306///
307/// In tests, a [`Source::TestOverride`] layer is pushed on demand
308/// by [`ConfigLock::override_key`]. This layer always takes
309/// precedence and is automatically removed when the guard drops.
310///
311/// In normal operation, a parent process may capture its config
312/// with [`attrs`] and pass it to a child during bootstrap. The
313/// child installs this snapshot as its [`Source::Runtime`] layer,
314/// ensuring the parent's values override Env/File/Defaults.
315static LAYERS: LazyLock<Arc<RwLock<Layers>>> = LazyLock::new(|| {
316    let env = super::from_env();
317    let layers = Layers {
318        ordered: vec![Layer::Env(env)],
319    };
320    Arc::new(RwLock::new(layers))
321});
322
323/// Monotonically increasing sequence used to assign unique tokens to
324/// each test override frame.
325///
326/// Tokens identify individual [`ConfigValueGuard`] instances within a
327/// key's override stack, allowing frames to be removed safely even
328/// when guards are dropped out of order. The counter starts at 1 and
329/// uses relaxed atomic ordering since exact sequencing across threads
330/// is not required—only uniqueness.
331static OVERRIDE_TOKEN_SEQ: AtomicU64 = AtomicU64::new(1);
332
333/// Acquire the global configuration lock.
334///
335/// This lock serializes all mutations of the global
336/// configuration, ensuring they cannot clobber each other. It
337/// returns a [`ConfigLock`] guard, which must be held for the
338/// duration of any mutation (e.g. inserting or overriding
339/// values).
340///
341/// Most commonly used in tests, where it provides exclusive
342/// access to push a [`Source::TestOverride`] layer via
343/// [`ConfigLock::override_key`]. The override layer is
344/// automatically removed when the guard drops, restoring the
345/// original state.
346///
347/// # Example
348/// ```rust,ignore
349/// let lock = hyperactor::config::global::lock();
350/// let _guard = lock.override_key(CONFIG_KEY, "test_value");
351/// // Code under test sees the overridden config.
352/// // On drop, the key is restored.
353/// ```
354pub fn lock() -> ConfigLock {
355    static MUTEX: LazyLock<std::sync::Mutex<()>> = LazyLock::new(|| std::sync::Mutex::new(()));
356    ConfigLock {
357        _guard: MUTEX.lock().unwrap(),
358    }
359}
360
361/// Initialize the global configuration from environment variables.
362///
363/// Reads values from process environment variables, using each key's
364/// `CONFIG.env_name` (from `@meta(CONFIG = ConfigAttr { … })`) to
365/// determine its mapping. The resulting values are installed as the
366/// [`Source::Env`] layer. Keys without a corresponding environment
367/// variable fall back to defaults or higher-priority sources.
368///
369/// Typically invoked once at process startup to overlay config values
370/// from the environment. Repeated calls replace the existing Env
371/// layer.
372pub fn init_from_env() {
373    set(Source::Env, super::from_env());
374}
375
376/// Initialize the global configuration from a YAML file.
377///
378/// Loads values from the specified YAML file and installs them as
379/// the [`Source::File`] layer. This is the lowest-priority
380/// explicit source: values from Env, Runtime, or TestOverride
381/// layers always take precedence. Keys not present in the file
382/// fall back to their defaults or higher-priority sources.
383///
384/// Typically invoked once at process startup to provide a
385/// baseline configuration. Repeated calls replace the existing
386/// File layer.
387pub fn init_from_yaml<P: AsRef<Path>>(path: P) -> Result<(), anyhow::Error> {
388    let file = super::from_yaml(path)?;
389    set(Source::File, file);
390    Ok(())
391}
392
393/// Get a key from the global configuration (Copy types).
394///
395/// Resolution order: TestOverride -> Runtime -> Env -> File ->
396/// Default. Panics if the key has no default and is not set in
397/// any layer.
398pub fn get<T: AttrValue + Copy>(key: Key<T>) -> T {
399    let layers = LAYERS.read().unwrap();
400    for layer in &layers.ordered {
401        let a = layer_attrs(layer);
402        if let Some(value) = a.get_value_by_name(key.name()) {
403            let t = value.as_any().downcast_ref::<T>().unwrap_or_else(|| {
404                panic!(
405                    "cannot cast to type {} for key {}",
406                    std::any::type_name::<T>(),
407                    key.name()
408                )
409            });
410            return *t;
411        }
412    }
413    *key.default().expect("key must have a default")
414}
415
416/// Return the override value for `key` if it is explicitly present in
417/// `overrides`, otherwise fall back to the global value for that key.
418pub fn override_or_global<T: AttrValue + Copy>(overrides: &Attrs, key: Key<T>) -> T {
419    if overrides.contains_key(key) {
420        *overrides.get(key).unwrap()
421    } else {
422        get(key)
423    }
424}
425
426/// Get a key by cloning the value.
427///
428/// Resolution order: TestOverride -> Runtime -> Env -> File ->
429/// Default. Panics if the key has no default and is not set in
430/// any layer.
431pub fn get_cloned<T: AttrValue>(key: Key<T>) -> T {
432    try_get_cloned(key)
433        .expect("key must have a default")
434        .clone()
435}
436
437/// Try to get a key by cloning the value.
438///
439/// Resolution order: TestOverride -> Runtime -> Env -> File ->
440/// Default. Returns None if the key has no default and is not set in
441/// any layer.
442pub fn try_get_cloned<T: AttrValue>(key: Key<T>) -> Option<T> {
443    let layers = LAYERS.read().unwrap();
444    for layer in &layers.ordered {
445        let a = layer_attrs(layer);
446        if a.contains_key(key) {
447            return a.get(key).cloned();
448        }
449    }
450    key.default().cloned()
451}
452
453/// Construct a [`Layer`] for the given [`Source`] using the provided
454/// `attrs`.
455///
456/// Used by [`set`] and [`create_or_merge`] when installing a new
457/// configuration layer.
458fn make_layer(source: Source, attrs: Attrs) -> Layer {
459    match source {
460        Source::File => Layer::File(attrs),
461        Source::Env => Layer::Env(attrs),
462        Source::Runtime => Layer::Runtime(attrs),
463        Source::TestOverride => Layer::TestOverride {
464            attrs,
465            stacks: HashMap::new(),
466        },
467        Source::ClientOverride => Layer::ClientOverride(attrs),
468    }
469}
470
471/// Insert or replace a configuration layer for the given source.
472///
473/// If a layer with the same [`Source`] already exists, its
474/// contents are replaced with the provided `attrs`. Otherwise a
475/// new layer is added. After insertion, layers are re-sorted so
476/// that higher-priority sources (e.g. [`Source::TestOverride`],
477/// [`Source::Runtime`]) appear before lower-priority ones
478/// ([`Source::Env`], [`Source::File`]).
479///
480/// This function is used by initialization routines (e.g.
481/// `init_from_env`, `init_from_yaml`) and by tests when
482/// overriding configuration values.
483pub fn set(source: Source, attrs: Attrs) {
484    let mut g = LAYERS.write().unwrap();
485    if let Some(l) = g.ordered.iter_mut().find(|l| layer_source(l) == source) {
486        *layer_attrs_mut(l) = attrs;
487    } else {
488        g.ordered.push(make_layer(source, attrs));
489    }
490    g.ordered.sort_by_key(|l| priority(layer_source(l))); // TestOverride < Runtime < Env < File < ClientOverride
491}
492
493/// Insert or update a configuration layer for the given [`Source`].
494///
495/// If a layer with the same [`Source`] already exists, its attributes
496/// are **updated in place**: all keys present in `attrs` are absorbed
497/// into the existing layer, overwriting any previous values for those
498/// keys while leaving all other keys in that layer unchanged.
499///
500/// If no layer for `source` exists yet, this behaves like [`set`]: a
501/// new layer is created with the provided `attrs`.
502///
503/// This is useful for incremental / additive updates (for example,
504/// runtime configuration driven by a Python API), where callers want
505/// to change a subset of keys without discarding previously installed
506/// values in the same layer.
507///
508/// By contrast, [`set`] replaces the entire layer for `source` with
509/// `attrs`, discarding any existing values in that layer.
510pub fn create_or_merge(source: Source, attrs: Attrs) {
511    let mut g = LAYERS.write().unwrap();
512    if let Some(layer) = g.ordered.iter_mut().find(|l| layer_source(l) == source) {
513        layer_attrs_mut(layer).merge(attrs);
514    } else {
515        g.ordered.push(make_layer(source, attrs));
516    }
517    g.ordered.sort_by_key(|l| priority(layer_source(l))); // TestOverride < Runtime < Env < File < ClientOverride
518}
519
520/// Remove the configuration layer for the given [`Source`], if
521/// present.
522///
523/// After this call, values from that source will no longer
524/// contribute to resolution in [`get`], [`get_cloned`], or
525/// [`attrs`]. Defaults and any remaining layers continue to apply
526/// in their normal priority order.
527#[allow(dead_code)]
528pub(crate) fn clear(source: Source) {
529    let mut g = LAYERS.write().unwrap();
530    g.ordered.retain(|l| layer_source(l) != source);
531}
532
533/// Return a complete, merged snapshot of the effective configuration
534/// **(only keys marked with `@meta(CONFIG = ...)`)**.
535///
536/// Resolution per key:
537/// 1) First explicit value found in layers (TestOverride →
538///    Runtime → Env → File).
539/// 2) Otherwise, the key's default (if any).
540///
541/// Notes:
542/// - This materializes defaults into the returned Attrs for all
543///   CONFIG-marked keys, so it's self-contained.
544/// - Keys without `CONFIG` meta are excluded.
545pub fn attrs() -> Attrs {
546    let layers = LAYERS.read().unwrap();
547    let mut merged = Attrs::new();
548
549    // Iterate all declared keys (registered via `declare_attrs!`
550    // + inventory).
551    for info in inventory::iter::<AttrKeyInfo>() {
552        // Skip keys not marked as `CONFIG`.
553        if info.meta.get(CONFIG).is_none() {
554            continue;
555        }
556
557        let name = info.name;
558
559        // Try to resolve from highest -> lowest priority layer.
560        let mut chosen: Option<Box<dyn crate::attrs::SerializableValue>> = None;
561        for layer in &layers.ordered {
562            if let Some(v) = layer_attrs(layer).get_value_by_name(name) {
563                chosen = Some(v.cloned());
564                break;
565            }
566        }
567
568        // If no explicit value, materialize the default if there
569        // is one.
570        let boxed = match chosen {
571            Some(b) => b,
572            None => {
573                if let Some(default) = info.default {
574                    default.cloned()
575                } else {
576                    // No explicit value and no default — skip
577                    // this key.
578                    continue;
579                }
580            }
581        };
582
583        merged.insert_value_by_name_unchecked(name, boxed);
584    }
585
586    merged
587}
588
589/// Reset the global configuration to only Defaults (for testing).
590///
591/// This clears all explicit layers (`File`, `Env`, `Runtime`, and
592/// `TestOverride`). Subsequent lookups will resolve keys entirely
593/// from their declared defaults.
594///
595/// Note: Should be called while holding [`global::lock`] in
596/// tests, to ensure no concurrent modifications happen.
597pub fn reset_to_defaults() {
598    let mut g = LAYERS.write().unwrap();
599    g.ordered.clear();
600}
601
602/// A guard that holds the global configuration lock and provides
603/// override functionality.
604///
605/// This struct acts as both a lock guard (preventing other tests from
606/// modifying global config) and as the only way to create
607/// configuration overrides. Override guards cannot outlive this
608/// ConfigLock, ensuring proper synchronization.
609pub struct ConfigLock {
610    _guard: std::sync::MutexGuard<'static, ()>,
611}
612
613impl ConfigLock {
614    /// Create a configuration override that is active until the
615    /// returned guard is dropped.
616    ///
617    /// Each call pushes a new frame onto a per-key override stack
618    /// within the [`Source::TestOverride`] layer. The topmost frame
619    /// defines the effective value seen by `get()` and in the
620    /// mirrored environment variable (if any). When a guard is
621    /// dropped, its frame is removed: if it was the top, the previous
622    /// frame (if any) becomes active or the key and env var are
623    /// restored to their prior state.
624    ///
625    /// The returned guard must not outlive this [`ConfigLock`].
626    pub fn override_key<'a, T: AttrValue>(
627        &'a self,
628        key: crate::attrs::Key<T>,
629        value: T,
630    ) -> ConfigValueGuard<'a, T> {
631        let token = OVERRIDE_TOKEN_SEQ.fetch_add(1, Ordering::Relaxed);
632
633        let mut g = LAYERS.write().unwrap();
634
635        // Ensure TestOverride layer exists.
636        let idx = if let Some(i) = test_override_index(&g) {
637            i
638        } else {
639            g.ordered.push(Layer::TestOverride {
640                attrs: Attrs::new(),
641                stacks: HashMap::new(),
642            });
643            g.ordered.sort_by_key(|l| priority(layer_source(l)));
644            test_override_index(&g).expect("just inserted TestOverride layer")
645        };
646
647        // Mutably access TestOverride's attrs + stacks.
648        let (attrs, stacks) = match &mut g.ordered[idx] {
649            Layer::TestOverride { attrs, stacks } => (attrs, stacks),
650            _ => unreachable!(),
651        };
652
653        // Compute env var (if any) for this key once.
654        let (env_var, env_str) = if let Some(cfg) = key.attrs().get(crate::config::CONFIG) {
655            if let Some(name) = &cfg.env_name {
656                (Some(name.clone()), value.display())
657            } else {
658                (None, String::new())
659            }
660        } else {
661            (None, String::new())
662        };
663
664        // Get per-key stack (by declared name).
665        let key_name = key.name();
666        let stack = stacks.entry(key_name).or_insert_with(|| OverrideStack {
667            env_var: env_var.clone(),
668            saved_env: env_var.as_ref().and_then(|n| std::env::var(n).ok()),
669            frames: Vec::new(),
670        });
671
672        // Push the new frame.
673        let boxed: Box<dyn crate::attrs::SerializableValue> = Box::new(value.clone());
674        stack.frames.push(OverrideFrame {
675            token,
676            value: boxed,
677            env_str,
678        });
679
680        // Make this frame the active value in TestOverride attrs.
681        attrs.set(key, value.clone());
682
683        // Update process env to reflect new top-of-stack.
684        if let (Some(var), Some(top)) = (stack.env_var.as_ref(), stack.frames.last()) {
685            // SAFETY: Under global ConfigLock during tests.
686            unsafe { std::env::set_var(var, &top.env_str) }
687        }
688
689        ConfigValueGuard {
690            key,
691            token,
692            _phantom: PhantomData,
693        }
694    }
695}
696
697/// When a [`ConfigLock`] is dropped, the special
698/// [`Source::TestOverride`] layer (if present) is removed
699/// entirely. This discards all temporary overrides created under
700/// the lock, ensuring they cannot leak into subsequent tests or
701/// callers. Other layers (`Runtime`, `Env`, `File`, and defaults)
702/// are left untouched.
703///
704/// Note: individual values within the TestOverride layer may
705/// already have been restored by [`ConfigValueGuard`]s as they
706/// drop. This final removal guarantees no residual layer remains
707/// once the lock itself is released.
708impl Drop for ConfigLock {
709    fn drop(&mut self) {
710        let mut guard = LAYERS.write().unwrap();
711        if let Some(pos) = test_override_index(&guard) {
712            guard.ordered.remove(pos);
713        }
714    }
715}
716
717/// A guard that restores a single configuration value when dropped
718pub struct ConfigValueGuard<'a, T: 'static> {
719    key: crate::attrs::Key<T>,
720    token: u64,
721    // This is here so we can hold onto a 'a lifetime.
722    _phantom: PhantomData<&'a ()>,
723}
724
725/// When a [`ConfigValueGuard`] is dropped, it restores configuration
726/// state for the key it was guarding.
727///
728/// Behavior:
729/// - Each key maintains a stack of override frames. The most recent
730///   frame (top of stack) defines the effective value in
731///   [`Source::TestOverride`].
732/// - Dropping a guard removes its frame. If it was the top frame, the
733///   next frame (if any) becomes active and both the config and
734///   mirrored env var are updated accordingly.
735/// - If the dropped frame was not on top, no changes occur until the
736///   active frame is dropped.
737/// - When the last frame for a key is removed, the key is deleted
738///   from the TestOverride layer and its associated environment
739///   variable (if any) is restored to its original value or removed
740///   if it did not exist.
741///
742/// This guarantees that nested or out-of-order test overrides are
743/// restored deterministically and without leaking state into
744/// subsequent tests.
745impl<T: 'static> Drop for ConfigValueGuard<'_, T> {
746    fn drop(&mut self) {
747        let mut g = LAYERS.write().unwrap();
748        let i = if let Some(i) = test_override_index(&g) {
749            i
750        } else {
751            return;
752        };
753
754        // Access TestOverride internals
755        let (attrs, stacks) = match &mut g.ordered[i] {
756            Layer::TestOverride { attrs, stacks } => (attrs, stacks),
757            _ => unreachable!("TestOverride index points to non-TestOverride layer"),
758        };
759
760        let key_name = self.key.name();
761
762        // We need a tiny scope for the &mut borrow of the stack so we
763        // can call `stacks.remove(key_name)` afterward if it becomes
764        // empty.
765        let mut remove_empty_stack = false;
766        let mut restore_env_var: Option<String> = None;
767        let mut restore_env_to: Option<String> = None;
768
769        if let Some(stack) = stacks.get_mut(key_name) {
770            // Find this guard's frame by token.
771            if let Some(pos) = stack.frames.iter().position(|f| f.token == self.token) {
772                let is_top = pos + 1 == stack.frames.len();
773
774                if is_top {
775                    // Pop the active frame
776                    stack.frames.pop();
777
778                    if let Some(new_top) = stack.frames.last() {
779                        // New top becomes active: update attrs and env.
780                        attrs.insert_value(self.key, (*new_top.value).cloned());
781                        if let Some(var) = stack.env_var.as_ref() {
782                            // SAFETY: Under global ConfigLock during tests.
783                            unsafe { std::env::set_var(var, &new_top.env_str) }
784                        }
785                    } else {
786                        // Stack empty: remove the key now, then after
787                        // releasing the &mut borrow of the stack,
788                        // restore the env var and remove the stack
789                        // entry.
790                        let _ = attrs.remove_value(self.key);
791
792                        // Capture restoration details while we still
793                        // have access to the stack.
794                        if let Some(var) = stack.env_var.as_ref() {
795                            restore_env_var = Some(var.clone());
796                            restore_env_to = stack.saved_env.clone(); // None => unset
797                        }
798                        remove_empty_stack = true
799                    }
800                } else {
801                    // Out-of-order drop: remove only that frame:
802                    // active top stays
803                    stack.frames.remove(pos);
804                    // No changes to attrs or env here.
805                }
806            } // else: token already handled; nothing to do
807        } // &must stack borrow ends here
808
809        // If we emptied the stack for this key, restore env and drop
810        // the stack entry.
811        if remove_empty_stack {
812            if let Some(var) = restore_env_var.as_ref() {
813                // SAFETY: Under global ConfigLock during tests.
814                unsafe {
815                    if let Some(val) = restore_env_to.as_ref() {
816                        std::env::set_var(var, val);
817                    } else {
818                        std::env::remove_var(var);
819                    }
820                }
821            }
822            // Now it's safe to remove the stack from the map.
823            let _ = stacks.remove(key_name);
824        }
825    }
826}
827
828#[cfg(test)]
829mod tests {
830
831    use super::*;
832
833    #[test]
834    fn test_global_config() {
835        let config = lock();
836
837        // Reset global config to defaults to avoid interference from other tests
838        reset_to_defaults();
839
840        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
841        {
842            let _guard = config.override_key(CODEC_MAX_FRAME_LENGTH, 1024);
843            assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 1024);
844            // The configuration will be automatically restored when _guard goes out of scope
845        }
846
847        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
848    }
849
850    #[test]
851    fn test_overrides() {
852        let config = lock();
853
854        // Reset global config to defaults to avoid interference from other tests
855        reset_to_defaults();
856
857        // Test the new lock/override API for individual config values
858        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
859        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
860
861        // Test single value override
862        {
863            let _guard = config.override_key(CODEC_MAX_FRAME_LENGTH, 2048);
864            assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2048);
865            assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30)); // Unchanged
866        }
867
868        // Values should be restored after guard is dropped
869        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
870
871        // Test multiple overrides
872        let orig_value = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").ok();
873        {
874            let _guard1 = config.override_key(CODEC_MAX_FRAME_LENGTH, 4096);
875            let _guard2 = config.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(60));
876
877            assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 4096);
878            assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(60));
879            // This was overridden:
880            assert_eq!(
881                std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap(),
882                "1m"
883            );
884        }
885        assert_eq!(
886            std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").ok(),
887            orig_value
888        );
889
890        // All values should be restored
891        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
892        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
893    }
894
895    #[test]
896    fn test_layer_precedence_env_over_file_and_replacement() {
897        let _lock = lock();
898        reset_to_defaults();
899
900        // File sets a value.
901        let mut file = Attrs::new();
902        file[CODEC_MAX_FRAME_LENGTH] = 1111;
903        set(Source::File, file);
904
905        // Env sets a different value.
906        let mut env = Attrs::new();
907        env[CODEC_MAX_FRAME_LENGTH] = 2222;
908        set(Source::Env, env);
909
910        // Env should win over File.
911        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2222);
912
913        // Replace Env layer with a new value.
914        let mut env2 = Attrs::new();
915        env2[CODEC_MAX_FRAME_LENGTH] = 3333;
916        set(Source::Env, env2);
917
918        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 3333);
919    }
920
921    #[test]
922    fn test_layer_precedence_read_file_if_not_found_in_env() {
923        let _lock = lock();
924        reset_to_defaults();
925
926        // Read the default value because no layers have been set.
927        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 10737418240);
928
929        // File sets a value.
930        let mut file = Attrs::new();
931        file[CODEC_MAX_FRAME_LENGTH] = 1111;
932        set(Source::File, file);
933
934        // Env does not have any attribute.
935        let env = Attrs::new();
936        set(Source::Env, env);
937
938        // Should read from File.
939        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 1111);
940
941        // Replace Env layer with a new value.
942        let mut env2 = Attrs::new();
943        env2[CODEC_MAX_FRAME_LENGTH] = 2222;
944        set(Source::Env, env2);
945
946        // Env should win over File.
947        assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2222);
948    }
949
950    #[test]
951    fn test_runtime_overrides_and_clear_restores_lower_layers() {
952        let _lock = lock();
953        reset_to_defaults();
954
955        // File baseline.
956        let mut file = Attrs::new();
957        file[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(30);
958        set(Source::File, file);
959
960        // Env override.
961        let mut env = Attrs::new();
962        env[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(40);
963        set(Source::Env, env);
964
965        // Runtime beats both.
966        let mut rt = Attrs::new();
967        rt[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(50);
968        set(Source::Runtime, rt);
969
970        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(50));
971
972        // Clearing Runtime should reveal Env again.
973        clear(Source::Runtime);
974
975        // With the Runtime layer gone, Env still wins over File.
976        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
977    }
978
979    #[test]
980    fn test_attrs_snapshot_materializes_defaults_and_omits_meta() {
981        let _lock = lock();
982        reset_to_defaults();
983
984        // No explicit layers: values should come from Defaults.
985        let snap = attrs();
986
987        // A few representative defaults are materialized:
988        assert_eq!(snap[CODEC_MAX_FRAME_LENGTH], 10 * 1024 * 1024 * 1024);
989        assert_eq!(snap[MESSAGE_DELIVERY_TIMEOUT], Duration::from_secs(30));
990
991        // CONFIG has no default and wasn't explicitly set: should be
992        // omitted.
993        let json = serde_json::to_string(&snap).unwrap();
994        assert!(
995            !json.contains("hyperactor::config::config"),
996            "CONFIG must not appear in snapshot unless explicitly set"
997        );
998    }
999
1000    #[test]
1001    fn test_parent_child_snapshot_as_runtime_layer() {
1002        let _lock = lock();
1003        reset_to_defaults();
1004
1005        // Parent effective config (pretend it's a parent process).
1006        let mut parent_env = Attrs::new();
1007        parent_env[MESSAGE_ACK_EVERY_N_MESSAGES] = 12345;
1008        set(Source::Env, parent_env);
1009
1010        let parent_snap = attrs();
1011
1012        // "Child" process: start clean, install parent snapshot as
1013        // Runtime.
1014        reset_to_defaults();
1015        set(Source::Runtime, parent_snap);
1016
1017        // Child should observe parent's effective value (as highest
1018        // stable layer).
1019        assert_eq!(get(MESSAGE_ACK_EVERY_N_MESSAGES), 12345);
1020    }
1021
1022    #[test]
1023    fn test_testoverride_layer_override_and_env_restore() {
1024        let lock = lock();
1025        reset_to_defaults();
1026
1027        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1028
1029        // SAFETY: single-threaded test.
1030        unsafe {
1031            std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1032        }
1033
1034        {
1035            let _guard = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(99));
1036            // Override wins:
1037            assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(99));
1038
1039            // Env should be mirrored to the same duration (string may
1040            // be "1m 39s")
1041            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1042            let parsed = humantime::parse_duration(&s).unwrap();
1043            assert_eq!(parsed, Duration::from_secs(99));
1044        }
1045
1046        // After drop, value and env restored:
1047        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1048        assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1049    }
1050
1051    #[test]
1052    fn test_reset_to_defaults_clears_all_layers() {
1053        let _lock = lock();
1054        reset_to_defaults();
1055
1056        // Seed multiple layers.
1057        let mut file = Attrs::new();
1058        file[SPLIT_MAX_BUFFER_SIZE] = 7;
1059        set(Source::File, file);
1060
1061        let mut env = Attrs::new();
1062        env[SPLIT_MAX_BUFFER_SIZE] = 8;
1063        set(Source::Env, env);
1064
1065        let mut rt = Attrs::new();
1066        rt[SPLIT_MAX_BUFFER_SIZE] = 9;
1067        set(Source::Runtime, rt);
1068
1069        // Sanity: highest wins.
1070        assert_eq!(get(SPLIT_MAX_BUFFER_SIZE), 9);
1071
1072        // Reset clears all explicit layers; defaults apply.
1073        reset_to_defaults();
1074        assert_eq!(get(SPLIT_MAX_BUFFER_SIZE), 5); // default
1075    }
1076
1077    #[test]
1078    fn test_get_cloned_resolution_matches_get() {
1079        let _lock = lock();
1080        reset_to_defaults();
1081
1082        let mut env = Attrs::new();
1083        env[CHANNEL_MULTIPART] = false;
1084        set(Source::Env, env);
1085
1086        assert!(!get(CHANNEL_MULTIPART));
1087        let v = get_cloned(CHANNEL_MULTIPART);
1088        assert!(!v);
1089    }
1090
1091    #[test]
1092    fn test_attrs_snapshot_respects_layer_precedence_per_key() {
1093        let _lock = lock();
1094        reset_to_defaults();
1095
1096        let mut file = Attrs::new();
1097        file[MESSAGE_TTL_DEFAULT] = 10;
1098        set(Source::File, file);
1099
1100        let mut env = Attrs::new();
1101        env[MESSAGE_TTL_DEFAULT] = 20;
1102        set(Source::Env, env);
1103
1104        let snap = attrs();
1105        assert_eq!(snap[MESSAGE_TTL_DEFAULT], 20); // Env beats File
1106    }
1107
1108    declare_attrs! {
1109      @meta(CONFIG = ConfigAttr {
1110          env_name: None,
1111          py_name: None,
1112      })
1113      pub attr CONFIG_KEY: bool = true;
1114
1115      pub attr NON_CONFIG_KEY: bool = true;
1116    }
1117
1118    #[test]
1119    fn test_attrs_excludes_non_config_keys() {
1120        let _lock = lock();
1121        reset_to_defaults();
1122
1123        let snap = attrs();
1124        let json = serde_json::to_string(&snap).unwrap();
1125
1126        // Expect our CONFIG_KEY to be present.
1127        assert!(
1128            json.contains("hyperactor::config::global::tests::config_key"),
1129            "attrs() should include keys with @meta(CONFIG = ...)"
1130        );
1131        // Expect our NON_CONFIG_KEY to be omitted.
1132        assert!(
1133            !json.contains("hyperactor::config::global::tests::non_config_key"),
1134            "attrs() should exclude keys without @meta(CONFIG = ...)"
1135        );
1136    }
1137
1138    #[test]
1139    fn test_testoverride_multiple_stacked_overrides_lifo() {
1140        let lock = lock();
1141        reset_to_defaults();
1142
1143        // Baseline sanity.
1144        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1145
1146        // Start from a clean env so we can assert restoration to "unset".
1147        // SAFETY: single-threaded tests.
1148        unsafe {
1149            std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1150        }
1151        assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1152
1153        // Stack A: 40s (becomes top)
1154        let guard_a = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(40));
1155        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1156        {
1157            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1158            assert_eq!(
1159                humantime::parse_duration(&s).unwrap(),
1160                Duration::from_secs(40)
1161            );
1162        }
1163
1164        // Stack B: 50s (new top)
1165        let guard_b = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(50));
1166        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(50));
1167        {
1168            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1169            assert_eq!(
1170                humantime::parse_duration(&s).unwrap(),
1171                Duration::from_secs(50)
1172            );
1173        }
1174
1175        // Drop B first → should reveal A (LIFO)
1176        std::mem::drop(guard_b);
1177        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1178        {
1179            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1180            assert_eq!(
1181                humantime::parse_duration(&s).unwrap(),
1182                Duration::from_secs(40)
1183            );
1184        }
1185
1186        // Drop A → should restore default and unset env.
1187        std::mem::drop(guard_a);
1188        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1189        assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1190    }
1191
1192    #[test]
1193    fn test_testoverride_out_of_order_drop_keeps_top_stable() {
1194        let lock = lock();
1195        reset_to_defaults();
1196
1197        // Clean env baseline.
1198        // SAFETY: single-threaded tests.
1199        unsafe {
1200            std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1201        }
1202        assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1203
1204        // Push three frames in order: A=40s, B=50s, C=70s (C is top).
1205        let guard_a = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(40));
1206        let guard_b = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(50));
1207        let guard_c = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(70));
1208
1209        // Top is C.
1210        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(70));
1211        {
1212            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1213            assert_eq!(
1214                humantime::parse_duration(&s).unwrap(),
1215                Duration::from_secs(70)
1216            );
1217        }
1218
1219        // Drop the *middle* frame (B) first → top must remain C, env unchanged.
1220        std::mem::drop(guard_b);
1221        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(70));
1222        {
1223            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1224            assert_eq!(
1225                humantime::parse_duration(&s).unwrap(),
1226                Duration::from_secs(70)
1227            );
1228        }
1229
1230        // Now drop C → A becomes top, env follows A.
1231        std::mem::drop(guard_c);
1232        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1233        {
1234            let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1235            assert_eq!(
1236                humantime::parse_duration(&s).unwrap(),
1237                Duration::from_secs(40)
1238            );
1239        }
1240
1241        // Drop A → restore default and clear env.
1242        std::mem::drop(guard_a);
1243        assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1244        assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1245    }
1246
1247    #[test]
1248    fn test_priority_order() {
1249        use Source::*;
1250        assert!(priority(TestOverride) < priority(Runtime));
1251        assert!(priority(Runtime) < priority(Env));
1252        assert!(priority(Env) < priority(File));
1253        assert!(priority(File) < priority(ClientOverride));
1254    }
1255
1256    #[test]
1257    fn test_create_or_merge_runtime_merges_keys() {
1258        let _lock = lock();
1259        reset_to_defaults();
1260
1261        // Seed Runtime with one key.
1262        let mut rt = Attrs::new();
1263        rt[MESSAGE_TTL_DEFAULT] = 10;
1264        set(Source::Runtime, rt);
1265
1266        // Now update Runtime with a different key via
1267        // `create_or_merge`.
1268        let mut update = Attrs::new();
1269        update[MESSAGE_ACK_EVERY_N_MESSAGES] = 123;
1270        create_or_merge(Source::Runtime, update);
1271
1272        // Both keys should now be visible from Runtime.
1273        assert_eq!(get(MESSAGE_TTL_DEFAULT), 10);
1274        assert_eq!(get(MESSAGE_ACK_EVERY_N_MESSAGES), 123);
1275    }
1276
1277    #[test]
1278    fn test_create_or_merge_runtime_creates_layer_if_missing() {
1279        let _lock = lock();
1280        reset_to_defaults();
1281
1282        let mut rt = Attrs::new();
1283        rt[MESSAGE_TTL_DEFAULT] = 42;
1284        create_or_merge(Source::Runtime, rt);
1285
1286        assert_eq!(get(MESSAGE_TTL_DEFAULT), 42);
1287    }
1288}