scalebox-sdk 1.0.1__py3-none-any.whl → 1.0.2__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/client/api/sandboxes/get_sandboxes.py +1 -1
- scalebox/api/client/models/error.py +1 -1
- scalebox/api/client/models/listed_sandbox.py +6 -3
- scalebox/api/client/models/sandbox.py +2 -2
- scalebox/code_interpreter/code_interpreter_async.py +3 -1
- scalebox/code_interpreter/code_interpreter_sync.py +3 -1
- scalebox/connection_config.py +2 -0
- scalebox/generated/api_pb2_connect.py +3 -3
- scalebox/sandbox/main.py +4 -4
- scalebox/test/bedrock_openai_adapter.py +8 -2
- scalebox/test/run_stress_code_interpreter_sync.py +18 -6
- scalebox/test/simple_upload_example.py +39 -31
- scalebox/test/stabitiy_test.py +82 -69
- scalebox/test/test_browser_use.py +4 -2
- scalebox/test/test_browser_use_scalebox.py +5 -4
- scalebox/test/test_code_interpreter_execcode.py +289 -211
- scalebox/test/test_code_interpreter_sync_comprehensive.py +2 -5
- scalebox/test/test_connect_pause_async.py +34 -11
- scalebox/test/test_connect_pause_sync.py +49 -16
- scalebox/test/test_csx_desktop_examples.py +3 -3
- scalebox/test/test_desktop_sandbox_sf.py +18 -23
- scalebox/test/test_download_url.py +6 -14
- scalebox/test/test_existing_sandbox.py +1037 -0
- scalebox/test/test_sandbox_async_comprehensive.py +4 -2
- scalebox/test/test_sandbox_object_storage_example.py +14 -9
- scalebox/test/test_sandbox_object_storage_example_async.py +6 -3
- scalebox/test/test_sandbox_sync_comprehensive.py +1 -1
- scalebox/test/test_sf.py +12 -8
- scalebox/test/test_watch_dir_async.py +6 -4
- scalebox/test/testagetinfo.py +1 -3
- scalebox/test/testsandbox_api.py +5 -3
- scalebox/test/testsandbox_async.py +17 -47
- scalebox/test/testsandbox_sync.py +18 -14
- scalebox/test/upload_100mb_example.py +77 -55
- scalebox/version.py +2 -2
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/METADATA +1 -1
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/RECORD +42 -41
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/WHEEL +0 -0
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {scalebox_sdk-1.0.1.dist-info → scalebox_sdk-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
测试用例:使用已有的sandbox URL和token进行测试(Debug模式)
|
|
4
|
+
跳过创建流程,直接连接已有的sandbox
|
|
5
|
+
|
|
6
|
+
测试功能包括:
|
|
7
|
+
1. Filesystem操作(读写、列表、存在性检查等)
|
|
8
|
+
2. 命令操作(process/commands)
|
|
9
|
+
3. 上传和下载文件
|
|
10
|
+
|
|
11
|
+
使用方法:
|
|
12
|
+
1. 设置环境变量:
|
|
13
|
+
export SANDBOX_ID='your-sandbox-id'
|
|
14
|
+
export ENVD_ACCESS_TOKEN='your-token'
|
|
15
|
+
export SBX_DEBUG_HOST='localhost' # 可选,默认为localhost,可设置为其他host如 '192.168.1.100'
|
|
16
|
+
export SBX_API_KEY='your-api-key' # 可选
|
|
17
|
+
|
|
18
|
+
2. 运行测试:
|
|
19
|
+
python3 scalebox/test/test_existing_sandbox.py
|
|
20
|
+
|
|
21
|
+
注意:
|
|
22
|
+
- 此测试用例使用debug模式(debug=True)
|
|
23
|
+
- 在debug模式下,连接地址为 http://{SBX_DEBUG_HOST}:8888
|
|
24
|
+
- 请确保sandbox在指定的host和8888端口上运行
|
|
25
|
+
|
|
26
|
+
关于后端日志错误:
|
|
27
|
+
- 如果看到 "error adjusting oom score" 错误,这是正常的,表示后端尝试设置进程的OOM score但权限不足
|
|
28
|
+
- 这些错误不影响测试用例的执行和结果
|
|
29
|
+
- 在debug模式下,这些权限相关的错误是可以接受的
|
|
30
|
+
- 如果看到 "exit status 127" (命令未找到) 或 "exit status 1" (非零退出码),这些是测试用例预期的行为
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import tempfile
|
|
36
|
+
import time
|
|
37
|
+
from io import StringIO, BytesIO
|
|
38
|
+
|
|
39
|
+
# 确保可以从项目根导入 scalebox
|
|
40
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
41
|
+
|
|
42
|
+
from scalebox.sandbox_sync.main import Sandbox
|
|
43
|
+
from scalebox.connection_config import ConnectionConfig
|
|
44
|
+
from scalebox.sandbox.commands.command_handle import CommandExitException
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_filesystem_operations(sandbox):
|
|
48
|
+
"""测试filesystem操作"""
|
|
49
|
+
print("\n" + "=" * 60)
|
|
50
|
+
print("测试 Filesystem 操作")
|
|
51
|
+
print("=" * 60)
|
|
52
|
+
|
|
53
|
+
# 1. 测试写入文本文件
|
|
54
|
+
print("\n[1] 测试写入文本文件...")
|
|
55
|
+
test_content = "Hello, this is a test file!\nCreated at: " + str(time.time())
|
|
56
|
+
test_path = "/tmp/test_file.txt"
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
print(f" 文件路径: {test_path}")
|
|
60
|
+
print(f" 内容长度: {len(test_content)} 字符")
|
|
61
|
+
print(f" 内容预览: {test_content[:50]}...")
|
|
62
|
+
result = sandbox.files.write(test_path, test_content)
|
|
63
|
+
print(f"✓ 文件写入成功")
|
|
64
|
+
print(f" 写入路径: {result.path}")
|
|
65
|
+
print(f" 文件类型: {getattr(result, 'type', 'N/A')}")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"✗ 文件写入失败: {e}")
|
|
68
|
+
import traceback
|
|
69
|
+
|
|
70
|
+
traceback.print_exc()
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
# 2. 测试检查文件是否存在
|
|
74
|
+
print("\n[2] 测试检查文件是否存在...")
|
|
75
|
+
try:
|
|
76
|
+
exists = sandbox.files.exists(test_path)
|
|
77
|
+
print(f"✓ 文件存在性检查: {exists}")
|
|
78
|
+
if not exists:
|
|
79
|
+
print("✗ 文件应该存在但检查结果为不存在")
|
|
80
|
+
return False
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"✗ 文件存在性检查失败: {e}")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# 3. 测试读取文件(文本格式)
|
|
86
|
+
print("\n[3] 测试读取文件(文本格式)...")
|
|
87
|
+
try:
|
|
88
|
+
print(f" 读取路径: {test_path}")
|
|
89
|
+
print(f" 格式: text")
|
|
90
|
+
content = sandbox.files.read(test_path, format="text")
|
|
91
|
+
print(f"✓ 文件读取成功")
|
|
92
|
+
print(f" 读取内容长度: {len(content)} 字符")
|
|
93
|
+
print(f" 内容预览: {content[:50]}...")
|
|
94
|
+
print(f" 原始内容长度: {len(test_content)} 字符")
|
|
95
|
+
if content.strip() != test_content.strip():
|
|
96
|
+
print("✗ 读取的内容与写入的内容不一致")
|
|
97
|
+
print(f" 原始内容: {test_content[:100]}")
|
|
98
|
+
print(f" 读取内容: {content[:100]}")
|
|
99
|
+
return False
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"✗ 文件读取失败: {e}")
|
|
102
|
+
import traceback
|
|
103
|
+
|
|
104
|
+
traceback.print_exc()
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
# 4. 测试读取文件(字节格式)
|
|
108
|
+
print("\n[4] 测试读取文件(字节格式)...")
|
|
109
|
+
try:
|
|
110
|
+
content_bytes = sandbox.files.read(test_path, format="bytes")
|
|
111
|
+
print(f"✓ 字节格式读取成功")
|
|
112
|
+
print(f" 字节数: {len(content_bytes)}")
|
|
113
|
+
if content_bytes.decode("utf-8").strip() != test_content.strip():
|
|
114
|
+
print("✗ 字节格式读取的内容与写入的内容不一致")
|
|
115
|
+
return False
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"✗ 字节格式读取失败: {e}")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# 5. 测试获取文件信息
|
|
121
|
+
print("\n[5] 测试获取文件信息...")
|
|
122
|
+
try:
|
|
123
|
+
info = sandbox.files.get_info(test_path)
|
|
124
|
+
print(f"✓ 文件信息获取成功")
|
|
125
|
+
print(f" 路径: {info.path}")
|
|
126
|
+
print(f" 大小: {info.size} 字节")
|
|
127
|
+
print(f" 类型: {info.type}")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"✗ 文件信息获取失败: {e}")
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
# 6. 测试写入字节文件
|
|
133
|
+
print("\n[6] 测试写入字节文件...")
|
|
134
|
+
binary_content = b"\x00\x01\x02\x03\xff\xfe\xfd"
|
|
135
|
+
binary_path = "/tmp/test_binary.bin"
|
|
136
|
+
try:
|
|
137
|
+
result = sandbox.files.write(binary_path, binary_content)
|
|
138
|
+
print(f"✓ 字节文件写入成功: {result.path}")
|
|
139
|
+
|
|
140
|
+
# 验证字节文件
|
|
141
|
+
read_binary = sandbox.files.read(binary_path, format="bytes")
|
|
142
|
+
if read_binary != binary_content:
|
|
143
|
+
print("✗ 字节文件内容不匹配")
|
|
144
|
+
return False
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f"✗ 字节文件写入失败: {e}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# 7. 测试写入StringIO
|
|
150
|
+
print("\n[7] 测试写入StringIO...")
|
|
151
|
+
stringio_content = "Content from StringIO\nLine 2\nLine 3"
|
|
152
|
+
stringio_path = "/tmp/test_stringio.txt"
|
|
153
|
+
try:
|
|
154
|
+
stringio = StringIO(stringio_content)
|
|
155
|
+
result = sandbox.files.write(stringio_path, stringio)
|
|
156
|
+
print(f"✓ StringIO写入成功: {result.path}")
|
|
157
|
+
|
|
158
|
+
# 验证内容
|
|
159
|
+
read_content = sandbox.files.read(stringio_path, format="text")
|
|
160
|
+
if read_content.strip() != stringio_content.strip():
|
|
161
|
+
print("✗ StringIO内容不匹配")
|
|
162
|
+
return False
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"✗ StringIO写入失败: {e}")
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
# 8. 测试写入BytesIO
|
|
168
|
+
print("\n[8] 测试写入BytesIO...")
|
|
169
|
+
bytesio_content = b"Content from BytesIO\x00\x01\x02"
|
|
170
|
+
bytesio_path = "/tmp/test_bytesio.bin"
|
|
171
|
+
try:
|
|
172
|
+
bytesio = BytesIO(bytesio_content)
|
|
173
|
+
result = sandbox.files.write(bytesio_path, bytesio)
|
|
174
|
+
print(f"✓ BytesIO写入成功: {result.path}")
|
|
175
|
+
|
|
176
|
+
# 验证内容
|
|
177
|
+
read_content = sandbox.files.read(bytesio_path, format="bytes")
|
|
178
|
+
if read_content != bytesio_content:
|
|
179
|
+
print("✗ BytesIO内容不匹配")
|
|
180
|
+
return False
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(f"✗ BytesIO写入失败: {e}")
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
# 9. 测试批量写入文件
|
|
186
|
+
print("\n[9] 测试批量写入文件...")
|
|
187
|
+
try:
|
|
188
|
+
files = [
|
|
189
|
+
{"path": "/tmp/batch1.txt", "data": "Batch file 1 content"},
|
|
190
|
+
{"path": "/tmp/batch2.txt", "data": "Batch file 2 content"},
|
|
191
|
+
{"path": "/tmp/batch3.bin", "data": b"Batch binary content"},
|
|
192
|
+
]
|
|
193
|
+
print(f" 准备批量写入 {len(files)} 个文件:")
|
|
194
|
+
for i, f in enumerate(files, 1):
|
|
195
|
+
data_size = len(f["data"]) if isinstance(f["data"], (str, bytes)) else "N/A"
|
|
196
|
+
data_type = type(f["data"]).__name__
|
|
197
|
+
print(f" {i}. {f['path']} ({data_type}, {data_size} 字节)")
|
|
198
|
+
|
|
199
|
+
results = sandbox.files.write(files)
|
|
200
|
+
print(f"✓ 批量写入成功,共 {len(results)} 个文件")
|
|
201
|
+
for i, result in enumerate(results, 1):
|
|
202
|
+
print(f" {i}. {result.path}")
|
|
203
|
+
|
|
204
|
+
# 验证所有文件
|
|
205
|
+
print(f" 验证文件存在性...")
|
|
206
|
+
for i, file_info in enumerate(files, 1):
|
|
207
|
+
exists = sandbox.files.exists(file_info["path"])
|
|
208
|
+
print(f" {i}. {file_info['path']}: {'存在' if exists else '不存在'}")
|
|
209
|
+
if not exists:
|
|
210
|
+
print(f"✗ 批量写入的文件 {file_info['path']} 不存在")
|
|
211
|
+
return False
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print(f"✗ 批量写入失败: {e}")
|
|
214
|
+
import traceback
|
|
215
|
+
|
|
216
|
+
traceback.print_exc()
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# 10. 测试列出目录
|
|
220
|
+
print("\n[10] 测试列出目录...")
|
|
221
|
+
try:
|
|
222
|
+
print(f" 正在列出目录: /tmp, depth=1")
|
|
223
|
+
entries = sandbox.files.list("/tmp", depth=1)
|
|
224
|
+
print(f"✓ 目录列表获取成功,共 {len(entries)} 项")
|
|
225
|
+
test_files_found = [
|
|
226
|
+
e.path for e in entries if "test_" in e.path or "batch" in e.path
|
|
227
|
+
]
|
|
228
|
+
print(f" 找到测试文件: {len(test_files_found)} 个")
|
|
229
|
+
print(f" 显示前5项:")
|
|
230
|
+
for i, entry in enumerate(entries[:5], 1):
|
|
231
|
+
print(
|
|
232
|
+
f" {i}. {entry.path} (类型: {entry.type}, 大小: {getattr(entry, 'size', 'N/A')} 字节)"
|
|
233
|
+
)
|
|
234
|
+
if len(entries) > 5:
|
|
235
|
+
print(f" ... 还有 {len(entries) - 5} 项未显示")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"✗ 目录列表获取失败: {e}")
|
|
238
|
+
import traceback
|
|
239
|
+
|
|
240
|
+
traceback.print_exc()
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
# 11. 测试创建目录
|
|
244
|
+
print("\n[11] 测试创建目录...")
|
|
245
|
+
test_dir = "/tmp/test_dir"
|
|
246
|
+
try:
|
|
247
|
+
sandbox.files.make_dir(test_dir)
|
|
248
|
+
print(f"✓ 目录创建成功: {test_dir}")
|
|
249
|
+
|
|
250
|
+
# 验证目录是否存在
|
|
251
|
+
exists = sandbox.files.exists(test_dir)
|
|
252
|
+
if not exists:
|
|
253
|
+
print("✗ 目录应该存在但检查结果为不存在")
|
|
254
|
+
return False
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print(f"✗ 目录创建失败: {e}")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
# 12. 测试在目录中创建文件
|
|
260
|
+
print("\n[12] 测试在目录中创建文件...")
|
|
261
|
+
try:
|
|
262
|
+
dir_file_path = "/tmp/test_dir/nested_file.txt"
|
|
263
|
+
dir_file_content = "Nested file content"
|
|
264
|
+
result = sandbox.files.write(dir_file_path, dir_file_content)
|
|
265
|
+
print(f"✓ 目录中文件创建成功: {result.path}")
|
|
266
|
+
|
|
267
|
+
# 验证文件
|
|
268
|
+
read_content = sandbox.files.read(dir_file_path, format="text")
|
|
269
|
+
if read_content.strip() != dir_file_content.strip():
|
|
270
|
+
print("✗ 目录中文件内容不匹配")
|
|
271
|
+
return False
|
|
272
|
+
except Exception as e:
|
|
273
|
+
print(f"✗ 目录中文件创建失败: {e}")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# 13. 测试删除文件
|
|
277
|
+
print("\n[13] 测试删除文件...")
|
|
278
|
+
try:
|
|
279
|
+
sandbox.files.remove(test_path)
|
|
280
|
+
print(f"✓ 文件删除成功: {test_path}")
|
|
281
|
+
|
|
282
|
+
# 验证文件是否已删除
|
|
283
|
+
exists = sandbox.files.exists(test_path)
|
|
284
|
+
if exists:
|
|
285
|
+
print("✗ 文件应该已删除但检查结果仍存在")
|
|
286
|
+
return False
|
|
287
|
+
except Exception as e:
|
|
288
|
+
print(f"✗ 文件删除失败: {e}")
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
# 14. 测试覆盖写入文件
|
|
292
|
+
print("\n[14] 测试覆盖写入文件...")
|
|
293
|
+
try:
|
|
294
|
+
original_content = "Original content"
|
|
295
|
+
overwrite_path = "/tmp/overwrite_test.txt"
|
|
296
|
+
sandbox.files.write(overwrite_path, original_content)
|
|
297
|
+
|
|
298
|
+
new_content = "New overwritten content"
|
|
299
|
+
sandbox.files.write(overwrite_path, new_content)
|
|
300
|
+
|
|
301
|
+
read_content = sandbox.files.read(overwrite_path, format="text")
|
|
302
|
+
if read_content.strip() != new_content.strip():
|
|
303
|
+
print("✗ 文件覆盖写入失败")
|
|
304
|
+
return False
|
|
305
|
+
print(f"✓ 文件覆盖写入成功")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
print(f"✗ 文件覆盖写入失败: {e}")
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
print("\n✓ 所有 Filesystem 操作测试通过")
|
|
311
|
+
return True
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_command_operations(sandbox):
|
|
315
|
+
"""测试命令操作(process)"""
|
|
316
|
+
print("\n" + "=" * 60)
|
|
317
|
+
print("测试 Command/Process 操作")
|
|
318
|
+
print("=" * 60)
|
|
319
|
+
print("\n注意: 后端可能会输出 'error adjusting oom score' 错误")
|
|
320
|
+
print(" 这是权限问题,不影响测试结果,可以忽略")
|
|
321
|
+
|
|
322
|
+
# 1. 测试执行简单命令
|
|
323
|
+
print("\n[1] 测试执行简单命令...")
|
|
324
|
+
cmd = "echo 'Hello from sandbox'"
|
|
325
|
+
print(f" 执行命令: {cmd}")
|
|
326
|
+
try:
|
|
327
|
+
result = sandbox.commands.run(cmd)
|
|
328
|
+
print(f"✓ 命令执行成功")
|
|
329
|
+
print(f" 退出码: {result.exit_code}")
|
|
330
|
+
print(f" 标准输出: {result.stdout.strip()}")
|
|
331
|
+
print(f" 标准错误: {result.stderr.strip() if result.stderr else '(空)'}")
|
|
332
|
+
if result.exit_code != 0:
|
|
333
|
+
print(f"✗ 命令退出码不为0: {result.exit_code}")
|
|
334
|
+
return False
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"✗ 命令执行失败: {e}")
|
|
337
|
+
import traceback
|
|
338
|
+
|
|
339
|
+
traceback.print_exc()
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
# 2. 测试执行带参数的命令
|
|
343
|
+
print("\n[2] 测试执行带参数的命令...")
|
|
344
|
+
cmd = "ls -la /tmp | head -5"
|
|
345
|
+
print(f" 执行命令: {cmd}")
|
|
346
|
+
try:
|
|
347
|
+
result = sandbox.commands.run(cmd)
|
|
348
|
+
print(f"✓ 命令执行成功")
|
|
349
|
+
print(f" 退出码: {result.exit_code}")
|
|
350
|
+
output_lines = result.stdout.splitlines()
|
|
351
|
+
print(f" 输出行数: {len(output_lines)}")
|
|
352
|
+
print(f" 输出预览 (前3行):")
|
|
353
|
+
for i, line in enumerate(output_lines[:3], 1):
|
|
354
|
+
print(f" {i}. {line[:80]}")
|
|
355
|
+
if result.exit_code != 0:
|
|
356
|
+
print(f"✗ 命令退出码不为0: {result.exit_code}")
|
|
357
|
+
print(f" stderr: {result.stderr}")
|
|
358
|
+
return False
|
|
359
|
+
except Exception as e:
|
|
360
|
+
print(f"✗ 命令执行失败: {e}")
|
|
361
|
+
import traceback
|
|
362
|
+
|
|
363
|
+
traceback.print_exc()
|
|
364
|
+
return False
|
|
365
|
+
|
|
366
|
+
# 3. 测试执行带环境变量的命令
|
|
367
|
+
print("\n[3] 测试执行带环境变量的命令...")
|
|
368
|
+
try:
|
|
369
|
+
result = sandbox.commands.run(
|
|
370
|
+
"echo $TEST_VAR", envs={"TEST_VAR": "test_value_123"}
|
|
371
|
+
)
|
|
372
|
+
print(f"✓ 带环境变量的命令执行成功")
|
|
373
|
+
print(f" 退出码: {result.exit_code}")
|
|
374
|
+
print(f" 输出: {result.stdout.strip()}")
|
|
375
|
+
if "test_value_123" not in result.stdout:
|
|
376
|
+
print("✗ 环境变量未正确传递")
|
|
377
|
+
return False
|
|
378
|
+
except Exception as e:
|
|
379
|
+
print(f"✗ 带环境变量的命令执行失败: {e}")
|
|
380
|
+
return False
|
|
381
|
+
|
|
382
|
+
# 4. 测试执行带工作目录的命令
|
|
383
|
+
print("\n[4] 测试执行带工作目录的命令...")
|
|
384
|
+
try:
|
|
385
|
+
result = sandbox.commands.run("pwd", cwd="/tmp")
|
|
386
|
+
print(f"✓ 带工作目录的命令执行成功")
|
|
387
|
+
print(f" 退出码: {result.exit_code}")
|
|
388
|
+
print(f" 输出: {result.stdout.strip()}")
|
|
389
|
+
if "/tmp" not in result.stdout:
|
|
390
|
+
print("✗ 工作目录未正确设置")
|
|
391
|
+
return False
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print(f"✗ 带工作目录的命令执行失败: {e}")
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
# 5. 测试执行Python命令(尝试多种方式)
|
|
397
|
+
print("\n[5] 测试执行Python命令...")
|
|
398
|
+
python_commands = [
|
|
399
|
+
("python3", "python3 -c \"import sys; print('Python3:', sys.version[:5])\""),
|
|
400
|
+
("python", "python -c \"import sys; print('Python:', sys.version[:5])\""),
|
|
401
|
+
("which python3", "which python3 || which python || echo 'Python not found'"),
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
python_found = False
|
|
405
|
+
for cmd_name, cmd in python_commands:
|
|
406
|
+
try:
|
|
407
|
+
result = sandbox.commands.run(cmd, timeout=10)
|
|
408
|
+
if result.exit_code == 0 and (
|
|
409
|
+
"Python" in result.stdout or "/" in result.stdout
|
|
410
|
+
):
|
|
411
|
+
print(f"✓ {cmd_name} 命令执行成功")
|
|
412
|
+
print(f" 输出: {result.stdout.strip()[:100]}")
|
|
413
|
+
python_found = True
|
|
414
|
+
break
|
|
415
|
+
except Exception as e:
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
if not python_found:
|
|
419
|
+
print("⚠ Python未找到,跳过Python相关测试")
|
|
420
|
+
|
|
421
|
+
# 6. 测试执行Shell脚本
|
|
422
|
+
print("\n[6] 测试执行Shell脚本...")
|
|
423
|
+
try:
|
|
424
|
+
script_content = """#!/bin/bash
|
|
425
|
+
echo "Script start"
|
|
426
|
+
for i in 1 2 3; do
|
|
427
|
+
echo "Iteration $i"
|
|
428
|
+
done
|
|
429
|
+
echo "Script end"
|
|
430
|
+
"""
|
|
431
|
+
script_path = "/tmp/test_script.sh"
|
|
432
|
+
sandbox.files.write(script_path, script_content)
|
|
433
|
+
|
|
434
|
+
# 添加执行权限并运行
|
|
435
|
+
result = sandbox.commands.run(f"chmod +x {script_path} && {script_path}")
|
|
436
|
+
print(f"✓ Shell脚本执行成功")
|
|
437
|
+
print(f" 退出码: {result.exit_code}")
|
|
438
|
+
if "Script start" not in result.stdout or "Script end" not in result.stdout:
|
|
439
|
+
print("✗ Shell脚本输出不符合预期")
|
|
440
|
+
return False
|
|
441
|
+
except Exception as e:
|
|
442
|
+
print(f"✗ Shell脚本执行失败: {e}")
|
|
443
|
+
return False
|
|
444
|
+
|
|
445
|
+
# 7. 测试执行带超时的命令
|
|
446
|
+
print("\n[7] 测试执行带超时的命令...")
|
|
447
|
+
try:
|
|
448
|
+
result = sandbox.commands.run("sleep 2 && echo 'Done'", timeout=5)
|
|
449
|
+
print(f"✓ 带超时的命令执行成功")
|
|
450
|
+
print(f" 退出码: {result.exit_code}")
|
|
451
|
+
print(f" 输出: {result.stdout.strip()}")
|
|
452
|
+
if result.exit_code != 0:
|
|
453
|
+
print(f"✗ 命令退出码不为0: {result.exit_code}")
|
|
454
|
+
return False
|
|
455
|
+
except Exception as e:
|
|
456
|
+
print(f"✗ 带超时的命令执行失败: {e}")
|
|
457
|
+
return False
|
|
458
|
+
|
|
459
|
+
# 8. 测试执行返回非零退出码的命令
|
|
460
|
+
print("\n[8] 测试执行返回非零退出码的命令...")
|
|
461
|
+
print(" 执行命令: 'false' (预期退出码: 1)")
|
|
462
|
+
try:
|
|
463
|
+
result = sandbox.commands.run("false") # false命令总是返回1
|
|
464
|
+
print(f" 实际退出码: {result.exit_code}")
|
|
465
|
+
print(f" stdout: {result.stdout}")
|
|
466
|
+
print(f" stderr: {result.stderr}")
|
|
467
|
+
if result.exit_code == 0:
|
|
468
|
+
print("✗ 命令应该返回非零退出码")
|
|
469
|
+
return False
|
|
470
|
+
print(f"✓ 非零退出码命令执行成功,退出码: {result.exit_code}")
|
|
471
|
+
except CommandExitException as e:
|
|
472
|
+
# CommandExitException 是正常的,当命令返回非零退出码时会抛出
|
|
473
|
+
print(f" 捕获到 CommandExitException (这是正常的)")
|
|
474
|
+
print(f" 退出码: {e.exit_code}")
|
|
475
|
+
print(f" stdout: {e.stdout}")
|
|
476
|
+
print(f" stderr: {e.stderr}")
|
|
477
|
+
if e.exit_code == 0:
|
|
478
|
+
print("✗ 命令应该返回非零退出码")
|
|
479
|
+
return False
|
|
480
|
+
print(f"✓ 非零退出码命令执行成功,退出码: {e.exit_code}")
|
|
481
|
+
except Exception as e:
|
|
482
|
+
print(f"✗ 非零退出码命令执行失败: {e}")
|
|
483
|
+
import traceback
|
|
484
|
+
|
|
485
|
+
traceback.print_exc()
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
# 9. 测试列出运行的进程
|
|
489
|
+
print("\n[9] 测试列出运行的进程...")
|
|
490
|
+
try:
|
|
491
|
+
processes = sandbox.commands.list()
|
|
492
|
+
print(f"✓ 进程列表获取成功,共 {len(processes)} 个进程")
|
|
493
|
+
for proc in processes[:3]: # 只显示前3个
|
|
494
|
+
print(f" - PID: {proc.pid}, CMD: {proc.cmd[:50]}")
|
|
495
|
+
except Exception as e:
|
|
496
|
+
print(f"✗ 进程列表获取失败: {e}")
|
|
497
|
+
return False
|
|
498
|
+
|
|
499
|
+
# 10. 测试执行命令并捕获stderr
|
|
500
|
+
print("\n[10] 测试执行命令并捕获stderr...")
|
|
501
|
+
try:
|
|
502
|
+
result = sandbox.commands.run(
|
|
503
|
+
"echo 'stdout message' >&1 && echo 'stderr message' >&2"
|
|
504
|
+
)
|
|
505
|
+
print(f"✓ 命令执行成功")
|
|
506
|
+
print(f" 退出码: {result.exit_code}")
|
|
507
|
+
print(f" stdout: {result.stdout.strip()}")
|
|
508
|
+
if result.stderr:
|
|
509
|
+
print(f" stderr: {result.stderr.strip()}")
|
|
510
|
+
except Exception as e:
|
|
511
|
+
print(f"✗ 命令执行失败: {e}")
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
# 11. 测试执行多行命令
|
|
515
|
+
print("\n[11] 测试执行多行命令...")
|
|
516
|
+
try:
|
|
517
|
+
multiline_cmd = """
|
|
518
|
+
echo "Line 1"
|
|
519
|
+
echo "Line 2"
|
|
520
|
+
echo "Line 3"
|
|
521
|
+
"""
|
|
522
|
+
result = sandbox.commands.run(multiline_cmd)
|
|
523
|
+
print(f"✓ 多行命令执行成功")
|
|
524
|
+
print(f" 退出码: {result.exit_code}")
|
|
525
|
+
output_lines = result.stdout.strip().split("\n")
|
|
526
|
+
print(f" 输出行数: {len(output_lines)}")
|
|
527
|
+
if len(output_lines) < 3:
|
|
528
|
+
print("✗ 多行命令输出行数不足")
|
|
529
|
+
return False
|
|
530
|
+
except Exception as e:
|
|
531
|
+
print(f"✗ 多行命令执行失败: {e}")
|
|
532
|
+
return False
|
|
533
|
+
|
|
534
|
+
# 12. 测试执行管道命令
|
|
535
|
+
print("\n[12] 测试执行管道命令...")
|
|
536
|
+
try:
|
|
537
|
+
result = sandbox.commands.run("echo 'test1\ntest2\ntest3' | grep 'test2'")
|
|
538
|
+
print(f"✓ 管道命令执行成功")
|
|
539
|
+
print(f" 退出码: {result.exit_code}")
|
|
540
|
+
print(f" 输出: {result.stdout.strip()}")
|
|
541
|
+
if "test2" not in result.stdout:
|
|
542
|
+
print("✗ 管道命令输出不符合预期")
|
|
543
|
+
return False
|
|
544
|
+
except Exception as e:
|
|
545
|
+
print(f"✗ 管道命令执行失败: {e}")
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
print("\n✓ 所有 Command/Process 操作测试通过")
|
|
549
|
+
return True
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def test_upload_download(sandbox):
|
|
553
|
+
"""测试上传和下载"""
|
|
554
|
+
print("\n" + "=" * 60)
|
|
555
|
+
print("测试上传和下载")
|
|
556
|
+
print("=" * 60)
|
|
557
|
+
|
|
558
|
+
# 1. 测试上传文本文件(使用files.write)
|
|
559
|
+
print("\n[1] 测试上传文本文件...")
|
|
560
|
+
test_content = "This is a test file for upload/download testing.\n" * 10
|
|
561
|
+
test_content += f"Timestamp: {time.time()}\n"
|
|
562
|
+
remote_path = "/tmp/uploaded_test.txt"
|
|
563
|
+
|
|
564
|
+
try:
|
|
565
|
+
print(f" 目标路径: {remote_path}")
|
|
566
|
+
print(
|
|
567
|
+
f" 内容大小: {len(test_content)} 字符 ({len(test_content.encode('utf-8'))} 字节)"
|
|
568
|
+
)
|
|
569
|
+
result = sandbox.files.write(remote_path, test_content)
|
|
570
|
+
print(f"✓ 文件上传成功")
|
|
571
|
+
print(f" 上传路径: {result.path}")
|
|
572
|
+
|
|
573
|
+
# 验证文件是否存在
|
|
574
|
+
print(f" 验证文件是否存在...")
|
|
575
|
+
exists = sandbox.files.exists(remote_path)
|
|
576
|
+
print(f" 文件存在: {exists}")
|
|
577
|
+
if not exists:
|
|
578
|
+
print("✗ 上传的文件不存在")
|
|
579
|
+
return False
|
|
580
|
+
except Exception as e:
|
|
581
|
+
print(f"✗ 文件上传失败: {e}")
|
|
582
|
+
import traceback
|
|
583
|
+
|
|
584
|
+
traceback.print_exc()
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
# 2. 测试下载文件(使用files.read,文本格式)
|
|
588
|
+
print("\n[2] 测试下载文件(文本格式)...")
|
|
589
|
+
try:
|
|
590
|
+
print(f" 下载路径: {remote_path}")
|
|
591
|
+
print(f" 格式: text")
|
|
592
|
+
downloaded_content = sandbox.files.read(remote_path, format="text")
|
|
593
|
+
print(f"✓ 文件下载成功")
|
|
594
|
+
print(f" 下载内容长度: {len(downloaded_content)} 字符")
|
|
595
|
+
print(f" 原始内容长度: {len(test_content)} 字符")
|
|
596
|
+
print(f" 内容预览: {downloaded_content[:50]}...")
|
|
597
|
+
|
|
598
|
+
if downloaded_content.strip() != test_content.strip():
|
|
599
|
+
print("✗ 下载的内容与上传的内容不一致")
|
|
600
|
+
print(f" 原始内容前100字符: {test_content[:100]}")
|
|
601
|
+
print(f" 下载内容前100字符: {downloaded_content[:100]}")
|
|
602
|
+
return False
|
|
603
|
+
print(f"✓ 内容验证通过")
|
|
604
|
+
except Exception as e:
|
|
605
|
+
print(f"✗ 文件下载失败: {e}")
|
|
606
|
+
import traceback
|
|
607
|
+
|
|
608
|
+
traceback.print_exc()
|
|
609
|
+
return False
|
|
610
|
+
|
|
611
|
+
# 3. 测试下载为字节格式
|
|
612
|
+
print("\n[3] 测试下载为字节格式...")
|
|
613
|
+
try:
|
|
614
|
+
downloaded_bytes = sandbox.files.read(remote_path, format="bytes")
|
|
615
|
+
print(f"✓ 字节格式下载成功")
|
|
616
|
+
print(f" 下载字节数: {len(downloaded_bytes)} 字节")
|
|
617
|
+
|
|
618
|
+
# 验证字节内容
|
|
619
|
+
expected_bytes = test_content.encode("utf-8")
|
|
620
|
+
if downloaded_bytes != expected_bytes:
|
|
621
|
+
print("✗ 下载的字节内容与上传的内容不一致")
|
|
622
|
+
return False
|
|
623
|
+
except Exception as e:
|
|
624
|
+
print(f"✗ 字节格式下载失败: {e}")
|
|
625
|
+
return False
|
|
626
|
+
|
|
627
|
+
# 4. 测试流式下载
|
|
628
|
+
print("\n[4] 测试流式下载...")
|
|
629
|
+
try:
|
|
630
|
+
stream = sandbox.files.read(remote_path, format="stream")
|
|
631
|
+
chunks = []
|
|
632
|
+
for chunk in stream:
|
|
633
|
+
chunks.append(chunk)
|
|
634
|
+
streamed_content = b"".join(chunks)
|
|
635
|
+
print(f"✓ 流式下载成功")
|
|
636
|
+
print(f" 下载块数: {len(chunks)}")
|
|
637
|
+
print(f" 总字节数: {len(streamed_content)} 字节")
|
|
638
|
+
|
|
639
|
+
if streamed_content != test_content.encode("utf-8"):
|
|
640
|
+
print("✗ 流式下载内容不匹配")
|
|
641
|
+
return False
|
|
642
|
+
except Exception as e:
|
|
643
|
+
print(f"✗ 流式下载失败: {e}")
|
|
644
|
+
return False
|
|
645
|
+
|
|
646
|
+
# 5. 测试获取下载URL
|
|
647
|
+
print("\n[5] 测试获取下载URL...")
|
|
648
|
+
try:
|
|
649
|
+
print(f" 文件路径: {remote_path}")
|
|
650
|
+
print(f" 用户: root")
|
|
651
|
+
print(f" 签名过期时间: 3600秒")
|
|
652
|
+
|
|
653
|
+
# 检查是否是debug模式
|
|
654
|
+
is_debug = sandbox.connection_config.debug
|
|
655
|
+
print(f" Debug模式: {is_debug}")
|
|
656
|
+
|
|
657
|
+
# 在debug模式下,可能不需要签名URL,或者token可能不存在
|
|
658
|
+
if is_debug:
|
|
659
|
+
print(f" ⚠ Debug模式下,跳过签名URL测试(debug模式通常不需要签名)")
|
|
660
|
+
print(f" ✓ Debug模式下URL测试跳过(这是正常的)")
|
|
661
|
+
else:
|
|
662
|
+
# 检查是否有访问token
|
|
663
|
+
has_token = False
|
|
664
|
+
token = None
|
|
665
|
+
try:
|
|
666
|
+
# 尝试通过property访问
|
|
667
|
+
token = sandbox._envd_access_token
|
|
668
|
+
has_token = token is not None and token != ""
|
|
669
|
+
print(
|
|
670
|
+
f" Token存在: {has_token}, 长度: {len(token) if has_token else 0}"
|
|
671
|
+
)
|
|
672
|
+
except AttributeError as ae:
|
|
673
|
+
print(f" ⚠ 无法通过property访问 _envd_access_token: {ae}")
|
|
674
|
+
# 尝试直接访问私有属性
|
|
675
|
+
try:
|
|
676
|
+
token = getattr(sandbox, "_Sandbox__envd_access_token", None)
|
|
677
|
+
if token is None:
|
|
678
|
+
# 尝试其他可能的属性名
|
|
679
|
+
token = getattr(sandbox, "__envd_access_token", None)
|
|
680
|
+
has_token = token is not None and token != ""
|
|
681
|
+
print(
|
|
682
|
+
f" 直接访问token: {has_token}, 长度: {len(token) if has_token else 0}"
|
|
683
|
+
)
|
|
684
|
+
except Exception:
|
|
685
|
+
print(f" ⚠ 无法访问token属性,将尝试不使用签名")
|
|
686
|
+
|
|
687
|
+
# 尝试获取下载URL
|
|
688
|
+
try:
|
|
689
|
+
if has_token:
|
|
690
|
+
download_url = sandbox.download_url(
|
|
691
|
+
path=remote_path,
|
|
692
|
+
user="root",
|
|
693
|
+
use_signature_expiration=3600, # 1小时有效期
|
|
694
|
+
)
|
|
695
|
+
else:
|
|
696
|
+
# 如果没有token,尝试不使用签名
|
|
697
|
+
print(f" 尝试不使用签名获取URL...")
|
|
698
|
+
download_url = sandbox.download_url(path=remote_path, user="root")
|
|
699
|
+
|
|
700
|
+
print(f"✓ 下载URL获取成功")
|
|
701
|
+
print(f" URL长度: {len(download_url)} 字符")
|
|
702
|
+
print(f" URL预览: {download_url[:100]}...")
|
|
703
|
+
if not download_url.startswith("http"):
|
|
704
|
+
print("✗ 下载URL格式不正确")
|
|
705
|
+
return False
|
|
706
|
+
except AttributeError as e:
|
|
707
|
+
print(f" ⚠ 获取下载URL失败(可能是debug模式不支持): {e}")
|
|
708
|
+
print(f" ✓ 在debug模式下跳过URL签名测试(这是正常的)")
|
|
709
|
+
except Exception as e:
|
|
710
|
+
print(f"✗ 下载URL获取失败: {e}")
|
|
711
|
+
# 在debug模式下,这个错误可能是可以接受的
|
|
712
|
+
if sandbox.connection_config.debug:
|
|
713
|
+
print(f" ⚠ Debug模式下URL签名功能可能不可用,跳过此测试")
|
|
714
|
+
print(f" ✓ Debug模式下URL测试跳过(这是正常的)")
|
|
715
|
+
else:
|
|
716
|
+
import traceback
|
|
717
|
+
|
|
718
|
+
traceback.print_exc()
|
|
719
|
+
return False
|
|
720
|
+
|
|
721
|
+
# 6. 测试获取上传URL
|
|
722
|
+
print("\n[6] 测试获取上传URL...")
|
|
723
|
+
try:
|
|
724
|
+
# 检查是否是debug模式
|
|
725
|
+
is_debug = sandbox.connection_config.debug
|
|
726
|
+
print(f" Debug模式: {is_debug}")
|
|
727
|
+
|
|
728
|
+
if is_debug:
|
|
729
|
+
print(f" ⚠ Debug模式下,跳过签名URL测试(debug模式通常不需要签名)")
|
|
730
|
+
print(f" ✓ Debug模式下URL测试跳过(这是正常的)")
|
|
731
|
+
else:
|
|
732
|
+
try:
|
|
733
|
+
upload_url = sandbox.upload_url(
|
|
734
|
+
path="/tmp/upload_via_url.txt",
|
|
735
|
+
user="root",
|
|
736
|
+
use_signature_expiration=3600, # 1小时有效期
|
|
737
|
+
)
|
|
738
|
+
print(f"✓ 上传URL获取成功")
|
|
739
|
+
print(f" URL长度: {len(upload_url)} 字符")
|
|
740
|
+
print(f" URL预览: {upload_url[:100]}...")
|
|
741
|
+
if not upload_url.startswith("http"):
|
|
742
|
+
print("✗ 上传URL格式不正确")
|
|
743
|
+
return False
|
|
744
|
+
except AttributeError as e:
|
|
745
|
+
print(f" ⚠ 获取上传URL失败(可能是debug模式不支持): {e}")
|
|
746
|
+
print(f" ✓ 在debug模式下跳过URL签名测试(这是正常的)")
|
|
747
|
+
except Exception as e:
|
|
748
|
+
print(f"✗ 上传URL获取失败: {e}")
|
|
749
|
+
# 在debug模式下,这个错误可能是可以接受的
|
|
750
|
+
if sandbox.connection_config.debug:
|
|
751
|
+
print(f" ⚠ Debug模式下URL签名功能可能不可用,跳过此测试")
|
|
752
|
+
print(f" ✓ Debug模式下URL测试跳过(这是正常的)")
|
|
753
|
+
else:
|
|
754
|
+
import traceback
|
|
755
|
+
|
|
756
|
+
traceback.print_exc()
|
|
757
|
+
return False
|
|
758
|
+
|
|
759
|
+
# 7. 测试上传小文件(1KB)
|
|
760
|
+
print("\n[7] 测试上传小文件(1KB)...")
|
|
761
|
+
small_content = b"X" * 1024
|
|
762
|
+
small_file_path = "/tmp/small_file.bin"
|
|
763
|
+
|
|
764
|
+
try:
|
|
765
|
+
start_time = time.time()
|
|
766
|
+
result = sandbox.files.write(small_file_path, small_content)
|
|
767
|
+
upload_time = time.time() - start_time
|
|
768
|
+
print(f"✓ 小文件上传成功: {result.path}")
|
|
769
|
+
print(f" 文件大小: {len(small_content)} 字节")
|
|
770
|
+
print(f" 上传耗时: {upload_time:.3f} 秒")
|
|
771
|
+
|
|
772
|
+
# 验证文件大小
|
|
773
|
+
info = sandbox.files.get_info(small_file_path)
|
|
774
|
+
if info.size != len(small_content):
|
|
775
|
+
print(f"✗ 文件大小不匹配: 期望 {len(small_content)}, 实际 {info.size}")
|
|
776
|
+
return False
|
|
777
|
+
except Exception as e:
|
|
778
|
+
print(f"✗ 小文件上传失败: {e}")
|
|
779
|
+
return False
|
|
780
|
+
|
|
781
|
+
# 8. 测试上传中等文件(100KB)
|
|
782
|
+
print("\n[8] 测试上传中等文件(100KB)...")
|
|
783
|
+
medium_content = b"Y" * (1024 * 100)
|
|
784
|
+
medium_file_path = "/tmp/medium_file.bin"
|
|
785
|
+
|
|
786
|
+
try:
|
|
787
|
+
start_time = time.time()
|
|
788
|
+
result = sandbox.files.write(medium_file_path, medium_content)
|
|
789
|
+
upload_time = time.time() - start_time
|
|
790
|
+
print(f"✓ 中等文件上传成功: {result.path}")
|
|
791
|
+
print(f" 文件大小: {len(medium_content) / 1024:.2f} KB")
|
|
792
|
+
print(f" 上传耗时: {upload_time:.2f} 秒")
|
|
793
|
+
if upload_time > 0:
|
|
794
|
+
print(f" 上传速度: {len(medium_content) / 1024 / upload_time:.2f} KB/s")
|
|
795
|
+
|
|
796
|
+
# 验证文件大小
|
|
797
|
+
info = sandbox.files.get_info(medium_file_path)
|
|
798
|
+
if info.size != len(medium_content):
|
|
799
|
+
print(f"✗ 文件大小不匹配: 期望 {len(medium_content)}, 实际 {info.size}")
|
|
800
|
+
return False
|
|
801
|
+
except Exception as e:
|
|
802
|
+
print(f"✗ 中等文件上传失败: {e}")
|
|
803
|
+
return False
|
|
804
|
+
|
|
805
|
+
# 9. 测试下载中等文件
|
|
806
|
+
print("\n[9] 测试下载中等文件...")
|
|
807
|
+
try:
|
|
808
|
+
start_time = time.time()
|
|
809
|
+
downloaded_medium = sandbox.files.read(medium_file_path, format="bytes")
|
|
810
|
+
download_time = time.time() - start_time
|
|
811
|
+
print(f"✓ 中等文件下载成功")
|
|
812
|
+
print(f" 文件大小: {len(downloaded_medium) / 1024:.2f} KB")
|
|
813
|
+
print(f" 下载耗时: {download_time:.2f} 秒")
|
|
814
|
+
if download_time > 0:
|
|
815
|
+
print(
|
|
816
|
+
f" 下载速度: {len(downloaded_medium) / 1024 / download_time:.2f} KB/s"
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
if downloaded_medium != medium_content:
|
|
820
|
+
print("✗ 下载的文件内容与上传的内容不一致")
|
|
821
|
+
return False
|
|
822
|
+
except Exception as e:
|
|
823
|
+
print(f"✗ 中等文件下载失败: {e}")
|
|
824
|
+
return False
|
|
825
|
+
|
|
826
|
+
# 10. 测试上传大文件(1MB)
|
|
827
|
+
print("\n[10] 测试上传大文件(1MB)...")
|
|
828
|
+
large_content = b"Z" * (1024 * 1024)
|
|
829
|
+
large_file_path = "/tmp/large_file.bin"
|
|
830
|
+
|
|
831
|
+
try:
|
|
832
|
+
start_time = time.time()
|
|
833
|
+
result = sandbox.files.write(large_file_path, large_content)
|
|
834
|
+
upload_time = time.time() - start_time
|
|
835
|
+
print(f"✓ 大文件上传成功: {result.path}")
|
|
836
|
+
print(f" 文件大小: {len(large_content) / (1024*1024):.2f} MB")
|
|
837
|
+
print(f" 上传耗时: {upload_time:.2f} 秒")
|
|
838
|
+
if upload_time > 0:
|
|
839
|
+
print(
|
|
840
|
+
f" 上传速度: {len(large_content) / (1024*1024) / upload_time:.2f} MB/s"
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
# 验证文件大小
|
|
844
|
+
info = sandbox.files.get_info(large_file_path)
|
|
845
|
+
if info.size != len(large_content):
|
|
846
|
+
print(f"✗ 文件大小不匹配: 期望 {len(large_content)}, 实际 {info.size}")
|
|
847
|
+
return False
|
|
848
|
+
except Exception as e:
|
|
849
|
+
print(f"✗ 大文件上传失败: {e}")
|
|
850
|
+
return False
|
|
851
|
+
|
|
852
|
+
# 11. 测试下载大文件
|
|
853
|
+
print("\n[11] 测试下载大文件...")
|
|
854
|
+
try:
|
|
855
|
+
start_time = time.time()
|
|
856
|
+
downloaded_large = sandbox.files.read(large_file_path, format="bytes")
|
|
857
|
+
download_time = time.time() - start_time
|
|
858
|
+
print(f"✓ 大文件下载成功")
|
|
859
|
+
print(f" 文件大小: {len(downloaded_large) / (1024*1024):.2f} MB")
|
|
860
|
+
print(f" 下载耗时: {download_time:.2f} 秒")
|
|
861
|
+
if download_time > 0:
|
|
862
|
+
print(
|
|
863
|
+
f" 下载速度: {len(downloaded_large) / (1024*1024) / download_time:.2f} MB/s"
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
if downloaded_large != large_content:
|
|
867
|
+
print("✗ 下载的大文件内容与上传的内容不一致")
|
|
868
|
+
return False
|
|
869
|
+
except Exception as e:
|
|
870
|
+
print(f"✗ 大文件下载失败: {e}")
|
|
871
|
+
return False
|
|
872
|
+
|
|
873
|
+
# 12. 测试批量上传和下载
|
|
874
|
+
print("\n[12] 测试批量上传和下载...")
|
|
875
|
+
try:
|
|
876
|
+
batch_files = [
|
|
877
|
+
{"path": "/tmp/batch_upload1.txt", "data": "Batch file 1"},
|
|
878
|
+
{"path": "/tmp/batch_upload2.txt", "data": "Batch file 2"},
|
|
879
|
+
{"path": "/tmp/batch_upload3.txt", "data": "Batch file 3"},
|
|
880
|
+
]
|
|
881
|
+
|
|
882
|
+
# 批量上传
|
|
883
|
+
upload_results = sandbox.files.write(batch_files)
|
|
884
|
+
print(f"✓ 批量上传成功,共 {len(upload_results)} 个文件")
|
|
885
|
+
|
|
886
|
+
# 批量下载并验证
|
|
887
|
+
for file_info in batch_files:
|
|
888
|
+
downloaded = sandbox.files.read(file_info["path"], format="text")
|
|
889
|
+
expected = file_info["data"]
|
|
890
|
+
if downloaded.strip() != expected.strip():
|
|
891
|
+
print(f"✗ 批量文件 {file_info['path']} 内容不匹配")
|
|
892
|
+
return False
|
|
893
|
+
|
|
894
|
+
print(f"✓ 批量下载验证成功")
|
|
895
|
+
except Exception as e:
|
|
896
|
+
print(f"✗ 批量上传下载失败: {e}")
|
|
897
|
+
return False
|
|
898
|
+
|
|
899
|
+
print("\n✓ 所有上传和下载测试通过")
|
|
900
|
+
return True
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def main():
|
|
904
|
+
"""主测试函数"""
|
|
905
|
+
print("=" * 60)
|
|
906
|
+
print("Sandbox 测试用例")
|
|
907
|
+
print("使用已有的 sandbox URL 和 token (Debug模式)")
|
|
908
|
+
print("=" * 60)
|
|
909
|
+
|
|
910
|
+
# 从环境变量或直接设置获取sandbox信息
|
|
911
|
+
# 用户需要设置这些环境变量或直接修改下面的值
|
|
912
|
+
sandbox_id = os.getenv("SANDBOX_ID")
|
|
913
|
+
sandbox_domain = os.getenv("SANDBOX_DOMAIN")
|
|
914
|
+
envd_access_token = os.getenv("ENVD_ACCESS_TOKEN")
|
|
915
|
+
api_key = os.getenv("SBX_API_KEY")
|
|
916
|
+
debug_host = os.getenv("SBX_DEBUG_HOST", "localhost") # 默认使用localhost
|
|
917
|
+
|
|
918
|
+
# 如果没有设置环境变量,提示用户
|
|
919
|
+
if not sandbox_id or not envd_access_token:
|
|
920
|
+
print("\n错误: 请设置以下环境变量:")
|
|
921
|
+
print(" - SANDBOX_ID: sandbox的ID")
|
|
922
|
+
print(" - ENVD_ACCESS_TOKEN: sandbox的访问token")
|
|
923
|
+
print(" - SBX_DEBUG_HOST: (可选) debug模式下的host,默认为localhost")
|
|
924
|
+
print(" - SBX_API_KEY: (可选) API密钥")
|
|
925
|
+
print("\n注意: 在debug模式下,SANDBOX_DOMAIN不是必需的")
|
|
926
|
+
print("\n或者直接修改脚本中的变量值")
|
|
927
|
+
print("\n示例:")
|
|
928
|
+
print(" export SANDBOX_ID='your-sandbox-id'")
|
|
929
|
+
print(" export ENVD_ACCESS_TOKEN='your-token'")
|
|
930
|
+
print(" export SBX_DEBUG_HOST='localhost' # 或自定义host,如 '192.168.1.100'")
|
|
931
|
+
print(" export SBX_API_KEY='your-api-key'")
|
|
932
|
+
return
|
|
933
|
+
|
|
934
|
+
print(f"\n使用以下配置 (Debug模式):")
|
|
935
|
+
print(f" Sandbox ID: {sandbox_id}")
|
|
936
|
+
print(f" Debug Host: {debug_host}")
|
|
937
|
+
print(
|
|
938
|
+
f" Access Token: {'*' * 20}...{envd_access_token[-4:] if len(envd_access_token) > 4 else '****'}"
|
|
939
|
+
)
|
|
940
|
+
if sandbox_domain:
|
|
941
|
+
print(f" Sandbox Domain: {sandbox_domain} (在debug模式下不使用)")
|
|
942
|
+
|
|
943
|
+
# 创建连接配置 - 使用debug模式
|
|
944
|
+
connection_headers = {"Authorization": "Bearer root"}
|
|
945
|
+
if envd_access_token:
|
|
946
|
+
connection_headers["X-Access-Token"] = envd_access_token
|
|
947
|
+
|
|
948
|
+
connection_config = ConnectionConfig(
|
|
949
|
+
api_key=api_key,
|
|
950
|
+
domain=None, # debug模式下不使用domain
|
|
951
|
+
debug=True, # 启用debug模式
|
|
952
|
+
debug_host=debug_host, # 设置debug host
|
|
953
|
+
request_timeout=60.0,
|
|
954
|
+
headers=connection_headers,
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
# 创建sandbox实例(直接连接,不创建新的)
|
|
958
|
+
# 在debug模式下,sandbox_domain可以是None或任意值,因为实际使用的是debug_host
|
|
959
|
+
print("\n正在连接sandbox (Debug模式)...")
|
|
960
|
+
try:
|
|
961
|
+
sandbox = Sandbox(
|
|
962
|
+
sandbox_id=sandbox_id,
|
|
963
|
+
sandbox_domain=sandbox_domain or "debug-sandbox", # debug模式下可以是任意值
|
|
964
|
+
envd_version="v1.0",
|
|
965
|
+
envd_access_token=envd_access_token,
|
|
966
|
+
connection_config=connection_config,
|
|
967
|
+
object_storage=None,
|
|
968
|
+
network_proxy=None,
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
# 检查sandbox是否运行
|
|
972
|
+
print(f" 尝试连接到: {sandbox.envd_api_url}")
|
|
973
|
+
if not sandbox.is_running():
|
|
974
|
+
print("✗ Sandbox未运行,无法进行测试")
|
|
975
|
+
print(f" 请确认sandbox在 {debug_host}:8888 上运行")
|
|
976
|
+
return
|
|
977
|
+
|
|
978
|
+
print(f"✓ Sandbox连接成功: {sandbox.sandbox_id}")
|
|
979
|
+
print(f" API URL: {sandbox.envd_api_url}")
|
|
980
|
+
|
|
981
|
+
except Exception as e:
|
|
982
|
+
print(f"✗ Sandbox连接失败: {e}")
|
|
983
|
+
print(f" 请确认:")
|
|
984
|
+
print(f" 1. Sandbox在 {debug_host}:8888 上运行")
|
|
985
|
+
print(f" 2. ENVD_ACCESS_TOKEN 正确")
|
|
986
|
+
print(f" 3. 网络连接正常")
|
|
987
|
+
import traceback
|
|
988
|
+
|
|
989
|
+
traceback.print_exc()
|
|
990
|
+
return
|
|
991
|
+
|
|
992
|
+
# 运行测试
|
|
993
|
+
results = []
|
|
994
|
+
|
|
995
|
+
try:
|
|
996
|
+
# 测试filesystem操作
|
|
997
|
+
results.append(("Filesystem操作", test_filesystem_operations(sandbox)))
|
|
998
|
+
|
|
999
|
+
# 测试命令操作
|
|
1000
|
+
results.append(("Command/Process操作", test_command_operations(sandbox)))
|
|
1001
|
+
|
|
1002
|
+
# 测试上传和下载
|
|
1003
|
+
results.append(("上传和下载", test_upload_download(sandbox)))
|
|
1004
|
+
|
|
1005
|
+
except Exception as e:
|
|
1006
|
+
print(f"\n✗ 测试过程中发生错误: {e}")
|
|
1007
|
+
import traceback
|
|
1008
|
+
|
|
1009
|
+
traceback.print_exc()
|
|
1010
|
+
|
|
1011
|
+
# 输出测试结果摘要
|
|
1012
|
+
print("\n" + "=" * 60)
|
|
1013
|
+
print("测试结果摘要")
|
|
1014
|
+
print("=" * 60)
|
|
1015
|
+
|
|
1016
|
+
all_passed = True
|
|
1017
|
+
for test_name, result in results:
|
|
1018
|
+
status = "✓ 通过" if result else "✗ 失败"
|
|
1019
|
+
print(f"{test_name}: {status}")
|
|
1020
|
+
if not result:
|
|
1021
|
+
all_passed = False
|
|
1022
|
+
|
|
1023
|
+
print("\n" + "=" * 60)
|
|
1024
|
+
if all_passed:
|
|
1025
|
+
print("✓ 所有测试通过!")
|
|
1026
|
+
print("\n提示:")
|
|
1027
|
+
print(" - 如果后端日志中有 'error adjusting oom score' 错误,这是正常的")
|
|
1028
|
+
print(" 这些错误表示后端尝试设置进程OOM score但权限不足,不影响功能")
|
|
1029
|
+
print(" - 在debug模式下,这些权限相关的警告可以安全忽略")
|
|
1030
|
+
print(" - 测试用例的功能验证都已通过")
|
|
1031
|
+
else:
|
|
1032
|
+
print("✗ 部分测试失败")
|
|
1033
|
+
print("=" * 60)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
if __name__ == "__main__":
|
|
1037
|
+
main()
|