Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ jobs:
bash tests/test-preflight-resolver-bug.sh
bash tests/test-desktop-agent-host-env.sh
bash tests/test-pixel-uninstall.sh
python3 tests/test_gpu_env_precedence.py
python3 tests/contracts/test-network-exposure-contracts.py
python3 tests/contracts/test-remote-provider-egress-policy.py
python3 tests/contracts/test-remote-provider-egress-service.py
Expand Down
19 changes: 14 additions & 5 deletions ods/extensions/services/dashboard-api/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,13 +549,22 @@ def _read_env_var_from_file_state(key: str) -> tuple[bool, str]:
"""Read a live .env value while preserving present-but-empty assignments."""
install_dir = os.environ.get("ODS_INSTALL_DIR", os.path.expanduser("~/ods"))
env_path = Path(install_dir) / ".env"
found = False
result = ""
prefix = f"{key}="
export_prefix = f"export {key}="
try:
for line in env_path.read_text().splitlines():
if line.startswith(f"{key}="):
return True, parse_env_value(line[len(key) + 1:])
except OSError:
for raw in env_path.read_text(encoding="utf-8", errors="replace").splitlines():
line = raw.strip()
if line.startswith(prefix):
found = True
result = parse_env_value(line[len(prefix):])
elif line.startswith(export_prefix):
found = True
result = parse_env_value(line[len(export_prefix):])
except (OSError, UnicodeError):
pass
return False, ""
return found, result


def _read_env_var_from_file(key: str) -> str:
Expand Down
62 changes: 62 additions & 0 deletions ods/tests/test_gpu_env_precedence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Regression test: gpu module env reader respects dotenv override precedence and export prefix."""

from __future__ import annotations

import os
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "extensions" / "services" / "dashboard-api"))

import gpu # noqa: E402


def test_gpu_read_env_var_precedence_and_export():
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
env_file = tmp_path / ".env"

# Baseline: initial assignment overridden by later assignment; second var uses export prefix
env_file.write_text(
"""
# Default configuration
GPU_BACKEND=cpu
# Custom override appended by installer or operator
GPU_BACKEND=nvidia
export GPU_ACCELERATOR=cuda
UNTOUCHED_VAR=kept
# Non-ASCII UTF-8 comment: 🚀
""",
encoding="utf-8",
)

old_dir = os.environ.get("ODS_INSTALL_DIR")
os.environ["ODS_INSTALL_DIR"] = str(tmp_path)
try:
# 1. Precedence: final assignment must override initial assignment
found, backend = gpu._read_env_var_from_file_state("GPU_BACKEND")
assert found is True
assert backend == "nvidia", f"Expected 'nvidia', got {backend!r}"

# 2. Export prefix: must parse key assigned with export prefix
found_acc, acc = gpu._read_env_var_from_file_state("GPU_ACCELERATOR")
assert found_acc is True
assert acc == "cuda", f"Expected 'cuda', got {acc!r}"

# 3. Missing key falls back cleanly to found=False
found_missing, missing = gpu._read_env_var_from_file_state("NON_EXISTENT")
assert found_missing is False
assert missing == ""
finally:
if old_dir is None:
os.environ.pop("ODS_INSTALL_DIR", None)
else:
os.environ["ODS_INSTALL_DIR"] = old_dir


if __name__ == "__main__":
test_gpu_read_env_var_precedence_and_export()
print("test_gpu_env_precedence: OK")
Loading