Skip to content

orca-pi breaks expected environment behaviour #264

Description

@kjh-giesbertz

Describe the bug
orca-pi breaks the expected os.environ behaviour be reassigning it to a copy. This breaks any tool that stores a reference to the os.environ object (monkeypatch, but also os.getenv if it's ever memoized, etc.).

To Reproduce

"""
Reproduces the opi._orca_environment bug where os.environ = dict_copy
breaks pytest monkeypatch (and any code holding a reference to os.environ).

Does NOT require ORCA to be installed.
"""
import os
import pytest


def _orca_environment_buggy(fn):
    """Minimal reproduction of opi.execution.core._orca_environment."""
    def wrapper(*args, **kwargs):
        org_env = os.environ.copy()   # plain dict copy
        try:
            os.environ["PATH"] = "/fake/orca/bin:" + os.environ.get("PATH", "")
            return fn(*args, **kwargs)
        finally:
            os.environ = org_env      # replaces the module attribute with a new dict
    return wrapper


@_orca_environment_buggy
def simulate_orca_run():
    pass  # pretend ORCA ran here


def test_env_leak(monkeypatch):
    simulate_orca_run()                    # os.environ is now dict D1 (a copy)
    monkeypatch.setenv("ORCA_MEMORY", "1") # monkeypatch captures D1
    simulate_orca_run()                    # os.environ is now dict D2 (copy of D1)
    # monkeypatch teardown will del D1["ORCA_MEMORY"]
    # but os.environ == D2, which still has ORCA_MEMORY=1


def test_env_still_set():
    """Passes when run alone, fails when run after test_env_leak."""
    assert os.environ.get("ORCA_MEMORY") is None, (
        f"ORCA_MEMORY leaked from previous test: {os.environ.get('ORCA_MEMORY')!r}\n"
        f"os.environ type: {type(os.environ).__name__}  "
        f"(expected os._Environ, got plain dict — set by _orca_environment)"
    )

Expected behavior
monkeypatch teardown should work

Version:

  • opi 2.0

Suggested fix pattern (Claude)

# current (broken for monkeypatch)
org_env = os.environ.copy()
try:
    add_to_env("PATH", ...)
    ...
finally:
    os.environ = org_env

# fixed
saved_path = os.environ.get("PATH")
saved_ldlib = os.environ.get("LD_LIBRARY_PATH")
try:
    add_to_env("PATH", ...)
    ...
finally:
    if saved_path is not None: os.environ["PATH"] = saved_path
    if saved_ldlib is not None: os.environ["LD_LIBRARY_PATH"] = saved_ldlib

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions