Story: Injectable environment provider in ores.platform for test-safe, parallel-safe env var access

Table of Contents

This page documents a story in Sprint 25. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

Goal

Any code needing environment variables (starting with environment_mapper_factory's consumers, and ores.testing's env-clearing test helpers) can be tested with fully isolated, parallel-safe environment state, instead of mutating the real process environment.

Status

Field Value
State DONE
Parent sprint Sprint 25
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-08-07

Acceptance

  • A short prior-art survey is written up (in this story's Notes, or a linked knowledge doc) before design work starts: how established C++ codebases/test frameworks solve "swap real environment access for a fake one in tests" – e.g. GoogleTest's ScopedEnvironmentVariable, Abseil's flag/env abstractions, dependency-injected config-provider patterns in other languages' test frameworks (Python's monkeypatch, Rust's temp-env crate), and any prior discussion/precedent already in this codebase (e.g. how database_helper or other scoped test fixtures solve analogous global-state problems). The survey should name at least 2-3 concrete approaches with their tradeoffs (thread-local override vs. DI'd interface vs. process-per-test isolation) so the design task below picks from evaluated options rather than inventing from scratch.
  • ores.platform exposes an environment-provider abstraction (e.g. a virtual interface or a thread-local/DI-swappable default) that ores::platform::environment::environment's get/set/unset calls go through; a test-only provider implementation exists for injecting fake values scoped to a test (RAII or fixture-based) with no real setenv/unsetenv/getenv calls and no cross-test/cross-thread interference; ores::testing::scoped_env_unset (or its replacement) is migrated to use the new provider instead of mutating real process environment; the 11 domain-service parser tests migrated in the parser-boilerplate task continue to pass, now via the injected-provider path.

Tasks

Task State Start End Description
Research prior art for swappable environment-access abstractions in tests DONE 2026-08-07 2026-08-07 Survey how established C++ codebases and test frameworks solve the general problem this story addresses: swapping real process-environment access for injected fake values in tests, safely under parallel test execution. Cover at minimum GoogleTest's ScopedEnvironmentVariable, Abseil's config/flag abstractions, and equivalent patterns in other languages' test frameworks (Python's monkeypatch, Rust's temp-env) as points of comparison, plus any existing precedent already in this codebase for solving analogous global-state problems in tests (e.g. database_helper, scoped_database_helper). Write up findings with at least 2-3 named concrete approaches and their tradeoffs (thread-local override vs. DI'd interface vs. process-per-test isolation).
Design and implement a swappable environment-provider abstraction in ores.platform DONE 2026-08-07 2026-08-07 Based on the prior-art research task's findings, design and implement a swappable environment-provider abstraction that ores::platform::environment::environment's get/set/unset go through, so tests can inject isolated fake environment state instead of mutating real process-global environment. Migrate ores::testing::scoped_env_unset (or replace it) to use the new provider. Migrate the 11 domain-service parser tests that currently use scoped_env_unset to the new mechanism.

Decisions

  • Approach 1 (provider interface with thread_local scoped injection) was selected over Approach 2 (DI'd interface wired at construction) and Approach 3 (global override table). Rationale recorded in the research task's Notes.
  • boost::program_options reads env vars from the real process environment (std::getenv), not through environment::get_value. The scoped_environment_override guard must therefore sync the real environment in addition to swapping the provider — a pure in-memory fake is insufficient for this codebase's parser tests.
  • The provider swap uses thread_local storage for per-test isolation; the real-environment sync remains process-global. Under Catch2's sequential execution model this is safe; if tests are ever run in parallel, the real-env sync would need a process-wide mutex or a move away from boost::program_options's direct getenv usage.
  • scoped_env_unset is superseded and deleted — all 11 consumers were migrated to scoped_environment_override; no remaining call-sites exist.

Out of scope

Emacs 29.3 (Org mode 9.6.15)