Skip to content

Commit 09fc635

Browse files
committed
fix(e2e): stabilize release gate harness
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
1 parent 49f3163 commit 09fc635

10 files changed

Lines changed: 112 additions & 26 deletions

test/deepagents-code-tui-startup-check.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ describe("Deep Agents Code TUI startup check helpers", () => {
6161
expect(validate("1; touch /tmp/nemoclaw-tui-timeout-injection")).toBe("invalid");
6262
});
6363

64+
it("skips non-Deep-Agents sandboxes before requiring expect", () => {
65+
const result = runTuiStartupCheckHelperResult(
66+
[
67+
"PASSED=0",
68+
"FAILED=0",
69+
"sandbox_exec() { printf 'NEMOCLAW_DCODE_PROBE:other\\n'; }",
70+
'command() { if [ "$1" = -v ] && [ "${2:-}" = expect ]; then return 1; fi; builtin command "$@"; }',
71+
"main",
72+
].join("; "),
73+
);
74+
75+
expect(result.status).toBe(0);
76+
expect(result.stdout).toContain("SKIP: sandbox");
77+
expect(result.stderr).not.toContain("expect is required");
78+
});
79+
6480
it("matches prompt-shaped TUI readiness text without accepting banner-only startup text", () => {
6581
const readiness = (capture: string) =>
6682
runTuiStartupCheckHelper(

test/e2e-scenario/fixtures/clients/provider.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export interface ProviderJsonResponse<T = unknown> {
3636
readonly result: ShellProbeResult;
3737
}
3838

39+
export interface ProviderReachabilityOptions extends ShellProbeRunOptions {
40+
readonly connectTimeoutSeconds?: number;
41+
readonly curlMaxTimeSeconds?: number;
42+
}
43+
3944
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
4045
const BLOCKED_HOSTS = new Set(["169.254.169.254", "metadata.google.internal"]);
4146

@@ -210,6 +215,28 @@ export class ProviderClient {
210215
);
211216
}
212217

218+
async probeReachability(
219+
endpoint: TrustedProviderEndpoint,
220+
options: ProviderReachabilityOptions = {},
221+
): Promise<ShellProbeResult> {
222+
const { connectTimeoutSeconds = 10, curlMaxTimeSeconds = 20, ...runOptions } = options;
223+
return await this.curl(
224+
endpoint,
225+
[
226+
"-sS",
227+
"--connect-timeout",
228+
validateCurlMaxTimeSeconds(connectTimeoutSeconds),
229+
"--max-time",
230+
validateCurlMaxTimeSeconds(curlMaxTimeSeconds),
231+
"-o",
232+
"/dev/null",
233+
"-w",
234+
"%{http_code}",
235+
],
236+
runOptions,
237+
);
238+
}
239+
213240
async requestJson<T = unknown>(
214241
endpoint: TrustedProviderEndpoint,
215242
options: ProviderJsonRequestOptions = {},

test/e2e-scenario/fixtures/redaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ const FIXTURE_ENV_ALLOWLIST: ReadonlySet<string> = new Set([
132132
"CI",
133133
"NEMOCLAW_NON_INTERACTIVE",
134134
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
135+
"NEMOCLAW_E2E_USE_HOSTED_INFERENCE",
135136
]);
136137

137138
const FIXTURE_ENV_PREFIXES: readonly string[] = ["E2E_", "NEMOCLAW_LOG_"];

test/e2e-scenario/live/hermes-e2e.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,20 +277,17 @@ test.skipIf(!shouldRunLiveE2EScenarios())(
277277

278278
expect(fs.existsSync(path.join(REPO_ROOT, "agents", "hermes", "manifest.yaml"))).toBe(true);
279279

280-
const providerModels = await provider.requestJson(
281-
trustedProviderEndpoint("https://inference-api.nvidia.com/v1/models", {
282-
allowedHosts: ["inference-api.nvidia.com"],
283-
}),
280+
const providerReachability = await provider.probeReachability(
281+
trustedProviderEndpoint(hosted.endpointUrl, { allowedHosts: ["inference-api.nvidia.com"] }),
284282
{
285-
artifactName: "phase-1-inference-models",
286-
curlMaxTimeSeconds: 15,
287-
headers: [`Authorization: Bearer ${apiKey}`],
283+
artifactName: "phase-1-inference-reachability",
288284
env: buildAvailabilityProbeEnv(),
289285
redactionValues,
290286
timeoutMs: 30_000,
291287
},
292288
);
293-
expect(providerModels.json).toBeTruthy();
289+
expect(providerReachability.exitCode, resultText(providerReachability)).toBe(0);
290+
expect(providerReachability.stdout.trim(), resultText(providerReachability)).not.toBe("000");
294291

295292
// Phase 2: real installer + non-interactive Hermes onboard.
296293
const install = await host.command("bash", ["install.sh", "--non-interactive"], {

test/e2e-scenario/live/model-router-provider-routed-inference.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,8 @@ test.skipIf(!shouldRunLiveE2EScenarios())(
108108
}
109109

110110
const apiKey = secrets.required("NVIDIA_INFERENCE_API_KEY");
111-
expect(apiKey.startsWith("nvapi-"), "NVIDIA_INFERENCE_API_KEY must start with nvapi-").toBe(
112-
true,
113-
);
111+
apiKey.startsWith("nvapi-") ||
112+
skip("provider-routed Model Router E2E requires a public NVIDIA Endpoints nvapi-* key");
114113

115114
await artifacts.writeJson("scenario.json", {
116115
id: "model-router-provider-routed-inference",
@@ -119,7 +118,7 @@ test.skipIf(!shouldRunLiveE2EScenarios())(
119118
legacySource: "test/e2e/test-model-router-provider-routed-inference.sh",
120119
contract: [
121120
"Docker is available before onboarding",
122-
"NVIDIA_INFERENCE_API_KEY is present and nvapi-prefixed",
121+
"NVIDIA_INFERENCE_API_KEY is present and nvapi-prefixed when the public routed provider is exercised",
123122
"nemoclaw onboard --fresh completes with NEMOCLAW_PROVIDER=routed",
124123
"host model-router health reports at least one healthy endpoint",
125124
"sandbox inference.local returns model nvidia-routed with PONG content",

test/e2e-scenario/live/sandbox-survival.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import path from "node:path";
1515

1616
import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
1717
import { assertExitZero, resultText, sandboxAccessEnv } from "../fixtures/clients/index.ts";
18+
import { trustedProviderEndpoint } from "../fixtures/clients/provider.ts";
1819
import { expect, test } from "../fixtures/e2e-test.ts";
1920
import { shouldRunLiveE2EScenarios } from "../fixtures/live-project-gate.ts";
2021
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
@@ -78,6 +79,7 @@ test.skipIf(!shouldRunLiveE2EScenarios())(
7879
cleanup,
7980
host,
8081
lifecycle,
82+
provider,
8183
runtime,
8284
sandbox,
8385
secrets,
@@ -115,17 +117,17 @@ test.skipIf(!shouldRunLiveE2EScenarios())(
115117
skip("Docker is required for sandbox survival E2E");
116118
}
117119

118-
const modelsReachable = await host.command(
119-
"curl",
120-
["-sf", "--max-time", "10", "https://inference-api.nvidia.com/v1/models"],
120+
const endpointReachable = await provider.probeReachability(
121+
trustedProviderEndpoint(hosted.endpointUrl, { allowedHosts: ["inference-api.nvidia.com"] }),
121122
{
122-
artifactName: "prereq-inference-api-models",
123+
artifactName: "prereq-inference-api-reachability",
123124
env: buildAvailabilityProbeEnv(),
124125
redactionValues: [apiKey],
125-
timeoutMs: 15_000,
126+
timeoutMs: 25_000,
126127
},
127128
);
128-
expect(modelsReachable.exitCode, resultText(modelsReachable)).toBe(0);
129+
expect(endpointReachable.exitCode, resultText(endpointReachable)).toBe(0);
130+
expect(endpointReachable.stdout.trim(), resultText(endpointReachable)).not.toBe("000");
129131
expect(fs.existsSync(path.join(REPO_ROOT, "install.sh"))).toBe(true);
130132

131133
await host.bestEffortCleanupSandbox(SANDBOX_NAME, {

test/e2e-scenario/live/sessions-agents-cli.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,23 @@ function parseJsonFromText(raw: string): unknown {
186186
const trimmed = line.trimStart();
187187
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
188188
const offset = cursor + line.length - trimmed.length;
189-
return JSON.parse(text.slice(offset));
189+
const candidate = text.slice(offset);
190+
const candidates = [
191+
candidate,
192+
...Array.from(candidate.matchAll(/[}\]]/g), ({ index = 0 }) =>
193+
candidate.slice(0, index + 1),
194+
).reverse(),
195+
];
196+
for (const jsonCandidate of candidates) {
197+
try {
198+
return JSON.parse(jsonCandidate);
199+
} catch {
200+
// Keep searching for the matching end of the first JSON envelope;
201+
// stderr warnings can be appended after a valid pretty-printed JSON
202+
// object when the E2E command captures diagnostics with 2>&1.
203+
}
204+
}
205+
throw new Error("JSON envelope was present but not parseable");
190206
}
191207
cursor += lineWithBreak.length;
192208
}

test/e2e-scenario/support-tests/hosted-inference.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import os from "node:os";
77
import path from "node:path";
88
import { describe, expect, it } from "vitest";
99

10+
import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts";
1011
import { startFakeOpenAiCompatibleServer } from "../fixtures/fake-openai-compatible.ts";
1112
import { requireHostedInferenceConfig } from "../fixtures/hosted-inference.ts";
1213

@@ -160,6 +161,18 @@ describe("hosted inference E2E config", () => {
160161
expect(cfg.credentialEnv).toBe("COMPATIBLE_API_KEY");
161162
});
162163

164+
it("preserves the hosted-compatible mode flag without passing source secrets by default", () => {
165+
const env = buildAvailabilityProbeEnv({
166+
HOME: "/tmp/home",
167+
PATH: "/usr/bin",
168+
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1",
169+
NVIDIA_INFERENCE_API_KEY: "repo-hosted-key",
170+
});
171+
172+
expect(env.NEMOCLAW_E2E_USE_HOSTED_INFERENCE).toBe("1");
173+
expect(env).not.toHaveProperty("NVIDIA_INFERENCE_API_KEY");
174+
});
175+
163176
it("uses a lightweight compatible reachability probe without API or auth requests", () => {
164177
const { result, calls } = runHostedProbe({
165178
env: {

test/e2e/e2e-cloud-experimental/checks/10-deepagents-code-tui-startup.sh

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ is_positive_integer() {
4444
[[ "$1" =~ ^[1-9][0-9]*$ ]]
4545
}
4646

47+
ensure_expect_available() {
48+
if command -v expect >/dev/null 2>&1; then
49+
return 0
50+
fi
51+
if [ "${GITHUB_ACTIONS:-}" = "true" ] && command -v sudo >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then
52+
info "expect is not preinstalled; installing expect for the Deep Agents Code TUI PTY check"
53+
if sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends expect; then
54+
command -v expect >/dev/null 2>&1
55+
return $?
56+
fi
57+
fi
58+
return 1
59+
}
60+
4761
contains_secret() {
4862
NEMOCLAW_TOKEN_SECRET_PATTERN="$SECRET_PATTERN" \
4963
NEMOCLAW_CONTEXT_SECRET_VALUE_PATTERN="$CONTEXT_SECRET_VALUE_PATTERN" \
@@ -194,11 +208,6 @@ main() {
194208
exit 1
195209
fi
196210

197-
if ! command -v expect >/dev/null 2>&1; then
198-
fail_test "expect is required for the Deep Agents Code TUI startup check"
199-
printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
200-
exit 1
201-
fi
202211
if ! command -v perl >/dev/null 2>&1; then
203212
fail_test "perl is required to sanitize and redact Deep Agents Code TUI captures"
204213
printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
@@ -224,6 +233,12 @@ main() {
224233
;;
225234
esac
226235

236+
if ! ensure_expect_available; then
237+
fail_test "expect is required for the Deep Agents Code TUI startup check"
238+
printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
239+
exit 1
240+
fi
241+
227242
local capture_dir raw_capture_file expect_log_file combined_capture_file plain_capture_file
228243
capture_dir="$(make_capture_dir)"
229244
raw_capture_file="${capture_dir}/${PREFIX}.raw.log"

test/e2e/test-sessions-agents-cli.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ for line in raw.splitlines(keepends=True):
145145
cursor += len(line)
146146
if offset < 0:
147147
sys.exit(1)
148-
json.loads(raw[offset:])
148+
json.JSONDecoder().raw_decode(raw[offset:])
149149
" 2>/dev/null
150150
}
151151

@@ -436,7 +436,7 @@ for line in raw.splitlines(keepends=True):
436436
cursor += len(line)
437437
if offset < 0:
438438
sys.exit(1)
439-
data = json.loads(raw[offset:])
439+
data, _ = json.JSONDecoder().raw_decode(raw[offset:])
440440
entries = data if isinstance(data, list) else data.get('agents', [])
441441
target = os.environ['TARGET']
442442
sys.exit(0 if any(entry.get('id') == target for entry in entries) else 1)

0 commit comments

Comments
 (0)