Skip to content

Commit 321f271

Browse files
committed
fix: isolate self-hosted e2e graph runs
1 parent dd970e5 commit 321f271

6 files changed

Lines changed: 83 additions & 9 deletions

File tree

.github/workflows/e2e-matrix.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ jobs:
5555
echo "VBGUI_E2E_VENV=$job_venv" >> "$GITHUB_ENV"
5656
echo "PYTHONPATH=" >> "$GITHUB_ENV"
5757
echo "PYTHONNOUSERSITE=1" >> "$GITHUB_ENV"
58+
- name: Allocate isolated E2E ports
59+
run: |
60+
port_base=$((22000 + (GITHUB_RUN_ID % 1000) * 20))
61+
port_offset=${{ matrix.shard }}
62+
echo "VBGUI_E2E_BACKEND_PORT=$((port_base + port_offset))" >> "$GITHUB_ENV"
63+
echo "VBGUI_E2E_FRONTEND_PORT=$((port_base + 10 + port_offset))" >> "$GITHUB_ENV"
5864
- name: Python deps
5965
run: |
6066
"$VBGUI_E2E_PYTHON" -m pip install --disable-pip-version-check \
@@ -122,6 +128,12 @@ jobs:
122128
echo "VBGUI_E2E_VENV=$job_venv" >> "$GITHUB_ENV"
123129
echo "PYTHONPATH=" >> "$GITHUB_ENV"
124130
echo "PYTHONNOUSERSITE=1" >> "$GITHUB_ENV"
131+
- name: Allocate isolated E2E ports
132+
run: |
133+
port_base=$((22000 + (GITHUB_RUN_ID % 1000) * 20))
134+
port_offset=8
135+
echo "VBGUI_E2E_BACKEND_PORT=$((port_base + port_offset))" >> "$GITHUB_ENV"
136+
echo "VBGUI_E2E_FRONTEND_PORT=$((port_base + 10 + port_offset))" >> "$GITHUB_ENV"
125137
- run: |
126138
"$VBGUI_E2E_PYTHON" -m pip install --disable-pip-version-check \
127139
-e ".[gui,parquet,widget]"
@@ -183,6 +195,12 @@ jobs:
183195
echo "VBGUI_E2E_VENV=$job_venv" >> "$GITHUB_ENV"
184196
echo "PYTHONPATH=" >> "$GITHUB_ENV"
185197
echo "PYTHONNOUSERSITE=1" >> "$GITHUB_ENV"
198+
- name: Allocate isolated E2E ports
199+
run: |
200+
port_base=$((22000 + (GITHUB_RUN_ID % 1000) * 20))
201+
port_offset=9
202+
echo "VBGUI_E2E_BACKEND_PORT=$((port_base + port_offset))" >> "$GITHUB_ENV"
203+
echo "VBGUI_E2E_FRONTEND_PORT=$((port_base + 10 + port_offset))" >> "$GITHUB_ENV"
186204
- run: |
187205
"$VBGUI_E2E_PYTHON" -m pip install --disable-pip-version-check \
188206
-e ".[gui,parquet,widget]"

cppmega_v4/jsonrpc/methods.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,18 @@ def _graph_to_specs(graph: GraphSpec) -> list[dict[str, Any]]:
111111
error; the caller should pre-resolve parallel-block emit on the
112112
frontend.
113113
"""
114+
# ``residual_add`` was emitted by older VBGui canvases as the wire kind
115+
# for an explicit residual join. The model/fusion contract calls that
116+
# executable identity brick ``residual``; the fan-in itself is represented
117+
# by graph edges. Accept the legacy wire spelling so saved canvases remain
118+
# runnable, but materialise only the canonical kind downstream.
119+
kind_aliases = {"residual_add": "residual"}
114120
return [
115-
{"kind": n.kind, "name": n.id, "params": dict(n.params)}
121+
{
122+
"kind": kind_aliases.get(n.kind, n.kind),
123+
"name": n.id,
124+
"params": dict(n.params),
125+
}
116126
for n in graph.nodes
117127
]
118128

@@ -747,4 +757,3 @@ def platform_get_info(
747757
available_topologies=info["available_topologies"],
748758
available_comm_backends=info["available_comm_backends"],
749759
)
750-

tests/test_workflow_runner_policy.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ def test_macos_e2e_uses_an_isolated_job_venv() -> None:
9494
assert workflow.count("native optimizer extension unavailable") == 3
9595

9696

97+
def test_macos_e2e_jobs_use_run_scoped_ports() -> None:
98+
workflow = (REPO_ROOT / ".github" / "workflows" / "e2e-matrix.yml").read_text(
99+
encoding="utf-8"
100+
)
101+
102+
assert workflow.count("Allocate isolated E2E ports") == 3
103+
assert workflow.count("GITHUB_RUN_ID % 1000") == 3
104+
assert workflow.count('echo "VBGUI_E2E_BACKEND_PORT=') == 3
105+
assert workflow.count('echo "VBGUI_E2E_FRONTEND_PORT=') == 3
106+
107+
97108
def test_build_backend_declares_mlx_imported_by_setup_py() -> None:
98109
config = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
99110

tests/v4/test_jsonrpc_methods.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from cppmega_v4.jsonrpc import LRUCache
1414
from cppmega_v4.jsonrpc.methods import (
15+
_graph_to_specs,
1516
_make_optim,
1617
build_preset_specs,
1718
probe_run,
@@ -50,6 +51,22 @@ def _simple_verify_params(**extra) -> VerifyParams:
5051
return VerifyParams.model_validate(payload)
5152

5253

54+
def test_graph_to_specs_canonicalizes_legacy_residual_add() -> None:
55+
params = _simple_verify_params(
56+
graph={
57+
"nodes": [
58+
{"id": "branch", "kind": "mlp"},
59+
{"id": "join", "kind": "residual_add"},
60+
],
61+
"edges": [{"src": "branch", "dst": "join"}],
62+
}
63+
)
64+
65+
specs = _graph_to_specs(params.graph)
66+
67+
assert [spec["kind"] for spec in specs] == ["mlp", "residual"]
68+
69+
5370
def test_make_optim_threads_mixed_precision_flag():
5471
optim = _make_optim(OptimSpecPayload(
5572
kind="adamw",

vbgui/e2e/globalSetup.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,32 @@ function spawnLogged(cmd: string, args: string[],
4343
return child;
4444
}
4545

46-
async function waitFor(url: string, label: string, timeoutMs: number) {
46+
function assertRunning(child: ChildProcess, label: string): void {
47+
if (child.exitCode !== null || child.signalCode !== null || child.pid == null) {
48+
throw new Error(
49+
`${label} exited before readiness ` +
50+
`(code=${child.exitCode}, signal=${child.signalCode})`,
51+
);
52+
}
53+
try {
54+
process.kill(child.pid, 0);
55+
} catch {
56+
throw new Error(`${label} process ${child.pid} is not running`);
57+
}
58+
}
59+
60+
async function waitFor(url: string, label: string, timeoutMs: number,
61+
child: ChildProcess) {
4762
const deadline = Date.now() + timeoutMs;
4863
let lastErr: string = "no attempts";
4964
while (Date.now() < deadline) {
65+
assertRunning(child, label);
5066
try {
5167
const res = await fetch(url);
52-
if (res.ok) return;
68+
if (res.ok) {
69+
assertRunning(child, label);
70+
return;
71+
}
5372
lastErr = `HTTP ${res.status}`;
5473
} catch (e) {
5574
lastErr = String(e);
@@ -99,7 +118,7 @@ export default async function globalSetup(): Promise<void> {
99118
}, null, 2));
100119

101120
await Promise.all([
102-
waitFor(`${BACKEND_URL}/health`, "backend", 30_000),
103-
waitFor(FRONTEND_URL, "frontend", 30_000),
121+
waitFor(`${BACKEND_URL}/health`, "backend", 30_000, handles.backend),
122+
waitFor(FRONTEND_URL, "frontend", 30_000, handles.frontend),
104123
]);
105124
}

vbgui/src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ function presetSpecsToNodes(specs: BrickSpec[], canvasWidth?: number): { nodes:
580580
id: addNodeName,
581581
type: "residual_add",
582582
position: { x: 0, y: 0 },
583-
data: { kind: "residual_add", params: {} } as any,
583+
data: { kind: "residual", params: {} } as any,
584584
});
585585

586586
branchNames.forEach((name) => {
@@ -618,7 +618,7 @@ function presetSpecsToNodes(specs: BrickSpec[], canvasWidth?: number): { nodes:
618618
id: addNodeName,
619619
type: "residual_add",
620620
position: { x: 0, y: 0 },
621-
data: { kind: "residual_add", params: {} } as any,
621+
data: { kind: "residual", params: {} } as any,
622622
});
623623

624624
edges.push({
@@ -1658,7 +1658,7 @@ export function App(): JSX.Element {
16581658
id: addNodeName,
16591659
type: "residual_add",
16601660
position: { x, y },
1661-
data: { kind: "residual_add", params: {} } as any,
1661+
data: { kind: "residual", params: {} } as any,
16621662
});
16631663

16641664
newEdges.push({

0 commit comments

Comments
 (0)