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