test(xceadmin): add env-var override for live E2E binary discovery

_live_e2e_common.py's CONTROLLER_CANDIDATES/XCEADMIN_CANDIDATES only ever
resolved to the three default profile-dir names (xce.build, build.release,
build.dbg), with no way to point the live E2E suite at a binary built to a
custom/explicit build dir. Adds XCE_CONTROLLER_BIN/XCEADMIN_BIN env-var
overrides via a new resolve_binary_candidates() helper -- when set, the
override becomes the sole candidate; otherwise the existing three-name
fallback list is unchanged. Mirrors the ${XCE_CONTROLLER_BIN:-default}
convention already used by clean_failover_preservation.sh/run-ha-soak.sh.

Skip messages in both live E2E test files now mention the override.

TDD: 4 new unit tests in test_live_e2e_common.py (no override, empty
override, override-becomes-sole-candidate, and an end-to-end reload proving
the real module constants respect their env vars). Full xceadmin suite
(288 tests) confirmed unaffected: 287 pass, 1 pre-existing unrelated
failure (a stale redirect-gap assertion, unrelated to this change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
Rongsong Shen 2026-08-27 02:39:52 +00:00
parent 7880ab4280
commit 3891bd909a
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 133 additions and 16 deletions

View file

@ -27,6 +27,7 @@ it -- the same mechanism that already lets every test file here import
from __future__ import annotations
import os
import re
import socket
import time
@ -36,19 +37,45 @@ SRC_DIR = Path(__file__).resolve().parents[2]
REPO_CONFIGS_DIR = SRC_DIR / "configs"
DEPLOY_HA_SH = SRC_DIR / "tools" / "deploy-ha.sh"
def resolve_binary_candidates(env_var: str, defaults: list[Path]) -> list[Path]:
"""If ``env_var`` is set to a non-empty value, it becomes the SOLE
candidate -- lets a caller point discovery at a binary built to a
custom/explicit build dir (``xce-build.sh``'s "legacy explicit
build_dir" override), which none of the fixed default profile-dir names
below can ever match. Otherwise falls back to ``defaults`` unchanged.
Mirrors the ``${XCE_CONTROLLER_BIN:-default}``-style env-var-override
convention already used by ``clean_failover_preservation.sh``/
``run-ha-soak.sh`` elsewhere in this repo, rather than a Python-only
ad hoc mechanism.
"""
override = os.environ.get(env_var)
if override:
return [Path(override)]
return defaults
# Mirrors deploy-ha.sh's own xceadmin/controller discovery order
# (src/tools/deploy-ha.sh) -- xce.build (asan, the default profile) first,
# then release, then debug.
CONTROLLER_CANDIDATES = [
SRC_DIR / "xce.build" / "controller" / "xce_controller",
SRC_DIR / "build.release" / "controller" / "xce_controller",
SRC_DIR / "build.dbg" / "controller" / "xce_controller",
]
XCEADMIN_CANDIDATES = [
SRC_DIR / "xce.build" / "xceadmin" / ".venv" / "bin" / "xceadmin",
SRC_DIR / "build.release" / "xceadmin" / ".venv" / "bin" / "xceadmin",
SRC_DIR / "build.dbg" / "xceadmin" / ".venv" / "bin" / "xceadmin",
]
# then release, then debug. XCE_CONTROLLER_BIN/XCEADMIN_BIN override this
# entirely when set (e.g. a build to a custom/explicit build dir).
CONTROLLER_CANDIDATES = resolve_binary_candidates(
"XCE_CONTROLLER_BIN",
[
SRC_DIR / "xce.build" / "controller" / "xce_controller",
SRC_DIR / "build.release" / "controller" / "xce_controller",
SRC_DIR / "build.dbg" / "controller" / "xce_controller",
],
)
XCEADMIN_CANDIDATES = resolve_binary_candidates(
"XCEADMIN_BIN",
[
SRC_DIR / "xce.build" / "xceadmin" / ".venv" / "bin" / "xceadmin",
SRC_DIR / "build.release" / "xceadmin" / ".venv" / "bin" / "xceadmin",
SRC_DIR / "build.dbg" / "xceadmin" / ".venv" / "bin" / "xceadmin",
],
)
READY_TIMEOUT_S = 20.0
VERSION_TAG_RE = re.compile(r"versionTag=(\d+)\s+ackedCount=(\d+)")

View file

@ -296,7 +296,8 @@ def live_ha_cluster(tmp_path: Path):
if controller_bin is None:
pytest.skip(
"no xce_controller binary found under xce.build/build.release/"
"build.dbg -- run xce-build.sh controller [profile] first to "
"build.dbg -- run xce-build.sh controller [profile] first, or "
"set XCE_CONTROLLER_BIN to a custom build's binary, to "
"exercise this live E2E test"
)
if shutil.which("openssl") is None:
@ -433,7 +434,11 @@ def test_live_config_push_against_primary_reaches_success_path(
"""
xceadmin_bin = find_first_executable(XCEADMIN_CANDIDATES)
if xceadmin_bin is None:
pytest.skip("no xceadmin binary found under xce.build/build.release/build.dbg")
pytest.skip(
"no xceadmin binary found under xce.build/build.release/"
"build.dbg -- set XCEADMIN_BIN to a custom build's binary "
"if using a non-default build dir"
)
cluster_bin = _build_cluster_bin(xceadmin_bin, tmp_path)
@ -519,7 +524,11 @@ def test_live_config_push_against_standby_returns_standby_rejection_not_redirect
"""
xceadmin_bin = find_first_executable(XCEADMIN_CANDIDATES)
if xceadmin_bin is None:
pytest.skip("no xceadmin binary found under xce.build/build.release/build.dbg")
pytest.skip(
"no xceadmin binary found under xce.build/build.release/"
"build.dbg -- set XCEADMIN_BIN to a custom build's binary "
"if using a non-default build dir"
)
cluster_bin = _build_cluster_bin(xceadmin_bin, tmp_path)

View file

@ -93,7 +93,8 @@ def live_controller(tmp_path: Path):
if controller_bin is None:
pytest.skip(
"no xce_controller binary found under xce.build/build.release/"
"build.dbg -- run xce-build.sh controller [profile] first to "
"build.dbg -- run xce-build.sh controller [profile] first, or "
"set XCE_CONTROLLER_BIN to a custom build's binary, to "
"exercise this live E2E test"
)
if shutil.which("openssl") is None:
@ -154,7 +155,11 @@ def test_live_config_push_against_real_single_node_controller(live_controller, t
controller_addr, log_file = live_controller
xceadmin_bin = _find_first_executable(_XCEADMIN_CANDIDATES)
if xceadmin_bin is None:
pytest.skip("no xceadmin binary found under xce.build/build.release/build.dbg")
pytest.skip(
"no xceadmin binary found under xce.build/build.release/"
"build.dbg -- set XCEADMIN_BIN to a custom build's binary "
"if using a non-default build dir"
)
cluster_bin = tmp_path / "cluster.bin"
build_result = subprocess.run(

View file

@ -0,0 +1,76 @@
"""Unit tests for ``_live_e2e_common``'s binary-discovery env-var override.
``CONTROLLER_CANDIDATES``/``XCEADMIN_CANDIDATES`` previously only ever
resolved to the three default profile-dir names (``xce.build``,
``build.release``, ``build.dbg``) -- no way to point the live E2E suite at
a binary built to a custom/explicit build dir (``xce-build.sh``'s
"legacy explicit build_dir" override) without editing this module directly.
This adds ``XCE_CONTROLLER_BIN``/``XCEADMIN_BIN`` env-var overrides,
consistent with the ``${XCE_CONTROLLER_BIN:-default}``-style pattern
already used by ``clean_failover_preservation.sh``/``run-ha-soak.sh``.
Tests the pure resolution helper directly (not the frozen module-level
``CONTROLLER_CANDIDATES``/``XCEADMIN_CANDIDATES`` constants, which are
computed once at import time from whatever the environment happened to be
-- re-importing the module under a patched environment is fragile/awkward
under pytest's module caching, so the override LOGIC is factored into a
directly-testable function instead).
"""
from __future__ import annotations
from pathlib import Path
from _live_e2e_common import resolve_binary_candidates
def test_no_env_override_returns_default_candidates_unchanged(monkeypatch):
monkeypatch.delenv("XCE_SOME_TEST_BIN", raising=False)
defaults = [Path("/a/xce.build/x"), Path("/a/build.release/x")]
result = resolve_binary_candidates("XCE_SOME_TEST_BIN", defaults)
assert result == defaults
def test_empty_env_override_falls_back_to_default_candidates(monkeypatch):
monkeypatch.setenv("XCE_SOME_TEST_BIN", "")
defaults = [Path("/a/xce.build/x")]
result = resolve_binary_candidates("XCE_SOME_TEST_BIN", defaults)
assert result == defaults
def test_env_override_becomes_sole_candidate(monkeypatch, tmp_path):
custom_bin = tmp_path / "my-custom-build" / "controller" / "xce_controller"
monkeypatch.setenv("XCE_SOME_TEST_BIN", str(custom_bin))
defaults = [Path("/a/xce.build/x"), Path("/a/build.dbg/x")]
result = resolve_binary_candidates("XCE_SOME_TEST_BIN", defaults)
assert result == [custom_bin]
def test_controller_and_xceadmin_candidates_respect_their_own_env_vars(monkeypatch, tmp_path):
"""End-to-end through the real module constants' construction: reload
the module with each override env var set and confirm the exported
candidate lists reduce to the single overridden path, proving the
actual production wiring (not just the helper in isolation) is correct.
"""
import importlib
import _live_e2e_common as mod
custom_controller = tmp_path / "custom" / "xce_controller"
custom_xceadmin = tmp_path / "custom" / "xceadmin"
monkeypatch.setenv("XCE_CONTROLLER_BIN", str(custom_controller))
monkeypatch.setenv("XCEADMIN_BIN", str(custom_xceadmin))
try:
importlib.reload(mod)
assert mod.CONTROLLER_CANDIDATES == [custom_controller]
assert mod.XCEADMIN_CANDIDATES == [custom_xceadmin]
finally:
monkeypatch.delenv("XCE_CONTROLLER_BIN", raising=False)
monkeypatch.delenv("XCEADMIN_BIN", raising=False)
importlib.reload(mod) # restore default candidates for any test after this one