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