chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,18 @@
import pytest
from e2b import NotFoundException, AsyncSandbox
async def test_connect_to_process(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("sleep 10", background=True)
pid = cmd.pid
process_info = await async_sandbox.commands.connect(pid)
assert process_info.pid == pid
async def test_connect_to_non_existing_process(async_sandbox: AsyncSandbox):
non_existing_pid = 999999
with pytest.raises(NotFoundException):
await async_sandbox.commands.connect(non_existing_pid)
@@ -0,0 +1,19 @@
import pytest
from e2b import AsyncSandbox, CommandExitException
async def test_kill_process(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("sleep 10", background=True)
pid = cmd.pid
await async_sandbox.commands.kill(pid)
with pytest.raises(CommandExitException):
await async_sandbox.commands.run(f"kill -0 {pid}")
async def test_kill_non_existing_process(async_sandbox: AsyncSandbox):
non_existing_pid = 999999
assert not await async_sandbox.commands.kill(non_existing_pid)
@@ -0,0 +1,13 @@
from e2b import AsyncSandbox
async def test_kill_process(async_sandbox: AsyncSandbox):
c1 = await async_sandbox.commands.run("sleep 10", background=True)
c2 = await async_sandbox.commands.run("sleep 10", background=True)
processes = await async_sandbox.commands.list()
assert len(processes) >= 2
pids = [p.pid for p in processes]
assert c1.pid in pids
assert c2.pid in pids
@@ -0,0 +1,35 @@
import pytest
from e2b import AsyncSandbox
async def test_command_envs(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("echo $FOO", envs={"FOO": "bar"})
assert cmd.stdout.strip() == "bar"
@pytest.mark.skip_debug()
async def test_sandbox_envs(async_sandbox_factory):
sbx = await async_sandbox_factory(envs={"FOO": "bar"})
cmd = await sbx.commands.run("echo $FOO")
assert cmd.stdout.strip() == "bar"
async def test_bash_command_scoped_env_vars(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("echo $FOO", envs={"FOO": "bar"})
assert cmd.exit_code == 0
assert cmd.stdout.strip() == "bar"
# test that it is secure and not accessible to subsequent commands
cmd2 = await async_sandbox.commands.run('sudo echo "$FOO"')
assert cmd2.exit_code == 0
assert cmd2.stdout.strip() == ""
async def test_python_command_scoped_env_vars(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run(
"python3 -c \"import os; print(os.environ['FOO'])\"", envs={"FOO": "bar"}
)
assert cmd.exit_code == 0
assert cmd.stdout.strip() == "bar"
@@ -0,0 +1,53 @@
import pytest
from e2b import AsyncSandbox, TimeoutException
async def test_run(async_sandbox: AsyncSandbox):
text = "Hello, World!"
cmd = await async_sandbox.commands.run(f'echo "{text}"')
assert cmd.exit_code == 0
assert cmd.stdout == f"{text}\n"
async def test_run_with_special_characters(async_sandbox: AsyncSandbox):
text = "!@#$%^&*()_+"
cmd = await async_sandbox.commands.run(f'echo "{text}"')
assert cmd.exit_code == 0
# assert cmd.stdout == f"{text}\n"
async def test_run_with_broken_utf8(async_sandbox: AsyncSandbox):
# Create a string with 8191 'a' characters followed by the problematic byte 0xe2
long_str = "a" * 8191 + "\\xe2"
result = await async_sandbox.commands.run(f'printf "{long_str}"')
assert result.exit_code == 0
# The broken UTF-8 bytes should be replaced with the Unicode replacement character
assert result.stdout == ("a" * 8191 + "\ufffd")
async def test_run_with_multiline_string(async_sandbox: AsyncSandbox):
text = "Hello,\nWorld!"
cmd = await async_sandbox.commands.run(f'echo "{text}"')
assert cmd.exit_code == 0
assert cmd.stdout == f"{text}\n"
async def test_run_with_timeout(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run('echo "Hello, World!"', timeout=10)
assert cmd.exit_code == 0
async def test_run_with_too_short_timeout(async_sandbox: AsyncSandbox):
with pytest.raises(TimeoutException):
await async_sandbox.commands.run("sleep 10", timeout=2)
@@ -0,0 +1,16 @@
import pytest
from e2b import AsyncSandbox, TimeoutException
@pytest.mark.skip_debug()
async def test_kill_sandbox_while_command_is_running(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("sleep 60", background=True)
await async_sandbox.kill()
with pytest.raises(TimeoutException) as exc_info:
await cmd.wait()
# The health check confirms the sandbox is gone, so the error states it outright
assert "sandbox was killed or reached its end of life" in str(exc_info.value)
@@ -0,0 +1,94 @@
import asyncio
from e2b import AsyncSandbox
async def test_send_stdin_to_process(async_sandbox: AsyncSandbox):
ev = asyncio.Event()
def handle_event(stdout: str):
ev.set()
cmd = await async_sandbox.commands.run(
"cat", background=True, on_stdout=handle_event, stdin=True
)
await async_sandbox.commands.send_stdin(cmd.pid, "Hello, World!")
await ev.wait()
assert cmd.stdout == "Hello, World!"
async def test_send_bytes_stdin_to_process(async_sandbox: AsyncSandbox):
ev = asyncio.Event()
def handle_event(stdout: str):
ev.set()
cmd = await async_sandbox.commands.run(
"cat", background=True, on_stdout=handle_event, stdin=True
)
await async_sandbox.commands.send_stdin(cmd.pid, b"Hello, World!")
await ev.wait()
assert cmd.stdout == "Hello, World!"
async def test_send_stdin_via_command_handle(async_sandbox: AsyncSandbox):
ev = asyncio.Event()
def handle_event(stdout: str):
ev.set()
cmd = await async_sandbox.commands.run(
"cat", background=True, on_stdout=handle_event, stdin=True
)
await cmd.send_stdin("Hello, World!")
await ev.wait()
assert cmd.stdout == "Hello, World!"
async def test_close_stdin_via_command_handle(async_sandbox: AsyncSandbox):
cmd = await async_sandbox.commands.run("cat", background=True, stdin=True)
await cmd.send_stdin("Hello, World!")
await cmd.close_stdin()
# `cat` exits once stdin is closed (EOF).
result = await cmd.wait()
assert result.exit_code == 0
assert result.stdout == "Hello, World!"
async def test_send_special_characters_to_process(async_sandbox: AsyncSandbox):
ev = asyncio.Event()
def handle_event(stdout: str):
ev.set()
cmd = await async_sandbox.commands.run(
"cat", background=True, on_stdout=handle_event, stdin=True
)
await async_sandbox.commands.send_stdin(cmd.pid, "!@#$%^&*()_+")
await ev.wait()
assert cmd.stdout == "!@#$%^&*()_+"
async def test_send_multiline_string_to_process(async_sandbox: AsyncSandbox):
ev = asyncio.Event()
def handle_event(stdout: str):
ev.set()
cmd = await async_sandbox.commands.run(
"cat", background=True, on_stdout=handle_event, stdin=True
)
await async_sandbox.commands.send_stdin(cmd.pid, "Hello,\nWorld!")
await ev.wait()
assert cmd.stdout == "Hello,\nWorld!"
@@ -0,0 +1,62 @@
from e2b import AsyncSandbox
from e2b.sandbox.filesystem.filesystem import WriteEntry
async def test_write_and_read_with_gzip(async_sandbox: AsyncSandbox, debug):
filename = "test_gzip_write.txt"
content = "This is a test file with gzip encoding."
info = await async_sandbox.files.write(filename, content, gzip=True)
assert info.path == f"/home/user/{filename}"
read_content = await async_sandbox.files.read(filename, gzip=True)
assert read_content == content
if debug:
await async_sandbox.files.remove(filename)
async def test_write_gzip_read_plain(async_sandbox: AsyncSandbox, debug):
filename = "test_gzip_write_plain_read.txt"
content = "Written with gzip, read without."
await async_sandbox.files.write(filename, content, gzip=True)
read_content = await async_sandbox.files.read(filename)
assert read_content == content
if debug:
await async_sandbox.files.remove(filename)
async def test_write_files_with_gzip(async_sandbox: AsyncSandbox, debug):
files = [
WriteEntry(path="gzip_multi_1.txt", data="File 1 content"),
WriteEntry(path="gzip_multi_2.txt", data="File 2 content"),
WriteEntry(path="gzip_multi_3.txt", data="File 3 content"),
]
infos = await async_sandbox.files.write_files(files, gzip=True)
assert len(infos) == len(files)
for file in files:
read_content = await async_sandbox.files.read(file["path"])
assert read_content == file["data"]
if debug:
for file in files:
await async_sandbox.files.remove(file["path"])
async def test_read_bytes_with_gzip(async_sandbox: AsyncSandbox, debug):
filename = "test_gzip_bytes.txt"
content = "Binary content with gzip."
await async_sandbox.files.write(filename, content)
read_bytes = await async_sandbox.files.read(filename, format="bytes", gzip=True)
assert isinstance(read_bytes, bytearray)
assert read_bytes.decode("utf-8") == content
if debug:
await async_sandbox.files.remove(filename)
@@ -0,0 +1,8 @@
from e2b import AsyncSandbox
async def test_exists(async_sandbox: AsyncSandbox):
filename = "test_exists.txt"
await async_sandbox.files.write(filename, "test")
assert await async_sandbox.files.exists(filename)
@@ -0,0 +1,248 @@
import uuid
from typing import Any
from e2b import AsyncSandbox, FileType
async def test_list_directory(async_sandbox: AsyncSandbox):
home_dir_name = "/home/user"
parent_dir_name = f"test_directory_{uuid.uuid4()}"
await async_sandbox.files.make_dir(parent_dir_name)
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1")
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2")
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_1")
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_2")
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_1")
await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_2")
await async_sandbox.files.write(f"{parent_dir_name}/file1.txt", "Hello, world!")
test_cases: list[dict[str, Any]] = [
{
"name": "default depth (1)",
"depth": None,
"expected_len": 3,
"expected_file_names": [
"file1.txt",
"subdir1",
"subdir2",
],
"expected_file_types": [
FileType.FILE,
FileType.DIR,
FileType.DIR,
],
"expected_file_paths": [
f"{home_dir_name}/{parent_dir_name}/file1.txt",
f"{home_dir_name}/{parent_dir_name}/subdir1",
f"{home_dir_name}/{parent_dir_name}/subdir2",
],
},
{
"name": "explicit depth 1",
"depth": 1,
"expected_len": 3,
"expected_file_names": [
"file1.txt",
"subdir1",
"subdir2",
],
"expected_file_types": [
FileType.FILE,
FileType.DIR,
FileType.DIR,
],
"expected_file_paths": [
f"{home_dir_name}/{parent_dir_name}/file1.txt",
f"{home_dir_name}/{parent_dir_name}/subdir1",
f"{home_dir_name}/{parent_dir_name}/subdir2",
],
},
{
"name": "explicit depth 2",
"depth": 2,
"expected_len": 7,
"expected_file_types": [
FileType.FILE,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
],
"expected_file_names": [
"file1.txt",
"subdir1",
"subdir1_1",
"subdir1_2",
"subdir2",
"subdir2_1",
"subdir2_2",
],
"expected_file_paths": [
f"{home_dir_name}/{parent_dir_name}/file1.txt",
f"{home_dir_name}/{parent_dir_name}/subdir1",
f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1",
f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2",
f"{home_dir_name}/{parent_dir_name}/subdir2",
f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1",
f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2",
],
},
{
"name": "explicit depth 3 (should be the same as depth 2)",
"depth": 3,
"expected_len": 7,
"expected_file_names": [
"file1.txt",
"subdir1",
"subdir1_1",
"subdir1_2",
"subdir2",
"subdir2_1",
"subdir2_2",
],
"expected_file_types": [
FileType.FILE,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
FileType.DIR,
],
"expected_file_paths": [
f"{home_dir_name}/{parent_dir_name}/file1.txt",
f"{home_dir_name}/{parent_dir_name}/subdir1",
f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1",
f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2",
f"{home_dir_name}/{parent_dir_name}/subdir2",
f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1",
f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2",
],
},
]
for test_case in test_cases:
files = await async_sandbox.files.list(
parent_dir_name,
depth=test_case["depth"] if test_case["depth"] is not None else None,
)
assert len(files) == test_case["expected_len"]
for i in range(len(test_case["expected_file_names"])):
assert files[i].name == test_case["expected_file_names"][i]
assert files[i].path == test_case["expected_file_paths"][i]
assert files[i].type == test_case["expected_file_types"][i]
await async_sandbox.files.remove(parent_dir_name)
async def test_list_directory_error_cases(async_sandbox: AsyncSandbox):
parent_dir_name = f"test_directory_{uuid.uuid4()}"
await async_sandbox.files.make_dir(parent_dir_name)
expected_error_message = "depth should be at least 1"
try:
await async_sandbox.files.list(parent_dir_name, depth=-1)
assert False, "Expected error but none was thrown"
except Exception as err:
assert expected_error_message in str(err), (
f'expected error message to include "{expected_error_message}"'
)
await async_sandbox.files.remove(parent_dir_name)
async def test_file_entry_details(async_sandbox: AsyncSandbox):
test_dir = "test-file-entry"
file_path = f"{test_dir}/test.txt"
content = "Hello, World!"
await async_sandbox.files.make_dir(test_dir)
await async_sandbox.files.write(file_path, content)
files = await async_sandbox.files.list(test_dir, depth=1)
assert len(files) == 1
file_entry = files[0]
assert file_entry.name == "test.txt"
assert file_entry.path == f"/home/user/{file_path}"
assert file_entry.type == FileType.FILE
assert file_entry.mode == 0o644
assert file_entry.permissions == "-rw-r--r--"
assert file_entry.owner == "user"
assert file_entry.group == "user"
assert file_entry.size == len(content)
assert file_entry.modified_time is not None
assert file_entry.symlink_target is None
await async_sandbox.files.remove(test_dir)
async def test_directory_entry_details(async_sandbox: AsyncSandbox):
test_dir = "test-entry-info"
sub_dir = f"{test_dir}/subdir"
await async_sandbox.files.make_dir(test_dir)
await async_sandbox.files.make_dir(sub_dir)
files = await async_sandbox.files.list(test_dir, depth=1)
assert len(files) == 1
dir_entry = files[0]
assert dir_entry.name == "subdir"
assert dir_entry.path == f"/home/user/{sub_dir}"
assert dir_entry.type == FileType.DIR
assert dir_entry.mode == 0o755
assert dir_entry.permissions == "drwxr-xr-x"
assert dir_entry.owner == "user"
assert dir_entry.group == "user"
assert dir_entry.modified_time is not None
assert dir_entry.symlink_target is None
await async_sandbox.files.remove(test_dir)
async def test_mixed_entries(async_sandbox: AsyncSandbox):
test_dir = "test-mixed-entries"
sub_dir = f"{test_dir}/subdir"
file_path = f"{test_dir}/test.txt"
content = "Hello, World!"
await async_sandbox.files.make_dir(test_dir)
await async_sandbox.files.make_dir(sub_dir)
await async_sandbox.files.write(file_path, content)
files = await async_sandbox.files.list(test_dir, depth=1)
assert len(files) == 2
# Create a dictionary of entries by name for easier verification
entries = {entry.name: entry for entry in files}
# Verify directory entry
dir_entry = entries.get("subdir")
assert dir_entry is not None
assert dir_entry.path == f"/home/user/{sub_dir}"
assert dir_entry.type == FileType.DIR
assert dir_entry.mode == 0o755
assert dir_entry.permissions == "drwxr-xr-x"
assert dir_entry.owner == "user"
assert dir_entry.group == "user"
assert dir_entry.modified_time is not None
# Verify file entry
file_entry = entries.get("test.txt")
assert file_entry is not None
assert file_entry.path == f"/home/user/{file_path}"
assert file_entry.type == FileType.FILE
assert file_entry.mode == 0o644
assert file_entry.permissions == "-rw-r--r--"
assert file_entry.owner == "user"
assert file_entry.group == "user"
assert file_entry.size == len(content)
assert file_entry.modified_time is not None
await async_sandbox.files.remove(test_dir)
@@ -0,0 +1,79 @@
import pytest
from e2b.exceptions import FileNotFoundException
from e2b import AsyncSandbox, FileType
@pytest.mark.asyncio
async def test_get_info_of_file(async_sandbox: AsyncSandbox):
filename = "test_file.txt"
await async_sandbox.files.write(filename, "test")
info = await async_sandbox.files.get_info(filename)
current_path = await async_sandbox.commands.run("pwd")
assert info.name == filename
assert info.type == FileType.FILE
assert info.path == f"{current_path.stdout.strip()}/{filename}"
assert info.size == 4
assert info.mode == 0o644
assert info.permissions == "-rw-r--r--"
assert info.owner == "user"
assert info.group == "user"
assert info.modified_time is not None
assert info.modified_time.tzinfo is not None
@pytest.mark.asyncio
async def test_get_info_of_nonexistent_file(async_sandbox: AsyncSandbox):
filename = "test_does_not_exist.txt"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.get_info(filename)
@pytest.mark.asyncio
async def test_get_info_of_directory(async_sandbox: AsyncSandbox):
dirname = "test_dir"
await async_sandbox.files.make_dir(dirname)
info = await async_sandbox.files.get_info(dirname)
current_path = await async_sandbox.commands.run("pwd")
assert info.name == dirname
assert info.type == FileType.DIR
assert info.path == f"{current_path.stdout.strip()}/{dirname}"
assert info.size > 0
assert info.mode == 0o755
assert info.permissions == "drwxr-xr-x"
assert info.owner == "user"
assert info.group == "user"
assert info.modified_time is not None
assert info.modified_time.tzinfo is not None
@pytest.mark.asyncio
async def test_get_info_of_nonexistent_directory(async_sandbox: AsyncSandbox):
dirname = "test_does_not_exist_dir"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.get_info(dirname)
async def test_file_symlink(async_sandbox: AsyncSandbox):
test_dir = "test-simlink-entry"
file_name = "test.txt"
content = "Hello, World!"
await async_sandbox.files.make_dir(test_dir)
await async_sandbox.files.write(f"{test_dir}/{file_name}", content)
symlink_name = "symlink_to_test.txt"
await async_sandbox.commands.run(f"ln -s {file_name} {symlink_name}", cwd=test_dir)
file = await async_sandbox.files.get_info(f"{test_dir}/{symlink_name}")
pwd = await async_sandbox.commands.run("pwd")
assert file.type == FileType.FILE
assert file.symlink_target == f"{pwd.stdout.strip()}/{test_dir}/{file_name}"
await async_sandbox.files.remove(test_dir)
@@ -0,0 +1,29 @@
import uuid
from e2b import AsyncSandbox
async def test_make_directory(async_sandbox: AsyncSandbox):
dir_name = f"test_directory_{uuid.uuid4()}"
await async_sandbox.files.make_dir(dir_name)
exists = await async_sandbox.files.exists(dir_name)
assert exists
async def test_make_directory_already_exists(async_sandbox: AsyncSandbox):
dir_name = f"test_directory_{uuid.uuid4()}"
created = await async_sandbox.files.make_dir(dir_name)
assert created
created = await async_sandbox.files.make_dir(dir_name)
assert not created
async def test_make_nested_directory(async_sandbox: AsyncSandbox):
nested_dir_name = f"test_directory_{uuid.uuid4()}/nested_directory"
await async_sandbox.files.make_dir(nested_dir_name)
exists = await async_sandbox.files.exists(nested_dir_name)
assert exists
@@ -0,0 +1,172 @@
import pytest
from e2b import AsyncSandbox
from e2b.exceptions import InvalidArgumentException
from e2b.sandbox.filesystem.filesystem import WriteEntry
async def test_write_file_with_metadata(async_sandbox: AsyncSandbox, debug):
filename = "test_metadata.txt"
content = "This is a test file with metadata."
metadata = {"author": "mish", "purpose": "upload"}
info = await async_sandbox.files.write(filename, content, metadata=metadata)
assert info.metadata == metadata
# Metadata is persisted and surfaced on subsequent reads.
stat = await async_sandbox.files.get_info(filename)
assert stat.metadata == metadata
if debug:
await async_sandbox.files.remove(filename)
async def test_write_file_with_metadata_octet_stream(
async_sandbox: AsyncSandbox, debug
):
filename = "test_metadata_octet.txt"
content = "This is a test file with metadata."
metadata = {"author": "mish", "purpose": "upload"}
info = await async_sandbox.files.write(
filename, content, metadata=metadata, use_octet_stream=True
)
assert info.metadata == metadata
stat = await async_sandbox.files.get_info(filename)
assert stat.metadata == metadata
if debug:
await async_sandbox.files.remove(filename)
async def test_write_file_without_metadata(async_sandbox: AsyncSandbox, debug):
filename = "test_no_metadata.txt"
info = await async_sandbox.files.write(filename, "no metadata here")
assert info.metadata is None
stat = await async_sandbox.files.get_info(filename)
assert stat.metadata is None
if debug:
await async_sandbox.files.remove(filename)
async def test_write_files_applies_metadata_to_every_file(
async_sandbox: AsyncSandbox, debug
):
# The same metadata is applied to every file in the upload.
metadata = {"source": "test-suite"}
files = [
WriteEntry(path="metadata_multi_1.txt", data="File 1"),
WriteEntry(path="metadata_multi_2.txt", data="File 2"),
]
infos = await async_sandbox.files.write_files(files, metadata=metadata)
assert len(infos) == len(files)
for info in infos:
assert info.metadata == metadata
stat = await async_sandbox.files.get_info(info.path)
assert stat.metadata == metadata
if debug:
for file in files:
await async_sandbox.files.remove(file["path"])
async def test_metadata_surfaced_when_listing(async_sandbox: AsyncSandbox, debug):
dirname = "metadata_list_dir"
filename = "listed.txt"
metadata = {"tag": "listed"}
await async_sandbox.files.make_dir(dirname)
await async_sandbox.files.write(
f"{dirname}/{filename}", "content", metadata=metadata
)
entries = await async_sandbox.files.list(dirname)
entry = next((e for e in entries if e.name == filename), None)
assert entry is not None
assert entry.metadata == metadata
if debug:
await async_sandbox.files.remove(dirname)
async def test_metadata_surfaced_after_rename(async_sandbox: AsyncSandbox, debug):
old_path = "metadata_rename_old.txt"
new_path = "metadata_rename_new.txt"
metadata = {"stage": "renamed"}
await async_sandbox.files.write(old_path, "content", metadata=metadata)
info = await async_sandbox.files.rename(old_path, new_path)
assert info.metadata == metadata
if debug:
await async_sandbox.files.remove(new_path)
async def test_overwriting_clears_stale_metadata(async_sandbox: AsyncSandbox, debug):
filename = "metadata_overwrite.txt"
await async_sandbox.files.write(filename, "first", metadata={"author": "mish"})
# Overwriting without metadata removes the previously stored metadata.
info = await async_sandbox.files.write(filename, "second")
assert info.metadata is None
stat = await async_sandbox.files.get_info(filename)
assert stat.metadata is None
if debug:
await async_sandbox.files.remove(filename)
async def test_metadata_set_via_xattrs_surfaced_in_get_info(
async_sandbox: AsyncSandbox, debug
):
filename = "metadata_xattr.txt"
await async_sandbox.files.write(filename, "content")
cmd = await async_sandbox.commands.run(f"realpath {filename}")
file_path = cmd.stdout.strip()
# Set an xattr directly in the `user.e2b.` namespace (out-of-band, not via
# the SDK upload); it should surface as metadata (with the namespace prefix
# stripped) when reading the file info.
await async_sandbox.commands.run(
f"python3 -c \"import os; os.setxattr('{file_path}', 'user.e2b.author', b'mish')\""
)
info = await async_sandbox.files.get_info(filename)
assert info.metadata == {"author": "mish"}
if debug:
await async_sandbox.files.remove(filename)
async def test_write_rejects_invalid_metadata(async_sandbox: AsyncSandbox):
filename = "invalid_metadata.txt"
# Key with a space is not a valid HTTP header token.
with pytest.raises(InvalidArgumentException):
await async_sandbox.files.write(filename, "x", metadata={"bad key": "value"})
# Empty key.
with pytest.raises(InvalidArgumentException):
await async_sandbox.files.write(filename, "x", metadata={"": "value"})
# Value with a non-printable / non-ASCII character.
with pytest.raises(InvalidArgumentException):
await async_sandbox.files.write(filename, "x", metadata={"good": "bad\nvalue"})
# Trailing newline (Python's `$` would accept it; `\Z` must not).
with pytest.raises(InvalidArgumentException):
await async_sandbox.files.write(filename, "x", metadata={"good": "value\n"})
with pytest.raises(InvalidArgumentException):
await async_sandbox.files.write(filename, "x", metadata={"key\n": "value"})
# The file must not have been created by a rejected write.
assert not await async_sandbox.files.exists(filename)
@@ -0,0 +1,95 @@
import pytest
from e2b import FileNotFoundException, NotFoundException, AsyncSandbox
async def test_read_file(async_sandbox: AsyncSandbox):
filename = "test_read.txt"
content = "Hello, world!"
await async_sandbox.files.write(filename, content)
read_content = await async_sandbox.files.read(filename)
assert read_content == content
async def test_read_non_existing_file(async_sandbox: AsyncSandbox):
filename = "non_existing_file.txt"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.read(filename)
async def test_read_non_existing_file_catches_with_deprecated_not_found_exception(
async_sandbox: AsyncSandbox,
):
filename = "non_existing_file.txt"
with pytest.raises(NotFoundException):
await async_sandbox.files.read(filename)
async def test_read_empty_file(async_sandbox: AsyncSandbox):
filename = "empty_file.txt"
content = ""
await async_sandbox.commands.run(f"touch {filename}")
read_content = await async_sandbox.files.read(filename)
assert read_content == content
async def test_read_file_as_stream(async_sandbox: AsyncSandbox):
filename = "test_read_stream.txt"
content = "Streamed read content. " * 10_000
await async_sandbox.files.write(filename, content)
stream = await async_sandbox.files.read(filename, format="stream")
chunks = []
async for chunk in stream:
chunks.append(chunk)
read_content = b"".join(chunks).decode("utf-8")
assert read_content == content
async def test_read_file_as_stream_with_gzip(async_sandbox: AsyncSandbox):
filename = "test_read_stream_gzip.txt"
content = "Streamed gzipped read content. " * 10_000
await async_sandbox.files.write(filename, content)
stream = await async_sandbox.files.read(filename, format="stream", gzip=True)
chunks = []
async for chunk in stream:
chunks.append(chunk)
read_content = b"".join(chunks).decode("utf-8")
assert read_content == content
async def test_read_non_existing_file_as_stream(async_sandbox: AsyncSandbox):
filename = "non_existing_file.txt"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.read(filename, format="stream")
async def test_read_file_as_stream_context_manager(async_sandbox: AsyncSandbox):
filename = "test_read_stream_ctx.txt"
content = "Streamed read content. " * 10_000
await async_sandbox.files.write(filename, content)
chunks = []
async with await async_sandbox.files.read(filename, format="stream") as stream:
async for chunk in stream:
chunks.append(chunk)
read_content = b"".join(chunks).decode("utf-8")
assert read_content == content
async def test_read_file_as_stream_partial_then_close(async_sandbox: AsyncSandbox):
filename = "test_read_stream_partial.txt"
content = "Streamed read content. " * 10_000
await async_sandbox.files.write(filename, content)
# Reading only the first chunk and closing must not raise or leak.
stream = await async_sandbox.files.read(filename, format="stream")
first = await stream.__anext__()
assert len(first) > 0
await stream.aclose()
@@ -0,0 +1,18 @@
from e2b import AsyncSandbox
async def test_remove_file(async_sandbox: AsyncSandbox):
filename = "test_remove.txt"
content = "This file will be removed."
await async_sandbox.files.write(filename, content)
await async_sandbox.files.remove(filename)
exists = await async_sandbox.files.exists(filename)
assert not exists
async def test_remove_non_existing_file(async_sandbox: AsyncSandbox):
filename = "non_existing_file.txt"
await async_sandbox.files.remove(filename)
@@ -0,0 +1,29 @@
import pytest
from e2b import FileNotFoundException, AsyncSandbox
async def test_rename_file(async_sandbox: AsyncSandbox):
old_filename = "test_rename_old.txt"
new_filename = "test_rename_new.txt"
content = "This file will be renamed."
await async_sandbox.files.write(old_filename, content)
info = await async_sandbox.files.rename(old_filename, new_filename)
assert info.path == f"/home/user/{new_filename}"
exists_old = await async_sandbox.files.exists(old_filename)
exists_new = await async_sandbox.files.exists(new_filename)
assert not exists_old
assert exists_new
read_content = await async_sandbox.files.read(new_filename)
assert read_content == content
async def test_rename_non_existing_file(async_sandbox: AsyncSandbox):
old_filename = "non_existing_file.txt"
new_filename = "new_non_existing_file.txt"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.rename(old_filename, new_filename)
@@ -0,0 +1,60 @@
import urllib.request
import urllib.error
import json
import pytest
from e2b.sandbox_async.main import AsyncSandbox
@pytest.mark.skip_debug()
async def test_download_url_with_signing(async_sandbox: AsyncSandbox):
file_path = "test_download_url_with_signing.txt"
file_content = "This file will be watched."
await async_sandbox.files.write(file_path, file_content)
signed_url = async_sandbox.download_url(file_path, "user")
with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
body_bytes = resp.read()
body_text = body_bytes.decode()
assert body_text == file_content
@pytest.mark.skip_debug()
async def test_download_url_with_signing_and_expiration(async_sandbox: AsyncSandbox):
file_path = "test_download_url_with_signing.txt"
file_content = "This file will be watched."
await async_sandbox.files.write(file_path, file_content)
signed_url = async_sandbox.download_url(file_path, "user", 120)
with urllib.request.urlopen(signed_url) as resp:
assert resp.status == 200
body_bytes = resp.read()
body_text = body_bytes.decode()
assert body_text == file_content
@pytest.mark.skip_debug()
async def test_download_url_with_expired_signing(async_sandbox: AsyncSandbox):
file_path = "test_download_url_with_signing.txt"
file_content = "This file will be watched."
await async_sandbox.files.write(file_path, file_content)
signed_url = async_sandbox.download_url(
file_path, "user", use_signature_expiration=-120
)
with pytest.raises(urllib.error.HTTPError) as exc_info:
urllib.request.urlopen(signed_url)
err = exc_info.value
assert err.code == 401, f"Unexpected status {err.code}"
error_json_str = err.read().decode() # bytes ➜ str
error_payload = json.loads(error_json_str) # str ➜ dict
expected_payload = {"code": 401, "message": "signature is already expired"}
assert error_payload == expected_payload
@@ -0,0 +1,185 @@
import pytest
from asyncio import Event
from e2b import (
FileNotFoundException,
AsyncSandbox,
FilesystemEvent,
FilesystemEventType,
FileType,
SandboxException,
)
async def test_watch_directory_changes_with_entry_info(async_sandbox: AsyncSandbox):
dirname = "test_watch_dir_entry"
filename = "test_watch.txt"
content = "This file will be watched."
new_content = "This file has been modified."
await async_sandbox.files.make_dir(dirname)
await async_sandbox.files.write(f"{dirname}/{filename}", content)
event_triggered = Event()
received: list[FilesystemEvent] = []
def handle_event(e: FilesystemEvent):
if e.type == FilesystemEventType.WRITE and e.name == filename:
received.append(e)
event_triggered.set()
handle = await async_sandbox.files.watch_dir(
dirname, on_event=handle_event, include_entry=True
)
await async_sandbox.files.write(f"{dirname}/{filename}", new_content)
await event_triggered.wait()
write_event = received[0]
# The entry is populated best-effort for events where the path still exists.
assert write_event.entry is not None
assert write_event.entry.name == filename
assert write_event.entry.path == f"/home/user/{dirname}/{filename}"
assert write_event.entry.type == FileType.FILE
await handle.stop()
async def test_watch_directory_changes_with_network_mounts_allowed(
async_sandbox: AsyncSandbox,
):
dirname = "test_watch_dir_network_mounts"
filename = "test_watch.txt"
content = "This file will be watched."
new_content = "This file has been modified."
await async_sandbox.files.make_dir(dirname)
await async_sandbox.files.write(f"{dirname}/{filename}", content)
event_triggered = Event()
def handle_event(e: FilesystemEvent):
if e.type == FilesystemEventType.WRITE and e.name == filename:
event_triggered.set()
# The flag only lifts the network-mount restriction — watching a regular
# directory must work the same with it enabled.
handle = await async_sandbox.files.watch_dir(
dirname, on_event=handle_event, allow_network_mounts=True
)
await async_sandbox.files.write(f"{dirname}/{filename}", new_content)
await event_triggered.wait()
await handle.stop()
async def test_watch_directory_changes(async_sandbox: AsyncSandbox):
dirname = "test_watch_dir"
filename = "test_watch.txt"
content = "This file will be watched."
new_content = "This file has been modified."
await async_sandbox.files.make_dir(dirname)
await async_sandbox.files.write(f"{dirname}/{filename}", content)
event_triggered = Event()
def handle_event(e: FilesystemEvent):
if e.type == FilesystemEventType.WRITE and e.name == filename:
event_triggered.set()
handle = await async_sandbox.files.watch_dir(dirname, on_event=handle_event)
await async_sandbox.files.write(f"{dirname}/{filename}", new_content)
await event_triggered.wait()
await handle.stop()
async def test_watch_recursive_directory_changes(async_sandbox: AsyncSandbox):
dirname = "test_recursive_watch_dir"
nested_dirname = "test_nested_watch_dir"
filename = "test_watch.txt"
content = "This file will be watched."
await async_sandbox.files.remove(dirname)
await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}")
event_triggered = Event()
expected_filename = f"{nested_dirname}/{filename}"
def handle_event(e: FilesystemEvent):
if e.type == FilesystemEventType.WRITE and e.name == expected_filename:
event_triggered.set()
handle = await async_sandbox.files.watch_dir(
dirname, on_event=handle_event, recursive=True
)
await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content)
await event_triggered.wait()
await handle.stop()
async def test_watch_recursive_directory_after_nested_folder_addition(
async_sandbox: AsyncSandbox,
):
dirname = "test_recursive_watch_dir_add"
nested_dirname = "test_nested_watch_dir"
filename = "test_watch.txt"
content = "This file will be watched."
await async_sandbox.files.remove(dirname)
await async_sandbox.files.make_dir(dirname)
event_triggered_file = Event()
event_triggered_folder = Event()
expected_filename = f"{nested_dirname}/{filename}"
def handle_event(e: FilesystemEvent):
if e.type == FilesystemEventType.WRITE and e.name == expected_filename:
event_triggered_file.set()
return
if e.type == FilesystemEventType.CREATE and e.name == nested_dirname:
event_triggered_folder.set()
handle = await async_sandbox.files.watch_dir(
dirname, on_event=handle_event, recursive=True
)
await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}")
await event_triggered_folder.wait()
await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content)
await event_triggered_file.wait()
await handle.stop()
async def test_watch_non_existing_directory(async_sandbox: AsyncSandbox):
dirname = "non_existing_watch_dir"
with pytest.raises(FileNotFoundException):
await async_sandbox.files.watch_dir(dirname, on_event=lambda e: None)
async def test_watch_file(async_sandbox: AsyncSandbox):
filename = "test_watch.txt"
await async_sandbox.files.write(filename, "This file will be watched.")
with pytest.raises(SandboxException):
await async_sandbox.files.watch_dir(filename, on_event=lambda e: None)
async def test_watch_file_with_secured_envd(async_sandbox):
await async_sandbox.files.watch_dir("/home/user/", on_event=lambda e: None)
await async_sandbox.files.write("test_watch.txt", "This file will be watched.")
@@ -0,0 +1,220 @@
import io
import uuid
from e2b import AsyncSandbox
from e2b.sandbox.filesystem.filesystem import FileType, WriteEntry
from e2b.sandbox_async.filesystem.filesystem import WriteInfo
async def test_write_text_file(async_sandbox: AsyncSandbox, debug):
filename = "test_write.txt"
content = "This is a test file."
info = await async_sandbox.files.write(filename, content)
assert info.path == f"/home/user/{filename}"
assert info.type == FileType.FILE
exists = await async_sandbox.files.exists(filename)
assert exists
read_content = await async_sandbox.files.read(filename)
assert read_content == content
if debug:
await async_sandbox.files.remove(filename)
async def test_write_binary_file(async_sandbox: AsyncSandbox, debug):
filename = "test_write.bin"
text = "This is a test binary file."
# equivalent to `open("path/to/local/file", "rb")`
content = io.BytesIO(text.encode("utf-8"))
info = await async_sandbox.files.write(filename, content)
assert info.path == f"/home/user/{filename}"
exists = await async_sandbox.files.exists(filename)
assert exists
read_content = await async_sandbox.files.read(filename)
assert read_content == text
if debug:
await async_sandbox.files.remove(filename)
async def test_write_multiple_files(async_sandbox: AsyncSandbox, debug):
num_test_files = 10
# Attempt to write with empty files array
empty_info = await async_sandbox.files.write_files([])
assert isinstance(empty_info, list)
assert len(empty_info) == 0
# Attempt to write with one file in array
one_file_path = "one_test_file.txt"
info = await async_sandbox.files.write_files(
[WriteEntry(path=one_file_path, data="This is a test file.")]
)
assert isinstance(info, list)
assert len(info) == 1
info = info[0]
assert isinstance(info, WriteInfo)
assert info.path == "/home/user/one_test_file.txt"
exists = await async_sandbox.files.exists(info.path)
assert exists
read_content = await async_sandbox.files.read(info.path)
assert read_content == "This is a test file."
# Attempt to write with multiple files in array
files = []
for i in range(num_test_files):
path = f"test_write_{i}.txt"
content = f"This is a test file {i}."
files.append(WriteEntry(path=path, data=content))
infos = await async_sandbox.files.write_files(files)
assert isinstance(infos, list)
assert len(infos) == len(files)
for i, info in enumerate(infos):
assert isinstance(info, WriteInfo)
assert info.path == f"/home/user/test_write_{i}.txt"
assert info.type == FileType.FILE
exists = await async_sandbox.files.exists(path)
assert exists
read_content = await async_sandbox.files.read(info.path)
assert read_content == files[i]["data"]
if debug:
await async_sandbox.files.remove(one_file_path)
for i in range(num_test_files):
await async_sandbox.files.remove(f"test_write_{i}.txt")
async def test_overwrite_file(async_sandbox: AsyncSandbox, debug):
filename = "test_overwrite.txt"
initial_content = "Initial content."
new_content = "New content."
await async_sandbox.files.write(filename, initial_content)
await async_sandbox.files.write(filename, new_content)
read_content = await async_sandbox.files.read(filename)
assert read_content == new_content
if debug:
await async_sandbox.files.remove(filename)
async def test_write_to_non_existing_directory(async_sandbox: AsyncSandbox, debug):
filename = f"non_existing_dir_{uuid.uuid4()}/test_write.txt"
content = "This should succeed too."
await async_sandbox.files.write(filename, content)
exists = await async_sandbox.files.exists(filename)
assert exists
read_content = await async_sandbox.files.read(filename)
assert read_content == content
if debug:
await async_sandbox.files.remove(filename)
async def test_write_with_secured_envd(async_sandbox_factory):
filename = f"non_existing_dir_{uuid.uuid4()}/test_write.txt"
content = "This should succeed too."
sbx = await async_sandbox_factory(timeout=30, secure=True)
assert await sbx.is_running()
assert sbx._envd_version is not None
assert sbx._envd_access_token is not None
await sbx.files.write(filename, content)
exists = await sbx.files.exists(filename)
assert exists
read_content = await sbx.files.read(filename)
assert read_content == content
async def test_write_files_with_different_data_types(
async_sandbox: AsyncSandbox, debug
):
text_data = "Text string data"
bytes_data = b"Bytes data"
bytes_io_data = io.BytesIO(b"BytesIO data")
string_io_data = io.StringIO("StringIO data")
files = [
WriteEntry(path="writefiles_text.txt", data=text_data),
WriteEntry(path="writefiles_bytes.bin", data=bytes_data),
WriteEntry(path="writefiles_bytesio.bin", data=bytes_io_data),
WriteEntry(path="writefiles_stringio.txt", data=string_io_data),
]
infos = await async_sandbox.files.write_files(files)
assert len(infos) == 4
text_content = await async_sandbox.files.read("writefiles_text.txt")
assert text_content == text_data
bytes_content = await async_sandbox.files.read("writefiles_bytes.bin")
assert bytes_content == "Bytes data"
bytes_io_content = await async_sandbox.files.read("writefiles_bytesio.bin")
assert bytes_io_content == "BytesIO data"
string_io_content = await async_sandbox.files.read("writefiles_stringio.txt")
assert string_io_content == "StringIO data"
if debug:
for file in files:
await async_sandbox.files.remove(file["path"])
async def test_write_io_with_octet_stream(async_sandbox: AsyncSandbox, debug):
filename = "test_write_octet_io.bin"
text = "Streamed octet-stream upload. " * 10_000
content = io.BytesIO(text.encode("utf-8"))
info = await async_sandbox.files.write(filename, content, use_octet_stream=True)
assert info.path == f"/home/user/{filename}"
read_content = await async_sandbox.files.read(filename)
assert read_content == text
if debug:
await async_sandbox.files.remove(filename)
async def test_write_text_io_with_octet_stream(async_sandbox: AsyncSandbox, debug):
filename = "test_write_octet_text_io.txt"
text = "Streamed text octet-stream upload."
await async_sandbox.files.write(filename, io.StringIO(text), use_octet_stream=True)
read_content = await async_sandbox.files.read(filename)
assert read_content == text
if debug:
await async_sandbox.files.remove(filename)
async def test_write_io_with_octet_stream_and_gzip(async_sandbox: AsyncSandbox, debug):
filename = "test_write_octet_io_gzip.bin"
text = "Streamed gzipped octet-stream upload. " * 10_000
content = io.BytesIO(text.encode("utf-8"))
await async_sandbox.files.write(filename, content, use_octet_stream=True, gzip=True)
read_content = await async_sandbox.files.read(filename)
assert read_content == text
if debug:
await async_sandbox.files.remove(filename)
@@ -0,0 +1,41 @@
import asyncio
from e2b import AsyncSandbox
from e2b.sandbox.commands.command_handle import PtySize
async def test_connect_to_pty(async_sandbox: AsyncSandbox):
output1 = []
output2 = []
def append_data(data: list, x: bytes):
data.append(x.decode("utf-8"))
# First, create a terminal and disconnect the on_data handler
terminal = await async_sandbox.pty.create(
PtySize(80, 24),
on_data=lambda x: append_data(output1, x),
envs={"FOO": "bar"},
)
await async_sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\n")
# Give time for the command output in the first connection
await asyncio.sleep(0.3)
await terminal.disconnect()
# Now connect again, with a new on_data handler
reconnect_handle = await async_sandbox.pty.connect(
terminal.pid, on_data=lambda x: append_data(output2, x)
)
await async_sandbox.pty.send_stdin(terminal.pid, b"echo $FOO\nexit\n")
await reconnect_handle.wait()
assert terminal.pid == reconnect_handle.pid
assert reconnect_handle.exit_code == 0
assert "bar" in "".join(output1)
assert "bar" in "".join(output2)
@@ -0,0 +1,21 @@
from e2b import AsyncSandbox
from e2b.sandbox.commands.command_handle import PtySize
async def test_pty_create(async_sandbox: AsyncSandbox):
output = []
def append_data(data: list, x: bytes):
data.append(x.decode("utf-8"))
terminal = await async_sandbox.pty.create(
PtySize(80, 24), on_data=lambda x: append_data(output, x), envs={"ABC": "123"}
)
await async_sandbox.pty.send_stdin(terminal.pid, b"echo $ABC\n")
await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n")
await terminal.wait()
assert terminal.exit_code == 0
assert "123" in "".join(output)
@@ -0,0 +1,20 @@
import pytest
from e2b import AsyncSandbox, CommandExitException
from e2b.sandbox.commands.command_handle import PtySize
async def test_kill_pty(async_sandbox: AsyncSandbox):
terminal = await async_sandbox.pty.create(PtySize(80, 24), on_data=lambda _: None)
assert await async_sandbox.pty.kill(terminal.pid)
# The PTY process should no longer be running.
with pytest.raises(CommandExitException):
await async_sandbox.commands.run(f"kill -0 {terminal.pid}")
async def test_kill_non_existing_pty(async_sandbox: AsyncSandbox):
non_existing_pid = 999999
assert not await async_sandbox.pty.kill(non_existing_pid)
@@ -0,0 +1,34 @@
from e2b import AsyncSandbox
from e2b.sandbox.commands.command_handle import PtySize
async def test_resize(async_sandbox: AsyncSandbox):
output = []
def append_data(data: list, x: bytes):
data.append(x.decode("utf-8"))
terminal = await async_sandbox.pty.create(
PtySize(cols=80, rows=24), on_data=lambda x: append_data(output, x)
)
await async_sandbox.pty.send_stdin(terminal.pid, b"tput cols\n")
await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n")
await terminal.wait()
assert terminal.exit_code == 0
assert "80" in "".join(output)
output = []
terminal = await async_sandbox.pty.create(
PtySize(cols=80, rows=24), on_data=lambda x: append_data(output, x)
)
await async_sandbox.pty.resize(terminal.pid, PtySize(cols=100, rows=24))
await async_sandbox.pty.send_stdin(terminal.pid, b"tput cols\n")
await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n")
await terminal.wait()
assert terminal.exit_code == 0
assert "100" in "".join(output)
@@ -0,0 +1,11 @@
from e2b import AsyncSandbox
from e2b.sandbox.commands.command_handle import PtySize
async def test_send_input(async_sandbox: AsyncSandbox):
terminal = await async_sandbox.pty.create(
PtySize(cols=80, rows=24), on_data=lambda x: print(x)
)
await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n")
await terminal.wait()
assert terminal.exit_code == 0
@@ -0,0 +1,128 @@
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from packaging.version import Version
from e2b import AsyncSandbox
from e2b.api import SandboxCreateResponse
from e2b.connection_config import ConnectionConfig
import e2b.sandbox_async.main as sandbox_async_main
BASE_DOMAIN = "base.e2b.dev"
BASE_REQUEST_TIMEOUT = 11
BASE_DEBUG = False
BASE_HEADERS = {"X-Test": "base"}
def create_sandbox(monkeypatch, api_key: str) -> AsyncSandbox:
dummy_transport = SimpleNamespace(pool=object())
monkeypatch.setattr(
sandbox_async_main, "get_transport", lambda *_args, **_kwargs: dummy_transport
)
monkeypatch.setattr(
sandbox_async_main.httpx, "AsyncClient", lambda *args, **kwargs: object()
)
monkeypatch.setattr(
sandbox_async_main, "Filesystem", lambda *args, **kwargs: object()
)
monkeypatch.setattr(
sandbox_async_main, "Commands", lambda *args, **kwargs: object()
)
monkeypatch.setattr(sandbox_async_main, "Pty", lambda *args, **kwargs: object())
monkeypatch.setattr(sandbox_async_main, "Git", lambda *args, **kwargs: object())
return AsyncSandbox(
sandbox_id="sbx-test",
sandbox_domain="sandbox.e2b.dev",
envd_version=Version("0.2.4"),
envd_access_token="tok",
traffic_access_token="tok",
connection_config=ConnectionConfig(
api_key=api_key,
domain=BASE_DOMAIN,
request_timeout=BASE_REQUEST_TIMEOUT,
debug=BASE_DEBUG,
api_headers=BASE_HEADERS,
),
)
@pytest.mark.skip_debug()
async def test_pause_passes_connection_config_without_overrides(
monkeypatch, test_api_key
):
mock_pause = AsyncMock(return_value="sbx-test")
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_pause", mock_pause)
sandbox = create_sandbox(monkeypatch, test_api_key)
await sandbox.pause()
mock_pause.assert_awaited_once()
assert mock_pause.call_args.kwargs["sandbox_id"] == "sbx-test"
assert mock_pause.call_args.kwargs["api_key"] == test_api_key
assert mock_pause.call_args.kwargs["domain"] == BASE_DOMAIN
assert mock_pause.call_args.kwargs["request_timeout"] == BASE_REQUEST_TIMEOUT
assert mock_pause.call_args.kwargs["debug"] == BASE_DEBUG
assert mock_pause.call_args.kwargs["headers"]["X-Test"] == BASE_HEADERS["X-Test"]
@pytest.mark.skip_debug()
async def test_pause_applies_overrides(monkeypatch, test_api_key):
mock_pause = AsyncMock(return_value="sbx-test")
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_pause", mock_pause)
sandbox = create_sandbox(monkeypatch, test_api_key)
await sandbox.pause(
domain="override.e2b.dev",
request_timeout=20,
api_headers={"X-Extra": "1"},
)
mock_pause.assert_awaited_once()
assert mock_pause.call_args.kwargs["sandbox_id"] == "sbx-test"
assert mock_pause.call_args.kwargs["api_key"] == test_api_key
assert mock_pause.call_args.kwargs["domain"] == "override.e2b.dev"
assert mock_pause.call_args.kwargs["request_timeout"] == 20
assert mock_pause.call_args.kwargs["debug"] == BASE_DEBUG
assert mock_pause.call_args.kwargs["headers"]["X-Test"] == BASE_HEADERS["X-Test"]
assert mock_pause.call_args.kwargs["headers"]["X-Extra"] == "1"
@pytest.mark.skip_debug()
async def test_connect_sets_stable_host_routing_headers(monkeypatch, test_api_key):
mock_connect = AsyncMock(
return_value=SandboxCreateResponse(
sandbox_id="sbx-test",
sandbox_domain="e2b.app",
envd_version="0.4.0",
envd_access_token="tok",
traffic_access_token="traffic",
)
)
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_connect", mock_connect)
monkeypatch.setattr(ConnectionConfig, "_integration", "testing/version")
config = ConnectionConfig(
api_key=test_api_key,
headers=BASE_HEADERS,
)
sandbox = await AsyncSandbox.connect(
"sbx-test",
**config.get_api_params(),
)
assert sandbox.envd_api_url == "https://sandbox.e2b.app"
assert "X-Test" not in sandbox.connection_config.sandbox_headers
assert sandbox.connection_config.sandbox_headers["User-Agent"].startswith(
"e2b-python-sdk/"
)
assert sandbox.connection_config.sandbox_headers["User-Agent"].endswith(
" testing/version"
)
assert sandbox.connection_config.sandbox_headers["E2b-Sandbox-Id"] == "sbx-test"
assert sandbox.connection_config.sandbox_headers["E2b-Sandbox-Port"] == str(
ConnectionConfig.envd_port
)
assert sandbox.connection_config.sandbox_headers["X-Access-Token"] == "tok"
@@ -0,0 +1,145 @@
import uuid
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from e2b import AsyncSandbox
from e2b.api.client.api.sandboxes import post_sandboxes_sandbox_id_connect
from e2b.api.client.models import Sandbox as SandboxModel
import e2b.sandbox_async.main as sandbox_async_main
@pytest.mark.skip_debug()
async def test_connect(async_sandbox_factory):
sbx = await async_sandbox_factory(timeout=10)
assert await sbx.is_running()
sbx_connection = await AsyncSandbox.connect(sbx.sandbox_id)
assert await sbx_connection.is_running()
@pytest.mark.skip_debug()
async def test_connect_with_secure(async_sandbox_factory):
dir_name = f"test_directory_{uuid.uuid4()}"
sbx = await async_sandbox_factory(timeout=10, secure=True)
assert await sbx.is_running()
sbx_connection = await AsyncSandbox.connect(sbx.sandbox_id)
await sbx_connection.files.make_dir(dir_name)
files = await sbx_connection.files.list(dir_name)
assert len(files) == 0
assert await sbx_connection.is_running()
@pytest.mark.skip_debug()
async def test_connect_to_paused_sandbox_resumes(async_sandbox):
await async_sandbox.pause()
assert not await async_sandbox.is_running()
resumed = await AsyncSandbox.connect(async_sandbox.sandbox_id)
assert await resumed.is_running()
@pytest.mark.skip_debug()
async def test_resume_does_not_shorten_timeout_on_running_sandbox(
async_sandbox_factory,
):
# Create sandbox with a 300 second timeout
sbx = await async_sandbox_factory(timeout=300)
assert await sbx.is_running()
# Get initial info to check end_at
info_before = await AsyncSandbox.get_info(sbx.sandbox_id)
# Connect with a shorter timeout (10 seconds)
await AsyncSandbox.connect(sbx.sandbox_id, timeout=10)
# Get info after connection
info_after = await AsyncSandbox.get_info(sbx.sandbox_id)
# The end_at time should not have been shortened. It should be the same
assert info_after.end_at == info_before.end_at, (
f"Timeout was changed: before={info_before.end_at}, after={info_after.end_at}"
)
@pytest.mark.skip_debug()
async def test_connect_extends_timeout_on_running_sandbox(async_sandbox):
# Create sandbox with a short timeout
assert await async_sandbox.is_running()
# Get initial info to check end_at
info_before = await async_sandbox.get_info()
# Connect with a longer timeout
await AsyncSandbox.connect(async_sandbox.sandbox_id, timeout=600)
# Get info after connection
info_after = await AsyncSandbox.get_info(async_sandbox.sandbox_id)
# The end_at time should have been extended
assert info_after.end_at > info_before.end_at, (
f"Timeout was not extended: before={info_before.end_at}, after={info_after.end_at}"
)
async def test_connect_in_debug_mode_does_not_call_api(monkeypatch, test_api_key):
mock_connect = AsyncMock()
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_connect", mock_connect)
sbx = await AsyncSandbox.connect("sbx-debug", debug=True, api_key=test_api_key)
mock_connect.assert_not_called()
assert sbx.sandbox_id == "sbx-debug"
assert sbx._envd_access_token is None
assert sbx.traffic_access_token is None
async def test_connect_in_env_debug_mode_does_not_call_api(monkeypatch, test_api_key):
monkeypatch.setenv("E2B_DEBUG", "true")
mock_connect = AsyncMock()
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_connect", mock_connect)
sbx = await AsyncSandbox.connect("sbx-debug", api_key=test_api_key)
mock_connect.assert_not_called()
assert sbx.sandbox_id == "sbx-debug"
async def test_instance_connect_in_debug_mode_does_not_call_api(
monkeypatch, test_api_key
):
mock_connect = AsyncMock()
monkeypatch.setattr(sandbox_async_main.SandboxApi, "_cls_connect", mock_connect)
sbx = await AsyncSandbox.connect("sbx-debug", debug=True, api_key=test_api_key)
assert await sbx.connect() is sbx
mock_connect.assert_not_called()
async def test_connect_normalizes_unset_tokens(monkeypatch, test_api_key):
# Tokens and domain are absent in the API response for non-secure sandboxes
model = SandboxModel(
client_id="client-id",
envd_version="0.2.4",
sandbox_id="sbx-test",
template_id="template-id",
)
mock_request = AsyncMock(
return_value=SimpleNamespace(status_code=200, parsed=model)
)
monkeypatch.setattr(
post_sandboxes_sandbox_id_connect, "asyncio_detailed", mock_request
)
sbx = await AsyncSandbox.connect("sbx-test", debug=False, api_key=test_api_key)
mock_request.assert_called_once()
assert sbx._envd_access_token is None
assert sbx.traffic_access_token is None
assert "signature" not in sbx.download_url("test.txt")
@@ -0,0 +1,194 @@
import asyncio
from typing import Any, cast
import httpx
import pytest
from e2b import AsyncSandbox, SandboxQuery, SandboxState
from e2b.api.client.models import (
NewSandbox,
SandboxAutoResumeConfig,
)
from e2b.exceptions import InvalidArgumentException
@pytest.mark.skip_debug()
async def test_start(async_sandbox):
assert await async_sandbox.is_running()
assert async_sandbox._envd_version is not None
@pytest.mark.skip_debug()
async def test_metadata(async_sandbox_factory):
sbx = await async_sandbox_factory(timeout=5, metadata={"test-key": "test-value"})
paginator = AsyncSandbox.list(
query=SandboxQuery(metadata={"test-key": "test-value"})
)
sandboxes = await paginator.next_items()
for sbx_info in sandboxes:
if sbx.sandbox_id == sbx_info.sandbox_id:
assert sbx_info.metadata is not None
assert sbx_info.metadata["test-key"] == "test-value"
break
else:
assert False, "Sandbox not found"
def test_create_payload_serializes_auto_resume_enabled():
body = NewSandbox(
template_id="template-id",
auto_pause=True,
auto_resume=SandboxAutoResumeConfig(enabled=True),
)
assert body.to_dict()["autoPause"] is True
assert body.to_dict()["autoResume"] == {"enabled": True}
def test_create_payload_deserializes_auto_resume_enabled():
body = NewSandbox.from_dict(
{
"templateID": "template-id",
"autoPause": False,
"autoResume": {"enabled": False},
}
)
assert isinstance(body.auto_resume, SandboxAutoResumeConfig)
assert body.auto_resume.to_dict() == {"enabled": False}
@pytest.mark.skip_debug()
async def test_filesystem_only_auto_pause_rejects_auto_resume():
# A filesystem-only auto-pause snapshot can only be resumed explicitly, so
# combining keep_memory=False with auto_resume is rejected client-side.
with pytest.raises(InvalidArgumentException):
await AsyncSandbox.create(
timeout=3,
lifecycle={
"on_timeout": {"action": "pause", "keep_memory": False},
"auto_resume": True,
},
)
@pytest.mark.skip_debug()
async def test_keep_memory_not_allowed_with_kill():
# The discriminated union forbids keep_memory on action="kill" at type-check
# time; the runtime guard rejects it for callers that bypass the type
# (cast(Any, ...) feeds the deliberately type-invalid input).
with pytest.raises(InvalidArgumentException):
await AsyncSandbox.create(
timeout=3,
lifecycle=cast(
Any, {"on_timeout": {"action": "kill", "keep_memory": False}}
),
)
@pytest.mark.skip_debug()
async def test_invalid_on_timeout_type_does_not_crash(async_sandbox_factory):
# An untyped/invalid on_timeout (e.g. None) must not crash create; it falls
# back to kill semantics like a missing on_timeout (the sandbox just starts).
sbx = await async_sandbox_factory(
timeout=10, lifecycle=cast(Any, {"on_timeout": None})
)
assert await sbx.is_running()
@pytest.mark.skip_debug()
async def test_keep_memory_none_defaults_to_full_memory(async_sandbox_factory):
# An explicit None keep_memory must default to full memory (not filesystem-only):
# the timeout auto-pause then resumes the SAME sandbox in place (memory restore),
# so the boot id is unchanged. A changed boot id would mean None was wrongly
# treated as filesystem-only (cold boot).
sbx = await async_sandbox_factory(
timeout=60,
lifecycle={"on_timeout": {"action": "pause", "keep_memory": None}},
)
boot_before = (await sbx.files.read("/proc/sys/kernel/random/boot_id")).strip()
await sbx.set_timeout(0) # force the timeout auto-pause now
for _ in range(150):
if not await sbx.is_running():
break
await asyncio.sleep(0.2)
assert not await sbx.is_running()
resumed = await sbx.connect()
assert resumed.sandbox_id == sbx.sandbox_id # same sandbox
boot_after = (await resumed.files.read("/proc/sys/kernel/random/boot_id")).strip()
assert boot_after == boot_before # memory restore in place, not a cold boot
@pytest.mark.skip_debug()
async def test_auto_pause_filesystem_only_reboots(async_sandbox_factory):
# keep_memory=False makes the timeout auto-pause filesystem-only, so resuming
# cold-boots the sandbox from disk.
sandbox = await async_sandbox_factory(
timeout=3,
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}},
)
marker = "auto-pause-fs-only"
await sandbox.files.write("/home/user/auto-pause-marker.txt", marker)
boot_before = (await sandbox.files.read("/proc/sys/kernel/random/boot_id")).strip()
await asyncio.sleep(5)
assert (await sandbox.get_info()).state == SandboxState.PAUSED
# A filesystem-only snapshot cannot auto-resume on traffic; connect resumes
# it by cold-booting.
resumed = await sandbox.connect()
persisted = (await resumed.files.read("/home/user/auto-pause-marker.txt")).strip()
assert persisted == marker
boot_after = (await resumed.files.read("/proc/sys/kernel/random/boot_id")).strip()
assert boot_after != boot_before
@pytest.mark.skip_debug()
async def test_auto_pause_without_auto_resume_requires_connect(async_sandbox_factory):
sandbox = await async_sandbox_factory(
timeout=3,
lifecycle={"on_timeout": "pause", "auto_resume": False},
)
await asyncio.sleep(5)
assert (await sandbox.get_info()).state == SandboxState.PAUSED
assert not await sandbox.is_running()
await sandbox.connect()
assert (await sandbox.get_info()).state == SandboxState.RUNNING
assert await sandbox.is_running()
@pytest.mark.skip_debug()
async def test_auto_resume_wakes_on_http_request(async_sandbox_factory):
sandbox = await async_sandbox_factory(
timeout=3,
lifecycle={"on_timeout": "pause", "auto_resume": True},
)
cmd = await sandbox.commands.run("python3 -m http.server 8000", background=True)
try:
await asyncio.sleep(5)
url = f"https://{sandbox.get_host(8000)}"
async with httpx.AsyncClient(timeout=15.0) as client:
res = await client.get(url)
assert res.status_code == 200
assert (await sandbox.get_info()).state == SandboxState.RUNNING
assert await sandbox.is_running()
finally:
try:
await cmd.kill()
except Exception:
pass
@@ -0,0 +1,30 @@
import asyncio
import httpx
from e2b import AsyncSandbox
async def test_ping_server(async_sandbox: AsyncSandbox, debug, helpers):
cmd = await async_sandbox.commands.run(
"python -m http.server 8000",
background=True,
)
disable = helpers.catch_cmd_exit_error_in_background(cmd)
try:
host = async_sandbox.get_host(8000)
status_code = None
async with httpx.AsyncClient() as client:
for _ in range(20):
res = await client.get(f"{'http' if debug else 'https'}://{host}")
status_code = res.status_code
if res.status_code == 200:
break
await asyncio.sleep(0.5)
assert status_code == 200
disable()
finally:
await cmd.kill()
@@ -0,0 +1,42 @@
import pytest
from e2b.sandbox.commands.command_handle import CommandExitException
@pytest.mark.skip_debug()
async def test_internet_access_enabled(async_sandbox_factory):
"""Test that sandbox with internet access enabled can reach external websites."""
sbx = await async_sandbox_factory(allow_internet_access=True)
# Test internet connectivity by making a curl request to a reliable external site
result = await sbx.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://connectivitycheck.gstatic.com/generate_204"
)
assert result.exit_code == 0
assert result.stdout.strip() == "204"
@pytest.mark.skip_debug()
async def test_internet_access_disabled(async_sandbox_factory):
"""Test that sandbox with internet access disabled cannot reach external websites."""
sbx = await async_sandbox_factory(allow_internet_access=False)
# Test that internet connectivity is blocked by making a curl request
with pytest.raises(CommandExitException) as exc_info:
await sbx.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://connectivitycheck.gstatic.com/generate_204"
)
# The command should fail or timeout when internet access is disabled
assert exc_info.value.exit_code != 0
@pytest.mark.skip_debug()
async def test_internet_access_default(async_sandbox):
"""Test that sandbox with default settings (no explicit allow_internet_access) has internet access."""
# Test internet connectivity by making a curl request to a reliable external site
result = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://connectivitycheck.gstatic.com/generate_204"
)
assert result.exit_code == 0
assert result.stdout.strip() == "204"
@@ -0,0 +1,16 @@
import pytest
from e2b import AsyncSandbox, SandboxQuery, SandboxState
@pytest.mark.skip_debug()
async def test_kill(async_sandbox: AsyncSandbox, sandbox_test_id: str):
await async_sandbox.kill()
paginator = AsyncSandbox.list(
query=SandboxQuery(
state=[SandboxState.RUNNING], metadata={"sandbox_test_id": sandbox_test_id}
)
)
sandboxes = await paginator.next_items()
assert async_sandbox.sandbox_id not in [s.sandbox_id for s in sandboxes]
@@ -0,0 +1,62 @@
import asyncio
import datetime
import pytest
@pytest.mark.skip_debug()
@pytest.mark.timeout(60)
async def test_sbx_metrics(async_sandbox_factory):
sbx = await async_sandbox_factory(timeout=60)
# Wait for the sandbox to have some metrics
metrics = []
for _ in range(60):
metrics = await sbx.get_metrics()
if len(metrics) > 0:
break
await asyncio.sleep(0.5)
assert len(metrics) > 0
metric = metrics[0]
assert metric.cpu_count is not None
assert metric.cpu_used_pct is not None
assert metric.mem_used is not None
assert metric.mem_total is not None
assert metric.disk_used is not None
assert metric.disk_total is not None
@pytest.mark.skip_debug()
@pytest.mark.timeout(60)
async def test_sbx_metrics_time_range(async_sandbox_factory):
start_time = datetime.datetime.now(datetime.timezone.utc)
sbx = await async_sandbox_factory(timeout=60)
# Wait for the sandbox to have some metrics within the test's time window
metrics = []
end_time = start_time
for _ in range(60):
end_time = datetime.datetime.now(datetime.timezone.utc)
metrics = await sbx.get_metrics(start=start_time, end=end_time)
if len(metrics) > 0:
break
await asyncio.sleep(0.5)
assert len(metrics) > 0
# All returned metrics must fall within the requested time range
# (10s slack - metric timestamps are aligned to collection buckets,
# currently 5s, and the query params are second-precision)
slack = 10
for metric in metrics:
assert metric.timestamp.timestamp() >= start_time.timestamp() - slack
assert metric.timestamp.timestamp() <= end_time.timestamp() + slack
# A time range from before the sandbox existed must return no metrics
metrics = await sbx.get_metrics(
start=start_time - datetime.timedelta(hours=1),
end=start_time - datetime.timedelta(minutes=30),
)
assert len(metrics) == 0
@@ -0,0 +1,318 @@
import asyncio
import json
import httpx
import pytest
from e2b import SandboxNetworkOpts
from e2b.sandbox.commands.command_handle import CommandExitException
async def wait_for_status(
client: httpx.AsyncClient,
url: str,
status_code: int,
headers: dict[str, str] | None = None,
timeout: float = 15,
) -> httpx.Response:
deadline = asyncio.get_running_loop().time() + timeout
response: httpx.Response | None = None
while asyncio.get_running_loop().time() < deadline:
response = await client.get(url, headers=headers, follow_redirects=True)
if response.status_code == status_code:
return response
await asyncio.sleep(1)
assert response is not None
return response
@pytest.mark.skip_debug()
async def test_allow_specific_ip_with_deny_all(async_sandbox_factory):
"""Test that sandbox with denyOut all and allowOut creates a whitelist."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(
deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1"]
)
)
# Test that allowed IP works
result = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert result.exit_code == 0
assert result.stdout.strip() == "301"
# Test that other IPs are denied
with pytest.raises(CommandExitException) as exc_info:
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8"
)
assert exc_info.value.exit_code != 0
@pytest.mark.skip_debug()
async def test_deny_specific_ip(async_sandbox_factory):
"""Test that sandbox with denyOut denies specified IP addresses."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(deny_out=["8.8.8.8"])
)
# Test that denied IP fails
with pytest.raises(CommandExitException) as exc_info:
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8"
)
assert exc_info.value.exit_code != 0
# Test that other IPs work
result = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert result.exit_code == 0
assert result.stdout.strip() == "301"
@pytest.mark.skip_debug()
async def test_deny_all_traffic(async_sandbox_factory):
"""Test that sandbox can deny all traffic using the all_traffic selector."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(deny_out=lambda ctx: [ctx.all_traffic]), timeout=30
)
# Test that all traffic is denied
with pytest.raises(CommandExitException) as exc_info:
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://1.1.1.1"
)
assert exc_info.value.exit_code != 0
with pytest.raises(CommandExitException) as exc_info:
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8"
)
assert exc_info.value.exit_code != 0
@pytest.mark.skip_debug()
async def test_allow_takes_precedence_over_deny(async_sandbox_factory):
"""Test that allowOut takes precedence over denyOut."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(
deny_out=lambda ctx: [ctx.all_traffic], allow_out=["1.1.1.1", "8.8.8.8"]
)
)
# Test that 1.1.1.1 works (explicitly allowed)
result1 = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert result1.exit_code == 0
assert result1.stdout.strip() == "301"
# Test that 8.8.8.8 also works (explicitly allowed, takes precedence over deny_out)
result2 = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert result2.exit_code == 0
assert result2.stdout.strip() == "302"
@pytest.mark.skip_debug()
async def test_allow_public_traffic_false(async_sandbox_factory):
"""Test that sandbox with allow_public_traffic=False requires traffic access token."""
async_sandbox = await async_sandbox_factory(
secure=True, network=SandboxNetworkOpts(allow_public_traffic=False)
)
# Verify the sandbox was created successfully and has a traffic access token
assert async_sandbox.traffic_access_token is not None
# Start a simple HTTP server in the sandbox
port = 8080
await async_sandbox.commands.run(
f"python3 -m http.server {port}", background=True, timeout=0
)
# Wait for server to start
await asyncio.sleep(3)
# Get the public URL for the sandbox
sandbox_url = f"https://{async_sandbox.get_host(port)}"
async with httpx.AsyncClient() as client:
# Test 1: Request without traffic access token should fail with 403
response = await client.get(sandbox_url, follow_redirects=True)
assert response.status_code == 403
# Test 2: Request with valid traffic access token should succeed
headers = {"e2b-traffic-access-token": async_sandbox.traffic_access_token}
response = await wait_for_status(client, sandbox_url, 200, headers=headers)
assert response.status_code == 200
@pytest.mark.skip_debug()
async def test_allow_public_traffic_true(async_sandbox_factory):
"""Test that sandbox with allow_public_traffic=True works without token."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(allow_public_traffic=True)
)
# Start a simple HTTP server in the sandbox
port = 8080
await async_sandbox.commands.run(
f"python3 -m http.server {port}", background=True, timeout=0
)
# Wait for server to start
await asyncio.sleep(3)
# Get the public URL for the sandbox
sandbox_url = f"https://{async_sandbox.get_host(port)}"
async with httpx.AsyncClient() as client:
# Request without traffic access token should succeed (public access enabled)
response = await wait_for_status(client, sandbox_url, 200)
assert response.status_code == 200
@pytest.mark.skip_debug()
async def test_firewall_transform_injects_headers(async_sandbox_factory):
"""Test that a firewall rule with a transform injects headers into outbound requests."""
injected_header = "X-E2B-Test-Token"
injected_value = "e2b-transform-value-123"
network: SandboxNetworkOpts = {
"rules": {
"httpbin.e2b.team": [
{"transform": {"headers": {injected_header: injected_value}}},
],
},
}
async_sandbox = await async_sandbox_factory(network=network)
result = await async_sandbox.commands.run(
"curl -sS --max-time 10 https://httpbin.e2b.team/headers"
)
assert result.exit_code == 0
parsed = json.loads(result.stdout)
reflected = parsed["headers"].get(injected_header)
assert reflected == injected_value, (
f"expected httpbin to reflect {injected_header}={injected_value}, "
f"got headers: {parsed['headers']}"
)
@pytest.mark.skip_debug()
async def test_update_network_applies_restrictions(async_sandbox_factory):
"""update_network can add egress restrictions to a running sandbox."""
async_sandbox = await async_sandbox_factory()
# Baseline: 8.8.8.8 reachable.
before = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert before.exit_code == 0
await async_sandbox.update_network({"deny_out": ["8.8.8.8"]})
# 8.8.8.8 is now denied.
with pytest.raises(CommandExitException) as exc_info:
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8"
)
assert exc_info.value.exit_code != 0
# Other destinations stay reachable.
result = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert result.exit_code == 0
@pytest.mark.skip_debug()
async def test_update_network_clears_existing_rules(async_sandbox_factory):
"""update_network replaces all egress rules; omitted fields are cleared."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(
deny_out=lambda ctx: [ctx.all_traffic],
allow_out=["1.1.1.1"],
)
)
# Baseline from create-time config: 8.8.8.8 denied.
with pytest.raises(CommandExitException):
await async_sandbox.commands.run(
"curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8"
)
# Empty update clears allow_out / deny_out entirely.
await async_sandbox.update_network({})
r1 = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert r1.exit_code == 0
r2 = await async_sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert r2.exit_code == 0
@pytest.mark.skip_debug()
async def test_mask_request_host(async_sandbox_factory):
"""Test that mask_request_host modifies the Host header correctly."""
async_sandbox = await async_sandbox_factory(
network=SandboxNetworkOpts(mask_request_host="custom-host.example.com:${PORT}"),
timeout=60,
)
import asyncio
import httpx
port = 8080
output_file = "/tmp/headers.txt"
# Start a Python HTTP server that captures request headers and writes them to a file
await async_sandbox.commands.run(
f"""python3 -c "
import http.server, json
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
with open('{output_file}', 'w') as f:
for k, v in self.headers.items():
f.write(k + ': ' + v + chr(10))
self.send_response(200)
self.end_headers()
def log_message(self, *a): pass
http.server.HTTPServer(('', {port}), H).handle_request()
" """,
background=True,
)
await asyncio.sleep(2)
# Get the public URL for the sandbox
sandbox_url = f"https://{async_sandbox.get_host(port)}"
# Make a request from OUTSIDE the sandbox through the proxy
# The Host header should be modified according to mask_request_host
async with httpx.AsyncClient() as client:
try:
await client.get(sandbox_url, timeout=5.0)
except Exception:
pass
await asyncio.sleep(1)
# Read the captured headers from inside the sandbox
result = await async_sandbox.commands.run(f"cat {output_file}")
# Verify the Host header was modified according to mask_request_host
assert "Host:" in result.stdout
assert "custom-host.example.com" in result.stdout
assert str(port) in result.stdout
@@ -0,0 +1,26 @@
import pytest
from e2b import AsyncSandbox
@pytest.mark.skip_debug()
async def test_start_secured(async_sandbox_factory):
sbx = await async_sandbox_factory(timeout=5, secure=True)
assert await sbx.is_running()
assert sbx._envd_version is not None
assert sbx._envd_access_token is not None
@pytest.mark.skip_debug()
async def test_connect_to_secured(async_sandbox_factory):
sbx = await async_sandbox_factory(timeout=100, secure=True)
assert await sbx.is_running()
assert sbx._envd_version is not None
assert sbx._envd_access_token is not None
sbx_connection = await AsyncSandbox.connect(sbx.sandbox_id)
assert await sbx_connection.is_running()
assert sbx_connection._envd_version is not None
assert sbx_connection._envd_access_token is not None
@@ -0,0 +1,15 @@
import pytest
from e2b import AsyncSandbox
@pytest.mark.skip_debug()
async def test_snapshot(async_sandbox: AsyncSandbox):
assert await async_sandbox.is_running()
await async_sandbox.pause()
assert not await async_sandbox.is_running()
resumed_sandbox = await async_sandbox.connect()
assert await async_sandbox.is_running()
assert await resumed_sandbox.is_running()
assert resumed_sandbox.sandbox_id == async_sandbox.sandbox_id
@@ -0,0 +1,164 @@
import pytest
from e2b import AsyncSandbox
@pytest.mark.skip_debug()
async def test_create_snapshot(async_sandbox: AsyncSandbox):
snapshot = await async_sandbox.create_snapshot()
assert snapshot.snapshot_id
assert len(snapshot.snapshot_id) > 0
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_create_sandbox_from_snapshot(async_sandbox: AsyncSandbox):
test_content = "content from original sandbox"
await async_sandbox.files.write("/home/user/test.txt", test_content)
snapshot = await async_sandbox.create_snapshot()
try:
new_sandbox = await AsyncSandbox.create(snapshot.snapshot_id)
try:
content = await new_sandbox.files.read("/home/user/test.txt")
assert content == test_content
finally:
await new_sandbox.kill()
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_create_multiple_sandboxes_from_snapshot(async_sandbox: AsyncSandbox):
test_content = "shared snapshot content"
await async_sandbox.files.write("/home/user/shared.txt", test_content)
snapshot = await async_sandbox.create_snapshot()
try:
sandbox1 = await AsyncSandbox.create(snapshot.snapshot_id)
sandbox2 = await AsyncSandbox.create(snapshot.snapshot_id)
try:
content1 = await sandbox1.files.read("/home/user/shared.txt")
content2 = await sandbox2.files.read("/home/user/shared.txt")
assert content1 == test_content
assert content2 == test_content
await sandbox1.files.write("/home/user/shared.txt", "modified in sandbox1")
modified_content = await sandbox1.files.read("/home/user/shared.txt")
unchanged_content = await sandbox2.files.read("/home/user/shared.txt")
assert modified_content == "modified in sandbox1"
assert unchanged_content == test_content
finally:
await sandbox1.kill()
await sandbox2.kill()
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_list_snapshots(async_sandbox: AsyncSandbox):
snapshot = await async_sandbox.create_snapshot()
try:
paginator = AsyncSandbox.list_snapshots()
assert paginator.has_next
snapshots = await paginator.next_items()
assert isinstance(snapshots, list)
found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots)
assert found
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_list_snapshots_for_sandbox(async_sandbox: AsyncSandbox):
snapshot = await async_sandbox.create_snapshot()
try:
paginator = AsyncSandbox.list_snapshots(
sandbox_id=async_sandbox.sandbox_id,
)
snapshots = await paginator.next_items()
found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots)
assert found
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_create_named_snapshot(async_sandbox: AsyncSandbox, sandbox_test_id: str):
snapshot_name = f"snap-{sandbox_test_id}"
snapshot = await async_sandbox.create_snapshot(name=snapshot_name)
try:
assert snapshot.snapshot_id
assert isinstance(snapshot.names, list)
assert len(snapshot.names) > 0
assert any(snapshot_name in n for n in snapshot.names)
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_delete_snapshot(async_sandbox: AsyncSandbox):
snapshot = await async_sandbox.create_snapshot()
deleted = await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
assert deleted is True
deleted_again = await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
assert deleted_again is False
@pytest.mark.skip_debug()
async def test_snapshot_preserves_filesystem(async_sandbox: AsyncSandbox):
app_dir = "/home/user/app"
config_path = f"{app_dir}/config.json"
config_content = '{"env": "test"}'
data_path = f"{app_dir}/data.txt"
data_content = "important data"
await async_sandbox.files.make_dir(app_dir)
await async_sandbox.files.write(config_path, config_content)
await async_sandbox.files.write(data_path, data_content)
snapshot = await async_sandbox.create_snapshot()
try:
new_sandbox = await AsyncSandbox.create(snapshot.snapshot_id)
try:
dir_exists = await new_sandbox.files.exists(app_dir)
assert dir_exists
config = await new_sandbox.files.read(config_path)
data = await new_sandbox.files.read(data_path)
assert config == config_content
assert data == data_content
finally:
await new_sandbox.kill()
finally:
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@pytest.mark.skip_debug()
async def test_create_snapshot_class_method(async_sandbox: AsyncSandbox):
snapshot = await AsyncSandbox.create_snapshot(async_sandbox.sandbox_id)
assert snapshot.snapshot_id
assert len(snapshot.snapshot_id) > 0
await AsyncSandbox.delete_snapshot(snapshot.snapshot_id)
@@ -0,0 +1,31 @@
import pytest
from e2b import AsyncSandbox
@pytest.mark.skip_debug()
async def test_pause_filesystem_only(async_sandbox: AsyncSandbox):
# A marker on the persisted rootfs and the kernel boot id before pausing.
await async_sandbox.files.write("/home/user/fs-only-marker.txt", "persisted")
boot_before = (
await async_sandbox.files.read("/proc/sys/kernel/random/boot_id")
).strip()
# Filesystem-only pause: only the rootfs is persisted, no memory snapshot.
assert await async_sandbox.pause(keep_memory=False)
assert not await async_sandbox.is_running()
# Resuming a filesystem-only snapshot cold-boots (reboots) from the rootfs.
resumed = await async_sandbox.connect()
assert await resumed.is_running()
assert resumed.sandbox_id == async_sandbox.sandbox_id
# connect() returns the same handle, and its credentials stay valid across
# the resume (the backend re-binds the same envd access token on the cold
# boot). The rootfs survives the reboot...
marker = (await resumed.files.read("/home/user/fs-only-marker.txt")).strip()
assert marker == "persisted"
# ...while a fresh kernel boot id proves the guest cold-booted rather than
# being restored from a memory snapshot.
boot_after = (await resumed.files.read("/proc/sys/kernel/random/boot_id")).strip()
assert boot_after != boot_before
@@ -0,0 +1,30 @@
import pytest
from datetime import datetime
from time import sleep
from e2b import AsyncSandbox
@pytest.mark.skip_debug()
async def test_shorten_timeout(async_sandbox: AsyncSandbox):
await async_sandbox.set_timeout(5)
sleep(6)
is_running = await async_sandbox.is_running()
assert is_running is False
@pytest.mark.skip_debug()
async def test_shorten_then_lengthen_timeout(async_sandbox: AsyncSandbox):
await async_sandbox.set_timeout(5)
sleep(1)
await async_sandbox.set_timeout(10)
sleep(6)
await async_sandbox.is_running()
@pytest.mark.skip_debug()
async def test_get_timeout(async_sandbox: AsyncSandbox):
info = await async_sandbox.get_info()
assert isinstance(info.end_at, datetime)