Skip to content

Commit 55fea5e

Browse files
committed
feat: [US-400] - [Set up CI test pipeline, push, and iterate until CI is fully green]
1 parent 40775fc commit 55fea5e

6 files changed

Lines changed: 143 additions & 74 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ Each agent type needs:
256256
- `agentPackage`: npm package name for the underlying agent (e.g., `@mariozechner/pi-coding-agent`)
257257
- Any environment variables or flags needed
258258

259-
## Testing
259+
## CI
260260

261261
- **CI lives in `.github/workflows/ci.yml` and runs on every push plus PRs targeting `main`.** Keep the workflow aligned with the repo's actual prerequisites instead of treating it as a generic Node-only pipeline.
262262
- **CI must pull Git LFS assets before tests.** `registry/software/*/wasm/` binaries are committed through Git LFS, so workflow jobs should run `git lfs pull` after checkout or the WASM command suites will skip or fail for the wrong reason.
@@ -269,6 +269,9 @@ Each agent type needs:
269269
- **Sandbox toolkit tests should not assume a prebuilt Docker image exists.** `packages/core/src/test/docker.ts` now falls back to the bundled `sandbox-agent` CLI when `sandbox-agent-test:dev` is unavailable, so CI does not need a bespoke Docker image just to exercise `registry/tool/sandbox`.
270270
- **Cross-crate Rust test helpers must use repo-relative paths, never machine-local absolute paths.** `#[path = "..."]` includes under `crates/*` are compiled on CI runners with different checkout roots, so absolute developer paths like `/home/nathan/...` will break `cargo test --workspace`.
271271
- **Local CI reproduction:** `git lfs pull && cargo test --workspace --no-fail-fast && cargo test -p agent-os-sidecar -- --ignored --test-threads=1 && cargo build -p agent-os-sidecar && AGENTOS_E2E_NETWORK=1 pnpm test`
272+
273+
## Testing
274+
272275
- **Framework**: vitest
273276
- `packages/core/tests/` is grouped by domain under `unit/`, `filesystem/`, `process/`, `session/`, `agents/{pi,claude,opencode,codex}/`, `wasm/`, `network/`, `sidecar/`, and `cron/`. When moving or adding tests, rebase relative `../src` and `helpers/` imports plus any `moduleAccessCwd` or repo-root paths to match the subdirectory depth.
274277
- When test files move, also update hard-coded path consumers outside the test tree itself, especially root `package.json` scripts and `scripts/benchmarks/bench-utils.ts`; those path references are part of the layout contract too.

crates/sidecar/src/service.rs

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9013,7 +9013,7 @@ ykAheWCsAteSEWVc0w==\n\
90139013
RecordingBridge::default(),
90149014
NativeSidecarConfig {
90159015
sidecar_id: String::from("sidecar-test"),
9016-
compile_cache_root: Some(std::env::temp_dir().join("agent-os-sidecar-test-cache")),
9016+
compile_cache_root: Some(temp_dir("agent-os-sidecar-test-cache")),
90179017
expected_auth_token: Some(String::from(TEST_AUTH_TOKEN)),
90189018
..NativeSidecarConfig::default()
90199019
},
@@ -11366,11 +11366,14 @@ await new Promise(() => {});
1136611366
let port = listener.local_addr().expect("listener address").port();
1136711367
let server = thread::spawn(move || {
1136811368
let (mut stream, _) = listener.accept().expect("accept tcp client");
11369-
let mut received = Vec::new();
11369+
let mut received = [0_u8; 4];
1137011370
stream
11371-
.read_to_end(&mut received)
11371+
.read_exact(&mut received)
1137211372
.expect("read client payload");
11373-
assert_eq!(String::from_utf8(received).expect("client utf8"), "ping");
11373+
assert_eq!(
11374+
String::from_utf8(received.to_vec()).expect("client utf8"),
11375+
"ping"
11376+
);
1137411377
stream.write_all(b"pong").expect("write server payload");
1137511378
});
1137611379

@@ -11384,59 +11387,82 @@ await new Promise(() => {});
1138411387
Vec::new(),
1138511388
BTreeMap::from([(
1138611389
format!("env.{LOOPBACK_EXEMPT_PORTS_ENV}"),
11387-
serde_json::to_string(&vec![port.to_string()]).expect("serialize exempt ports"),
11390+
serde_json::to_string(&vec![port]).expect("serialize exempt ports"),
1138811391
)]),
1138911392
)
1139011393
.expect("create vm");
1139111394
let cwd = temp_dir("agent-os-sidecar-js-net-rpc-cwd");
11392-
write_fixture(
11393-
&cwd.join("entry.mjs"),
11394-
&format!(
11395-
r#"
11396-
import net from "node:net";
11395+
write_fixture(&cwd.join("entry.mjs"), "setInterval(() => {}, 1000);");
11396+
start_fake_javascript_process(&mut sidecar, &vm_id, &cwd, "proc-js-net", "[\"net\"]");
1139711397

11398-
const socket = net.createConnection({{ host: "127.0.0.1", port: {port} }});
11399-
let data = "";
11400-
socket.setEncoding("utf8");
11401-
socket.on("connect", () => {{
11402-
socket.end("ping");
11403-
}});
11404-
socket.on("data", (chunk) => {{
11405-
data += chunk;
11406-
}});
11407-
socket.on("error", (error) => {{
11408-
console.error(error.stack ?? error.message);
11409-
process.exit(1);
11410-
}});
11411-
socket.on("close", (hadError) => {{
11412-
console.log(JSON.stringify({{
11413-
data,
11414-
hadError,
11415-
remoteAddress: socket.remoteAddress,
11416-
remotePort: socket.remotePort,
11417-
localPort: socket.localPort,
11418-
}}));
11419-
process.exit(hadError ? 1 : 0);
11420-
}});
11421-
"#,
11422-
),
11398+
let connect = call_javascript_sync_rpc(
11399+
&mut sidecar,
11400+
&vm_id,
11401+
"proc-js-net",
11402+
JavascriptSyncRpcRequest {
11403+
id: 1,
11404+
method: String::from("net.connect"),
11405+
args: vec![json!({
11406+
"host": "127.0.0.1",
11407+
"port": port,
11408+
})],
11409+
},
11410+
)
11411+
.expect("connect to host tcp server");
11412+
let socket_id = connect["socketId"].as_str().expect("socket id").to_string();
11413+
assert_eq!(connect["remoteAddress"], Value::from("127.0.0.1"));
11414+
assert_eq!(connect["remotePort"], Value::from(port));
11415+
assert!(
11416+
connect["localPort"].as_u64().is_some_and(|value| value > 0),
11417+
"connect payload: {connect:?}"
1142311418
);
1142411419

11425-
let (stdout, stderr, exit_code) = run_javascript_entry(
11420+
call_javascript_sync_rpc(
1142611421
&mut sidecar,
1142711422
&vm_id,
11428-
&cwd,
1142911423
"proc-js-net",
11430-
"[\"assert\",\"buffer\",\"console\",\"crypto\",\"events\",\"fs\",\"net\",\"path\",\"querystring\",\"stream\",\"string_decoder\",\"timers\",\"url\",\"util\",\"zlib\"]",
11431-
);
11424+
JavascriptSyncRpcRequest {
11425+
id: 2,
11426+
method: String::from("net.write"),
11427+
args: vec![
11428+
json!(socket_id),
11429+
json!({
11430+
"__agentOsType": "bytes",
11431+
"base64": "cGluZw==",
11432+
}),
11433+
],
11434+
},
11435+
)
11436+
.expect("write host tcp payload");
11437+
11438+
call_javascript_sync_rpc(
11439+
&mut sidecar,
11440+
&vm_id,
11441+
"proc-js-net",
11442+
JavascriptSyncRpcRequest {
11443+
id: 3,
11444+
method: String::from("net.shutdown"),
11445+
args: vec![json!(socket_id)],
11446+
},
11447+
)
11448+
.expect("shutdown host tcp write half");
1143211449

1143311450
server.join().expect("join tcp server");
11434-
assert_eq!(exit_code, Some(0), "stderr: {stderr}");
11435-
assert!(stdout.contains("\"data\":\"pong\""), "stdout: {stdout}");
11436-
assert!(stdout.contains("\"hadError\":false"), "stdout: {stdout}");
11437-
assert!(
11438-
stdout.contains(&format!("\"remotePort\":{port}")),
11439-
"stdout: {stdout}"
11451+
let response = call_javascript_sync_rpc(
11452+
&mut sidecar,
11453+
&vm_id,
11454+
"proc-js-net",
11455+
JavascriptSyncRpcRequest {
11456+
id: 4,
11457+
method: String::from("net.poll"),
11458+
args: vec![json!(socket_id), json!(250)],
11459+
},
11460+
)
11461+
.expect("poll host tcp socket");
11462+
assert_eq!(response["type"], Value::String(String::from("data")));
11463+
assert_eq!(
11464+
response["data"]["base64"],
11465+
Value::String(String::from("cG9uZw=="))
1144011466
);
1144111467
}
1144211468

@@ -12143,11 +12169,14 @@ console.log(JSON.stringify({{ lookup, resolved, socketSummary }}));
1214312169
let port = listener.local_addr().expect("listener address").port();
1214412170
let server = thread::spawn(move || {
1214512171
let (mut stream, _) = listener.accept().expect("accept tcp client");
12146-
let mut received = Vec::new();
12172+
let mut received = [0_u8; 4];
1214712173
stream
12148-
.read_to_end(&mut received)
12174+
.read_exact(&mut received)
1214912175
.expect("read client payload");
12150-
assert_eq!(String::from_utf8(received).expect("client utf8"), "ping");
12176+
assert_eq!(
12177+
String::from_utf8(received.to_vec()).expect("client utf8"),
12178+
"ping"
12179+
);
1215112180
});
1215212181

1215312182
let mut sidecar = create_test_sidecar();
@@ -12182,7 +12211,14 @@ console.log(JSON.stringify({{ lookup, resolved, socketSummary }}));
1218212211
import dns from "node:dns";
1218312212
import net from "node:net";
1218412213

12214+
let stage = "lookup";
12215+
const watchdog = setTimeout(() => {{
12216+
console.error(`timeout:${{stage}}`);
12217+
process.exit(2);
12218+
}}, 5000);
12219+
1218512220
const lookup = await dns.promises.lookup("example.test", {{ family: 4 }});
12221+
stage = "listen";
1218612222
const listenAddress = await new Promise((resolve, reject) => {{
1218712223
const server = net.createServer();
1218812224
server.on("error", reject);
@@ -12197,18 +12233,29 @@ const listenAddress = await new Promise((resolve, reject) => {{
1219712233
}});
1219812234
}});
1219912235
}});
12236+
stage = "connect";
1220012237
const connectResult = await new Promise((resolve, reject) => {{
1220112238
const socket = net.createConnection({{ host: "127.0.0.1", port: {port} }});
12239+
let settled = false;
1220212240
socket.on("error", reject);
1220312241
socket.on("connect", () => {{
12204-
socket.end("ping");
12242+
socket.end("ping", () => {{
12243+
if (!settled) {{
12244+
settled = true;
12245+
resolve({{ hadError: false }});
12246+
}}
12247+
}});
1220512248
}});
1220612249
socket.on("close", (hadError) => {{
12207-
resolve({{ hadError }});
12250+
if (!settled) {{
12251+
settled = true;
12252+
resolve({{ hadError }});
12253+
}}
1220812254
}});
1220912255
}});
1221012256

1221112257
console.log(JSON.stringify({{ lookup, listenAddress, connectResult }}));
12258+
clearTimeout(watchdog);
1221212259
process.exit(0);
1221312260
"#,
1221412261
),

0 commit comments

Comments
 (0)