Skip to content

Replace in-process environment mutation in tests with dependency injection #165

Description

@leynos

Summary

A test-quality sweep found that several test helpers still mutate the process environment in-process (std::env::set_var / std::env::remove_var), relying on a shared mutex or OnceLock for safety. Project policy (AGENTS.md) prefers dependency injection via the mockable crate for environment-dependent behaviour; mutex-guarded in-process mutation remains a process-wide side effect, is unsafe in Rust 2024, and can still leak between tests when a guard is dropped early, a test panics, or a third-party library reads the environment on another thread. Sub-process isolated environments (e.g. Command::env) are fine and are not in scope.

Findings

tests/test_utils.rs:

  • clear_podbot_env()std::env::remove_var("PODBOT_CONFIG_PATH") and a loop removing every variable in env_var_names() (lines ~89 and ~96), guarded by EnvGuard.
  • set_env_var(&EnvGuard, key, value)std::env::set_var (line ~125).
  • clean_env rstest fixture — wraps clear_podbot_env() (line ~197).
  • TestStdinForwardingGuard::disable() / Drop — sets and removes PODBOT_DISABLE_STDIN_FORWARDING_FOR_TESTS (lines ~161 and ~174).

Consumers of TestStdinForwardingGuard (in-process mutation at one remove):

  • tests/bdd_interactive_exec_helpers/steps.rs line ~108.
  • tests/bdd_orchestration_helpers/steps.rs line ~109.

tests/bdd_repository_cloning_e2e_helpers/container.rs:

  • ensure_docker_host()std::env::set_var("DOCKER_HOST", &endpoint) inside a OnceLock::get_or_init (lines ~114-116). This one exists because testcontainers reads DOCKER_HOST during client construction and exposes no in-process alternative; it may need an upstream fix or a documented, narrowly scoped exception.

For contrast, the config-loader, hosting-config-loader, and engine-connection BDD suites already use mockable::MockEnv with per-scenario state maps and need no changes; they are the model to converge on.

Proposed enforcement

Adopt the clippy disallowed-methods prohibition used in leynos/netsuke, which denies the environment-mutation and environment-read APIs with actionable reasons:

# clippy.toml
disallowed-methods = [
  { path = "std::env::var", reason = "inject an environment reader" },
  { path = "std::env::var_os", reason = "inject an environment reader" },
  { path = "std::env::vars", reason = "inject an environment reader" },
  { path = "std::env::vars_os", reason = "inject an environment reader" },
  { path = "std::env::set_var", reason = "use a stub environment in tests" },
  { path = "std::env::remove_var", reason = "use a stub environment in tests" },
]

with disallowed_methods = "deny" in the workspace lint table. As in netsuke, any site that cannot yet migrate (e.g. the testcontainers DOCKER_HOST case) carries #[expect(clippy::disallowed_methods, reason = "..")] rather than allow, so the expectation warns once the site is migrated and the backlog removes itself.

Suggested migration

  1. Rework load_config-style entry points and the stdin-forwarding knob to accept an injected mockable::Env (most call paths already support this).
  2. Replace clean_env/set_env_var/TestStdinForwardingGuard usage with MockEnv-backed state, following the config-loader BDD suites.
  3. Decide the DOCKER_HOST/testcontainers exception: documented #[expect] at a single choke point, or move that suite to a sub-process harness.
  4. Add the clippy prohibitions above so regressions fail the lint gate.

Found during the test-quality sweep on branch test-quality-sweep (flagged only; deliberately not fixed there).

Metadata

Metadata

Assignees

No one assigned

    Labels

    concurrencyConcurrency, parallelism, and synchronization work, including races and deadlocks.mediumRoadmap items to schedule within the current quarter. Clear scope, normal review cycles.refactorBehaviour-preserving restructuring that improves code health.testingTest coverage, test infrastructure, and verification tooling work.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions