Skip to content

Commit 00a41a8

Browse files
authored
fix: resolve compatibility test server startup failure (#1)
* fix: resolve compatibility test server startup failure The conftest unconditionally passed `-config devcloud.yaml`, but the file doesn't exist in the repo. When `-config` is explicit, main.go uses config.Load() which calls os.Exit(1) on missing file. Since stderr was DEVNULL, the error was invisible and _wait_for_server timed out at 30s. - Only pass `-config devcloud.yaml` when the file exists (fallback to embedded defaults) - Capture stderr and include it in the error message for debugging * test: add tests for conftest helpers and fix PIPE blocking risk Address code review comments: use DEVNULL for the long-running server process to avoid blocking, only spawn a short-lived PIPE subprocess on failure. Extract _build_devcloud_cmd and _start_server_error helpers with full test coverage. * fix: add project root to sys.path for CI import resolution * fix: use pyproject.toml pythonpath instead of sys.path hack
1 parent eebaa0e commit 00a41a8

3 files changed

Lines changed: 127 additions & 5 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.pytest.ini_options]
2+
pythonpath = ["."]

tests/compatibility/conftest.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,44 @@
1313
DEVCLOUD_URL = os.environ.get("DEVCLOUD_URL", f"http://localhost:{DEVCLOUD_PORT}")
1414

1515

16+
def _build_devcloud_cmd(project_root, bin_path):
17+
"""Build the devcloud command, conditionally adding -config if devcloud.yaml exists."""
18+
config_path = os.path.join(project_root, "devcloud.yaml")
19+
20+
if bin_path:
21+
cmd = [bin_path]
22+
else:
23+
cmd = ["go", "run", "./cmd/devcloud"]
24+
25+
if os.path.isfile(config_path):
26+
cmd.extend(["-config", "devcloud.yaml"])
27+
28+
return cmd
29+
30+
31+
def _start_server_error(cmd, project_root, env):
32+
"""Re-run server with PIPE to capture stderr, then raise with diagnostic info."""
33+
debug_proc = subprocess.Popen(
34+
cmd,
35+
cwd=project_root,
36+
env=env,
37+
stdout=subprocess.PIPE,
38+
stderr=subprocess.PIPE,
39+
)
40+
try:
41+
_wait_for_server(DEVCLOUD_URL, timeout=5)
42+
except RuntimeError:
43+
pass
44+
debug_proc.kill()
45+
debug_proc.wait()
46+
stderr = debug_proc.stderr.read().decode(errors="replace")
47+
raise RuntimeError(
48+
f"devcloud server did not start within 30s.\n"
49+
f"command: {' '.join(cmd)}\n"
50+
f"stderr:\n{stderr}"
51+
) from None
52+
53+
1654
def _wait_for_server(url, timeout=30, interval=0.5):
1755
"""Poll server until it responds or timeout."""
1856
deadline = time.time() + timeout
@@ -46,10 +84,7 @@ def devcloud_server():
4684
# collide with stale data. The directory is cleaned up after the session.
4785
data_dir = tempfile.mkdtemp(prefix="devcloud-test-")
4886

49-
if bin_path:
50-
cmd = [bin_path, "-config", "devcloud.yaml"]
51-
else:
52-
cmd = ["go", "run", "./cmd/devcloud", "-config", "devcloud.yaml"]
87+
cmd = _build_devcloud_cmd(project_root, bin_path)
5388

5489
env = os.environ.copy()
5590
env["CGO_ENABLED"] = "1"
@@ -62,7 +97,12 @@ def devcloud_server():
6297
stdout=subprocess.DEVNULL,
6398
stderr=subprocess.DEVNULL,
6499
)
65-
_wait_for_server(DEVCLOUD_URL)
100+
try:
101+
_wait_for_server(DEVCLOUD_URL)
102+
except RuntimeError:
103+
proc.kill()
104+
proc.wait()
105+
_start_server_error(cmd, project_root, env)
66106
yield proc
67107
proc.send_signal(signal.SIGINT)
68108
try:
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import io
2+
3+
from tests.compatibility.conftest import _build_devcloud_cmd, _start_server_error
4+
5+
6+
class TestBuildDevcloudCmd:
7+
def test_includes_config_when_file_exists(self, monkeypatch, tmp_path):
8+
def fake_isfile(path):
9+
return path.endswith("devcloud.yaml")
10+
11+
monkeypatch.setattr("tests.compatibility.conftest.os.path.isfile", fake_isfile)
12+
13+
cmd = _build_devcloud_cmd(str(tmp_path), None)
14+
15+
assert cmd[:3] == ["go", "run", "./cmd/devcloud"]
16+
assert "-config" in cmd
17+
assert "devcloud.yaml" in cmd
18+
19+
def test_omits_config_when_file_missing(self, monkeypatch, tmp_path):
20+
monkeypatch.setattr(
21+
"tests.compatibility.conftest.os.path.isfile", lambda p: False
22+
)
23+
24+
cmd = _build_devcloud_cmd(str(tmp_path), None)
25+
26+
assert cmd == ["go", "run", "./cmd/devcloud"]
27+
assert "-config" not in cmd
28+
assert "devcloud.yaml" not in cmd
29+
30+
def test_uses_bin_path_when_provided(self, monkeypatch, tmp_path):
31+
monkeypatch.setattr(
32+
"tests.compatibility.conftest.os.path.isfile", lambda p: False
33+
)
34+
35+
cmd = _build_devcloud_cmd(str(tmp_path), "/usr/local/bin/devcloud")
36+
37+
assert cmd == ["/usr/local/bin/devcloud"]
38+
39+
def test_bin_path_with_config(self, monkeypatch, tmp_path):
40+
def fake_isfile(path):
41+
return path.endswith("devcloud.yaml")
42+
43+
monkeypatch.setattr("tests.compatibility.conftest.os.path.isfile", fake_isfile)
44+
45+
cmd = _build_devcloud_cmd(str(tmp_path), "/usr/local/bin/devcloud")
46+
47+
assert cmd[0] == "/usr/local/bin/devcloud"
48+
assert "-config" in cmd
49+
50+
51+
class TestDevcloudServerErrorHandling:
52+
def test_raises_runtime_error_with_stderr_on_startup_failure(self, monkeypatch):
53+
fake_stderr = io.BytesIO(b"fatal: config file not found\n")
54+
55+
class FakeProc:
56+
def kill(self):
57+
pass
58+
59+
def wait(self):
60+
return 1
61+
62+
stderr = fake_stderr
63+
64+
monkeypatch.setattr(
65+
"tests.compatibility.conftest.subprocess.Popen", lambda *a, **kw: FakeProc()
66+
)
67+
monkeypatch.setattr(
68+
"tests.compatibility.conftest._wait_for_server",
69+
lambda *a, **kw: (_ for _ in ()).throw(RuntimeError("timeout")),
70+
)
71+
72+
import pytest
73+
74+
with pytest.raises(RuntimeError) as exc_info:
75+
_start_server_error(["go", "run", "./cmd/devcloud"], "/tmp/project", {})
76+
77+
msg = str(exc_info.value)
78+
assert "fatal: config file not found" in msg
79+
assert "command: go run ./cmd/devcloud" in msg
80+
assert "stderr:" in msg

0 commit comments

Comments
 (0)