Skip to content

Commit 8014191

Browse files
authored
Keep failed sandboxes in tests alive (#1428)
1 parent b6544db commit 8014191

File tree

6 files changed

+130
-126
lines changed

6 files changed

+130
-126
lines changed

tests/periodic-test/create-file.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { Sandbox } from "@e2b/code-interpreter";
2-
import { log } from "./utils.ts";
2+
import { log, runTestWithSandbox } from "./utils.ts";
33

44
const sandbox = await Sandbox.create();
55
log("ℹ️ sandbox created", sandbox.sandboxId);
66

7-
await sandbox.files.write("/hello.txt", "Hello World");
8-
log("File written");
9-
const result = await sandbox.files.read("/hello.txt");
7+
await runTestWithSandbox(sandbox, "create-file", async () => {
8+
await sandbox.files.write("/hello.txt", "Hello World");
9+
log("File written");
10+
const result = await sandbox.files.read("/hello.txt");
1011

11-
if (result !== "Hello World") {
12-
log("Failed to read file");
13-
throw new Error("Failed to create file");
14-
}
12+
if (result !== "Hello World") {
13+
log("Failed to read file");
14+
throw new Error("Failed to create file");
15+
}
1516

16-
log("File created successfully");
17+
log("File created successfully");
18+
});
Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import { Sandbox } from "@e2b/code-interpreter";
2-
import { log } from "./utils.ts";
2+
import { log, runTestWithSandbox } from "./utils.ts";
33

44
log("Starting sandbox logs test");
55

6-
let sandbox: Sandbox | null = null;
6+
// Create sandbox
7+
log("creating sandbox");
8+
const sandbox = await Sandbox.create();
9+
log("ℹ️ sandbox created", sandbox.sandboxId);
710

8-
if (Deno.env.get("E2B_DOMAIN") === "e2b-juliett.dev") {
9-
log("Skipping test on juliett.dev b/c internet is disabled");
10-
Deno.exit(0);
11-
}
12-
13-
try {
14-
// Create sandbox
15-
log("creating sandbox");
16-
sandbox = await Sandbox.create();
17-
log("ℹ️ sandbox created", sandbox.sandboxId);
18-
19-
const out = await sandbox.commands.run("wget https://www.gstatic.com/generate_204", {
20-
requestTimeoutMs: 10000,
21-
});
11+
await runTestWithSandbox(sandbox, "internet-works", async () => {
12+
const out = await sandbox.commands.run(
13+
"wget https://www.gstatic.com/generate_204",
14+
{
15+
requestTimeoutMs: 10000,
16+
}
17+
);
2218
log("wget output", out.stderr);
2319

2420
const internetWorking = out.stderr.includes("204 No Content");
@@ -29,15 +25,4 @@ try {
2925
}
3026

3127
log("Test passed successfully");
32-
} catch (error) {
33-
log("Test failed:", error);
34-
throw error;
35-
} finally {
36-
if (sandbox) {
37-
try {
38-
await sandbox.kill();
39-
} catch (error) {
40-
console.error("Error closing sandbox:", error);
41-
}
42-
}
43-
}
28+
});

tests/periodic-test/run-code.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
import { Sandbox } from "@e2b/code-interpreter";
2-
import { log } from "./utils.ts";
2+
import { log, runTestWithSandbox } from "./utils.ts";
33

4-
let sandbox: Sandbox | null = null;
4+
// Create a E2B Code Interpreter with JavaScript kernel
5+
log("creating sandbox");
6+
const sandbox = await Sandbox.create();
7+
log("ℹ️ sandbox created", sandbox.sandboxId);
58

6-
try {
7-
// Create a E2B Code Interpreter with JavaScript kernel
8-
log("creating sandbox");
9-
sandbox = await Sandbox.create();
10-
log("ℹ️ sandbox created", sandbox.sandboxId);
11-
} catch (error) {
12-
log("Test failed:", error);
13-
throw new Error("error creating sandbox", {
14-
cause: error,
15-
});
16-
}
17-
18-
try {
9+
await runTestWithSandbox(sandbox, "run-code", async () => {
1910
// Execute JavaScript cells
2011
log("running code");
2112
await sandbox.runCode("x = 1");
@@ -24,15 +15,6 @@ try {
2415
log("second code executed");
2516
// Output result
2617
log(execution.text);
27-
} catch (error) {
28-
log("Test failed:", error);
29-
throw new Error("error running code", {
30-
cause: error,
31-
});
32-
} finally {
33-
log("killing sandbox");
34-
await sandbox?.kill();
35-
log("sandbox killed");
36-
}
18+
});
3719

3820
log("done");
Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
import { Sandbox } from "@e2b/code-interpreter";
2-
import { log } from "./utils.ts";
2+
import { log, runTestWithSandbox } from "./utils.ts";
33

44
const sbx = await Sandbox.create();
55
log("ℹ️ sandbox created", sbx.sandboxId);
66

7-
await sbx.runCode("x = 1");
8-
log("Sandbox code executed");
7+
await runTestWithSandbox(sbx, "snapshot-and-resume", async () => {
8+
await sbx.runCode("x = 1");
9+
log("Sandbox code executed");
910

10-
const success = await sbx.betaPause();
11-
log("Sandbox paused", success);
11+
const success = await sbx.betaPause();
12+
log("Sandbox paused", success);
1213

13-
// Resume the sandbox from the same state
14-
const sameSbx = await Sandbox.connect(sbx.sandboxId);
15-
log("Sandbox resumed", sameSbx.sandboxId);
14+
// Resume the sandbox from the same state
15+
const sameSbx = await Sandbox.connect(sbx.sandboxId);
16+
log("Sandbox resumed", sameSbx.sandboxId);
1617

17-
const execution = await sameSbx.runCode("x+=1; x");
18-
// Output result
19-
log("RunCode Output:", execution.text);
18+
const execution = await sameSbx.runCode("x+=1; x");
19+
// Output result
20+
log("RunCode Output:", execution.text);
2021

21-
if (execution.text !== "2") {
22-
log("Test failed:", "The expected runCode output doesn't match");
23-
throw new Error("Failed to runCode in resumed sandbox");
24-
}
25-
log("Sandbox resumed successfully");
26-
27-
await sbx.kill();
28-
log("Sandbox deleted");
22+
if (execution.text !== "2") {
23+
log("Test failed:", "The expected runCode output doesn't match");
24+
throw new Error("Failed to runCode in resumed sandbox");
25+
}
26+
log("Sandbox resumed successfully");
27+
});

tests/periodic-test/time-is-synchronized/index.ts

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Sandbox } from "@e2b/code-interpreter";
2+
import { log, runTestWithSandbox } from "../utils.ts";
23

34
// Helper function to stream command output
45
async function streamCommandOutput(command: string, args: string[]) {
@@ -15,7 +16,7 @@ async function streamCommandOutput(command: string, args: string[]) {
1516

1617
const readStream = async (
1718
stream: ReadableStream<Uint8Array>,
18-
logFn: (msg: Uint8Array) => void,
19+
logFn: (msg: Uint8Array) => void
1920
) => {
2021
for await (const chunk of stream) {
2122
logFn(chunk);
@@ -38,6 +39,22 @@ async function streamCommandOutput(command: string, args: string[]) {
3839
return { status, output };
3940
}
4041

42+
async function deleteTemplate(templateID: string) {
43+
const output = await streamCommandOutput("deno", [
44+
"run",
45+
"--allow-all",
46+
"@e2b/cli",
47+
"template",
48+
"delete",
49+
"-y",
50+
templateID,
51+
]);
52+
53+
if (output.status.code !== 0) {
54+
throw new Error(`❌ Delete failed with code ${output.status.code}`);
55+
}
56+
}
57+
4158
const uniqueID = crypto.randomUUID();
4259
const templateName = `test-template-${uniqueID}`;
4360
console.log("ℹ️ templateName:", templateName);
@@ -73,53 +90,43 @@ if (!templateID) {
7390
// sleep for 15 seconds to create a time delta
7491
await new Promise((resolve) => setTimeout(resolve, 15000));
7592

76-
try {
77-
// remove the file to make script idempotent in local testing
78-
await Deno.remove("e2b.toml");
93+
// remove the file to make script idempotent in local testing
94+
await Deno.remove("e2b.toml");
7995

80-
if (!templateID) {
81-
throw new Error("❌ Template not found");
82-
}
83-
console.log("ℹ️ creating sandbox");
84-
const sandbox = await Sandbox.create(templateID, { timeoutMs: 10000 });
85-
console.log("ℹ️ sandbox created", sandbox.sandboxId);
86-
87-
console.log("ℹ️ running command");
88-
89-
console.log("ℹ️ starting command");
90-
const localDateStart = new Date().getTime() / 1000;
91-
const date = await sandbox.commands.run("date +%s%3N");
92-
const localDateEnd = new Date().getTime() / 1000;
93-
const dateUnix = parseFloat(date.stdout) / 1000;
94-
95-
console.log("local date - start of request", localDateStart);
96-
console.log("local date - end of request", localDateEnd);
97-
console.log("sandbox date", dateUnix);
98-
99-
// check if the diff between sandbox time and local time is less than 1 second (taking into consideration the request latency)
100-
if (dateUnix < localDateStart - 1 || dateUnix > localDateEnd + 1) {
101-
throw new Error("❌ Date is not synchronized");
102-
}
103-
104-
console.log("✅ date is synchronized");
96+
if (!templateID) {
97+
throw new Error("❌ Template not found");
98+
}
10599

106-
// kill sandbox
107-
await sandbox.kill();
100+
log("ℹ️ creating sandbox");
101+
let sandbox: Sandbox;
102+
try {
103+
sandbox = await Sandbox.create(templateID, { timeoutMs: 10000 });
104+
log("ℹ️ sandbox created", sandbox.sandboxId);
108105
} catch (e) {
109-
console.error("Error while running sandbox or commands", e);
106+
await deleteTemplate(templateID);
110107
throw e;
111-
} finally { // delete template
112-
const output = await streamCommandOutput("deno", [
113-
"run",
114-
"--allow-all",
115-
"@e2b/cli",
116-
"template",
117-
"delete",
118-
"-y",
119-
templateID,
120-
]);
108+
}
121109

122-
if (output.status.code !== 0) {
123-
throw new Error(`❌ Delete failed with code ${output.status.code}`);
124-
}
110+
try {
111+
await runTestWithSandbox(sandbox, "time-is-synchronized", async () => {
112+
log("ℹ️ starting command");
113+
const localDateStart = new Date().getTime() / 1000;
114+
const date = await sandbox.commands.run("date +%s%3N");
115+
const localDateEnd = new Date().getTime() / 1000;
116+
const dateUnix = parseFloat(date.stdout) / 1000;
117+
118+
log("local date - start of request", localDateStart);
119+
log("local date - end of request", localDateEnd);
120+
log("sandbox date", dateUnix);
121+
122+
// check if the diff between sandbox time and local time is less than 1 second (taking into consideration the request latency)
123+
if (dateUnix < localDateStart - 1 || dateUnix > localDateEnd + 1) {
124+
throw new Error("❌ Date is not synchronized");
125+
}
126+
127+
log("✅ date is synchronized");
128+
});
129+
} finally {
130+
// delete template
131+
await deleteTemplate(templateID);
125132
}

tests/periodic-test/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
import { Sandbox } from "@e2b/code-interpreter";
2+
3+
export const DEBUG_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
4+
15
export function log(...msg: unknown[]) {
26
console.log(new Date().toISOString(), ...msg);
37
}
8+
9+
/**
10+
* Runs a test function with a sandbox, handling errors and cleanup.
11+
* On failure, sandboxes are kept alive for debugging when E2B_TEST_DEBUG_MODE=true.
12+
*/
13+
export async function runTestWithSandbox(
14+
sandbox: Sandbox,
15+
testName: string,
16+
testFn: () => Promise<void>
17+
): Promise<void> {
18+
try {
19+
await testFn();
20+
await sandbox.kill();
21+
log(`${testName}: sandbox killed`);
22+
} catch (error) {
23+
log(`${testName} failed:`, error);
24+
if (sandbox) {
25+
await sandbox.setTimeout(DEBUG_TIMEOUT_MS);
26+
log(
27+
`\n🔍 Debug this failed sandbox:\n e2b sandbox connect ${sandbox.sandboxId}\n`
28+
);
29+
}
30+
throw new Error(`error in ${testName}`, { cause: error });
31+
}
32+
}

0 commit comments

Comments
 (0)