test(issue-28): make the host-dependent tests portable; POSIX ps in the process scanners
The 33 failures on the Linux / Python 3.14 host (#28) were environment dependencies, not regressions; each test is worth keeping, so fix the dependency instead of deleting the test: - test_workspace_scope: `source` under `shell=True` ran in dash; run the command string under bash. - conftest autouse: MAM_ENV_FILE points at an absent file for every test, so a workspace .mam.env with a reachable broker no longer breaks the "broker unavailable" tests (T1-4). - help-capture guard: compare normalized help text (whitespace collapsed, "optional arguments"/"options" unified) so argparse re-wrapping across Python versions is not drift. - deploy freshness D-11..D-19 and D-22..D-30: skip with a reason when the nats-docker submodule is not initialized. - lib_py.agents.base.conversation_in_use and heartbeat._process_table: `ps -eo pid=,ppid=,args=` (POSIX) instead of the BSD `-axo ... command=` form, the most likely cause of the 12 purge failures on Linux (the holder scan returned nothing); to be confirmed on that host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
46ea4cc1e4
commit
7a94ba2c3e
6 files changed
+48
-4
No files matched your search
@@ -279,12 +279,19 @@ def _resolve_private_server_doc() -> str:
|
||||
|
||||
|
||||
PRIVATE_SERVER_DOC_PATH = _resolve_private_server_doc()
|
||||
# Issue #28: these guards read the nats-docker submodule. A checkout without
|
||||
# `git submodule update --init` has nothing to check; skip with the reason
|
||||
# instead of failing on every host that cloned without submodules.
|
||||
needs_private_server_doc = pytest.mark.skipif(
|
||||
not os.path.exists(PRIVATE_SERVER_DOC_PATH),
|
||||
reason="nats-docker submodule not initialized (git submodule update --init)")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# D-11 — (G-D1) PRIVATE_SERVER.md must only document MQTT_* environment
|
||||
# variables that broker_config_from_env() actually parses.
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d11_private_server_env_names_valid():
|
||||
sys.path.insert(0, os.path.join(REPO_ROOT, ".agents", "skills", "multi-agent-mux-delegate-job", "scripts"))
|
||||
import mqtt_common
|
||||
@@ -315,6 +322,7 @@ def test_d11_private_server_env_names_valid():
|
||||
# D-12 — (G-D2) PRIVATE_SERVER.md must not contain invalid MAM_MQTT_* in
|
||||
# active configuration code blocks.
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d12_private_server_no_mam_mqtt_in_code_fences():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -331,6 +339,7 @@ def test_d12_private_server_no_mam_mqtt_in_code_fences():
|
||||
# D-13 — (G-D3) nats-server launch instructions in PRIVATE_SERVER.md must
|
||||
# use valid config blocks (mqtt {) and not HTTP port flag (-m 1883).
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d13_private_server_nats_config_valid():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -347,6 +356,7 @@ def test_d13_private_server_nats_config_valid():
|
||||
# D-14 — (G-D4) Verification commands in PRIVATE_SERVER.md must use valid
|
||||
# CLI flags matching the actual scripts' argparse parsers.
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d14_private_server_cli_args_valid():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -367,6 +377,7 @@ def test_d14_private_server_cli_args_valid():
|
||||
# D-15 — (G-D5) store_dir in code blocks must be absolute path (/ or $HOME)
|
||||
# and heredocs writing it must be unquoted (<<EOF).
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d15_private_server_store_dir_valid():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -388,6 +399,7 @@ def test_d15_private_server_store_dir_valid():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-16 — (G-D6) nats image references in code fences must use pinned alpine
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d16_private_server_nats_image_alpine_pinned():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -404,6 +416,7 @@ def test_d16_private_server_nats_image_alpine_pinned():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-17 — (G-D7) Port 8222 in docker examples must be bound to 127.0.0.1
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d17_private_server_monitoring_port_localhost_bound():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -421,6 +434,7 @@ def test_d17_private_server_monitoring_port_localhost_bound():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-18 — (G-D8) TLS examples must not use IP literals for MQTT_BROKER
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d18_private_server_tls_examples_use_domain_names():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -437,6 +451,7 @@ def test_d18_private_server_tls_examples_use_domain_names():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-19 — (G-D9) Subject literals in config examples match DEFAULT_TOPIC_ROOT
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_private_server_doc
|
||||
def test_d19_private_server_subject_literals_match_default_topic_root():
|
||||
doc_path = PRIVATE_SERVER_DOC_PATH
|
||||
with open(doc_path, "r", encoding="utf-8") as f:
|
||||
@@ -492,6 +507,9 @@ def _resolve_docker_dir() -> str:
|
||||
|
||||
DOCKER_DIR = _resolve_docker_dir()
|
||||
COMPOSE_PATH = os.path.join(DOCKER_DIR, "docker-compose.yaml")
|
||||
needs_docker_assets = pytest.mark.skipif(
|
||||
not os.path.exists(COMPOSE_PATH),
|
||||
reason="nats-docker submodule not initialized (git submodule update --init)")
|
||||
NATS_CONF_PATH = os.path.join(DOCKER_DIR, "nats.conf")
|
||||
ENV_EXAMPLE_PATH = os.path.join(DOCKER_DIR, ".env.example")
|
||||
DOCKER_README_PATH = os.path.join(DOCKER_DIR, "README.md")
|
||||
@@ -523,6 +541,7 @@ def _active_conf_lines():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-22 — docker/ assets exist and are populated
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d22_docker_assets_exist_and_are_populated():
|
||||
for p in [COMPOSE_PATH, NATS_CONF_PATH, ENV_EXAMPLE_PATH, DOCKER_README_PATH]:
|
||||
assert os.path.exists(p), f"Required asset {p} does not exist"
|
||||
@@ -533,6 +552,7 @@ def test_d22_docker_assets_exist_and_are_populated():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-23 — compose image matches doc and is alpine
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d23_compose_image_matches_doc_and_is_alpine():
|
||||
_, svc = _load_compose()
|
||||
compose_img = svc.get("image", "")
|
||||
@@ -552,6 +572,7 @@ def test_d23_compose_image_matches_doc_and_is_alpine():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-24 — compose port exposure contract
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d24_compose_port_exposure_contract():
|
||||
_, svc = _load_compose()
|
||||
ports = svc.get("ports", [])
|
||||
@@ -573,6 +594,7 @@ def test_d24_compose_port_exposure_contract():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-25 — secrets are fail closed
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d25_secrets_are_fail_closed():
|
||||
_, svc = _load_compose()
|
||||
env = svc.get("environment", {})
|
||||
@@ -615,6 +637,7 @@ def test_d25_secrets_are_fail_closed():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-26 — nats_conf jetstream and mqtt contract
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d26_nats_conf_jetstream_and_mqtt_contract():
|
||||
_, svc = _load_compose()
|
||||
volumes = svc.get("volumes", [])
|
||||
@@ -645,6 +668,7 @@ def test_d26_nats_conf_jetstream_and_mqtt_contract():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-27 — observer permissions match topic root
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d27_observer_permissions_match_topic_root():
|
||||
sys.path.insert(0, os.path.join(REPO_ROOT, ".agents", "skills", "multi-agent-mux-delegate-job", "scripts"))
|
||||
import mqtt_common
|
||||
@@ -666,6 +690,7 @@ def test_d27_observer_permissions_match_topic_root():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-28 — healthcheck contract and image coupling
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d28_healthcheck_contract_and_image_coupling():
|
||||
_, svc = _load_compose()
|
||||
hc = svc.get("healthcheck")
|
||||
@@ -684,6 +709,7 @@ def test_d28_healthcheck_contract_and_image_coupling():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-29 — env secrets never tracked
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d29_env_secrets_never_tracked():
|
||||
is_submodule = os.path.exists(os.path.join(REPO_ROOT, ".gitmodules")) and "nats-docker" in DOCKER_DIR
|
||||
target_repo = os.path.join(REPO_ROOT, "nats-docker") if is_submodule else REPO_ROOT
|
||||
@@ -703,6 +729,7 @@ def test_d29_env_secrets_never_tracked():
|
||||
# --------------------------------------------------------------------------
|
||||
# D-30 — websocket origin policy is startable
|
||||
# --------------------------------------------------------------------------
|
||||
@needs_docker_assets
|
||||
def test_d30_websocket_origin_policy_is_startable():
|
||||
active_lines = _active_conf_lines()
|
||||
active_text = "\n".join(active_lines)
|
||||
|
||||
Reference in new issue
Block a user