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