@langchain/sandbox-standard-tests 0.0.3 → 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +3 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -3
- package/dist/{sandbox-C1ApHibz.js → sandbox-BbDX1T-X.js} +283 -210
- package/dist/sandbox-BbDX1T-X.js.map +1 -0
- package/dist/{sandbox-BX7bEJLz.d.cts → sandbox-BmvI_rV6.d.ts} +36 -7
- package/dist/{sandbox-CWDp3zl_.d.ts → sandbox-C81RxygO.d.cts} +36 -7
- package/dist/{sandbox-C22TOwhI.cjs → sandbox-D7MiOPSd.cjs} +293 -221
- package/dist/sandbox-D7MiOPSd.cjs.map +1 -0
- package/dist/vitest.cjs +3 -4
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +4 -4
- package/dist/vitest.d.ts +4 -4
- package/dist/vitest.js +2 -3
- package/dist/vitest.js.map +1 -1
- package/package.json +12 -13
- package/dist/sandbox-C1ApHibz.js.map +0 -1
- package/dist/sandbox-C22TOwhI.cjs.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adaptSandboxProtocol } from "deepagents";
|
|
1
2
|
//#region src/tests/lifecycle.ts
|
|
2
3
|
/**
|
|
3
4
|
* Register sandbox lifecycle tests (create, isRunning, close, two-step init).
|
|
@@ -7,8 +8,6 @@
|
|
|
7
8
|
*/
|
|
8
9
|
function registerLifecycleTests(getShared, config, timeout) {
|
|
9
10
|
const { describe, it, expect, beforeAll, afterAll } = config.runner;
|
|
10
|
-
const describeSkipIf = describe.skipIf ?? ((condition) => condition ? describe.skip ?? (() => {}) : describe);
|
|
11
|
-
const itSkipIf = it.skipIf ?? ((condition) => condition ? () => {} : it);
|
|
12
11
|
describe("sandbox lifecycle", () => {
|
|
13
12
|
it("should create sandbox and have a valid id", () => {
|
|
14
13
|
const shared = getShared();
|
|
@@ -20,7 +19,7 @@ function registerLifecycleTests(getShared, config, timeout) {
|
|
|
20
19
|
it("should have isRunning as true after creation", () => {
|
|
21
20
|
expect(getShared().isRunning).toBe(true);
|
|
22
21
|
}, timeout);
|
|
23
|
-
|
|
22
|
+
if (typeof config.closeSandbox === "function") describe("close", () => {
|
|
24
23
|
let tmp;
|
|
25
24
|
beforeAll(async () => {
|
|
26
25
|
tmp = await withRetry(() => config.createSandbox());
|
|
@@ -36,14 +35,14 @@ function registerLifecycleTests(getShared, config, timeout) {
|
|
|
36
35
|
expect(tmp.isRunning).toBe(false);
|
|
37
36
|
}, timeout);
|
|
38
37
|
});
|
|
39
|
-
describe("two-step initialization", () => {
|
|
38
|
+
if (config.createUninitializedSandbox) describe("two-step initialization", () => {
|
|
40
39
|
let tmp;
|
|
41
40
|
afterAll(async () => {
|
|
42
41
|
try {
|
|
43
42
|
if (tmp) await config.closeSandbox?.(tmp);
|
|
44
43
|
} catch {}
|
|
45
44
|
}, timeout);
|
|
46
|
-
|
|
45
|
+
it("should work with two-step initialization", async () => {
|
|
47
46
|
tmp = config.createUninitializedSandbox();
|
|
48
47
|
expect(tmp.isRunning).toBe(false);
|
|
49
48
|
await withRetry(() => tmp.initialize());
|
|
@@ -53,7 +52,6 @@ function registerLifecycleTests(getShared, config, timeout) {
|
|
|
53
52
|
});
|
|
54
53
|
});
|
|
55
54
|
}
|
|
56
|
-
|
|
57
55
|
//#endregion
|
|
58
56
|
//#region src/tests/command-execution.ts
|
|
59
57
|
/**
|
|
@@ -91,7 +89,42 @@ function registerCommandExecutionTests(getShared, config, timeout) {
|
|
|
91
89
|
}, timeout);
|
|
92
90
|
});
|
|
93
91
|
}
|
|
94
|
-
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/adapter.ts
|
|
94
|
+
/**
|
|
95
|
+
* Adapter to convert v1 SandboxInstance to v2 SandboxInstanceV2.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Adapt a sandbox instance (v1 or v2) to SandboxInstanceV2.
|
|
99
|
+
*
|
|
100
|
+
* This builds on {@link adaptSandboxProtocol} from deepagents, which handles
|
|
101
|
+
* the core protocol adaptation (BackendProtocol → BackendProtocolV2 and
|
|
102
|
+
* sandbox properties execute/id).
|
|
103
|
+
*
|
|
104
|
+
* This function additionally preserves test-specific properties:
|
|
105
|
+
* - `isRunning` (required)
|
|
106
|
+
* - `close` (optional)
|
|
107
|
+
* - `initialize` (optional)
|
|
108
|
+
* - `uploadFiles` (required, though optional in base protocol)
|
|
109
|
+
* - `downloadFiles` (required, though optional in base protocol)
|
|
110
|
+
*
|
|
111
|
+
* @param sandbox - Sandbox instance (v1 or v2)
|
|
112
|
+
* @returns SandboxInstanceV2-compatible sandbox
|
|
113
|
+
*/
|
|
114
|
+
function adaptSandboxInstance(sandbox) {
|
|
115
|
+
const adapted = adaptSandboxProtocol(sandbox);
|
|
116
|
+
const sb = sandbox;
|
|
117
|
+
if (typeof sb.close === "function") adapted.close = () => sb.close();
|
|
118
|
+
if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(sandbox), "isRunning")?.get) Object.defineProperty(adapted, "isRunning", {
|
|
119
|
+
get: () => sb.isRunning,
|
|
120
|
+
enumerable: true
|
|
121
|
+
});
|
|
122
|
+
else if ("isRunning" in sandbox) adapted.isRunning = sb.isRunning;
|
|
123
|
+
if (typeof sb.initialize === "function") adapted.initialize = () => sb.initialize();
|
|
124
|
+
if (typeof sb.uploadFiles === "function") adapted.uploadFiles = (files) => sb.uploadFiles(files);
|
|
125
|
+
if (typeof sb.downloadFiles === "function") adapted.downloadFiles = (paths) => sb.downloadFiles(paths);
|
|
126
|
+
return adapted;
|
|
127
|
+
}
|
|
95
128
|
//#endregion
|
|
96
129
|
//#region src/tests/file-operations.ts
|
|
97
130
|
/**
|
|
@@ -129,11 +162,11 @@ function registerFileOperationTests(getShared, config, timeout) {
|
|
|
129
162
|
expect(results[0].error).toBe("file_not_found");
|
|
130
163
|
}, timeout);
|
|
131
164
|
it("should use inherited read method from BaseSandbox", async () => {
|
|
132
|
-
const shared = getShared();
|
|
165
|
+
const shared = adaptSandboxInstance(getShared());
|
|
133
166
|
const filePath = config.resolvePath("read-test.txt");
|
|
134
167
|
const encoder = new TextEncoder();
|
|
135
168
|
await shared.uploadFiles([[filePath, encoder.encode("Read test content")]]);
|
|
136
|
-
expect(await shared.read(filePath)).toContain("Read test content");
|
|
169
|
+
expect((await shared.read(filePath)).content).toContain("Read test content");
|
|
137
170
|
}, timeout);
|
|
138
171
|
it("should use inherited write method from BaseSandbox", async () => {
|
|
139
172
|
const shared = getShared();
|
|
@@ -168,7 +201,6 @@ function registerFileOperationTests(getShared, config, timeout) {
|
|
|
168
201
|
}, timeout);
|
|
169
202
|
});
|
|
170
203
|
}
|
|
171
|
-
|
|
172
204
|
//#endregion
|
|
173
205
|
//#region src/tests/write.ts
|
|
174
206
|
/**
|
|
@@ -253,7 +285,6 @@ function registerWriteTests(getShared, config, timeout) {
|
|
|
253
285
|
}, timeout);
|
|
254
286
|
});
|
|
255
287
|
}
|
|
256
|
-
|
|
257
288
|
//#endregion
|
|
258
289
|
//#region src/tests/read.ts
|
|
259
290
|
/**
|
|
@@ -264,120 +295,119 @@ function registerReadTests(getShared, config, timeout) {
|
|
|
264
295
|
const { describe, it, expect } = config.runner;
|
|
265
296
|
describe("read", () => {
|
|
266
297
|
it("should read a file with line numbers", async () => {
|
|
267
|
-
const shared = getShared();
|
|
298
|
+
const shared = adaptSandboxInstance(getShared());
|
|
268
299
|
const filePath = config.resolvePath("rd-basic.txt");
|
|
269
300
|
await shared.write(filePath, "Line 1\nLine 2\nLine 3");
|
|
270
301
|
const result = await shared.read(filePath);
|
|
271
|
-
expect(result).
|
|
272
|
-
expect(result).toContain("Line 1");
|
|
273
|
-
expect(result).toContain("Line 2");
|
|
274
|
-
expect(result).toContain("Line 3");
|
|
302
|
+
expect(result.error).toBeUndefined();
|
|
303
|
+
expect(result.content).toContain("Line 1");
|
|
304
|
+
expect(result.content).toContain("Line 2");
|
|
305
|
+
expect(result.content).toContain("Line 3");
|
|
275
306
|
}, timeout);
|
|
276
307
|
it("should return error for nonexistent file", async () => {
|
|
277
308
|
const filePath = config.resolvePath("rd-nonexistent-xyz.txt");
|
|
278
|
-
const result = await getShared().read(filePath);
|
|
279
|
-
expect(result).
|
|
280
|
-
expect(result.toLowerCase()).toContain("not found");
|
|
309
|
+
const result = await adaptSandboxInstance(getShared()).read(filePath);
|
|
310
|
+
expect(result.error).toBeDefined();
|
|
311
|
+
expect(result.error.toLowerCase()).toContain("not found");
|
|
281
312
|
}, timeout);
|
|
282
313
|
it("should handle reading an empty file", async () => {
|
|
283
|
-
const shared = getShared();
|
|
314
|
+
const shared = adaptSandboxInstance(getShared());
|
|
284
315
|
const filePath = config.resolvePath("rd-empty.txt");
|
|
285
316
|
await shared.write(filePath, "");
|
|
286
|
-
expect((await shared.read(filePath)).
|
|
317
|
+
expect((await shared.read(filePath)).error).toBeUndefined();
|
|
287
318
|
}, timeout);
|
|
288
319
|
it("should read with offset parameter", async () => {
|
|
289
|
-
const shared = getShared();
|
|
320
|
+
const shared = adaptSandboxInstance(getShared());
|
|
290
321
|
const filePath = config.resolvePath("rd-offset.txt");
|
|
291
322
|
const content = Array.from({ length: 10 }, (_, i) => `Row_${i + 1}_content`).join("\n");
|
|
292
323
|
await shared.write(filePath, content);
|
|
293
324
|
const result = await shared.read(filePath, 5);
|
|
294
|
-
expect(result).toContain("Row_6_content");
|
|
295
|
-
expect(result).not.toContain("Row_1_content");
|
|
325
|
+
expect(result.content).toContain("Row_6_content");
|
|
326
|
+
expect(result.content).not.toContain("Row_1_content");
|
|
296
327
|
}, timeout);
|
|
297
328
|
it("should read with limit parameter", async () => {
|
|
298
|
-
const shared = getShared();
|
|
329
|
+
const shared = adaptSandboxInstance(getShared());
|
|
299
330
|
const filePath = config.resolvePath("rd-limit.txt");
|
|
300
331
|
const content = Array.from({ length: 100 }, (_, i) => `Row_${i + 1}_content`).join("\n");
|
|
301
332
|
await shared.write(filePath, content);
|
|
302
333
|
const result = await shared.read(filePath, 0, 5);
|
|
303
|
-
expect(result).toContain("Row_1_content");
|
|
304
|
-
expect(result).toContain("Row_5_content");
|
|
305
|
-
expect(result).not.toContain("Row_6_content");
|
|
334
|
+
expect(result.content).toContain("Row_1_content");
|
|
335
|
+
expect(result.content).toContain("Row_5_content");
|
|
336
|
+
expect(result.content).not.toContain("Row_6_content");
|
|
306
337
|
}, timeout);
|
|
307
338
|
it("should read with both offset and limit", async () => {
|
|
308
|
-
const shared = getShared();
|
|
339
|
+
const shared = adaptSandboxInstance(getShared());
|
|
309
340
|
const filePath = config.resolvePath("rd-offset-limit.txt");
|
|
310
341
|
const content = Array.from({ length: 20 }, (_, i) => `Row_${i + 1}_content`).join("\n");
|
|
311
342
|
await shared.write(filePath, content);
|
|
312
343
|
const result = await shared.read(filePath, 10, 5);
|
|
313
|
-
expect(result).toContain("Row_11_content");
|
|
314
|
-
expect(result).toContain("Row_15_content");
|
|
315
|
-
expect(result).not.toContain("Row_10_content");
|
|
316
|
-
expect(result).not.toContain("Row_16_content");
|
|
344
|
+
expect(result.content).toContain("Row_11_content");
|
|
345
|
+
expect(result.content).toContain("Row_15_content");
|
|
346
|
+
expect(result.content).not.toContain("Row_10_content");
|
|
347
|
+
expect(result.content).not.toContain("Row_16_content");
|
|
317
348
|
}, timeout);
|
|
318
349
|
it("should read unicode content", async () => {
|
|
319
|
-
const shared = getShared();
|
|
350
|
+
const shared = adaptSandboxInstance(getShared());
|
|
320
351
|
const filePath = config.resolvePath("rd-unicode.txt");
|
|
321
352
|
await shared.write(filePath, "Hello 👋 世界\nПривет мир\nمرحبا العالم");
|
|
322
353
|
const result = await shared.read(filePath);
|
|
323
|
-
expect(result).
|
|
324
|
-
expect(result).toContain("👋");
|
|
325
|
-
expect(result).toContain("世界");
|
|
326
|
-
expect(result).toContain("Привет");
|
|
354
|
+
expect(result.error).toBeUndefined();
|
|
355
|
+
expect(result.content).toContain("👋");
|
|
356
|
+
expect(result.content).toContain("世界");
|
|
357
|
+
expect(result.content).toContain("Привет");
|
|
327
358
|
}, timeout);
|
|
328
359
|
it("should handle files with very long lines", async () => {
|
|
329
|
-
const shared = getShared();
|
|
360
|
+
const shared = adaptSandboxInstance(getShared());
|
|
330
361
|
const filePath = config.resolvePath("rd-long-lines.txt");
|
|
331
362
|
const content = `Short line\n${"x".repeat(3e3)}\nAnother short line`;
|
|
332
363
|
await shared.write(filePath, content);
|
|
333
364
|
const result = await shared.read(filePath);
|
|
334
|
-
expect(result).
|
|
335
|
-
expect(result).toContain("Short line");
|
|
365
|
+
expect(result.error).toBeUndefined();
|
|
366
|
+
expect(result.content).toContain("Short line");
|
|
336
367
|
}, timeout);
|
|
337
368
|
it("should return nothing with limit=0", async () => {
|
|
338
|
-
const shared = getShared();
|
|
369
|
+
const shared = adaptSandboxInstance(getShared());
|
|
339
370
|
const filePath = config.resolvePath("rd-zero-limit.txt");
|
|
340
371
|
await shared.write(filePath, "Line 1\nLine 2\nLine 3");
|
|
341
|
-
expect(await shared.read(filePath, 0, 0)).not.toContain("Line 1");
|
|
372
|
+
expect((await shared.read(filePath, 0, 0)).content).not.toContain("Line 1");
|
|
342
373
|
}, timeout);
|
|
343
374
|
it("should handle offset beyond file length", async () => {
|
|
344
|
-
const shared = getShared();
|
|
375
|
+
const shared = adaptSandboxInstance(getShared());
|
|
345
376
|
const filePath = config.resolvePath("rd-offset-beyond.txt");
|
|
346
377
|
await shared.write(filePath, "Line 1\nLine 2\nLine 3");
|
|
347
378
|
const result = await shared.read(filePath, 100, 10);
|
|
348
|
-
expect(result).not.toContain("Line 1");
|
|
349
|
-
expect(result).not.toContain("Line 2");
|
|
350
|
-
expect(result).not.toContain("Line 3");
|
|
379
|
+
expect(result.content ?? "").not.toContain("Line 1");
|
|
380
|
+
expect(result.content ?? "").not.toContain("Line 2");
|
|
381
|
+
expect(result.content ?? "").not.toContain("Line 3");
|
|
351
382
|
}, timeout);
|
|
352
383
|
it("should handle offset exactly at file length", async () => {
|
|
353
|
-
const shared = getShared();
|
|
384
|
+
const shared = adaptSandboxInstance(getShared());
|
|
354
385
|
const filePath = config.resolvePath("rd-offset-exact.txt");
|
|
355
386
|
const content = Array.from({ length: 5 }, (_, i) => `Line ${i + 1}`).join("\n");
|
|
356
387
|
await shared.write(filePath, content);
|
|
357
388
|
const result = await shared.read(filePath, 5, 10);
|
|
358
|
-
expect(result).not.toContain("Line 1");
|
|
359
|
-
expect(result).not.toContain("Line 5");
|
|
389
|
+
expect(result.content ?? "").not.toContain("Line 1");
|
|
390
|
+
expect(result.content ?? "").not.toContain("Line 5");
|
|
360
391
|
}, timeout);
|
|
361
392
|
it("should read a large file in chunks", async () => {
|
|
362
|
-
const shared = getShared();
|
|
393
|
+
const shared = adaptSandboxInstance(getShared());
|
|
363
394
|
const filePath = config.resolvePath("rd-chunked.txt");
|
|
364
395
|
const content = Array.from({ length: 1e3 }, (_, i) => `Line_${String(i).padStart(4, "0")}_content`).join("\n");
|
|
365
396
|
await shared.write(filePath, content);
|
|
366
397
|
const chunk1 = await shared.read(filePath, 0, 100);
|
|
367
|
-
expect(chunk1).toContain("Line_0000_content");
|
|
368
|
-
expect(chunk1).toContain("Line_0099_content");
|
|
369
|
-
expect(chunk1).not.toContain("Line_0100_content");
|
|
398
|
+
expect(chunk1.content).toContain("Line_0000_content");
|
|
399
|
+
expect(chunk1.content).toContain("Line_0099_content");
|
|
400
|
+
expect(chunk1.content).not.toContain("Line_0100_content");
|
|
370
401
|
const chunk2 = await shared.read(filePath, 500, 100);
|
|
371
|
-
expect(chunk2).toContain("Line_0500_content");
|
|
372
|
-
expect(chunk2).toContain("Line_0599_content");
|
|
373
|
-
expect(chunk2).not.toContain("Line_0499_content");
|
|
402
|
+
expect(chunk2.content).toContain("Line_0500_content");
|
|
403
|
+
expect(chunk2.content).toContain("Line_0599_content");
|
|
404
|
+
expect(chunk2.content).not.toContain("Line_0499_content");
|
|
374
405
|
const chunk3 = await shared.read(filePath, 900, 100);
|
|
375
|
-
expect(chunk3).toContain("Line_0900_content");
|
|
376
|
-
expect(chunk3).toContain("Line_0999_content");
|
|
406
|
+
expect(chunk3.content).toContain("Line_0900_content");
|
|
407
|
+
expect(chunk3.content).toContain("Line_0999_content");
|
|
377
408
|
}, timeout);
|
|
378
409
|
});
|
|
379
410
|
}
|
|
380
|
-
|
|
381
411
|
//#endregion
|
|
382
412
|
//#region src/tests/edit.ts
|
|
383
413
|
/**
|
|
@@ -389,26 +419,26 @@ function registerEditTests(getShared, config, timeout) {
|
|
|
389
419
|
const { describe, it, expect } = config.runner;
|
|
390
420
|
describe("edit", () => {
|
|
391
421
|
it("should edit a single occurrence", async () => {
|
|
392
|
-
const shared = getShared();
|
|
422
|
+
const shared = adaptSandboxInstance(getShared());
|
|
393
423
|
const filePath = config.resolvePath("ed-single.txt");
|
|
394
424
|
await shared.write(filePath, "Hello world\nGoodbye world\nHello again");
|
|
395
425
|
const result = await shared.edit(filePath, "Goodbye", "Farewell");
|
|
396
426
|
expect(result.error).toBeUndefined();
|
|
397
427
|
expect(result.occurrences).toBe(1);
|
|
398
428
|
const content = await shared.read(filePath);
|
|
399
|
-
expect(content).toContain("Farewell world");
|
|
400
|
-
expect(content).not.toContain("Goodbye");
|
|
429
|
+
expect(content.content).toContain("Farewell world");
|
|
430
|
+
expect(content.content).not.toContain("Goodbye");
|
|
401
431
|
}, timeout);
|
|
402
432
|
it("should fail with multiple occurrences without replaceAll", async () => {
|
|
403
|
-
const shared = getShared();
|
|
433
|
+
const shared = adaptSandboxInstance(getShared());
|
|
404
434
|
const filePath = config.resolvePath("ed-multi-fail.txt");
|
|
405
435
|
await shared.write(filePath, "apple\nbanana\napple\norange\napple");
|
|
406
436
|
const result = await shared.edit(filePath, "apple", "pear", false);
|
|
407
437
|
expect(result.error).toBeDefined();
|
|
408
438
|
expect(result.error.toLowerCase()).toContain("multiple");
|
|
409
439
|
const content = await shared.read(filePath);
|
|
410
|
-
expect(content).toContain("apple");
|
|
411
|
-
expect(content).not.toContain("pear");
|
|
440
|
+
expect(content.content).toContain("apple");
|
|
441
|
+
expect(content.content).not.toContain("pear");
|
|
412
442
|
}, timeout);
|
|
413
443
|
it("should replace all occurrences with replaceAll=true", async () => {
|
|
414
444
|
const shared = getShared();
|
|
@@ -436,70 +466,70 @@ function registerEditTests(getShared, config, timeout) {
|
|
|
436
466
|
expect(result.error.toLowerCase()).toContain("not found");
|
|
437
467
|
}, timeout);
|
|
438
468
|
it("should handle special characters and regex metacharacters", async () => {
|
|
439
|
-
const shared = getShared();
|
|
469
|
+
const shared = adaptSandboxInstance(getShared());
|
|
440
470
|
const filePath = config.resolvePath("ed-special.txt");
|
|
441
471
|
await shared.write(filePath, "Price: $100.00\nPattern: [a-z]*\nPath: /usr/bin");
|
|
442
472
|
expect((await shared.edit(filePath, "$100.00", "$200.00")).error).toBeUndefined();
|
|
443
473
|
expect((await shared.edit(filePath, "[a-z]*", "[0-9]+")).error).toBeUndefined();
|
|
444
474
|
const content = await shared.read(filePath);
|
|
445
|
-
expect(content).toContain("$200.00");
|
|
446
|
-
expect(content).toContain("[0-9]+");
|
|
475
|
+
expect(content.content).toContain("$200.00");
|
|
476
|
+
expect(content.content).toContain("[0-9]+");
|
|
447
477
|
}, timeout);
|
|
448
478
|
it("should handle multiline string replacement", async () => {
|
|
449
|
-
const shared = getShared();
|
|
479
|
+
const shared = adaptSandboxInstance(getShared());
|
|
450
480
|
const filePath = config.resolvePath("ed-multiline.txt");
|
|
451
481
|
await shared.write(filePath, "Line 1\nLine 2\nLine 3");
|
|
452
482
|
const result = await shared.edit(filePath, "Line 1\nLine 2", "Combined");
|
|
453
483
|
expect(result.error).toBeUndefined();
|
|
454
484
|
expect(result.occurrences).toBe(1);
|
|
455
485
|
const content = await shared.read(filePath);
|
|
456
|
-
expect(content).toContain("Combined");
|
|
457
|
-
expect(content).toContain("Line 3");
|
|
458
|
-
expect(content).not.toContain("Line 1");
|
|
486
|
+
expect(content.content).toContain("Combined");
|
|
487
|
+
expect(content.content).toContain("Line 3");
|
|
488
|
+
expect(content.content).not.toContain("Line 1");
|
|
459
489
|
}, timeout);
|
|
460
490
|
it("should delete content by replacing with empty string", async () => {
|
|
461
|
-
const shared = getShared();
|
|
491
|
+
const shared = adaptSandboxInstance(getShared());
|
|
462
492
|
const filePath = config.resolvePath("ed-delete.txt");
|
|
463
493
|
await shared.write(filePath, "Keep this\nDelete this part\nKeep this too");
|
|
464
494
|
const result = await shared.edit(filePath, "Delete this part\n", "");
|
|
465
495
|
expect(result.error).toBeUndefined();
|
|
466
496
|
expect(result.occurrences).toBe(1);
|
|
467
497
|
const content = await shared.read(filePath);
|
|
468
|
-
expect(content).toContain("Keep this");
|
|
469
|
-
expect(content).toContain("Keep this too");
|
|
470
|
-
expect(content).not.toContain("Delete this part");
|
|
498
|
+
expect(content.content).toContain("Keep this");
|
|
499
|
+
expect(content.content).toContain("Keep this too");
|
|
500
|
+
expect(content.content).not.toContain("Delete this part");
|
|
471
501
|
}, timeout);
|
|
472
502
|
it("should handle identical old and new strings", async () => {
|
|
473
|
-
const shared = getShared();
|
|
503
|
+
const shared = adaptSandboxInstance(getShared());
|
|
474
504
|
const filePath = config.resolvePath("ed-identical.txt");
|
|
475
505
|
await shared.write(filePath, "Same text");
|
|
476
506
|
const result = await shared.edit(filePath, "Same text", "Same text");
|
|
477
507
|
expect(result.error).toBeUndefined();
|
|
478
508
|
expect(result.occurrences).toBe(1);
|
|
479
|
-
expect(await shared.read(filePath)).toContain("Same text");
|
|
509
|
+
expect((await shared.read(filePath)).content).toContain("Same text");
|
|
480
510
|
}, timeout);
|
|
481
511
|
it("should handle unicode content", async () => {
|
|
482
|
-
const shared = getShared();
|
|
512
|
+
const shared = adaptSandboxInstance(getShared());
|
|
483
513
|
const filePath = config.resolvePath("ed-unicode.txt");
|
|
484
514
|
await shared.write(filePath, "Hello 👋 world\n世界 is beautiful");
|
|
485
515
|
const result = await shared.edit(filePath, "👋", "🌍");
|
|
486
516
|
expect(result.error).toBeUndefined();
|
|
487
517
|
expect(result.occurrences).toBe(1);
|
|
488
518
|
const content = await shared.read(filePath);
|
|
489
|
-
expect(content).toContain("🌍");
|
|
490
|
-
expect(content).not.toContain("👋");
|
|
519
|
+
expect(content.content).toContain("🌍");
|
|
520
|
+
expect(content.content).not.toContain("👋");
|
|
491
521
|
}, timeout);
|
|
492
522
|
it("should handle whitespace-only strings", async () => {
|
|
493
|
-
const shared = getShared();
|
|
523
|
+
const shared = adaptSandboxInstance(getShared());
|
|
494
524
|
const filePath = config.resolvePath("ed-whitespace.txt");
|
|
495
525
|
await shared.write(filePath, "Line1 Line2");
|
|
496
526
|
const result = await shared.edit(filePath, " ", " ");
|
|
497
527
|
expect(result.error).toBeUndefined();
|
|
498
528
|
expect(result.occurrences).toBe(1);
|
|
499
|
-
expect(await shared.read(filePath)).toContain("Line1 Line2");
|
|
529
|
+
expect((await shared.read(filePath)).content).toContain("Line1 Line2");
|
|
500
530
|
}, timeout);
|
|
501
531
|
it("should handle very long old and new strings", async () => {
|
|
502
|
-
const shared = getShared();
|
|
532
|
+
const shared = adaptSandboxInstance(getShared());
|
|
503
533
|
const filePath = config.resolvePath("ed-long.txt");
|
|
504
534
|
const oldString = "x".repeat(1e3);
|
|
505
535
|
const newString = "y".repeat(1e3);
|
|
@@ -508,18 +538,18 @@ function registerEditTests(getShared, config, timeout) {
|
|
|
508
538
|
expect(result.error).toBeUndefined();
|
|
509
539
|
expect(result.occurrences).toBe(1);
|
|
510
540
|
const content = await shared.read(filePath);
|
|
511
|
-
expect(content).toContain("y".repeat(100));
|
|
512
|
-
expect(content).not.toContain("x".repeat(100));
|
|
541
|
+
expect(content.content).toContain("y".repeat(100));
|
|
542
|
+
expect(content.content).not.toContain("x".repeat(100));
|
|
513
543
|
}, timeout);
|
|
514
544
|
it("should preserve line endings correctly", async () => {
|
|
515
|
-
const shared = getShared();
|
|
545
|
+
const shared = adaptSandboxInstance(getShared());
|
|
516
546
|
const filePath = config.resolvePath("ed-line-endings.txt");
|
|
517
547
|
await shared.write(filePath, "Line 1\nLine 2\nLine 3\n");
|
|
518
548
|
expect((await shared.edit(filePath, "Line 2", "Modified Line 2")).error).toBeUndefined();
|
|
519
549
|
const content = await shared.read(filePath);
|
|
520
|
-
expect(content).toContain("Line 1");
|
|
521
|
-
expect(content).toContain("Modified Line 2");
|
|
522
|
-
expect(content).toContain("Line 3");
|
|
550
|
+
expect(content.content).toContain("Line 1");
|
|
551
|
+
expect(content.content).toContain("Modified Line 2");
|
|
552
|
+
expect(content.content).toContain("Line 3");
|
|
523
553
|
}, timeout);
|
|
524
554
|
it("should edit a substring within a line", async () => {
|
|
525
555
|
const shared = getShared();
|
|
@@ -532,32 +562,35 @@ function registerEditTests(getShared, config, timeout) {
|
|
|
532
562
|
}, timeout);
|
|
533
563
|
});
|
|
534
564
|
}
|
|
535
|
-
|
|
536
565
|
//#endregion
|
|
537
566
|
//#region src/tests/ls-info.ts
|
|
538
567
|
/**
|
|
539
|
-
* Register
|
|
568
|
+
* Register ls() tests (absolute paths, files + subdirs, empty dir,
|
|
540
569
|
* nonexistent dir, hidden files, spaces, unicode, large dir, trailing slash,
|
|
541
570
|
* special characters).
|
|
542
571
|
*/
|
|
543
572
|
function registerLsInfoTests(getShared, config, timeout) {
|
|
544
573
|
const { describe, it, expect } = config.runner;
|
|
545
|
-
describe("
|
|
574
|
+
describe("ls", () => {
|
|
546
575
|
it("should return absolute paths", async () => {
|
|
547
|
-
const shared = getShared();
|
|
576
|
+
const shared = adaptSandboxInstance(getShared());
|
|
548
577
|
const baseDir = config.resolvePath("li-absolute");
|
|
549
578
|
await shared.write(`${baseDir}/file.txt`, "content");
|
|
550
|
-
const
|
|
579
|
+
const lsResult = await shared.ls(baseDir);
|
|
580
|
+
expect(lsResult.error).toBeUndefined();
|
|
581
|
+
const result = lsResult.files || [];
|
|
551
582
|
expect(result.length).toBe(1);
|
|
552
583
|
expect(result[0].path).toBe(`${baseDir}/file.txt`);
|
|
553
584
|
}, timeout);
|
|
554
585
|
it("should list files and subdirectories", async () => {
|
|
555
|
-
const shared = getShared();
|
|
586
|
+
const shared = adaptSandboxInstance(getShared());
|
|
556
587
|
const baseDir = config.resolvePath("li-basic");
|
|
557
588
|
await shared.write(`${baseDir}/file1.txt`, "content1");
|
|
558
589
|
await shared.write(`${baseDir}/file2.txt`, "content2");
|
|
559
590
|
await shared.execute(`mkdir -p '${baseDir}/subdir'`);
|
|
560
|
-
const
|
|
591
|
+
const lsResult = await shared.ls(baseDir);
|
|
592
|
+
expect(lsResult.error).toBeUndefined();
|
|
593
|
+
const result = lsResult.files || [];
|
|
561
594
|
expect(result.length).toBe(3);
|
|
562
595
|
const paths = result.map((info) => info.path.replace(/\/$/, ""));
|
|
563
596
|
expect(paths).toContain(`${baseDir}/file1.txt`);
|
|
@@ -567,86 +600,101 @@ function registerLsInfoTests(getShared, config, timeout) {
|
|
|
567
600
|
else expect(info.is_dir).toBe(false);
|
|
568
601
|
}, timeout);
|
|
569
602
|
it("should return empty list for empty directory", async () => {
|
|
570
|
-
const shared = getShared();
|
|
603
|
+
const shared = adaptSandboxInstance(getShared());
|
|
571
604
|
const emptyDir = config.resolvePath("li-empty-dir");
|
|
572
605
|
await shared.execute(`mkdir -p '${emptyDir}'`);
|
|
573
|
-
|
|
606
|
+
const lsResult = await shared.ls(emptyDir);
|
|
607
|
+
expect(lsResult.error).toBeUndefined();
|
|
608
|
+
expect(lsResult.files).toEqual([]);
|
|
574
609
|
}, timeout);
|
|
575
610
|
it("should return empty list for nonexistent directory", async () => {
|
|
576
611
|
const nonexistentDir = config.resolvePath("li-does-not-exist-12345");
|
|
577
|
-
|
|
612
|
+
const lsResult = await adaptSandboxInstance(getShared()).ls(nonexistentDir);
|
|
613
|
+
expect(lsResult.error).toBeUndefined();
|
|
614
|
+
expect(lsResult.files).toEqual([]);
|
|
578
615
|
}, timeout);
|
|
579
616
|
it("should include hidden files", async () => {
|
|
580
|
-
const shared = getShared();
|
|
617
|
+
const shared = adaptSandboxInstance(getShared());
|
|
581
618
|
const baseDir = config.resolvePath("li-hidden");
|
|
582
619
|
await shared.write(`${baseDir}/.hidden`, "hidden content");
|
|
583
620
|
await shared.write(`${baseDir}/visible.txt`, "visible content");
|
|
584
|
-
const
|
|
621
|
+
const lsResult = await shared.ls(baseDir);
|
|
622
|
+
expect(lsResult.error).toBeUndefined();
|
|
623
|
+
const paths = (lsResult.files || []).map((info) => info.path);
|
|
585
624
|
expect(paths).toContain(`${baseDir}/.hidden`);
|
|
586
625
|
expect(paths).toContain(`${baseDir}/visible.txt`);
|
|
587
626
|
}, timeout);
|
|
588
627
|
it("should handle directories with spaces in names", async () => {
|
|
589
|
-
const shared = getShared();
|
|
628
|
+
const shared = adaptSandboxInstance(getShared());
|
|
590
629
|
const baseDir = config.resolvePath("li-spaces");
|
|
591
630
|
await shared.write(`${baseDir}/file with spaces.txt`, "content");
|
|
592
631
|
await shared.execute(`mkdir -p '${baseDir}/dir with spaces'`);
|
|
593
|
-
const
|
|
632
|
+
const lsResult = await shared.ls(baseDir);
|
|
633
|
+
expect(lsResult.error).toBeUndefined();
|
|
634
|
+
const paths = (lsResult.files || []).map((info) => info.path.replace(/\/$/, ""));
|
|
594
635
|
expect(paths).toContain(`${baseDir}/file with spaces.txt`);
|
|
595
636
|
expect(paths).toContain(`${baseDir}/dir with spaces`);
|
|
596
637
|
}, timeout);
|
|
597
638
|
it("should handle unicode filenames", async () => {
|
|
598
|
-
const shared = getShared();
|
|
639
|
+
const shared = adaptSandboxInstance(getShared());
|
|
599
640
|
const baseDir = config.resolvePath("li-unicode");
|
|
600
641
|
await shared.write(`${baseDir}/\u6D4B\u8BD5\u6587\u4EF6.txt`, "content");
|
|
601
642
|
await shared.write(`${baseDir}/\u0444\u0430\u0439\u043B.txt`, "content");
|
|
602
|
-
|
|
643
|
+
const lsResult = await shared.ls(baseDir);
|
|
644
|
+
expect(lsResult.error).toBeUndefined();
|
|
645
|
+
expect((lsResult.files || []).length).toBe(2);
|
|
603
646
|
}, timeout);
|
|
604
647
|
it("should handle large directories", async () => {
|
|
605
|
-
const shared = getShared();
|
|
648
|
+
const shared = adaptSandboxInstance(getShared());
|
|
606
649
|
const baseDir = config.resolvePath("li-large");
|
|
607
650
|
await shared.execute(`mkdir -p '${baseDir}' && cd '${baseDir}' && for i in \$(seq 0 49); do echo 'content' > file_\$(printf '%03d' \$i).txt; done`);
|
|
608
|
-
const
|
|
651
|
+
const lsResult = await shared.ls(baseDir);
|
|
652
|
+
expect(lsResult.error).toBeUndefined();
|
|
653
|
+
const result = lsResult.files || [];
|
|
609
654
|
expect(result.length).toBe(50);
|
|
610
655
|
const paths = result.map((info) => info.path);
|
|
611
656
|
expect(paths).toContain(`${baseDir}/file_000.txt`);
|
|
612
657
|
expect(paths).toContain(`${baseDir}/file_049.txt`);
|
|
613
658
|
}, timeout);
|
|
614
659
|
it("should handle trailing slash in path", async () => {
|
|
615
|
-
const shared = getShared();
|
|
660
|
+
const shared = adaptSandboxInstance(getShared());
|
|
616
661
|
const baseDir = config.resolvePath("li-trailing");
|
|
617
662
|
await shared.write(`${baseDir}/file.txt`, "content");
|
|
618
|
-
|
|
663
|
+
const lsResult = await shared.ls(`${baseDir}/`);
|
|
664
|
+
expect(lsResult.error).toBeUndefined();
|
|
665
|
+
expect((lsResult.files || []).length).toBeGreaterThanOrEqual(1);
|
|
619
666
|
}, timeout);
|
|
620
667
|
it("should handle special characters in filenames", async () => {
|
|
621
|
-
const shared = getShared();
|
|
668
|
+
const shared = adaptSandboxInstance(getShared());
|
|
622
669
|
const baseDir = config.resolvePath("li-special-chars");
|
|
623
670
|
await shared.write(`${baseDir}/file(1).txt`, "content");
|
|
624
671
|
await shared.write(`${baseDir}/file-3.txt`, "content");
|
|
625
|
-
const
|
|
672
|
+
const lsResult = await shared.ls(baseDir);
|
|
673
|
+
expect(lsResult.error).toBeUndefined();
|
|
674
|
+
const paths = (lsResult.files || []).map((info) => info.path);
|
|
626
675
|
expect(paths).toContain(`${baseDir}/file(1).txt`);
|
|
627
676
|
expect(paths).toContain(`${baseDir}/file-3.txt`);
|
|
628
677
|
}, timeout);
|
|
629
678
|
});
|
|
630
679
|
}
|
|
631
|
-
|
|
632
680
|
//#endregion
|
|
633
681
|
//#region src/tests/grep-raw.ts
|
|
634
682
|
/**
|
|
635
|
-
* Register
|
|
683
|
+
* Register grep() tests (basic search, glob filter, no matches,
|
|
636
684
|
* multi matches, literal matching, unicode, case sensitivity, special chars,
|
|
637
685
|
* empty dir, nested dirs, line numbers).
|
|
638
686
|
*/
|
|
639
687
|
function registerGrepRawTests(getShared, config, timeout) {
|
|
640
688
|
const { describe, it, expect } = config.runner;
|
|
641
|
-
describe("
|
|
689
|
+
describe("grep", () => {
|
|
642
690
|
it("should find basic literal pattern matches", async () => {
|
|
643
|
-
const shared = getShared();
|
|
691
|
+
const shared = adaptSandboxInstance(getShared());
|
|
644
692
|
const baseDir = config.resolvePath("gr-basic");
|
|
645
693
|
await shared.write(`${baseDir}/file1.txt`, "Hello world\nGoodbye world");
|
|
646
694
|
await shared.write(`${baseDir}/file2.txt`, "Hello there\nGoodbye friend");
|
|
647
|
-
const result = await shared.
|
|
648
|
-
expect(
|
|
649
|
-
const matches = result;
|
|
695
|
+
const result = await shared.grep("Hello", baseDir);
|
|
696
|
+
expect(result.error).toBeUndefined();
|
|
697
|
+
const matches = result.matches;
|
|
650
698
|
expect(matches.length).toBe(2);
|
|
651
699
|
const paths = matches.map((m) => m.path);
|
|
652
700
|
expect(paths.some((p) => p.includes("file1.txt"))).toBe(true);
|
|
@@ -657,32 +705,32 @@ function registerGrepRawTests(getShared, config, timeout) {
|
|
|
657
705
|
}
|
|
658
706
|
}, timeout);
|
|
659
707
|
it("should filter files with glob pattern", async () => {
|
|
660
|
-
const shared = getShared();
|
|
708
|
+
const shared = adaptSandboxInstance(getShared());
|
|
661
709
|
const baseDir = config.resolvePath("gr-glob");
|
|
662
710
|
await shared.write(`${baseDir}/test.txt`, "pattern_match");
|
|
663
711
|
await shared.write(`${baseDir}/test.py`, "pattern_match");
|
|
664
712
|
await shared.write(`${baseDir}/test.md`, "pattern_match");
|
|
665
|
-
const result = await shared.
|
|
666
|
-
expect(
|
|
667
|
-
const matches = result;
|
|
713
|
+
const result = await shared.grep("pattern_match", baseDir, "*.py");
|
|
714
|
+
expect(result.error).toBeUndefined();
|
|
715
|
+
const matches = result.matches;
|
|
668
716
|
expect(matches.length).toBe(1);
|
|
669
717
|
expect(matches[0].path).toContain("test.py");
|
|
670
718
|
}, timeout);
|
|
671
|
-
it("should return empty
|
|
672
|
-
const shared = getShared();
|
|
719
|
+
it("should return empty matches when no matches found", async () => {
|
|
720
|
+
const shared = adaptSandboxInstance(getShared());
|
|
673
721
|
const baseDir = config.resolvePath("gr-no-match");
|
|
674
722
|
await shared.write(`${baseDir}/file.txt`, "Hello world");
|
|
675
|
-
const result = await shared.
|
|
676
|
-
expect(
|
|
677
|
-
expect(result.length).toBe(0);
|
|
723
|
+
const result = await shared.grep("nonexistent_str", baseDir);
|
|
724
|
+
expect(result.error).toBeUndefined();
|
|
725
|
+
expect(result.matches.length).toBe(0);
|
|
678
726
|
}, timeout);
|
|
679
727
|
it("should find multiple matches in a single file", async () => {
|
|
680
|
-
const shared = getShared();
|
|
728
|
+
const shared = adaptSandboxInstance(getShared());
|
|
681
729
|
const baseDir = config.resolvePath("gr-multi");
|
|
682
730
|
await shared.write(`${baseDir}/fruits.txt`, "apple\nbanana\napple\norange\napple");
|
|
683
|
-
const result = await shared.
|
|
684
|
-
expect(
|
|
685
|
-
const matches = result;
|
|
731
|
+
const result = await shared.grep("apple", baseDir);
|
|
732
|
+
expect(result.error).toBeUndefined();
|
|
733
|
+
const matches = result.matches;
|
|
686
734
|
expect(matches.length).toBe(3);
|
|
687
735
|
expect(matches.map((m) => m.line)).toEqual([
|
|
688
736
|
1,
|
|
@@ -691,99 +739,101 @@ function registerGrepRawTests(getShared, config, timeout) {
|
|
|
691
739
|
]);
|
|
692
740
|
}, timeout);
|
|
693
741
|
it("should match literal strings not regex", async () => {
|
|
694
|
-
const shared = getShared();
|
|
742
|
+
const shared = adaptSandboxInstance(getShared());
|
|
695
743
|
const baseDir = config.resolvePath("gr-literal");
|
|
696
744
|
await shared.write(`${baseDir}/numbers.txt`, "test123\ntest456\nabcdef");
|
|
697
|
-
const result = await shared.
|
|
698
|
-
expect(
|
|
699
|
-
const matches = result;
|
|
745
|
+
const result = await shared.grep("test123", baseDir);
|
|
746
|
+
expect(result.error).toBeUndefined();
|
|
747
|
+
const matches = result.matches;
|
|
700
748
|
expect(matches.length).toBe(1);
|
|
701
749
|
expect(matches[0].text).toContain("test123");
|
|
702
750
|
}, timeout);
|
|
703
751
|
it("should find unicode patterns", async () => {
|
|
704
|
-
const shared = getShared();
|
|
752
|
+
const shared = adaptSandboxInstance(getShared());
|
|
705
753
|
const baseDir = config.resolvePath("gr-unicode");
|
|
706
754
|
await shared.write(`${baseDir}/unicode.txt`, "Hello 世界\nПривет мир\n测试 pattern");
|
|
707
|
-
const result = await shared.
|
|
708
|
-
expect(
|
|
709
|
-
const matches = result;
|
|
755
|
+
const result = await shared.grep("世界", baseDir);
|
|
756
|
+
expect(result.error).toBeUndefined();
|
|
757
|
+
const matches = result.matches;
|
|
710
758
|
expect(matches.length).toBe(1);
|
|
711
759
|
expect(matches[0].text).toContain("世界");
|
|
712
760
|
}, timeout);
|
|
713
761
|
it("should be case-sensitive by default", async () => {
|
|
714
|
-
const shared = getShared();
|
|
762
|
+
const shared = adaptSandboxInstance(getShared());
|
|
715
763
|
const baseDir = config.resolvePath("gr-case");
|
|
716
764
|
await shared.write(`${baseDir}/case.txt`, "Hello\nhello\nHELLO");
|
|
717
|
-
const result = await shared.
|
|
718
|
-
expect(
|
|
719
|
-
const matches = result;
|
|
765
|
+
const result = await shared.grep("Hello", baseDir);
|
|
766
|
+
expect(result.error).toBeUndefined();
|
|
767
|
+
const matches = result.matches;
|
|
720
768
|
expect(matches.length).toBe(1);
|
|
721
769
|
expect(matches[0].text).toContain("Hello");
|
|
722
770
|
}, timeout);
|
|
723
771
|
it("should handle special characters as literals", async () => {
|
|
724
|
-
const shared = getShared();
|
|
772
|
+
const shared = adaptSandboxInstance(getShared());
|
|
725
773
|
const baseDir = config.resolvePath("gr-special");
|
|
726
774
|
await shared.write(`${baseDir}/special.txt`, "Price: $100\nPath: /usr/bin\nPattern: [a-z]*");
|
|
727
|
-
const result1 = await shared.
|
|
728
|
-
expect(
|
|
729
|
-
const matches1 = result1;
|
|
775
|
+
const result1 = await shared.grep("$100", baseDir);
|
|
776
|
+
expect(result1.error).toBeUndefined();
|
|
777
|
+
const matches1 = result1.matches;
|
|
730
778
|
expect(matches1.length).toBe(1);
|
|
731
779
|
expect(matches1[0].text).toContain("$100");
|
|
732
|
-
const result2 = await shared.
|
|
733
|
-
expect(
|
|
734
|
-
const matches2 = result2;
|
|
780
|
+
const result2 = await shared.grep("[a-z]*", baseDir);
|
|
781
|
+
expect(result2.error).toBeUndefined();
|
|
782
|
+
const matches2 = result2.matches;
|
|
735
783
|
expect(matches2.length).toBe(1);
|
|
736
784
|
expect(matches2[0].text).toContain("[a-z]*");
|
|
737
785
|
}, timeout);
|
|
738
|
-
it("should return empty
|
|
739
|
-
const shared = getShared();
|
|
786
|
+
it("should return empty matches for empty directory", async () => {
|
|
787
|
+
const shared = adaptSandboxInstance(getShared());
|
|
740
788
|
const baseDir = config.resolvePath("gr-empty-dir");
|
|
741
789
|
await shared.execute(`mkdir -p '${baseDir}'`);
|
|
742
|
-
const result = await shared.
|
|
743
|
-
expect(
|
|
744
|
-
expect(result.length).toBe(0);
|
|
790
|
+
const result = await shared.grep("anything", baseDir);
|
|
791
|
+
expect(result.error).toBeUndefined();
|
|
792
|
+
expect(result.matches.length).toBe(0);
|
|
745
793
|
}, timeout);
|
|
746
794
|
it("should search recursively across nested directories", async () => {
|
|
747
|
-
const shared = getShared();
|
|
795
|
+
const shared = adaptSandboxInstance(getShared());
|
|
748
796
|
const baseDir = config.resolvePath("gr-nested");
|
|
749
797
|
await shared.write(`${baseDir}/root.txt`, "target_nested here");
|
|
750
798
|
await shared.write(`${baseDir}/sub1/level1.txt`, "target_nested here");
|
|
751
799
|
await shared.write(`${baseDir}/sub1/sub2/level2.txt`, "target_nested here");
|
|
752
|
-
const result = await shared.
|
|
753
|
-
expect(
|
|
754
|
-
|
|
800
|
+
const result = await shared.grep("target_nested", baseDir);
|
|
801
|
+
expect(result.error).toBeUndefined();
|
|
802
|
+
const matches = result.matches;
|
|
803
|
+
expect(matches.length).toBe(3);
|
|
755
804
|
}, timeout);
|
|
756
805
|
it("should report correct line numbers", async () => {
|
|
757
|
-
const shared = getShared();
|
|
806
|
+
const shared = adaptSandboxInstance(getShared());
|
|
758
807
|
const baseDir = config.resolvePath("gr-line-nums");
|
|
759
808
|
const content = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`).join("\n");
|
|
760
809
|
await shared.write(`${baseDir}/long.txt`, content);
|
|
761
|
-
const result = await shared.
|
|
762
|
-
expect(
|
|
763
|
-
const matches = result;
|
|
810
|
+
const result = await shared.grep("Line 50", baseDir);
|
|
811
|
+
expect(result.error).toBeUndefined();
|
|
812
|
+
const matches = result.matches;
|
|
764
813
|
expect(matches.length).toBe(1);
|
|
765
814
|
expect(matches[0].line).toBe(50);
|
|
766
815
|
}, timeout);
|
|
767
816
|
});
|
|
768
817
|
}
|
|
769
|
-
|
|
770
818
|
//#endregion
|
|
771
819
|
//#region src/tests/glob-info.ts
|
|
772
820
|
/**
|
|
773
|
-
* Register
|
|
821
|
+
* Register glob() tests (wildcard, recursive, no matches, directories,
|
|
774
822
|
* extension filter, hidden files, character classes, question mark,
|
|
775
823
|
* multiple extensions, deeply nested).
|
|
776
824
|
*/
|
|
777
825
|
function registerGlobInfoTests(getShared, config, timeout) {
|
|
778
826
|
const { describe, it, expect } = config.runner;
|
|
779
|
-
describe("
|
|
827
|
+
describe("glob", () => {
|
|
780
828
|
it("should match basic wildcard pattern", async () => {
|
|
781
|
-
const shared = getShared();
|
|
829
|
+
const shared = adaptSandboxInstance(getShared());
|
|
782
830
|
const baseDir = config.resolvePath("gl-basic");
|
|
783
831
|
await shared.write(`${baseDir}/file1.txt`, "content");
|
|
784
832
|
await shared.write(`${baseDir}/file2.txt`, "content");
|
|
785
833
|
await shared.write(`${baseDir}/file3.py`, "content");
|
|
786
|
-
const
|
|
834
|
+
const globResult = await shared.glob("*.txt", baseDir);
|
|
835
|
+
expect(globResult.error).toBeUndefined();
|
|
836
|
+
const result = globResult.files || [];
|
|
787
837
|
expect(result.length).toBe(2);
|
|
788
838
|
const paths = result.map((info) => info.path);
|
|
789
839
|
expect(paths).toContain("file1.txt");
|
|
@@ -791,29 +841,35 @@ function registerGlobInfoTests(getShared, config, timeout) {
|
|
|
791
841
|
expect(paths.every((p) => !p.endsWith(".py"))).toBe(true);
|
|
792
842
|
}, timeout);
|
|
793
843
|
it("should match recursive pattern (**)", async () => {
|
|
794
|
-
const shared = getShared();
|
|
844
|
+
const shared = adaptSandboxInstance(getShared());
|
|
795
845
|
const baseDir = config.resolvePath("gl-recursive");
|
|
796
846
|
await shared.write(`${baseDir}/root.txt`, "content");
|
|
797
847
|
await shared.write(`${baseDir}/subdir1/nested1.txt`, "content");
|
|
798
848
|
await shared.write(`${baseDir}/subdir2/nested2.txt`, "content");
|
|
799
|
-
const
|
|
849
|
+
const globResult = await shared.glob("**/*.txt", baseDir);
|
|
850
|
+
expect(globResult.error).toBeUndefined();
|
|
851
|
+
const result = globResult.files || [];
|
|
800
852
|
expect(result.length).toBeGreaterThanOrEqual(2);
|
|
801
853
|
const paths = result.map((info) => info.path);
|
|
802
854
|
expect(paths.some((p) => p.includes("nested1.txt"))).toBe(true);
|
|
803
855
|
expect(paths.some((p) => p.includes("nested2.txt"))).toBe(true);
|
|
804
856
|
}, timeout);
|
|
805
857
|
it("should return empty array when no matches", async () => {
|
|
806
|
-
const shared = getShared();
|
|
858
|
+
const shared = adaptSandboxInstance(getShared());
|
|
807
859
|
const baseDir = config.resolvePath("gl-no-match");
|
|
808
860
|
await shared.write(`${baseDir}/file.txt`, "content");
|
|
809
|
-
|
|
861
|
+
const globResult = await shared.glob("*.py", baseDir);
|
|
862
|
+
expect(globResult.error).toBeUndefined();
|
|
863
|
+
expect(globResult.files).toEqual([]);
|
|
810
864
|
}, timeout);
|
|
811
865
|
it("should include directories in results", async () => {
|
|
812
|
-
const shared = getShared();
|
|
866
|
+
const shared = adaptSandboxInstance(getShared());
|
|
813
867
|
const baseDir = config.resolvePath("gl-dirs");
|
|
814
868
|
await shared.execute(`mkdir -p '${baseDir}/dir1' '${baseDir}/dir2'`);
|
|
815
869
|
await shared.write(`${baseDir}/file.txt`, "content");
|
|
816
|
-
const
|
|
870
|
+
const globResult = await shared.glob("*", baseDir);
|
|
871
|
+
expect(globResult.error).toBeUndefined();
|
|
872
|
+
const result = globResult.files || [];
|
|
817
873
|
expect(result.length).toBe(3);
|
|
818
874
|
const dirCount = result.filter((info) => info.is_dir).length;
|
|
819
875
|
const fileCount = result.filter((info) => !info.is_dir).length;
|
|
@@ -821,33 +877,39 @@ function registerGlobInfoTests(getShared, config, timeout) {
|
|
|
821
877
|
expect(fileCount).toBe(1);
|
|
822
878
|
}, timeout);
|
|
823
879
|
it("should match specific file extensions", async () => {
|
|
824
|
-
const shared = getShared();
|
|
880
|
+
const shared = adaptSandboxInstance(getShared());
|
|
825
881
|
const baseDir = config.resolvePath("gl-ext");
|
|
826
882
|
await shared.write(`${baseDir}/test.py`, "content");
|
|
827
883
|
await shared.write(`${baseDir}/test.txt`, "content");
|
|
828
884
|
await shared.write(`${baseDir}/test.md`, "content");
|
|
829
|
-
const
|
|
885
|
+
const globResult = await shared.glob("*.py", baseDir);
|
|
886
|
+
expect(globResult.error).toBeUndefined();
|
|
887
|
+
const result = globResult.files || [];
|
|
830
888
|
expect(result.length).toBe(1);
|
|
831
889
|
expect(result[0].path).toContain("test.py");
|
|
832
890
|
}, timeout);
|
|
833
891
|
it("should match hidden files explicitly", async () => {
|
|
834
|
-
const shared = getShared();
|
|
892
|
+
const shared = adaptSandboxInstance(getShared());
|
|
835
893
|
const baseDir = config.resolvePath("gl-hidden");
|
|
836
894
|
await shared.write(`${baseDir}/.hidden1`, "content");
|
|
837
895
|
await shared.write(`${baseDir}/.hidden2`, "content");
|
|
838
896
|
await shared.write(`${baseDir}/visible.txt`, "content");
|
|
839
|
-
const
|
|
897
|
+
const globResult = await shared.glob(".*", baseDir);
|
|
898
|
+
expect(globResult.error).toBeUndefined();
|
|
899
|
+
const paths = (globResult.files || []).map((info) => info.path);
|
|
840
900
|
expect(paths.some((p) => p.includes(".hidden1") || p.includes(".hidden2"))).toBe(true);
|
|
841
901
|
expect(paths.every((p) => !p.includes("visible"))).toBe(true);
|
|
842
902
|
}, timeout);
|
|
843
903
|
it("should match character class patterns", async () => {
|
|
844
|
-
const shared = getShared();
|
|
904
|
+
const shared = adaptSandboxInstance(getShared());
|
|
845
905
|
const baseDir = config.resolvePath("gl-charclass");
|
|
846
906
|
await shared.write(`${baseDir}/file1.txt`, "content");
|
|
847
907
|
await shared.write(`${baseDir}/file2.txt`, "content");
|
|
848
908
|
await shared.write(`${baseDir}/file3.txt`, "content");
|
|
849
909
|
await shared.write(`${baseDir}/fileA.txt`, "content");
|
|
850
|
-
const
|
|
910
|
+
const globResult = await shared.glob("file[1-2].txt", baseDir);
|
|
911
|
+
expect(globResult.error).toBeUndefined();
|
|
912
|
+
const result = globResult.files || [];
|
|
851
913
|
expect(result.length).toBe(2);
|
|
852
914
|
const paths = result.map((info) => info.path);
|
|
853
915
|
expect(paths).toContain("file1.txt");
|
|
@@ -856,39 +918,46 @@ function registerGlobInfoTests(getShared, config, timeout) {
|
|
|
856
918
|
expect(paths).not.toContain("fileA.txt");
|
|
857
919
|
}, timeout);
|
|
858
920
|
it("should match single character wildcard (?)", async () => {
|
|
859
|
-
const shared = getShared();
|
|
921
|
+
const shared = adaptSandboxInstance(getShared());
|
|
860
922
|
const baseDir = config.resolvePath("gl-question");
|
|
861
923
|
await shared.write(`${baseDir}/file1.txt`, "content");
|
|
862
924
|
await shared.write(`${baseDir}/file2.txt`, "content");
|
|
863
925
|
await shared.write(`${baseDir}/file10.txt`, "content");
|
|
864
|
-
const
|
|
926
|
+
const globResult = await shared.glob("file?.txt", baseDir);
|
|
927
|
+
expect(globResult.error).toBeUndefined();
|
|
928
|
+
const result = globResult.files || [];
|
|
865
929
|
expect(result.length).toBe(2);
|
|
866
930
|
expect(result.map((info) => info.path)).not.toContain("file10.txt");
|
|
867
931
|
}, timeout);
|
|
868
932
|
it("should match multiple extensions separately", async () => {
|
|
869
|
-
const shared = getShared();
|
|
933
|
+
const shared = adaptSandboxInstance(getShared());
|
|
870
934
|
const baseDir = config.resolvePath("gl-multi-ext");
|
|
871
935
|
await shared.write(`${baseDir}/file.txt`, "content");
|
|
872
936
|
await shared.write(`${baseDir}/file.py`, "content");
|
|
873
937
|
await shared.write(`${baseDir}/file.md`, "content");
|
|
874
938
|
await shared.write(`${baseDir}/file.js`, "content");
|
|
875
|
-
const
|
|
876
|
-
|
|
939
|
+
const globResultTxt = await shared.glob("*.txt", baseDir);
|
|
940
|
+
expect(globResultTxt.error).toBeUndefined();
|
|
941
|
+
const resultTxt = globResultTxt.files || [];
|
|
942
|
+
const globResultPy = await shared.glob("*.py", baseDir);
|
|
943
|
+
expect(globResultPy.error).toBeUndefined();
|
|
944
|
+
const resultPy = globResultPy.files || [];
|
|
877
945
|
expect(resultTxt.length).toBe(1);
|
|
878
946
|
expect(resultPy.length).toBe(1);
|
|
879
947
|
}, timeout);
|
|
880
948
|
it("should match deeply nested patterns", async () => {
|
|
881
|
-
const shared = getShared();
|
|
949
|
+
const shared = adaptSandboxInstance(getShared());
|
|
882
950
|
const baseDir = config.resolvePath("gl-deep");
|
|
883
951
|
await shared.write(`${baseDir}/a/b/c/d/deep.txt`, "content");
|
|
884
952
|
await shared.write(`${baseDir}/a/b/other.txt`, "content");
|
|
885
|
-
const
|
|
953
|
+
const globResult = await shared.glob("**/deep.txt", baseDir);
|
|
954
|
+
expect(globResult.error).toBeUndefined();
|
|
955
|
+
const result = globResult.files || [];
|
|
886
956
|
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
887
957
|
expect(result.some((info) => info.path.includes("deep.txt"))).toBe(true);
|
|
888
958
|
}, timeout);
|
|
889
959
|
});
|
|
890
960
|
}
|
|
891
|
-
|
|
892
961
|
//#endregion
|
|
893
962
|
//#region src/tests/initial-files.ts
|
|
894
963
|
/**
|
|
@@ -948,7 +1017,7 @@ function registerInitialFilesTests(config, timeout) {
|
|
|
948
1017
|
const filePath = config.resolvePath("init-read-test.txt");
|
|
949
1018
|
const tmp = await withRetry(() => config.createSandbox({ initialFiles: { [filePath]: "Content for read test" } }));
|
|
950
1019
|
try {
|
|
951
|
-
expect(await tmp.read(filePath)).toContain("Content for read test");
|
|
1020
|
+
expect((await adaptSandboxInstance(tmp).read(filePath)).content).toContain("Content for read test");
|
|
952
1021
|
} finally {
|
|
953
1022
|
await config.closeSandbox?.(tmp);
|
|
954
1023
|
}
|
|
@@ -976,19 +1045,20 @@ function registerInitialFilesTests(config, timeout) {
|
|
|
976
1045
|
await config.closeSandbox?.(tmp);
|
|
977
1046
|
}
|
|
978
1047
|
}, timeout);
|
|
979
|
-
it("should make initialFiles in subdirectories visible via
|
|
1048
|
+
it("should make initialFiles in subdirectories visible via ls()", async () => {
|
|
980
1049
|
const dirPath = config.resolvePath("init-ls-dir");
|
|
981
1050
|
const filePath = `${dirPath}/file.txt`;
|
|
982
1051
|
const tmp = await withRetry(() => config.createSandbox({ initialFiles: { [filePath]: "ls test content" } }));
|
|
983
1052
|
try {
|
|
984
|
-
|
|
1053
|
+
const lsResult = await adaptSandboxInstance(tmp).ls(dirPath);
|
|
1054
|
+
expect(lsResult.error).toBeUndefined();
|
|
1055
|
+
expect((lsResult.files || []).map((e) => e.path.replace(/\/$/, ""))).toContain(filePath);
|
|
985
1056
|
} finally {
|
|
986
1057
|
await config.closeSandbox?.(tmp);
|
|
987
1058
|
}
|
|
988
1059
|
}, timeout);
|
|
989
1060
|
});
|
|
990
1061
|
}
|
|
991
|
-
|
|
992
1062
|
//#endregion
|
|
993
1063
|
//#region src/tests/integration.ts
|
|
994
1064
|
/**
|
|
@@ -998,34 +1068,37 @@ function registerIntegrationTests(getShared, config, timeout) {
|
|
|
998
1068
|
const { describe, it, expect } = config.runner;
|
|
999
1069
|
describe("integration workflows", () => {
|
|
1000
1070
|
it("should complete a write-read-edit-read workflow", async () => {
|
|
1001
|
-
const shared = getShared();
|
|
1071
|
+
const shared = adaptSandboxInstance(getShared());
|
|
1002
1072
|
const filePath = config.resolvePath("intg-workflow.txt");
|
|
1003
1073
|
expect((await shared.write(filePath, "Original content")).error).toBeUndefined();
|
|
1004
|
-
expect(await shared.read(filePath)).toContain("Original content");
|
|
1074
|
+
expect((await shared.read(filePath)).content).toContain("Original content");
|
|
1005
1075
|
expect((await shared.edit(filePath, "Original", "Modified")).error).toBeUndefined();
|
|
1006
1076
|
const updatedContent = await shared.read(filePath);
|
|
1007
|
-
expect(updatedContent).toContain("Modified content");
|
|
1008
|
-
expect(updatedContent).not.toContain("Original");
|
|
1077
|
+
expect(updatedContent.content).toContain("Modified content");
|
|
1078
|
+
expect(updatedContent.content).not.toContain("Original");
|
|
1009
1079
|
}, timeout);
|
|
1010
1080
|
it("should handle complex directory operations", async () => {
|
|
1011
|
-
const shared = getShared();
|
|
1081
|
+
const shared = adaptSandboxInstance(getShared());
|
|
1012
1082
|
const baseDir = config.resolvePath("intg-complex");
|
|
1013
1083
|
await shared.write(`${baseDir}/root.txt`, "root file");
|
|
1014
1084
|
await shared.write(`${baseDir}/subdir1/file1.txt`, "file 1");
|
|
1015
1085
|
await shared.write(`${baseDir}/subdir1/file2.py`, "file 2");
|
|
1016
1086
|
await shared.write(`${baseDir}/subdir2/file3.txt`, "file 3");
|
|
1017
|
-
const
|
|
1087
|
+
const lsResult = await shared.ls(baseDir);
|
|
1088
|
+
expect(lsResult.error).toBeUndefined();
|
|
1089
|
+
const lsPaths = (lsResult.files || []).map((info) => info.path.replace(/\/$/, ""));
|
|
1018
1090
|
expect(lsPaths).toContain(`${baseDir}/root.txt`);
|
|
1019
1091
|
expect(lsPaths).toContain(`${baseDir}/subdir1`);
|
|
1020
1092
|
expect(lsPaths).toContain(`${baseDir}/subdir2`);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
expect(
|
|
1024
|
-
|
|
1093
|
+
const globResult = await shared.glob("**/*.txt", baseDir);
|
|
1094
|
+
expect(globResult.error).toBeUndefined();
|
|
1095
|
+
expect((globResult.files || []).length).toBe(3);
|
|
1096
|
+
const grepResult = await shared.grep("file", baseDir);
|
|
1097
|
+
expect(grepResult.error).toBeUndefined();
|
|
1098
|
+
expect(grepResult.matches.length).toBeGreaterThanOrEqual(3);
|
|
1025
1099
|
}, timeout);
|
|
1026
1100
|
});
|
|
1027
1101
|
}
|
|
1028
|
-
|
|
1029
1102
|
//#endregion
|
|
1030
1103
|
//#region src/sandbox.ts
|
|
1031
1104
|
/**
|
|
@@ -1050,9 +1123,9 @@ function registerIntegrationTests(getShared, config, timeout) {
|
|
|
1050
1123
|
* - write() (new file, parent dirs, existing file, special chars, unicode, long content)
|
|
1051
1124
|
* - read() (basic, nonexistent, offset, limit, offset+limit, unicode, chunked)
|
|
1052
1125
|
* - edit() (single/multi occurrence, replaceAll, not found, special chars, multiline, unicode)
|
|
1053
|
-
* -
|
|
1054
|
-
* -
|
|
1055
|
-
* -
|
|
1126
|
+
* - ls() (basic listing, empty dir, hidden files, large dir, absolute paths)
|
|
1127
|
+
* - grep() (basic search, glob filter, case sensitivity, nested dirs, unicode)
|
|
1128
|
+
* - glob() (wildcard, recursive, extension filter, character classes, deeply nested)
|
|
1056
1129
|
* - Initial files support (basic, nested, empty)
|
|
1057
1130
|
* - Integration workflows (write-read-edit, complex directory operations)
|
|
1058
1131
|
* - Error handling (file not found, non-existent command)
|
|
@@ -1151,7 +1224,7 @@ function sandboxStandardTests(config) {
|
|
|
1151
1224
|
registerIntegrationTests(getShared, config, timeout);
|
|
1152
1225
|
});
|
|
1153
1226
|
}
|
|
1154
|
-
|
|
1155
1227
|
//#endregion
|
|
1156
1228
|
export { withRetry as n, sandboxStandardTests as t };
|
|
1157
|
-
|
|
1229
|
+
|
|
1230
|
+
//# sourceMappingURL=sandbox-BbDX1T-X.js.map
|