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.
- scalebox/__init__.py +1 -1
- scalebox/api/__init__.py +128 -128
- scalebox/api/client/__init__.py +8 -8
- scalebox/api/client/api/sandboxes/get_sandboxes.py +5 -3
- scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
- scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
- scalebox/api/client/client.py +288 -288
- scalebox/api/client/models/listed_sandbox.py +11 -9
- scalebox/api/client/models/new_sandbox.py +1 -1
- scalebox/api/client/models/sandbox.py +125 -125
- scalebox/api/client/models/sandbox_state.py +1 -0
- scalebox/api/client/types.py +46 -46
- scalebox/code_interpreter/code_interpreter_async.py +370 -369
- scalebox/code_interpreter/code_interpreter_sync.py +318 -317
- scalebox/connection_config.py +92 -92
- scalebox/csx_desktop/main.py +12 -12
- scalebox/generated/api_pb2_connect.py +17 -66
- scalebox/sandbox_async/commands/command.py +307 -307
- scalebox/sandbox_async/commands/command_handle.py +187 -187
- scalebox/sandbox_async/commands/pty.py +187 -187
- scalebox/sandbox_async/filesystem/filesystem.py +557 -557
- scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
- scalebox/sandbox_async/main.py +647 -646
- scalebox/sandbox_async/sandbox_api.py +365 -365
- scalebox/sandbox_async/utils.py +7 -7
- scalebox/sandbox_sync/__init__.py +2 -2
- scalebox/sandbox_sync/commands/command.py +300 -300
- scalebox/sandbox_sync/commands/command_handle.py +150 -150
- scalebox/sandbox_sync/commands/pty.py +181 -181
- scalebox/sandbox_sync/filesystem/filesystem.py +543 -543
- scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
- scalebox/sandbox_sync/main.py +789 -790
- scalebox/sandbox_sync/sandbox_api.py +356 -356
- scalebox/test/CODE_INTERPRETER_TESTS_READY.md +256 -256
- scalebox/test/README.md +164 -164
- scalebox/test/aclient.py +72 -72
- scalebox/test/code_interpreter_centext.py +21 -21
- scalebox/test/code_interpreter_centext_sync.py +21 -21
- scalebox/test/code_interpreter_test.py +1 -1
- scalebox/test/code_interpreter_test_sync.py +1 -1
- scalebox/test/run_all_validation_tests.py +334 -334
- scalebox/test/test_basic.py +78 -78
- scalebox/test/test_code_interpreter_async_comprehensive.py +2653 -2653
- scalebox/test/{test_code_interpreter_e2bsync_comprehensive.py → test_code_interpreter_execcode.py} +328 -392
- scalebox/test/test_code_interpreter_sync_comprehensive.py +3416 -3412
- scalebox/test/test_csx_desktop_examples.py +130 -0
- scalebox/test/test_sandbox_async_comprehensive.py +736 -738
- scalebox/test/test_sandbox_stress_and_edge_cases.py +778 -778
- scalebox/test/test_sandbox_sync_comprehensive.py +779 -770
- scalebox/test/test_sandbox_usage_examples.py +987 -987
- scalebox/test/testacreate.py +24 -24
- scalebox/test/testagetinfo.py +18 -18
- scalebox/test/testcodeinterpreter_async.py +508 -508
- scalebox/test/testcodeinterpreter_sync.py +239 -239
- scalebox/test/testcomputeuse.py +2 -2
- scalebox/test/testnovnc.py +12 -12
- scalebox/test/testsandbox_api.py +15 -0
- scalebox/test/testsandbox_async.py +202 -118
- scalebox/test/testsandbox_sync.py +71 -38
- scalebox/version.py +2 -2
- {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/METADATA +104 -103
- {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/RECORD +66 -66
- scalebox/test/test_code_interpreter_e2basync_comprehensive.py +0 -2655
- scalebox/test/test_e2b_first.py +0 -11
- {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/WHEEL +0 -0
- {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-0.1.4.dist-info → scalebox_sdk-0.1.25.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
24
|
-
from scalebox.
|
|
25
|
-
from scalebox.sandbox.
|
|
26
|
-
from scalebox.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
assert self.sandbox
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
assert info
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.sandbox
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
assert result
|
|
129
|
-
assert result.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
assert result
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{"path": "/tmp/
|
|
165
|
-
{"path": "/tmp/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
assert
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
assert
|
|
183
|
-
assert "
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
assert
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
assert entry
|
|
224
|
-
assert entry.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
assert
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
assert
|
|
237
|
-
|
|
238
|
-
def
|
|
239
|
-
"""
|
|
240
|
-
assert self.sandbox is not None
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
assert
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
assert
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
#
|
|
269
|
-
result = self.sandbox.files.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
"""
|
|
281
|
-
assert
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
assert self.sandbox.files.exists("/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
""
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
assert
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def
|
|
303
|
-
"""
|
|
304
|
-
assert self.sandbox is not None
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
#
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
assert
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
assert
|
|
390
|
-
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
print(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
#
|
|
401
|
-
self.sandbox.commands.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
assert
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
assert
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
#
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
self.run_test(self.
|
|
668
|
-
self.run_test(self.
|
|
669
|
-
self.run_test(self.
|
|
670
|
-
self.run_test(self.
|
|
671
|
-
self.run_test(self.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
self.run_test(self.
|
|
675
|
-
self.run_test(self.
|
|
676
|
-
self.run_test(self.
|
|
677
|
-
self.run_test(self.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
self.run_test(self.
|
|
681
|
-
self.run_test(self.
|
|
682
|
-
self.run_test(self.
|
|
683
|
-
self.run_test(self.
|
|
684
|
-
self.run_test(self.
|
|
685
|
-
self.run_test(self.
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
self.run_test(self.
|
|
694
|
-
|
|
695
|
-
#
|
|
696
|
-
self.run_test(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
self.run_test(
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
#
|
|
705
|
-
self.run_test(
|
|
706
|
-
self.
|
|
707
|
-
)
|
|
708
|
-
self.run_test(
|
|
709
|
-
self.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
""
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
def
|
|
734
|
-
"""
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
print("
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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()
|