scalebox-sdk 0.1.4__py3-none-any.whl → 0.1.25__py3-none-any.whl

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.
Files changed (68) hide show
  1. scalebox/__init__.py +1 -1
  2. scalebox/api/__init__.py +128 -128
  3. scalebox/api/client/__init__.py +8 -8
  4. scalebox/api/client/api/sandboxes/get_sandboxes.py +5 -3
  5. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
  6. scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
  7. scalebox/api/client/client.py +288 -288
  8. scalebox/api/client/models/listed_sandbox.py +11 -9
  9. scalebox/api/client/models/new_sandbox.py +1 -1
  10. scalebox/api/client/models/sandbox.py +125 -125
  11. scalebox/api/client/models/sandbox_state.py +1 -0
  12. scalebox/api/client/types.py +46 -46
  13. scalebox/code_interpreter/code_interpreter_async.py +370 -369
  14. scalebox/code_interpreter/code_interpreter_sync.py +318 -317
  15. scalebox/connection_config.py +92 -92
  16. scalebox/csx_desktop/main.py +12 -12
  17. scalebox/generated/api_pb2_connect.py +17 -66
  18. scalebox/sandbox_async/commands/command.py +307 -307
  19. scalebox/sandbox_async/commands/command_handle.py +187 -187
  20. scalebox/sandbox_async/commands/pty.py +187 -187
  21. scalebox/sandbox_async/filesystem/filesystem.py +557 -557
  22. scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
  23. scalebox/sandbox_async/main.py +647 -646
  24. scalebox/sandbox_async/sandbox_api.py +365 -365
  25. scalebox/sandbox_async/utils.py +7 -7
  26. scalebox/sandbox_sync/__init__.py +2 -2
  27. scalebox/sandbox_sync/commands/command.py +300 -300
  28. scalebox/sandbox_sync/commands/command_handle.py +150 -150
  29. scalebox/sandbox_sync/commands/pty.py +181 -181
  30. scalebox/sandbox_sync/filesystem/filesystem.py +543 -543
  31. scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
  32. scalebox/sandbox_sync/main.py +789 -790
  33. scalebox/sandbox_sync/sandbox_api.py +356 -356
  34. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +256 -256
  35. scalebox/test/README.md +164 -164
  36. scalebox/test/aclient.py +72 -72
  37. scalebox/test/code_interpreter_centext.py +21 -21
  38. scalebox/test/code_interpreter_centext_sync.py +21 -21
  39. scalebox/test/code_interpreter_test.py +1 -1
  40. scalebox/test/code_interpreter_test_sync.py +1 -1
  41. scalebox/test/run_all_validation_tests.py +334 -334
  42. scalebox/test/test_basic.py +78 -78
  43. scalebox/test/test_code_interpreter_async_comprehensive.py +2653 -2653
  44. scalebox/test/{test_code_interpreter_e2bsync_comprehensive.py → test_code_interpreter_execcode.py} +328 -392
  45. scalebox/test/test_code_interpreter_sync_comprehensive.py +3416 -3412
  46. scalebox/test/test_csx_desktop_examples.py +130 -0
  47. scalebox/test/test_sandbox_async_comprehensive.py +736 -738
  48. scalebox/test/test_sandbox_stress_and_edge_cases.py +778 -778
  49. scalebox/test/test_sandbox_sync_comprehensive.py +779 -770
  50. scalebox/test/test_sandbox_usage_examples.py +987 -987
  51. scalebox/test/testacreate.py +24 -24
  52. scalebox/test/testagetinfo.py +18 -18
  53. scalebox/test/testcodeinterpreter_async.py +508 -508
  54. scalebox/test/testcodeinterpreter_sync.py +239 -239
  55. scalebox/test/testcomputeuse.py +2 -2
  56. scalebox/test/testnovnc.py +12 -12
  57. scalebox/test/testsandbox_api.py +15 -0
  58. scalebox/test/testsandbox_async.py +202 -118
  59. scalebox/test/testsandbox_sync.py +71 -38
  60. scalebox/version.py +2 -2
  61. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/METADATA +104 -103
  62. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/RECORD +66 -66
  63. scalebox/test/test_code_interpreter_e2basync_comprehensive.py +0 -2655
  64. scalebox/test/test_e2b_first.py +0 -11
  65. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/WHEEL +0 -0
  66. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/entry_points.txt +0 -0
  67. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/licenses/LICENSE +0 -0
  68. {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/top_level.txt +0 -0
@@ -1,770 +1,779 @@
1
- #!/usr/bin/env python3
2
- """
3
- Comprehensive validation test for sandbox_sync module.
4
-
5
- This test suite demonstrates and validates all key functionality of the Sandbox:
6
- - Sandbox lifecycle management (create, connect, kill)
7
- - File system operations (read, write, list, remove, etc.)
8
- - Command execution (foreground, background, PTY)
9
- - Static methods and class methods
10
- - Error handling and edge cases
11
- - Performance testing
12
- """
13
-
14
- import datetime
15
- import logging
16
- import os
17
- import tempfile
18
- import threading
19
- import time
20
- from io import BytesIO, StringIO
21
- from typing import List, Optional
22
-
23
- from scalebox.exceptions import SandboxException
24
- from scalebox.sandbox.commands.command_handle import CommandExitException, PtySize
25
- from scalebox.sandbox.filesystem.filesystem import EntryInfo, FileType, WriteInfo
26
- from scalebox.sandbox_sync.main import Sandbox
27
-
28
- # 配置日志
29
- logging.basicConfig(
30
- level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
31
- )
32
- logger = logging.getLogger(__name__)
33
-
34
-
35
- class SandboxValidator:
36
- """Comprehensive Sandbox validation test suite."""
37
-
38
- def __init__(self):
39
- self.sandbox: Optional[Sandbox] = None
40
- self.test_results = []
41
- self.failed_tests = []
42
-
43
- def log_test_result(
44
- self, test_name: str, success: bool, message: str = "", duration: float = 0
45
- ):
46
- """记录测试结果"""
47
- status = "✅ PASS" if success else "❌ FAIL"
48
- result = {
49
- "test": test_name,
50
- "success": success,
51
- "message": message,
52
- "duration": duration,
53
- }
54
- self.test_results.append(result)
55
-
56
- if not success:
57
- self.failed_tests.append(test_name)
58
-
59
- logger.info(f"{status} {test_name} ({duration:.3f}s) {message}")
60
-
61
- def run_test(self, test_func, test_name: str):
62
- """运行单个测试并记录结果"""
63
- start_time = time.time()
64
- try:
65
- test_func()
66
- duration = time.time() - start_time
67
- self.log_test_result(test_name, True, duration=duration)
68
- except Exception as e:
69
- duration = time.time() - start_time
70
- self.log_test_result(test_name, False, str(e), duration=duration)
71
-
72
- # ======================== 基础沙箱操作测试 ========================
73
-
74
- def test_sandbox_creation(self):
75
- """测试沙箱创建"""
76
- self.sandbox = Sandbox(
77
- template="base",
78
- debug=True,
79
- timeout=300,
80
- metadata={"test": "sync_validation"},
81
- envs={"TEST_ENV": "sync_test"},
82
- )
83
- assert self.sandbox is not None
84
- assert self.sandbox.sandbox_id is not None
85
- logger.info(f"Created sandbox with ID: {self.sandbox.sandbox_id}")
86
-
87
- def test_sandbox_is_running(self):
88
- """测试沙箱运行状态检查"""
89
- assert self.sandbox is not None
90
- is_running = self.sandbox.is_running()
91
- assert is_running == True
92
- logger.info(f"Sandbox is running: {is_running}")
93
-
94
- def test_sandbox_get_info(self):
95
- """测试获取沙箱信息"""
96
- assert self.sandbox is not None
97
- info = self.sandbox.get_info()
98
- assert info is not None
99
- assert info.sandbox_id == self.sandbox.sandbox_id
100
- logger.info(f"Sandbox info: {info}")
101
-
102
- def test_sandbox_set_timeout(self):
103
- """测试设置沙箱超时"""
104
- assert self.sandbox is not None
105
- self.sandbox.set_timeout(600) # 设置10分钟超时
106
- logger.info("Successfully set sandbox timeout to 600 seconds")
107
-
108
- def test_sandbox_get_metrics(self):
109
- """测试获取沙箱指标"""
110
- assert self.sandbox is not None
111
- try:
112
- metrics = self.sandbox.get_metrics()
113
- logger.info(f"Sandbox metrics: {len(metrics) if metrics else 0} entries")
114
- except Exception as e:
115
- # 某些版本可能不支持指标
116
- logger.warning(f"Metrics not supported: {e}")
117
-
118
- # ======================== 文件系统操作测试 ========================
119
-
120
- def test_filesystem_write_text(self):
121
- """测试写入文本文件"""
122
- assert self.sandbox is not None
123
- test_content = "Hello, Sandbox!\nThis is a test file.\n测试中文内容"
124
-
125
- result = self.sandbox.files.write("/tmp/test_sync.txt", test_content)
126
- print(result)
127
- assert isinstance(result, WriteInfo)
128
- assert result.path == "/tmp/test_sync.txt"
129
- assert result.type == FileType.FILE
130
- logger.info(f"Written file: {result}")
131
-
132
- def test_filesystem_write_bytes(self):
133
- """测试写入字节文件"""
134
- assert self.sandbox is not None
135
- test_content = b"Binary content: \x00\x01\x02\x03\xff"
136
-
137
- result = self.sandbox.files.write("/tmp/test_binary.bin", test_content)
138
- print(result)
139
- assert isinstance(result, WriteInfo)
140
- assert result.path == "/tmp/test_binary.bin"
141
-
142
- def test_filesystem_write_io(self):
143
- """测试写入IO对象"""
144
- assert self.sandbox is not None
145
-
146
- # 测试StringIO
147
- string_io = StringIO("Content from StringIO\nSecond line")
148
- result1 = self.sandbox.files.write("/tmp/test_stringio.txt", string_io)
149
- print(result1)
150
- assert isinstance(result1, WriteInfo)
151
-
152
- # 测试BytesIO
153
- bytes_io = BytesIO(b"Content from BytesIO")
154
- result2 = self.sandbox.files.write("/tmp/test_bytesio.bin", bytes_io)
155
- print(result2)
156
- assert isinstance(result2, WriteInfo)
157
-
158
- def test_filesystem_write_multiple(self):
159
- """测试批量写入文件"""
160
- assert self.sandbox is not None
161
-
162
- files = [
163
- {"path": "/tmp/multi1.txt", "data": "Content of file 1"},
164
- {"path": "/tmp/multi2.txt", "data": "Content of file 2"},
165
- {"path": "/tmp/multi3.bin", "data": b"Binary content of file 3"},
166
- ]
167
-
168
- results = self.sandbox.files.write(files)
169
- print(results)
170
- assert isinstance(results, list)
171
- assert len(results) == 3
172
- for result in results:
173
- assert isinstance(result, WriteInfo)
174
-
175
- def test_filesystem_read_text(self):
176
- """测试读取文本文件"""
177
- assert self.sandbox is not None
178
-
179
- content = self.sandbox.files.read("/tmp/test_sync.txt", format="text")
180
- print(content)
181
- assert isinstance(content, str)
182
- assert "Hello, Sandbox!" in content
183
- assert "测试中文内容" in content
184
-
185
- def test_filesystem_read_bytes(self):
186
- """测试读取字节文件"""
187
- assert self.sandbox is not None
188
-
189
- content = self.sandbox.files.read("/tmp/test_binary.bin", format="bytes")
190
- print(content)
191
- assert isinstance(content, bytearray)
192
- expected = b"Binary content: \x00\x01\x02\x03\xff"
193
- assert bytes(content) == expected
194
-
195
- def test_filesystem_read_stream(self):
196
- """测试流式读取文件"""
197
- assert self.sandbox is not None
198
-
199
- stream = self.sandbox.files.read("/tmp/test_sync.txt", format="stream")
200
- chunks = []
201
- for chunk in stream:
202
- chunks.append(chunk)
203
-
204
- content = b"".join(chunks).decode("utf-8")
205
- print(content)
206
- assert "Hello, Sandbox!" in content
207
-
208
- def test_filesystem_list(self):
209
- """测试列出目录内容"""
210
- assert self.sandbox is not None
211
-
212
- entries = self.sandbox.files.list("/workspace/tmp", depth=1)
213
- print(entries)
214
- assert isinstance(entries, list)
215
- assert len(entries) > 0
216
-
217
- # 检查我们创建的文件是否存在
218
- filenames = [entry.name for entry in entries]
219
- assert "test_sync.txt" in filenames
220
-
221
- for entry in entries:
222
- assert isinstance(entry, EntryInfo)
223
- assert entry.name is not None
224
- assert entry.path is not None
225
-
226
- def test_filesystem_exists(self):
227
- """测试文件存在性检查"""
228
- assert self.sandbox is not None
229
-
230
- # 检查存在的文件
231
- exists = self.sandbox.files.exists("/workspace/tmp/test_sync.txt")
232
- assert exists == True
233
-
234
- # 检查不存在的文件
235
- not_exists = self.sandbox.files.exists("/workspace/tmp/nonexistent.txt")
236
- assert not_exists == False
237
-
238
- def test_filesystem_get_info(self):
239
- """测试获取文件信息"""
240
- assert self.sandbox is not None
241
-
242
- info = self.sandbox.files.get_info("/workspace/tmp/test_sync.txt")
243
- assert isinstance(info, EntryInfo)
244
- assert info.name == "test_sync.txt"
245
- assert info.type == FileType.FILE
246
- assert info.path == "/workspace/tmp/test_sync.txt"
247
- assert info.size > 0
248
-
249
- def test_filesystem_make_dir(self):
250
- """测试创建目录"""
251
- assert self.sandbox is not None
252
-
253
- # 创建新目录
254
- result = self.sandbox.files.make_dir("/workspace/tmp/test_dir/nested_dir")
255
- assert result == True
256
-
257
- # 再次创建同一目录应该返回False
258
- result = self.sandbox.files.make_dir("/workspace/tmp/test_dir")
259
- assert result == False
260
-
261
- def test_filesystem_rename(self):
262
- """测试重命名文件"""
263
- assert self.sandbox is not None
264
-
265
- # 创建一个测试文件
266
- self.sandbox.files.write("/tmp/old_name.txt", "content to rename")
267
-
268
- # 重命名
269
- result = self.sandbox.files.rename(
270
- "/workspace/tmp/old_name.txt", "/workspace/tmp/new_name.txt"
271
- )
272
- assert isinstance(result, EntryInfo)
273
- assert result.name == "new_name.txt"
274
-
275
- # 确认旧文件不存在,新文件存在
276
- assert self.sandbox.files.exists("/workspace/tmp/old_name.txt") == False
277
- assert self.sandbox.files.exists("/workspace/tmp/new_name.txt") == True
278
-
279
- def test_filesystem_remove(self):
280
- """测试删除文件和目录"""
281
- assert self.sandbox is not None
282
-
283
- # 删除文件
284
- self.sandbox.files.remove("/workspace/tmp/new_name.txt")
285
- assert self.sandbox.files.exists("/workspace/tmp/new_name.txt") == False
286
-
287
- # 删除目录
288
- self.sandbox.files.remove("/workspace/tmp/test_dir")
289
- assert self.sandbox.files.exists("/workspace/tmp/test_dir") == False
290
-
291
- # ======================== 命令执行测试 ========================
292
-
293
- def test_command_run_foreground(self):
294
- """测试前台命令执行"""
295
- assert self.sandbox is not None
296
-
297
- result = self.sandbox.commands.run("echo 'Hello from command'")
298
- assert result.exit_code == 0
299
- assert "Hello from command" in result.stdout
300
- assert result.stderr == ""
301
-
302
- def test_command_run_with_error(self):
303
- """测试执行失败的命令"""
304
- assert self.sandbox is not None
305
- try:
306
- result = self.sandbox.commands.run("ls /nonexistent_directory 2>&1")
307
- except CommandExitException as e:
308
- assert e.exit_code != 0
309
- assert (
310
- "No such file or directory" in e.stdout or "cannot access" in e.stdout
311
- )
312
-
313
- def test_command_run_background(self):
314
- """测试后台命令执行"""
315
- assert self.sandbox is not None
316
-
317
- # 启动后台命令
318
- handle = self.sandbox.commands.run(
319
- "sleep 2 && echo 'Background task completed'", background=True
320
- )
321
- print(handle)
322
- # 等待命令完成
323
- result = handle.wait()
324
- print(result)
325
- assert result.exit_code == 0
326
- # assert "Process is running in background" in result.stderr
327
-
328
- def test_command_with_env_and_cwd(self):
329
- """测试带环境变量和工作目录的命令"""
330
- assert self.sandbox is not None
331
-
332
- # 创建测试目录
333
- self.sandbox.files.make_dir("/tmp/test_cwd")
334
-
335
- result = self.sandbox.commands.run(
336
- "echo $TEST_VAR && pwd",
337
- envs={"TEST_VAR": "test_value"},
338
- cwd="/tmp/test_cwd",
339
- )
340
-
341
- assert result.exit_code == 0
342
- assert "test_value" in result.stdout
343
- assert "/tmp/test_cwd" in result.stdout
344
-
345
- def test_command_with_callbacks(self):
346
- """测试带回调的命令执行"""
347
- assert self.sandbox is not None
348
-
349
- stdout_data = []
350
- stderr_data = []
351
-
352
- def stdout_handler(data):
353
- stdout_data.append(data)
354
-
355
- def stderr_handler(data):
356
- stderr_data.append(data)
357
-
358
- result = self.sandbox.commands.run(
359
- "echo 'stdout message' && echo 'stderr message' >&2",
360
- on_stdout=stdout_handler,
361
- on_stderr=stderr_handler,
362
- )
363
-
364
- assert result.exit_code == 0
365
- # Note: 回调在命令结束后触发
366
-
367
- def test_command_list(self):
368
- """测试列出运行中的命令"""
369
- assert self.sandbox is not None
370
-
371
- # 启动一个长时间运行的后台命令
372
- handle = self.sandbox.commands.run("sleep 30 ", background=True)
373
- # 列出进程
374
- time.sleep(1)
375
- processes = self.sandbox.commands.list()
376
- assert isinstance(processes, list)
377
-
378
- # 查找我们的进程
379
- found = any(p.pid == handle.pid for p in processes)
380
- assert found == True
381
-
382
- # 清理:杀死进程
383
- killed = self.sandbox.commands.kill(handle.pid)
384
- print(killed)
385
- assert killed == True
386
-
387
- def test_command_send_stdin(self):
388
- """测试向命令发送标准输入"""
389
- assert self.sandbox is not None
390
-
391
- # 启动一个等待输入的命令
392
- handle = self.sandbox.commands.run("cat", background=True)
393
- print(handle.pid)
394
- # 发送输入
395
- self.sandbox.commands.send_stdin(handle.pid, "Hello stdin\n")
396
-
397
- # 等待一点时间让命令处理输入
398
- time.sleep(1)
399
-
400
- # 杀死进程并获取结果
401
- self.sandbox.commands.kill(handle.pid)
402
- result = handle.wait()
403
-
404
- # cat命令会被SIGKILL杀死,所以exit_code不会是0
405
- # 但我们验证了send_stdin没有抛出异常
406
-
407
- def test_command_connect(self):
408
- """测试连接到运行中的命令"""
409
- assert self.sandbox is not None
410
-
411
- # 启动后台命令
412
- handle1 = self.sandbox.commands.run("sleep 30", background=True)
413
- print(handle1.pid)
414
-
415
- # 连接到同一个进程
416
- handle2 = self.sandbox.commands.connect(handle1.pid)
417
- print(handle2.events)
418
-
419
- assert handle1.pid == handle2.pid
420
-
421
- # 清理
422
- self.sandbox.commands.kill(handle1.pid)
423
-
424
- # ======================== PTY操作测试 ========================
425
-
426
- def test_pty_create(self):
427
- """测试创建PTY"""
428
- assert self.sandbox is not None
429
-
430
- pty_data = []
431
-
432
- def pty_handler(data):
433
- pty_data.append(data)
434
-
435
- pty_handle = self.sandbox.pty.create(
436
- size=PtySize(rows=24, cols=80),
437
- # on_data=pty_handler,
438
- cwd="/tmp",
439
- envs={"PTY_TEST": "value"},
440
- )
441
-
442
- assert pty_handle is not None
443
- assert pty_handle.pid > 0
444
-
445
- # 发送命令并等待
446
- self.sandbox.pty.send_stdin(pty_handle.pid, b"echo 'PTY test'\n")
447
- time.sleep(2)
448
-
449
- # 调整大小
450
- self.sandbox.pty.resize(pty_handle.pid, PtySize(rows=30, cols=100))
451
-
452
- # 清理
453
- killed = self.sandbox.pty.kill(pty_handle.pid)
454
- assert killed == True
455
-
456
- # ======================== 静态方法测试 ========================
457
-
458
- def test_static_methods(self):
459
- """测试静态方法"""
460
- assert self.sandbox is not None
461
- sandbox_id = self.sandbox.sandbox_id
462
-
463
- # 静态方法获取信息
464
- info = Sandbox.get_info(sandbox_id)
465
- assert info.sandbox_id == sandbox_id
466
-
467
- # 静态方法设置超时
468
- Sandbox.set_timeout(sandbox_id, 900)
469
-
470
- # 静态方法获取指标(可能不支持)
471
- try:
472
- metrics = Sandbox.get_metrics(sandbox_id)
473
- logger.info(f"Static method metrics: {len(metrics) if metrics else 0}")
474
- except Exception as e:
475
- logger.warning(f"Static method metrics not supported: {e}")
476
-
477
- # ======================== 错误处理测试 ========================
478
-
479
- def test_error_handling_file_operations(self):
480
- """测试文件操作错误处理"""
481
- assert self.sandbox is not None
482
-
483
- # 读取不存在的文件
484
- try:
485
- self.sandbox.files.read("/nonexistent/file.txt")
486
- assert False, "应该抛出异常"
487
- except Exception as e:
488
- logger.info(f"正确捕获文件读取错误: {type(e).__name__}")
489
-
490
- # 写入到只读目录
491
- try:
492
- self.sandbox.files.write("/proc/test.txt", "content")
493
- assert False, "应该抛出异常"
494
- except Exception as e:
495
- logger.info(f"正确捕获文件写入错误: {type(e).__name__}")
496
-
497
- def test_error_handling_command_operations(self):
498
- """测试命令操作错误处理"""
499
- assert self.sandbox is not None
500
-
501
- # 杀死不存在的进程
502
- killed = self.sandbox.commands.kill(99999)
503
- assert killed == False
504
-
505
- # 连接到不存在的进程
506
- try:
507
- self.sandbox.commands.connect(99999)
508
- assert False, "应该抛出异常"
509
- except Exception as e:
510
- logger.info(f"正确捕获进程连接错误: {type(e).__name__}")
511
-
512
- # ======================== 性能测试 ========================
513
-
514
- def test_performance_file_operations(self):
515
- """测试文件操作性能"""
516
- assert self.sandbox is not None
517
-
518
- # 批量文件操作
519
- start_time = time.time()
520
-
521
- # 创建100个小文件
522
- files = [
523
- {"path": f"/tmp/perf_test_{i}.txt", "data": f"Content of file {i}"}
524
- for i in range(100)
525
- ]
526
-
527
- self.sandbox.files.write(files)
528
- duration = time.time() - start_time
529
-
530
- logger.info(f"Created 100 files in {duration:.3f}s")
531
- assert duration < 30 # 应该在30秒内完成
532
-
533
- # 清理
534
- for i in range(100):
535
- try:
536
- self.sandbox.files.remove(f"/tmp/perf_test_{i}.txt")
537
- except:
538
- pass
539
-
540
- def test_performance_command_operations(self):
541
- """测试命令操作性能"""
542
- assert self.sandbox is not None
543
-
544
- start_time = time.time()
545
-
546
- # 执行10个顺序命令(同步版本)
547
- for i in range(10):
548
- result = self.sandbox.commands.run(f"echo 'Command {i}'")
549
- assert result.exit_code == 0
550
- assert f"Command {i}" in result.stdout
551
-
552
- duration = time.time() - start_time
553
- logger.info(f"Executed 10 sequential commands in {duration:.3f}s")
554
-
555
- def test_performance_concurrent_commands(self):
556
- """测试并发命令性能(使用线程池)"""
557
- assert self.sandbox is not None
558
-
559
- def run_command(i):
560
- result = self.sandbox.commands.run(f"echo 'Concurrent command {i}'")
561
- assert result.exit_code == 0
562
- return result
563
-
564
- start_time = time.time()
565
-
566
- # 使用线程池执行并发命令
567
- import concurrent.futures
568
-
569
- with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
570
- futures = [executor.submit(run_command, i) for i in range(10)]
571
- results = [
572
- future.result() for future in concurrent.futures.as_completed(futures)
573
- ]
574
-
575
- duration = time.time() - start_time
576
- logger.info(f"Executed 10 concurrent commands in {duration:.3f}s")
577
- assert len(results) == 10
578
-
579
- # ======================== 连接测试 ========================
580
-
581
- def test_sandbox_connect(self):
582
- """测试连接到现有沙箱"""
583
- assert self.sandbox is not None
584
- original_id = self.sandbox.sandbox_id
585
-
586
- # 连接到现有沙箱
587
- connected_sandbox = Sandbox.connect(original_id)
588
- assert connected_sandbox.sandbox_id == original_id
589
-
590
- # 验证连接的沙箱可以正常使用
591
- is_running = connected_sandbox.is_running()
592
- logger.info(f"Connected sandbox running status: {is_running}")
593
-
594
- # 在连接的沙箱中执行操作
595
- result = connected_sandbox.commands.run("echo 'Connected sandbox test'")
596
- assert result.exit_code == 0
597
- assert "Connected sandbox test" in result.stdout
598
-
599
- # ======================== 上下文管理器测试 ========================
600
-
601
- def test_context_manager(self):
602
- """测试上下文管理器"""
603
- # 使用上下文管理器创建临时沙箱
604
- with Sandbox(template="base") as temp_sandbox:
605
- assert temp_sandbox is not None
606
-
607
- # 在上下文中使用沙箱
608
- result = temp_sandbox.commands.run("echo 'Context manager test'")
609
- assert result.exit_code == 0
610
-
611
- temp_id = temp_sandbox.sandbox_id
612
-
613
- # 沙箱应该已经被自动清理
614
-
615
- # ======================== 文件系统高级测试 ========================
616
-
617
- def test_filesystem_watch_dir(self):
618
- """测试目录监控"""
619
- assert self.sandbox is not None
620
-
621
- # 创建测试目录
622
- self.sandbox.files.make_dir("/tmp/watch_test")
623
-
624
- events_received = []
625
-
626
- def event_handler(event):
627
- events_received.append(event)
628
-
629
- # 开始监控目录
630
- try:
631
- watch_handle = self.sandbox.files.watch_dir(
632
- "/tmp/watch_test", on_event=event_handler, timeout=10
633
- )
634
-
635
- # 在监控的目录中创建文件
636
- self.sandbox.files.write("/tmp/watch_test/new_file.txt", "test content")
637
-
638
- # 等待事件
639
- time.sleep(2)
640
-
641
- # 停止监控
642
- watch_handle.stop()
643
-
644
- logger.info(f"Received {len(events_received)} filesystem events")
645
-
646
- except Exception as e:
647
- logger.warning(
648
- f"Directory watching test failed (may not be supported): {e}"
649
- )
650
-
651
- # ======================== 主测试执行器 ========================
652
-
653
- def run_all_tests(self):
654
- """运行所有测试"""
655
- logger.info("开始Sandbox综合验证测试...")
656
-
657
- # 基础操作测试
658
- self.run_test(self.test_sandbox_creation, "Sandbox Creation")
659
- self.run_test(self.test_sandbox_is_running, "Sandbox Running Status")
660
- self.run_test(self.test_sandbox_get_info, "Sandbox Get Info")
661
- self.run_test(self.test_sandbox_set_timeout, "Sandbox Set Timeout")
662
- self.run_test(self.test_sandbox_get_metrics, "Sandbox Get Metrics")
663
-
664
- # 文件系统操作测试
665
- self.run_test(self.test_filesystem_write_text, "Filesystem Write Text")
666
- self.run_test(self.test_filesystem_write_bytes, "Filesystem Write Bytes")
667
- self.run_test(self.test_filesystem_write_io, "Filesystem Write IO")
668
- self.run_test(self.test_filesystem_write_multiple, "Filesystem Write Multiple")
669
- self.run_test(self.test_filesystem_read_text, "Filesystem Read Text")
670
- self.run_test(self.test_filesystem_read_bytes, "Filesystem Read Bytes")
671
- self.run_test(self.test_filesystem_read_stream, "Filesystem Read Stream")
672
- self.run_test(self.test_filesystem_list, "Filesystem List")
673
- self.run_test(self.test_filesystem_exists, "Filesystem Exists")
674
- self.run_test(self.test_filesystem_get_info, "Filesystem Get Info")
675
- self.run_test(self.test_filesystem_make_dir, "Filesystem Make Dir")
676
- self.run_test(self.test_filesystem_rename, "Filesystem Rename")
677
- self.run_test(self.test_filesystem_remove, "Filesystem Remove")
678
-
679
- # 命令执行测试
680
- self.run_test(self.test_command_run_foreground, "Command Run Foreground")
681
- self.run_test(self.test_command_run_with_error, "Command Run With Error")
682
- self.run_test(self.test_command_run_background, "Command Run Background")
683
- self.run_test(self.test_command_with_env_and_cwd, "Command With Env And CWD")
684
- self.run_test(self.test_command_with_callbacks, "Command With Callbacks")
685
- self.run_test(self.test_command_list, "Command List")
686
- # self.run_test(self.test_command_send_stdin, "Command Send Stdin")
687
- # self.run_test(self.test_command_connect, "Command Connect")
688
-
689
- # PTY测试
690
- # self.run_test(self.test_pty_create, "PTY Create")
691
-
692
- # 静态方法测试
693
- self.run_test(self.test_static_methods, "Static Methods")
694
-
695
- # 错误处理测试
696
- self.run_test(
697
- self.test_error_handling_file_operations, "Error Handling File Operations"
698
- )
699
- self.run_test(
700
- self.test_error_handling_command_operations,
701
- "Error Handling Command Operations",
702
- )
703
-
704
- # 性能测试
705
- self.run_test(
706
- self.test_performance_file_operations, "Performance File Operations"
707
- )
708
- self.run_test(
709
- self.test_performance_command_operations, "Performance Command Operations"
710
- )
711
- self.run_test(
712
- self.test_performance_concurrent_commands, "Performance Concurrent Commands"
713
- )
714
-
715
- # 连接测试
716
- self.run_test(self.test_sandbox_connect, "Sandbox Connect")
717
-
718
- # 上下文管理器测试
719
- self.run_test(self.test_context_manager, "Context Manager")
720
-
721
- # 高级文件系统测试
722
- self.run_test(self.test_filesystem_watch_dir, "Filesystem Watch Dir")
723
-
724
- def cleanup(self):
725
- """清理资源"""
726
- if self.sandbox:
727
- try:
728
- self.sandbox.kill()
729
- logger.info("Sandbox cleaned up successfully")
730
- except Exception as e:
731
- logger.error(f"Error cleaning up sandbox: {e}")
732
-
733
- def print_summary(self):
734
- """打印测试摘要"""
735
- total_tests = len(self.test_results)
736
- passed_tests = sum(1 for r in self.test_results if r["success"])
737
- failed_tests = total_tests - passed_tests
738
-
739
- total_duration = sum(r["duration"] for r in self.test_results)
740
-
741
- print("\n" + "=" * 60)
742
- print("Sandbox综合验证测试报告")
743
- print("=" * 60)
744
- print(f"总测试数: {total_tests}")
745
- print(f"通过数: {passed_tests}")
746
- print(f"失败数: {failed_tests}")
747
- print(f"总耗时: {total_duration:.3f}秒")
748
- print(f"成功率: {(passed_tests/total_tests*100):.1f}%")
749
-
750
- if self.failed_tests:
751
- print(f"\n失败的测试:")
752
- for test in self.failed_tests:
753
- print(f" {test}")
754
-
755
- print("=" * 60)
756
-
757
-
758
- def main():
759
- """主函数"""
760
- validator = SandboxValidator()
761
-
762
- try:
763
- validator.run_all_tests()
764
- finally:
765
- validator.cleanup()
766
- validator.print_summary()
767
-
768
-
769
- if __name__ == "__main__":
770
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ Comprehensive validation test for sandbox_sync module.
4
+
5
+ This test suite demonstrates and validates all key functionality of the Sandbox:
6
+ - Sandbox lifecycle management (create, connect, kill)
7
+ - File system operations (read, write, list, remove, etc.)
8
+ - Command execution (foreground, background, PTY)
9
+ - Static methods and class methods
10
+ - Error handling and edge cases
11
+ - Performance testing
12
+ """
13
+
14
+ import datetime
15
+ import logging
16
+ import os
17
+ import tempfile
18
+ import threading
19
+ import time
20
+ from io import BytesIO, StringIO
21
+ from typing import List, Optional
22
+ from venv import create
23
+
24
+ from scalebox.exceptions import SandboxException
25
+ from scalebox.sandbox.commands.command_handle import CommandExitException, PtySize
26
+ from scalebox.sandbox.filesystem.filesystem import EntryInfo, FileType, WriteInfo
27
+ from scalebox.sandbox_sync.main import Sandbox
28
+
29
+ # 配置日志
30
+ logging.basicConfig(
31
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
32
+ )
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class SandboxValidator:
37
+ """Comprehensive Sandbox validation test suite."""
38
+
39
+ def __init__(self):
40
+ self.sandbox: Optional[Sandbox] = None
41
+ self.test_results = []
42
+ self.failed_tests = []
43
+
44
+ def log_test_result(
45
+ self, test_name: str, success: bool, message: str = "", duration: float = 0
46
+ ):
47
+ """记录测试结果"""
48
+ status = "✅ PASS" if success else "❌ FAIL"
49
+ result = {
50
+ "test": test_name,
51
+ "success": success,
52
+ "message": message,
53
+ "duration": duration,
54
+ }
55
+ self.test_results.append(result)
56
+
57
+ if not success:
58
+ self.failed_tests.append(test_name)
59
+
60
+ logger.info(f"{status} {test_name} ({duration:.3f}s) {message}")
61
+
62
+ def run_test(self, test_func, test_name: str):
63
+ """运行单个测试并记录结果"""
64
+ start_time = time.time()
65
+ try:
66
+ test_func()
67
+ duration = time.time() - start_time
68
+ self.log_test_result(test_name, True, duration=duration)
69
+ except Exception as e:
70
+ duration = time.time() - start_time
71
+ self.log_test_result(test_name, False, str(e), duration=duration)
72
+
73
+ # ======================== 基础沙箱操作测试 ========================
74
+
75
+ def test_sandbox_creation(self):
76
+ """测试沙箱创建"""
77
+ self.sandbox = Sandbox.create(
78
+ template="base",
79
+ # debug=True,
80
+ timeout=3600,
81
+ metadata={"test": "sync_validation"},
82
+ envs={"TEST_ENV": "sync_test"},
83
+ )
84
+ assert self.sandbox is not None
85
+ assert self.sandbox.sandbox_id is not None
86
+ logger.info(f"Created sandbox with ID: {self.sandbox.sandbox_id}")
87
+
88
+ def test_sandbox_is_running(self):
89
+ """测试沙箱运行状态检查"""
90
+ assert self.sandbox is not None
91
+ is_running = self.sandbox.is_running()
92
+ assert is_running == True
93
+ logger.info(f"Sandbox is running: {is_running}")
94
+
95
+ def test_sandbox_get_info(self):
96
+ """测试获取沙箱信息"""
97
+ assert self.sandbox is not None
98
+ info = self.sandbox.get_info()
99
+ assert info is not None
100
+ assert info.sandbox_id == self.sandbox.sandbox_id
101
+ logger.info(f"Sandbox info: {info}")
102
+
103
+ def test_sandbox_set_timeout(self):
104
+ """测试设置沙箱超时"""
105
+ assert self.sandbox is not None
106
+ self.sandbox.set_timeout(600) # 设置10分钟超时
107
+ logger.info("Successfully set sandbox timeout to 600 seconds")
108
+
109
+ def test_sandbox_get_metrics(self):
110
+ """测试获取沙箱指标"""
111
+ assert self.sandbox is not None
112
+ try:
113
+ metrics = self.sandbox.get_metrics()
114
+ logger.info(f"Sandbox metrics: {len(metrics) if metrics else 0} entries")
115
+ except Exception as e:
116
+ # 某些版本可能不支持指标
117
+ logger.warning(f"Metrics not supported: {e}")
118
+
119
+ # ======================== 文件系统操作测试 ========================
120
+
121
+ def test_filesystem_write_text(self):
122
+ """测试写入文本文件"""
123
+ assert self.sandbox is not None
124
+ test_content = "Hello, Sandbox!\nThis is a test file.\n测试中文内容"
125
+
126
+ result = self.sandbox.files.write("/tmp/test_sync.txt", test_content)
127
+ print(result)
128
+ assert isinstance(result, WriteInfo)
129
+ assert result.path == "/tmp/test_sync.txt"
130
+ assert result.type == FileType.FILE
131
+ logger.info(f"Written file: {result}")
132
+
133
+ def test_filesystem_write_bytes(self):
134
+ """测试写入字节文件"""
135
+ assert self.sandbox is not None
136
+ test_content = b"Binary content: \x00\x01\x02\x03\xff"
137
+
138
+ result = self.sandbox.files.write("/tmp/test_binary.bin", test_content)
139
+ print(result)
140
+ assert isinstance(result, WriteInfo)
141
+ assert result.path == "/tmp/test_binary.bin"
142
+
143
+ def test_filesystem_write_io(self):
144
+ """测试写入IO对象"""
145
+ assert self.sandbox is not None
146
+
147
+ # 测试StringIO
148
+ string_io = StringIO("Content from StringIO\nSecond line")
149
+ result1 = self.sandbox.files.write("/tmp/test_stringio.txt", string_io)
150
+ print(result1)
151
+ assert isinstance(result1, WriteInfo)
152
+
153
+ # 测试BytesIO
154
+ bytes_io = BytesIO(b"Content from BytesIO")
155
+ result2 = self.sandbox.files.write("/tmp/test_bytesio.bin", bytes_io)
156
+ print(result2)
157
+ assert isinstance(result2, WriteInfo)
158
+
159
+ def test_filesystem_write_multiple(self):
160
+ """测试批量写入文件"""
161
+ assert self.sandbox is not None
162
+
163
+ files = [
164
+ {"path": "/tmp/multi1.txt", "data": "Content of file 1"},
165
+ {"path": "/tmp/multi2.txt", "data": "Content of file 2"},
166
+ {"path": "/tmp/multi3.bin", "data": b"Binary content of file 3"},
167
+ ]
168
+
169
+ results = self.sandbox.files.write(files)
170
+ print(results)
171
+ assert isinstance(results, list)
172
+ assert len(results) == 3
173
+ for result in results:
174
+ assert isinstance(result, WriteInfo)
175
+
176
+ def test_filesystem_read_text(self):
177
+ """测试读取文本文件"""
178
+ assert self.sandbox is not None
179
+
180
+ content = self.sandbox.files.read("/tmp/test_sync.txt", format="text")
181
+ print(content)
182
+ assert isinstance(content, str)
183
+ assert "Hello, Sandbox!" in content
184
+ assert "测试中文内容" in content
185
+
186
+ def test_filesystem_read_bytes(self):
187
+ """测试读取字节文件"""
188
+ assert self.sandbox is not None
189
+
190
+ content = self.sandbox.files.read("/tmp/test_binary.bin", format="bytes")
191
+ print(content)
192
+ assert isinstance(content, bytearray)
193
+ expected = b"Binary content: \x00\x01\x02\x03\xff"
194
+ assert bytes(content) == expected
195
+
196
+ def test_filesystem_read_stream(self):
197
+ """测试流式读取文件"""
198
+ assert self.sandbox is not None
199
+
200
+ stream = self.sandbox.files.read("/tmp/test_sync.txt", format="stream")
201
+ chunks = []
202
+ for chunk in stream:
203
+ chunks.append(chunk)
204
+
205
+ content = b"".join(chunks).decode("utf-8")
206
+ print(content)
207
+ assert "Hello, Sandbox!" in content
208
+
209
+ def test_filesystem_list(self):
210
+ """测试列出目录内容"""
211
+ assert self.sandbox is not None
212
+
213
+ entries = self.sandbox.files.list("/tmp", depth=1)
214
+ print(entries)
215
+ assert isinstance(entries, list)
216
+ assert len(entries) > 0
217
+
218
+ # 检查我们创建的文件是否存在
219
+ filenames = [entry.name for entry in entries]
220
+ assert "test_sync.txt" in filenames
221
+
222
+ for entry in entries:
223
+ assert isinstance(entry, EntryInfo)
224
+ assert entry.name is not None
225
+ assert entry.path is not None
226
+ entries = self.sandbox.files.list("/", depth=1)
227
+ print(entries)
228
+ assert isinstance(entries, list)
229
+ assert len(entries) > 0
230
+
231
+ self.sandbox.files.make_dir("/data/test_dir")
232
+
233
+ entries = self.sandbox.files.list("/data", depth=1)
234
+ print(entries)
235
+ assert isinstance(entries, list)
236
+ assert len(entries) > 0
237
+
238
+ def test_filesystem_exists(self):
239
+ """测试文件存在性检查"""
240
+ assert self.sandbox is not None
241
+
242
+ # 检查存在的文件
243
+ exists = self.sandbox.files.exists("/tmp/test_sync.txt")
244
+ assert exists == True
245
+
246
+ # 检查不存在的文件
247
+ not_exists = self.sandbox.files.exists("/tmp/nonexistent.txt")
248
+ assert not_exists == False
249
+
250
+ def test_filesystem_get_info(self):
251
+ """测试获取文件信息"""
252
+ assert self.sandbox is not None
253
+ info = self.sandbox.files.get_info("/tmp/test_sync.txt")
254
+ assert isinstance(info, EntryInfo)
255
+ assert info.name == "test_sync.txt"
256
+ assert info.type == FileType.FILE
257
+ assert info.path == "/tmp/test_sync.txt"
258
+ assert info.size > 0
259
+
260
+ def test_filesystem_make_dir(self):
261
+ """测试创建目录"""
262
+ assert self.sandbox is not None
263
+
264
+ # 创建新目录
265
+ result = self.sandbox.files.make_dir("/tmp/test_dir/nested_dir")
266
+ assert result == True
267
+
268
+ # 再次创建同一目录应该返回False
269
+ result = self.sandbox.files.make_dir("/tmp/test_dir")
270
+ assert result == False
271
+
272
+ def test_filesystem_rename(self):
273
+ """测试重命名文件"""
274
+ assert self.sandbox is not None
275
+
276
+ # 创建一个测试文件
277
+ self.sandbox.files.write("/tmp/old_name.txt", "content to rename")
278
+
279
+ # 重命名
280
+ result = self.sandbox.files.rename("/tmp/old_name.txt", "/tmp/new_name.txt")
281
+ assert isinstance(result, EntryInfo)
282
+ assert result.name == "new_name.txt"
283
+
284
+ # 确认旧文件不存在,新文件存在
285
+ assert self.sandbox.files.exists("/tmp/old_name.txt") == False
286
+ assert self.sandbox.files.exists("/tmp/new_name.txt") == True
287
+
288
+ def test_filesystem_remove(self):
289
+ """测试删除文件和目录"""
290
+ assert self.sandbox is not None
291
+
292
+ # 删除文件
293
+ self.sandbox.files.remove("/tmp/new_name.txt")
294
+ assert self.sandbox.files.exists("/tmp/new_name.txt") == False
295
+
296
+ # 删除目录
297
+ self.sandbox.files.remove("/tmp/test_dir")
298
+ assert self.sandbox.files.exists("/tmp/test_dir") == False
299
+
300
+ # ======================== 命令执行测试 ========================
301
+
302
+ def test_command_run_foreground(self):
303
+ """测试前台命令执行"""
304
+ assert self.sandbox is not None
305
+
306
+ result = self.sandbox.commands.run("echo 'Hello from command'")
307
+ assert result.exit_code == 0
308
+ assert "Hello from command" in result.stdout
309
+ assert result.stderr == ""
310
+
311
+ def test_command_run_with_error(self):
312
+ """测试执行失败的命令"""
313
+ assert self.sandbox is not None
314
+ try:
315
+ result = self.sandbox.commands.run("ls /nonexistent_directory 2>&1")
316
+ except CommandExitException as e:
317
+ assert e.exit_code != 0
318
+ assert (
319
+ "No such file or directory" in e.stdout or "cannot access" in e.stdout
320
+ )
321
+
322
+ def test_command_run_background(self):
323
+ """测试后台命令执行"""
324
+ assert self.sandbox is not None
325
+
326
+ # 启动后台命令
327
+ handle = self.sandbox.commands.run(
328
+ "sleep 2 && echo 'Background task completed'", background=True
329
+ )
330
+ print(handle)
331
+ # 等待命令完成
332
+ result = handle.wait()
333
+ print(result)
334
+ assert result.exit_code == 0
335
+ # assert "Process is running in background" in result.stderr
336
+
337
+ def test_command_with_env_and_cwd(self):
338
+ """测试带环境变量和工作目录的命令"""
339
+ assert self.sandbox is not None
340
+
341
+ # 创建测试目录
342
+ self.sandbox.files.make_dir("/tmp/test_cwd")
343
+
344
+ result = self.sandbox.commands.run(
345
+ "echo $TEST_VAR && pwd",
346
+ envs={"TEST_VAR": "test_value"},
347
+ cwd="/tmp/test_cwd",
348
+ )
349
+
350
+ assert result.exit_code == 0
351
+ assert "test_value" in result.stdout
352
+ assert "/tmp/test_cwd" in result.stdout
353
+
354
+ def test_command_with_callbacks(self):
355
+ """测试带回调的命令执行"""
356
+ assert self.sandbox is not None
357
+
358
+ stdout_data = []
359
+ stderr_data = []
360
+
361
+ def stdout_handler(data):
362
+ stdout_data.append(data)
363
+
364
+ def stderr_handler(data):
365
+ stderr_data.append(data)
366
+
367
+ result = self.sandbox.commands.run(
368
+ "echo 'stdout message' && echo 'stderr message' >&2",
369
+ on_stdout=stdout_handler,
370
+ on_stderr=stderr_handler,
371
+ )
372
+
373
+ assert result.exit_code == 0
374
+ # Note: 回调在命令结束后触发
375
+
376
+ def test_command_list(self):
377
+ """测试列出运行中的命令"""
378
+ assert self.sandbox is not None
379
+
380
+ # 启动一个长时间运行的后台命令
381
+ handle = self.sandbox.commands.run("sleep 30 ", background=True)
382
+ # 列出进程
383
+ time.sleep(1)
384
+ processes = self.sandbox.commands.list()
385
+ assert isinstance(processes, list)
386
+
387
+ # 查找我们的进程
388
+ found = any(p.pid == handle.pid for p in processes)
389
+ assert found == True
390
+
391
+ # 清理:杀死进程
392
+ killed = self.sandbox.commands.kill(handle.pid)
393
+ print(killed)
394
+ assert killed == True
395
+
396
+ def test_command_send_stdin(self):
397
+ """测试向命令发送标准输入"""
398
+ assert self.sandbox is not None
399
+
400
+ # 启动一个等待输入的命令
401
+ handle = self.sandbox.commands.run("cat", background=True)
402
+ print(handle.pid)
403
+ # 发送输入
404
+ self.sandbox.commands.send_stdin(handle.pid, "Hello stdin\n")
405
+
406
+ # 等待一点时间让命令处理输入
407
+ time.sleep(1)
408
+
409
+ # 杀死进程并获取结果
410
+ self.sandbox.commands.kill(handle.pid)
411
+ result = handle.wait()
412
+
413
+ # cat命令会被SIGKILL杀死,所以exit_code不会是0
414
+ # 但我们验证了send_stdin没有抛出异常
415
+
416
+ def test_command_connect(self):
417
+ """测试连接到运行中的命令"""
418
+ assert self.sandbox is not None
419
+
420
+ # 启动后台命令
421
+ handle1 = self.sandbox.commands.run("sleep 30", background=True)
422
+ print(handle1.pid)
423
+
424
+ # 连接到同一个进程
425
+ handle2 = self.sandbox.commands.connect(handle1.pid)
426
+ print(handle2.events)
427
+
428
+ assert handle1.pid == handle2.pid
429
+
430
+ # 清理
431
+ self.sandbox.commands.kill(handle1.pid)
432
+
433
+ # ======================== PTY操作测试 ========================
434
+
435
+ def test_pty_create(self):
436
+ """测试创建PTY"""
437
+ assert self.sandbox is not None
438
+
439
+ pty_data = []
440
+
441
+ def pty_handler(data):
442
+ pty_data.append(data)
443
+
444
+ pty_handle = self.sandbox.pty.create(
445
+ size=PtySize(rows=24, cols=80),
446
+ # on_data=pty_handler,
447
+ cwd="/tmp",
448
+ envs={"PTY_TEST": "value"},
449
+ )
450
+
451
+ assert pty_handle is not None
452
+ assert pty_handle.pid > 0
453
+
454
+ # 发送命令并等待
455
+ self.sandbox.pty.send_stdin(pty_handle.pid, b"echo 'PTY test'\n")
456
+ time.sleep(2)
457
+
458
+ # 调整大小
459
+ self.sandbox.pty.resize(pty_handle.pid, PtySize(rows=30, cols=100))
460
+
461
+ # 清理
462
+ killed = self.sandbox.pty.kill(pty_handle.pid)
463
+ assert killed == True
464
+
465
+ # ======================== 静态方法测试 ========================
466
+
467
+ def test_static_methods(self):
468
+ """测试静态方法"""
469
+ assert self.sandbox is not None
470
+ sandbox_id = self.sandbox.sandbox_id
471
+
472
+ # 静态方法获取信息
473
+ info = Sandbox.get_info(sandbox_id)
474
+ assert info.sandbox_id == sandbox_id
475
+
476
+ # 静态方法设置超时
477
+ Sandbox.set_timeout(sandbox_id, 900)
478
+
479
+ # 静态方法获取指标(可能不支持)
480
+ try:
481
+ metrics = Sandbox.get_metrics(sandbox_id)
482
+ logger.info(f"Static method metrics: {len(metrics) if metrics else 0}")
483
+ except Exception as e:
484
+ logger.warning(f"Static method metrics not supported: {e}")
485
+
486
+ # ======================== 错误处理测试 ========================
487
+
488
+ def test_error_handling_file_operations(self):
489
+ """测试文件操作错误处理"""
490
+ assert self.sandbox is not None
491
+
492
+ # 读取不存在的文件
493
+ try:
494
+ self.sandbox.files.read("/nonexistent/file.txt")
495
+ assert False, "应该抛出异常"
496
+ except Exception as e:
497
+ logger.info(f"正确捕获文件读取错误: {type(e).__name__}")
498
+
499
+ # 写入到只读目录
500
+ try:
501
+ self.sandbox.files.write("/proc/test.txt", "content")
502
+ assert False, "应该抛出异常"
503
+ except Exception as e:
504
+ logger.info(f"正确捕获文件写入错误: {type(e).__name__}")
505
+
506
+ def test_error_handling_command_operations(self):
507
+ """测试命令操作错误处理"""
508
+ assert self.sandbox is not None
509
+
510
+ # 杀死不存在的进程
511
+ killed = self.sandbox.commands.kill(99999)
512
+ assert killed == False
513
+
514
+ # 连接到不存在的进程
515
+ try:
516
+ self.sandbox.commands.connect(99999)
517
+ assert False, "应该抛出异常"
518
+ except Exception as e:
519
+ logger.info(f"正确捕获进程连接错误: {type(e).__name__}")
520
+
521
+ # ======================== 性能测试 ========================
522
+
523
+ def test_performance_file_operations(self):
524
+ """测试文件操作性能"""
525
+ assert self.sandbox is not None
526
+
527
+ # 批量文件操作
528
+ start_time = time.time()
529
+
530
+ # 创建100个小文件
531
+ files = [
532
+ {"path": f"/tmp/perf_test_{i}.txt", "data": f"Content of file {i}"}
533
+ for i in range(100)
534
+ ]
535
+
536
+ self.sandbox.files.write(files)
537
+ duration = time.time() - start_time
538
+
539
+ logger.info(f"Created 100 files in {duration:.3f}s")
540
+ assert duration < 30 # 应该在30秒内完成
541
+
542
+ # 清理
543
+ for i in range(100):
544
+ try:
545
+ self.sandbox.files.remove(f"/tmp/perf_test_{i}.txt")
546
+ except:
547
+ pass
548
+
549
+ def test_performance_command_operations(self):
550
+ """测试命令操作性能"""
551
+ assert self.sandbox is not None
552
+
553
+ start_time = time.time()
554
+
555
+ # 执行10个顺序命令(同步版本)
556
+ for i in range(10):
557
+ result = self.sandbox.commands.run(f"echo 'Command {i}'")
558
+ assert result.exit_code == 0
559
+ assert f"Command {i}" in result.stdout
560
+
561
+ duration = time.time() - start_time
562
+ logger.info(f"Executed 10 sequential commands in {duration:.3f}s")
563
+
564
+ def test_performance_concurrent_commands(self):
565
+ """测试并发命令性能(使用线程池)"""
566
+ assert self.sandbox is not None
567
+
568
+ def run_command(i):
569
+ result = self.sandbox.commands.run(f"echo 'Concurrent command {i}'")
570
+ assert result.exit_code == 0
571
+ return result
572
+
573
+ start_time = time.time()
574
+
575
+ # 使用线程池执行并发命令
576
+ import concurrent.futures
577
+
578
+ with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
579
+ futures = [executor.submit(run_command, i) for i in range(10)]
580
+ results = [
581
+ future.result() for future in concurrent.futures.as_completed(futures)
582
+ ]
583
+
584
+ duration = time.time() - start_time
585
+ logger.info(f"Executed 10 concurrent commands in {duration:.3f}s")
586
+ assert len(results) == 10
587
+
588
+ # ======================== 连接测试 ========================
589
+
590
+ def test_sandbox_connect(self):
591
+ """测试连接到现有沙箱"""
592
+ assert self.sandbox is not None
593
+ original_id = self.sandbox.sandbox_id
594
+
595
+ # 连接到现有沙箱
596
+ connected_sandbox = Sandbox.connect(original_id)
597
+ assert connected_sandbox.sandbox_id == original_id
598
+
599
+ # 验证连接的沙箱可以正常使用
600
+ is_running = connected_sandbox.is_running()
601
+ logger.info(f"Connected sandbox running status: {is_running}")
602
+
603
+ # 在连接的沙箱中执行操作
604
+ result = connected_sandbox.commands.run("echo 'Connected sandbox test'")
605
+ assert result.exit_code == 0
606
+ assert "Connected sandbox test" in result.stdout
607
+
608
+ # ======================== 上下文管理器测试 ========================
609
+
610
+ def test_context_manager(self):
611
+ """测试上下文管理器"""
612
+ # 使用上下文管理器创建临时沙箱
613
+ with Sandbox(template="base") as temp_sandbox:
614
+ assert temp_sandbox is not None
615
+
616
+ # 在上下文中使用沙箱
617
+ result = temp_sandbox.commands.run("echo 'Context manager test'")
618
+ assert result.exit_code == 0
619
+
620
+ temp_id = temp_sandbox.sandbox_id
621
+
622
+ # 沙箱应该已经被自动清理
623
+
624
+ # ======================== 文件系统高级测试 ========================
625
+
626
+ def test_filesystem_watch_dir(self):
627
+ """测试目录监控"""
628
+ assert self.sandbox is not None
629
+
630
+ # 创建测试目录
631
+ self.sandbox.files.make_dir("/tmp/watch_test")
632
+
633
+ events_received = []
634
+
635
+ def event_handler(event):
636
+ events_received.append(event)
637
+
638
+ # 开始监控目录
639
+ try:
640
+ watch_handle = self.sandbox.files.watch_dir(
641
+ "/tmp/watch_test", on_event=event_handler, timeout=10
642
+ )
643
+
644
+ # 在监控的目录中创建文件
645
+ self.sandbox.files.write("/tmp/watch_test/new_file.txt", "test content")
646
+
647
+ # 等待事件
648
+ time.sleep(2)
649
+
650
+ # 停止监控
651
+ watch_handle.stop()
652
+
653
+ logger.info(f"Received {len(events_received)} filesystem events")
654
+
655
+ except Exception as e:
656
+ logger.warning(
657
+ f"Directory watching test failed (may not be supported): {e}"
658
+ )
659
+
660
+ # ======================== 主测试执行器 ========================
661
+
662
+ def run_all_tests(self):
663
+ """运行所有测试"""
664
+ logger.info("开始Sandbox综合验证测试...")
665
+
666
+ # 基础操作测试
667
+ self.run_test(self.test_sandbox_creation, "Sandbox Creation")
668
+ self.run_test(self.test_sandbox_is_running, "Sandbox Running Status")
669
+ self.run_test(self.test_sandbox_get_info, "Sandbox Get Info")
670
+ self.run_test(self.test_sandbox_set_timeout, "Sandbox Set Timeout")
671
+ self.run_test(self.test_sandbox_get_metrics, "Sandbox Get Metrics")
672
+
673
+ # 文件系统操作测试
674
+ self.run_test(self.test_filesystem_write_text, "Filesystem Write Text")
675
+ self.run_test(self.test_filesystem_write_bytes, "Filesystem Write Bytes")
676
+ self.run_test(self.test_filesystem_write_io, "Filesystem Write IO")
677
+ self.run_test(self.test_filesystem_write_multiple, "Filesystem Write Multiple")
678
+ self.run_test(self.test_filesystem_read_text, "Filesystem Read Text")
679
+ self.run_test(self.test_filesystem_read_bytes, "Filesystem Read Bytes")
680
+ self.run_test(self.test_filesystem_read_stream, "Filesystem Read Stream")
681
+ self.run_test(self.test_filesystem_list, "Filesystem List")
682
+ self.run_test(self.test_filesystem_exists, "Filesystem Exists")
683
+ self.run_test(self.test_filesystem_get_info, "Filesystem Get Info")
684
+ self.run_test(self.test_filesystem_make_dir, "Filesystem Make Dir")
685
+ self.run_test(self.test_filesystem_rename, "Filesystem Rename")
686
+ self.run_test(self.test_filesystem_remove, "Filesystem Remove")
687
+
688
+ # 命令执行测试
689
+ self.run_test(self.test_command_run_foreground, "Command Run Foreground")
690
+ self.run_test(self.test_command_run_with_error, "Command Run With Error")
691
+ self.run_test(self.test_command_run_background, "Command Run Background")
692
+ self.run_test(self.test_command_with_env_and_cwd, "Command With Env And CWD")
693
+ self.run_test(self.test_command_with_callbacks, "Command With Callbacks")
694
+ self.run_test(self.test_command_list, "Command List")
695
+ # self.run_test(self.test_command_send_stdin, "Command Send Stdin")
696
+ # self.run_test(self.test_command_connect, "Command Connect")
697
+
698
+ # PTY测试
699
+ # self.run_test(self.test_pty_create, "PTY Create")
700
+
701
+ # 静态方法测试
702
+ self.run_test(self.test_static_methods, "Static Methods")
703
+
704
+ # 错误处理测试
705
+ self.run_test(
706
+ self.test_error_handling_file_operations, "Error Handling File Operations"
707
+ )
708
+ self.run_test(
709
+ self.test_error_handling_command_operations,
710
+ "Error Handling Command Operations",
711
+ )
712
+
713
+ # 性能测试
714
+ self.run_test(
715
+ self.test_performance_file_operations, "Performance File Operations"
716
+ )
717
+ self.run_test(
718
+ self.test_performance_command_operations, "Performance Command Operations"
719
+ )
720
+ self.run_test(
721
+ self.test_performance_concurrent_commands, "Performance Concurrent Commands"
722
+ )
723
+
724
+ # 连接测试
725
+ self.run_test(self.test_sandbox_connect, "Sandbox Connect")
726
+
727
+ # 上下文管理器测试
728
+ self.run_test(self.test_context_manager, "Context Manager")
729
+
730
+ # 高级文件系统测试
731
+ self.run_test(self.test_filesystem_watch_dir, "Filesystem Watch Dir")
732
+
733
+ def cleanup(self):
734
+ """清理资源"""
735
+ if self.sandbox:
736
+ try:
737
+ # self.sandbox.kill()
738
+ logger.info("Sandbox cleaned up successfully")
739
+ except Exception as e:
740
+ logger.error(f"Error cleaning up sandbox: {e}")
741
+
742
+ def print_summary(self):
743
+ """打印测试摘要"""
744
+ total_tests = len(self.test_results)
745
+ passed_tests = sum(1 for r in self.test_results if r["success"])
746
+ failed_tests = total_tests - passed_tests
747
+
748
+ total_duration = sum(r["duration"] for r in self.test_results)
749
+
750
+ print("\n" + "=" * 60)
751
+ print("Sandbox综合验证测试报告")
752
+ print("=" * 60)
753
+ print(f"总测试数: {total_tests}")
754
+ print(f"通过数: {passed_tests}")
755
+ print(f"失败数: {failed_tests}")
756
+ print(f"总耗时: {total_duration:.3f}秒")
757
+ print(f"成功率: {(passed_tests/total_tests*100):.1f}%")
758
+
759
+ if self.failed_tests:
760
+ print(f"\n失败的测试:")
761
+ for test in self.failed_tests:
762
+ print(f" ❌ {test}")
763
+
764
+ print("=" * 60)
765
+
766
+
767
+ def main():
768
+ """主函数"""
769
+ validator = SandboxValidator()
770
+
771
+ try:
772
+ validator.run_all_tests()
773
+ finally:
774
+ validator.cleanup()
775
+ validator.print_summary()
776
+
777
+
778
+ if __name__ == "__main__":
779
+ main()