Skip to content
Merged
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.pytest.ini_options]
pythonpath = ["."]
50 changes: 45 additions & 5 deletions tests/compatibility/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,44 @@
DEVCLOUD_URL = os.environ.get("DEVCLOUD_URL", f"http://localhost:{DEVCLOUD_PORT}")


def _build_devcloud_cmd(project_root, bin_path):
"""Build the devcloud command, conditionally adding -config if devcloud.yaml exists."""
config_path = os.path.join(project_root, "devcloud.yaml")

if bin_path:
cmd = [bin_path]
else:
cmd = ["go", "run", "./cmd/devcloud"]

if os.path.isfile(config_path):
cmd.extend(["-config", "devcloud.yaml"])

return cmd


def _start_server_error(cmd, project_root, env):
"""Re-run server with PIPE to capture stderr, then raise with diagnostic info."""
debug_proc = subprocess.Popen(
cmd,
cwd=project_root,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
try:
_wait_for_server(DEVCLOUD_URL, timeout=5)
except RuntimeError:
pass
debug_proc.kill()
debug_proc.wait()
stderr = debug_proc.stderr.read().decode(errors="replace")
raise RuntimeError(
f"devcloud server did not start within 30s.\n"
f"command: {' '.join(cmd)}\n"
f"stderr:\n{stderr}"
) from None


def _wait_for_server(url, timeout=30, interval=0.5):
"""Poll server until it responds or timeout."""
deadline = time.time() + timeout
Expand Down Expand Up @@ -46,10 +84,7 @@ def devcloud_server():
# collide with stale data. The directory is cleaned up after the session.
data_dir = tempfile.mkdtemp(prefix="devcloud-test-")

if bin_path:
cmd = [bin_path, "-config", "devcloud.yaml"]
else:
cmd = ["go", "run", "./cmd/devcloud", "-config", "devcloud.yaml"]
cmd = _build_devcloud_cmd(project_root, bin_path)

env = os.environ.copy()
env["CGO_ENABLED"] = "1"
Expand All @@ -62,7 +97,12 @@ def devcloud_server():
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
_wait_for_server(DEVCLOUD_URL)
try:
_wait_for_server(DEVCLOUD_URL)
except RuntimeError:
proc.kill()
proc.wait()
_start_server_error(cmd, project_root, env)
yield proc
proc.send_signal(signal.SIGINT)
try:
Expand Down
80 changes: 80 additions & 0 deletions tests/compatibility/test_conftest_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import io

from tests.compatibility.conftest import _build_devcloud_cmd, _start_server_error


class TestBuildDevcloudCmd:
def test_includes_config_when_file_exists(self, monkeypatch, tmp_path):
def fake_isfile(path):
return path.endswith("devcloud.yaml")

monkeypatch.setattr("tests.compatibility.conftest.os.path.isfile", fake_isfile)

cmd = _build_devcloud_cmd(str(tmp_path), None)

assert cmd[:3] == ["go", "run", "./cmd/devcloud"]
assert "-config" in cmd
assert "devcloud.yaml" in cmd

def test_omits_config_when_file_missing(self, monkeypatch, tmp_path):
monkeypatch.setattr(
"tests.compatibility.conftest.os.path.isfile", lambda p: False
)

cmd = _build_devcloud_cmd(str(tmp_path), None)

assert cmd == ["go", "run", "./cmd/devcloud"]
assert "-config" not in cmd
assert "devcloud.yaml" not in cmd

def test_uses_bin_path_when_provided(self, monkeypatch, tmp_path):
monkeypatch.setattr(
"tests.compatibility.conftest.os.path.isfile", lambda p: False
)

cmd = _build_devcloud_cmd(str(tmp_path), "/usr/local/bin/devcloud")

assert cmd == ["/usr/local/bin/devcloud"]

def test_bin_path_with_config(self, monkeypatch, tmp_path):
def fake_isfile(path):
return path.endswith("devcloud.yaml")

monkeypatch.setattr("tests.compatibility.conftest.os.path.isfile", fake_isfile)

cmd = _build_devcloud_cmd(str(tmp_path), "/usr/local/bin/devcloud")

assert cmd[0] == "/usr/local/bin/devcloud"
assert "-config" in cmd


class TestDevcloudServerErrorHandling:
def test_raises_runtime_error_with_stderr_on_startup_failure(self, monkeypatch):
fake_stderr = io.BytesIO(b"fatal: config file not found\n")

class FakeProc:
def kill(self):
pass

def wait(self):
return 1

stderr = fake_stderr

monkeypatch.setattr(
"tests.compatibility.conftest.subprocess.Popen", lambda *a, **kw: FakeProc()
)
monkeypatch.setattr(
"tests.compatibility.conftest._wait_for_server",
lambda *a, **kw: (_ for _ in ()).throw(RuntimeError("timeout")),
)

import pytest

with pytest.raises(RuntimeError) as exc_info:
_start_server_error(["go", "run", "./cmd/devcloud"], "/tmp/project", {})

msg = str(exc_info.value)
assert "fatal: config file not found" in msg
assert "command: go run ./cmd/devcloud" in msg
assert "stderr:" in msg
Loading