chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
from e2b.sandbox.filesystem.filesystem import WriteEntry
|
||||
|
||||
|
||||
def test_write_and_read_with_gzip(sandbox, debug):
|
||||
filename = "test_gzip_write.txt"
|
||||
content = "This is a test file with gzip encoding."
|
||||
|
||||
info = sandbox.files.write(filename, content, gzip=True)
|
||||
assert info.path == f"/home/user/{filename}"
|
||||
|
||||
read_content = sandbox.files.read(filename, gzip=True)
|
||||
assert read_content == content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_gzip_read_plain(sandbox, debug):
|
||||
filename = "test_gzip_write_plain_read.txt"
|
||||
content = "Written with gzip, read without."
|
||||
|
||||
sandbox.files.write(filename, content, gzip=True)
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_files_with_gzip(sandbox, 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 = sandbox.files.write_files(files, gzip=True)
|
||||
assert len(infos) == len(files)
|
||||
|
||||
for i, file in enumerate(files):
|
||||
read_content = sandbox.files.read(file["path"])
|
||||
assert read_content == file["data"]
|
||||
|
||||
if debug:
|
||||
for file in files:
|
||||
sandbox.files.remove(file["path"])
|
||||
|
||||
|
||||
def test_read_bytes_with_gzip(sandbox, debug):
|
||||
filename = "test_gzip_bytes.txt"
|
||||
content = "Binary content with gzip."
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
|
||||
read_bytes = sandbox.files.read(filename, format="bytes", gzip=True)
|
||||
assert isinstance(read_bytes, bytearray)
|
||||
assert read_bytes.decode("utf-8") == content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
@@ -0,0 +1,8 @@
|
||||
from e2b import Sandbox
|
||||
|
||||
|
||||
def test_exists(sandbox: Sandbox):
|
||||
filename = "test_exists.txt"
|
||||
|
||||
sandbox.files.write(filename, "test")
|
||||
assert sandbox.files.exists(filename)
|
||||
@@ -0,0 +1,247 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from e2b import Sandbox, FileType
|
||||
|
||||
|
||||
def test_list_directory(sandbox: Sandbox):
|
||||
home_dir_name = "/home/user"
|
||||
parent_dir_name = f"test_directory_{uuid.uuid4()}"
|
||||
|
||||
sandbox.files.make_dir(parent_dir_name)
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir1")
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir2")
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_1")
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_2")
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_1")
|
||||
sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_2")
|
||||
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 = 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]
|
||||
|
||||
sandbox.files.remove(parent_dir_name)
|
||||
|
||||
|
||||
def test_list_directory_error_cases(sandbox: Sandbox):
|
||||
parent_dir_name = f"test_directory_{uuid.uuid4()}"
|
||||
sandbox.files.make_dir(parent_dir_name)
|
||||
|
||||
expected_error_message = "depth should be at least 1"
|
||||
try:
|
||||
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}"'
|
||||
)
|
||||
|
||||
sandbox.files.remove(parent_dir_name)
|
||||
|
||||
|
||||
def test_file_entry_details(sandbox: Sandbox):
|
||||
test_dir = "test-file-entry"
|
||||
file_path = f"{test_dir}/test.txt"
|
||||
content = "Hello, World!"
|
||||
|
||||
sandbox.files.make_dir(test_dir)
|
||||
sandbox.files.write(file_path, content)
|
||||
|
||||
files = 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
|
||||
|
||||
sandbox.files.remove(test_dir)
|
||||
|
||||
|
||||
def test_directory_entry_details(sandbox: Sandbox):
|
||||
test_dir = "test-entry-info"
|
||||
sub_dir = f"{test_dir}/subdir"
|
||||
|
||||
sandbox.files.make_dir(test_dir)
|
||||
sandbox.files.make_dir(sub_dir)
|
||||
|
||||
files = 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
|
||||
|
||||
sandbox.files.remove(test_dir)
|
||||
|
||||
|
||||
def test_mixed_entries(sandbox: Sandbox):
|
||||
test_dir = "test-mixed-entries"
|
||||
sub_dir = f"{test_dir}/subdir"
|
||||
file_path = f"{test_dir}/test.txt"
|
||||
content = "Hello, World!"
|
||||
|
||||
sandbox.files.make_dir(test_dir)
|
||||
sandbox.files.make_dir(sub_dir)
|
||||
sandbox.files.write(file_path, content)
|
||||
|
||||
files = 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
|
||||
|
||||
sandbox.files.remove(test_dir)
|
||||
@@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
from e2b.exceptions import FileNotFoundException
|
||||
from e2b import Sandbox, FileType
|
||||
|
||||
|
||||
def test_get_info_of_file(sandbox: Sandbox):
|
||||
filename = "test_file.txt"
|
||||
|
||||
sandbox.files.write(filename, "test")
|
||||
info = sandbox.files.get_info(filename)
|
||||
current_path = 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
|
||||
|
||||
|
||||
def test_get_info_of_nonexistent_file(sandbox: Sandbox):
|
||||
filename = "test_does_not_exist.txt"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.get_info(filename)
|
||||
|
||||
|
||||
def test_get_info_of_directory(sandbox: Sandbox):
|
||||
dirname = "test_dir"
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
info = sandbox.files.get_info(dirname)
|
||||
current_path = 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
|
||||
|
||||
|
||||
def test_get_info_of_nonexistent_directory(sandbox: Sandbox):
|
||||
dirname = "test_does_not_exist_dir"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.get_info(dirname)
|
||||
|
||||
|
||||
def test_file_symlink(sandbox: Sandbox):
|
||||
test_dir = "test-simlink-entry"
|
||||
file_name = "test.txt"
|
||||
content = "Hello, World!"
|
||||
|
||||
sandbox.files.make_dir(test_dir)
|
||||
sandbox.files.write(f"{test_dir}/{file_name}", content)
|
||||
|
||||
symlink_name = "symlink_to_test.txt"
|
||||
sandbox.commands.run(f"ln -s {file_name} {symlink_name}", cwd=test_dir)
|
||||
|
||||
file = sandbox.files.get_info(f"{test_dir}/{symlink_name}")
|
||||
|
||||
pwd = sandbox.commands.run("pwd")
|
||||
assert file.type == FileType.FILE
|
||||
assert file.symlink_target == f"{pwd.stdout.strip()}/{test_dir}/{file_name}"
|
||||
|
||||
sandbox.files.remove(test_dir)
|
||||
@@ -0,0 +1,29 @@
|
||||
import uuid
|
||||
|
||||
from e2b import Sandbox
|
||||
|
||||
|
||||
def test_make_directory(sandbox: Sandbox):
|
||||
dir_name = f"test_directory_{uuid.uuid4()}"
|
||||
|
||||
sandbox.files.make_dir(dir_name)
|
||||
exists = sandbox.files.exists(dir_name)
|
||||
assert exists
|
||||
|
||||
|
||||
async def test_make_directory_already_exists(sandbox: Sandbox):
|
||||
dir_name = f"test_directory_{uuid.uuid4()}"
|
||||
|
||||
created = sandbox.files.make_dir(dir_name)
|
||||
assert created
|
||||
|
||||
created = sandbox.files.make_dir(dir_name)
|
||||
assert not created
|
||||
|
||||
|
||||
def test_make_nested_directory(sandbox: Sandbox):
|
||||
nested_dir_name = f"test_directory_{uuid.uuid4()}/nested_directory"
|
||||
|
||||
sandbox.files.make_dir(nested_dir_name)
|
||||
exists = sandbox.files.exists(nested_dir_name)
|
||||
assert exists
|
||||
@@ -0,0 +1,163 @@
|
||||
import pytest
|
||||
|
||||
from e2b.exceptions import InvalidArgumentException
|
||||
|
||||
|
||||
def test_write_file_with_metadata(sandbox, debug):
|
||||
filename = "test_metadata.txt"
|
||||
content = "This is a test file with metadata."
|
||||
metadata = {"author": "mish", "purpose": "upload"}
|
||||
|
||||
info = sandbox.files.write(filename, content, metadata=metadata)
|
||||
assert info.metadata == metadata
|
||||
|
||||
# Metadata is persisted and surfaced on subsequent reads.
|
||||
stat = sandbox.files.get_info(filename)
|
||||
assert stat.metadata == metadata
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_file_with_metadata_octet_stream(sandbox, debug):
|
||||
filename = "test_metadata_octet.txt"
|
||||
content = "This is a test file with metadata."
|
||||
metadata = {"author": "mish", "purpose": "upload"}
|
||||
|
||||
info = sandbox.files.write(
|
||||
filename, content, metadata=metadata, use_octet_stream=True
|
||||
)
|
||||
assert info.metadata == metadata
|
||||
|
||||
stat = sandbox.files.get_info(filename)
|
||||
assert stat.metadata == metadata
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_file_without_metadata(sandbox, debug):
|
||||
filename = "test_no_metadata.txt"
|
||||
|
||||
info = sandbox.files.write(filename, "no metadata here")
|
||||
assert info.metadata is None
|
||||
|
||||
stat = sandbox.files.get_info(filename)
|
||||
assert stat.metadata is None
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_files_applies_metadata_to_every_file(sandbox, debug):
|
||||
from e2b.sandbox.filesystem.filesystem import WriteEntry
|
||||
|
||||
# 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 = sandbox.files.write_files(files, metadata=metadata)
|
||||
assert len(infos) == len(files)
|
||||
|
||||
for info in infos:
|
||||
assert info.metadata == metadata
|
||||
stat = sandbox.files.get_info(info.path)
|
||||
assert stat.metadata == metadata
|
||||
|
||||
if debug:
|
||||
for file in files:
|
||||
sandbox.files.remove(file["path"])
|
||||
|
||||
|
||||
def test_metadata_surfaced_when_listing(sandbox, debug):
|
||||
dirname = "metadata_list_dir"
|
||||
filename = "listed.txt"
|
||||
metadata = {"tag": "listed"}
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", "content", metadata=metadata)
|
||||
|
||||
entries = 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:
|
||||
sandbox.files.remove(dirname)
|
||||
|
||||
|
||||
def test_metadata_surfaced_after_rename(sandbox, debug):
|
||||
old_path = "metadata_rename_old.txt"
|
||||
new_path = "metadata_rename_new.txt"
|
||||
metadata = {"stage": "renamed"}
|
||||
|
||||
sandbox.files.write(old_path, "content", metadata=metadata)
|
||||
info = sandbox.files.rename(old_path, new_path)
|
||||
assert info.metadata == metadata
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(new_path)
|
||||
|
||||
|
||||
def test_overwriting_clears_stale_metadata(sandbox, debug):
|
||||
filename = "metadata_overwrite.txt"
|
||||
|
||||
sandbox.files.write(filename, "first", metadata={"author": "mish"})
|
||||
|
||||
# Overwriting without metadata removes the previously stored metadata.
|
||||
info = sandbox.files.write(filename, "second")
|
||||
assert info.metadata is None
|
||||
|
||||
stat = sandbox.files.get_info(filename)
|
||||
assert stat.metadata is None
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_metadata_set_via_xattrs_surfaced_in_get_info(sandbox, debug):
|
||||
filename = "metadata_xattr.txt"
|
||||
sandbox.files.write(filename, "content")
|
||||
|
||||
file_path = sandbox.commands.run(f"realpath {filename}").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.
|
||||
sandbox.commands.run(
|
||||
f"python3 -c \"import os; os.setxattr('{file_path}', 'user.e2b.author', b'mish')\""
|
||||
)
|
||||
|
||||
info = sandbox.files.get_info(filename)
|
||||
assert info.metadata == {"author": "mish"}
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_rejects_invalid_metadata(sandbox):
|
||||
filename = "invalid_metadata.txt"
|
||||
|
||||
# Key with a space is not a valid HTTP header token.
|
||||
with pytest.raises(InvalidArgumentException):
|
||||
sandbox.files.write(filename, "x", metadata={"bad key": "value"})
|
||||
|
||||
# Empty key.
|
||||
with pytest.raises(InvalidArgumentException):
|
||||
sandbox.files.write(filename, "x", metadata={"": "value"})
|
||||
|
||||
# Value with a non-printable / non-ASCII character.
|
||||
with pytest.raises(InvalidArgumentException):
|
||||
sandbox.files.write(filename, "x", metadata={"good": "bad\nvalue"})
|
||||
|
||||
# Trailing newline (Python's `$` would accept it; `\Z` must not).
|
||||
with pytest.raises(InvalidArgumentException):
|
||||
sandbox.files.write(filename, "x", metadata={"good": "value\n"})
|
||||
with pytest.raises(InvalidArgumentException):
|
||||
sandbox.files.write(filename, "x", metadata={"key\n": "value"})
|
||||
|
||||
# The file must not have been created by a rejected write.
|
||||
assert not sandbox.files.exists(filename)
|
||||
@@ -0,0 +1,83 @@
|
||||
import pytest
|
||||
from e2b import FileNotFoundException, NotFoundException
|
||||
|
||||
|
||||
def test_read_file(sandbox):
|
||||
filename = "test_read.txt"
|
||||
content = "Hello, world!"
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_read_non_existing_file(sandbox):
|
||||
filename = "non_existing_file.txt"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.read(filename)
|
||||
|
||||
|
||||
def test_read_non_existing_file_catches_with_deprecated_not_found_exception(sandbox):
|
||||
filename = "non_existing_file.txt"
|
||||
|
||||
with pytest.raises(NotFoundException):
|
||||
sandbox.files.read(filename)
|
||||
|
||||
|
||||
def test_read_empty_file(sandbox):
|
||||
filename = "empty_file.txt"
|
||||
content = ""
|
||||
|
||||
sandbox.commands.run(f"touch {filename}")
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_read_file_as_stream(sandbox):
|
||||
filename = "test_read_stream.txt"
|
||||
content = "Streamed read content. " * 10_000
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
stream = sandbox.files.read(filename, format="stream")
|
||||
read_content = b"".join(stream).decode("utf-8")
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_read_file_as_stream_with_gzip(sandbox):
|
||||
filename = "test_read_stream_gzip.txt"
|
||||
content = "Streamed gzipped read content. " * 10_000
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
stream = sandbox.files.read(filename, format="stream", gzip=True)
|
||||
read_content = b"".join(stream).decode("utf-8")
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_read_non_existing_file_as_stream(sandbox):
|
||||
filename = "non_existing_file.txt"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.read(filename, format="stream")
|
||||
|
||||
|
||||
def test_read_file_as_stream_context_manager(sandbox):
|
||||
filename = "test_read_stream_ctx.txt"
|
||||
content = "Streamed read content. " * 10_000
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
with sandbox.files.read(filename, format="stream") as stream:
|
||||
read_content = b"".join(stream).decode("utf-8")
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_read_file_as_stream_partial_then_close(sandbox):
|
||||
filename = "test_read_stream_partial.txt"
|
||||
content = "Streamed read content. " * 10_000
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
# Reading only the first chunk and closing must not raise or leak.
|
||||
stream = sandbox.files.read(filename, format="stream")
|
||||
first = next(iter(stream))
|
||||
assert len(first) > 0
|
||||
stream.close()
|
||||
@@ -0,0 +1,18 @@
|
||||
from e2b import Sandbox
|
||||
|
||||
|
||||
def test_remove_file(sandbox: Sandbox):
|
||||
filename = "test_remove.txt"
|
||||
content = "This file will be removed."
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
exists = sandbox.files.exists(filename)
|
||||
assert not exists
|
||||
|
||||
|
||||
def test_remove_non_existing_file(sandbox):
|
||||
filename = "non_existing_file.txt"
|
||||
sandbox.files.remove(filename)
|
||||
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
from e2b import FileNotFoundException, Sandbox
|
||||
|
||||
|
||||
def test_rename_file(sandbox: Sandbox):
|
||||
old_filename = "test_rename_old.txt"
|
||||
new_filename = "test_rename_new.txt"
|
||||
content = "This file will be renamed."
|
||||
|
||||
sandbox.files.write(old_filename, content)
|
||||
|
||||
info = sandbox.files.rename(old_filename, new_filename)
|
||||
assert info.path == f"/home/user/{new_filename}"
|
||||
|
||||
exists_old = sandbox.files.exists(old_filename)
|
||||
exists_new = sandbox.files.exists(new_filename)
|
||||
assert not exists_old
|
||||
assert exists_new
|
||||
read_content = sandbox.files.read(new_filename)
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_rename_non_existing_file(sandbox):
|
||||
old_filename = "non_existing_file.txt"
|
||||
new_filename = "new_non_existing_file.txt"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.rename(old_filename, new_filename)
|
||||
@@ -0,0 +1,59 @@
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.skip_debug()
|
||||
def test_download_url_with_signing(sandbox_factory):
|
||||
sbx = sandbox_factory(timeout=100, secure=True)
|
||||
file_path = "test_download_url_with_signing.txt"
|
||||
file_content = "This file will be watched."
|
||||
|
||||
sbx.files.write(file_path, file_content)
|
||||
signed_url = sbx.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()
|
||||
def test_download_url_with_signing_and_expiration(sandbox_factory):
|
||||
sbx = sandbox_factory(timeout=100, secure=True)
|
||||
file_path = "test_download_url_with_signing.txt"
|
||||
file_content = "This file will be watched."
|
||||
|
||||
sbx.files.write(file_path, file_content)
|
||||
signed_url = sbx.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()
|
||||
def test_download_url_with_expired_signing(sandbox_factory):
|
||||
sbx = sandbox_factory(timeout=100, secure=True)
|
||||
file_path = "test_download_url_with_signing.txt"
|
||||
file_content = "This file will be watched."
|
||||
|
||||
sbx.files.write(file_path, file_content)
|
||||
|
||||
signed_url = sbx.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,192 @@
|
||||
import pytest
|
||||
|
||||
from e2b import (
|
||||
FileNotFoundException,
|
||||
FileType,
|
||||
FilesystemEventType,
|
||||
Sandbox,
|
||||
SandboxException,
|
||||
)
|
||||
|
||||
|
||||
def test_watch_directory_changes_with_entry_info(sandbox: Sandbox):
|
||||
dirname = "test_watch_dir_entry"
|
||||
filename = "test_watch.txt"
|
||||
content = "This file will be watched."
|
||||
new_content = "This file has been modified."
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", content)
|
||||
|
||||
handle = sandbox.files.watch_dir(dirname, include_entry=True)
|
||||
sandbox.files.write(f"{dirname}/{filename}", new_content)
|
||||
|
||||
events = handle.get_new_events()
|
||||
write_event = None
|
||||
for event in events:
|
||||
if event.type == FilesystemEventType.WRITE and event.name == filename:
|
||||
write_event = event
|
||||
break
|
||||
|
||||
assert write_event is not None, (
|
||||
f"Expected WRITE event for {filename}, but got events: {events}"
|
||||
)
|
||||
# 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
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_directory_changes_with_network_mounts_allowed(sandbox: Sandbox):
|
||||
dirname = "test_watch_dir_network_mounts"
|
||||
filename = "test_watch.txt"
|
||||
content = "This file will be watched."
|
||||
new_content = "This file has been modified."
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", content)
|
||||
|
||||
# The flag only lifts the network-mount restriction — watching a regular
|
||||
# directory must work the same with it enabled.
|
||||
handle = sandbox.files.watch_dir(dirname, allow_network_mounts=True)
|
||||
sandbox.files.write(f"{dirname}/{filename}", new_content)
|
||||
|
||||
events = handle.get_new_events()
|
||||
write_event = None
|
||||
for event in events:
|
||||
if event.type == FilesystemEventType.WRITE and event.name == filename:
|
||||
write_event = event
|
||||
break
|
||||
|
||||
assert write_event is not None, (
|
||||
f"Expected WRITE event for {filename}, but got events: {events}"
|
||||
)
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_directory_changes(sandbox: Sandbox):
|
||||
dirname = "test_watch_dir"
|
||||
filename = "test_watch.txt"
|
||||
content = "This file will be watched."
|
||||
new_content = "This file has been modified."
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", content)
|
||||
|
||||
handle = sandbox.files.watch_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", new_content)
|
||||
|
||||
events = handle.get_new_events()
|
||||
write_event = None
|
||||
for event in events:
|
||||
if event.type == FilesystemEventType.WRITE and event.name == filename:
|
||||
write_event = event
|
||||
break
|
||||
|
||||
assert write_event is not None, (
|
||||
f"Expected WRITE event for {filename}, but got events: {events}"
|
||||
)
|
||||
assert write_event.name == filename
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_iterated(sandbox: Sandbox):
|
||||
dirname = "test_watch_dir_iterated"
|
||||
filename = "test_watch_iterated.txt"
|
||||
content = "This file will be watched."
|
||||
new_content = "This file has been modified."
|
||||
|
||||
sandbox.files.make_dir(dirname)
|
||||
handle = sandbox.files.watch_dir(dirname)
|
||||
sandbox.files.write(f"{dirname}/{filename}", content)
|
||||
|
||||
events = handle.get_new_events()
|
||||
assert len(events) == 3
|
||||
|
||||
sandbox.files.write(f"{dirname}/{filename}", new_content)
|
||||
events = handle.get_new_events()
|
||||
for event in events:
|
||||
if event.type == FilesystemEventType.WRITE and event.name == filename:
|
||||
break
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_recursive_directory_changes(sandbox: Sandbox):
|
||||
dirname = "test_recursive_watch_dir"
|
||||
nested_dirname = "test_nested_watch_dir"
|
||||
filename = "test_watch.txt"
|
||||
content = "This file will be watched."
|
||||
|
||||
sandbox.files.remove(dirname)
|
||||
sandbox.files.make_dir(f"{dirname}/{nested_dirname}")
|
||||
|
||||
handle = sandbox.files.watch_dir(dirname, recursive=True)
|
||||
sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content)
|
||||
|
||||
events = handle.get_new_events()
|
||||
assert len(events) == 3
|
||||
expected_filename = f"{nested_dirname}/{filename}"
|
||||
assert events[0].type == FilesystemEventType.CREATE
|
||||
assert events[0].name == expected_filename
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_recursive_directory_after_nested_folder_addition(sandbox: Sandbox):
|
||||
dirname = "test_recursive_watch_dir_add"
|
||||
nested_dirname = "test_nested_watch_dir"
|
||||
filename = "test_watch.txt"
|
||||
content = "This file will be watched."
|
||||
|
||||
sandbox.files.remove(dirname)
|
||||
sandbox.files.make_dir(dirname)
|
||||
|
||||
handle = sandbox.files.watch_dir(dirname, recursive=True)
|
||||
|
||||
sandbox.files.make_dir(f"{dirname}/{nested_dirname}")
|
||||
sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content)
|
||||
|
||||
expected_filename = f"{nested_dirname}/{filename}"
|
||||
|
||||
events = handle.get_new_events()
|
||||
file_changed = False
|
||||
folder_created = False
|
||||
for event in events:
|
||||
if event.type == FilesystemEventType.WRITE and event.name == expected_filename:
|
||||
file_changed = True
|
||||
continue
|
||||
if event.type == FilesystemEventType.CREATE and event.name == nested_dirname:
|
||||
folder_created = True
|
||||
|
||||
assert folder_created
|
||||
assert file_changed
|
||||
|
||||
handle.stop()
|
||||
|
||||
|
||||
def test_watch_non_existing_directory(sandbox: Sandbox):
|
||||
dirname = "non_existing_watch_dir"
|
||||
|
||||
with pytest.raises(FileNotFoundException):
|
||||
sandbox.files.watch_dir(dirname)
|
||||
|
||||
|
||||
def test_watch_file(sandbox: Sandbox):
|
||||
filename = "test_watch.txt"
|
||||
sandbox.files.write(filename, "This file will be watched.")
|
||||
|
||||
with pytest.raises(SandboxException):
|
||||
sandbox.files.watch_dir(filename)
|
||||
|
||||
|
||||
def test_watch_file_with_secured_envd(sandbox_factory):
|
||||
sbx = sandbox_factory(timeout=30, secure=True)
|
||||
|
||||
sbx.files.watch_dir("/home/user/")
|
||||
sbx.files.write("test_watch.txt", "This file will be watched.")
|
||||
@@ -0,0 +1,218 @@
|
||||
import io
|
||||
import uuid
|
||||
|
||||
from e2b.sandbox.filesystem.filesystem import FileType, WriteInfo, WriteEntry
|
||||
|
||||
|
||||
def test_write_text_file(sandbox, debug):
|
||||
filename = "test_write.txt"
|
||||
content = "This is a test file."
|
||||
|
||||
info = sandbox.files.write(filename, content)
|
||||
assert info.path == f"/home/user/{filename}"
|
||||
assert info.type == FileType.FILE
|
||||
|
||||
exists = sandbox.files.exists(filename)
|
||||
assert exists
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_binary_file(sandbox, 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 = sandbox.files.write(filename, content)
|
||||
assert info.path == f"/home/user/{filename}"
|
||||
|
||||
exists = sandbox.files.exists(filename)
|
||||
assert exists
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == text
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_multiple_files(sandbox, debug):
|
||||
num_test_files = 10
|
||||
|
||||
# Attempt to write with empty files array
|
||||
empty_info = sandbox.files.write_files([])
|
||||
assert isinstance(empty_info, list)
|
||||
assert len(empty_info) == 0
|
||||
|
||||
# Attempt to write with no files
|
||||
assert sandbox.files.write_files([]) == []
|
||||
|
||||
# Attempt to write with one file in array
|
||||
one_file_path = "one_test_file.txt"
|
||||
info = 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 = sandbox.files.exists(info.path)
|
||||
assert exists
|
||||
|
||||
read_content = 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 = 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 = sandbox.files.exists(path)
|
||||
assert exists
|
||||
|
||||
read_content = sandbox.files.read(info.path)
|
||||
assert read_content == files[i]["data"]
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(one_file_path)
|
||||
for i in range(num_test_files):
|
||||
sandbox.files.remove(f"test_write_{i}.txt")
|
||||
|
||||
|
||||
def test_overwrite_file(sandbox, debug):
|
||||
filename = "test_overwrite.txt"
|
||||
initial_content = "Initial content."
|
||||
new_content = "New content."
|
||||
|
||||
sandbox.files.write(filename, initial_content)
|
||||
sandbox.files.write(filename, new_content)
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == new_content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_to_non_existing_directory(sandbox, debug):
|
||||
filename = "non_existing_dir/test_write.txt"
|
||||
content = "This should succeed too."
|
||||
|
||||
sandbox.files.write(filename, content)
|
||||
exists = sandbox.files.exists(filename)
|
||||
assert exists
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_with_secured_envd(sandbox_factory):
|
||||
filename = f"non_existing_dir_{uuid.uuid4()}/test_write.txt"
|
||||
content = "This should succeed too."
|
||||
|
||||
sbx = sandbox_factory(timeout=30, secure=True)
|
||||
assert sbx.is_running()
|
||||
assert sbx._envd_version is not None
|
||||
assert sbx._envd_access_token is not None
|
||||
|
||||
sbx.files.write(filename, content)
|
||||
|
||||
exists = sbx.files.exists(filename)
|
||||
assert exists
|
||||
|
||||
read_content = sbx.files.read(filename)
|
||||
assert read_content == content
|
||||
|
||||
|
||||
def test_write_files_with_different_data_types(sandbox, 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 = sandbox.files.write_files(files)
|
||||
|
||||
assert len(infos) == 4
|
||||
|
||||
text_content = sandbox.files.read("writefiles_text.txt")
|
||||
assert text_content == text_data
|
||||
|
||||
bytes_content = sandbox.files.read("writefiles_bytes.bin")
|
||||
assert bytes_content == "Bytes data"
|
||||
|
||||
bytes_io_content = sandbox.files.read("writefiles_bytesio.bin")
|
||||
assert bytes_io_content == "BytesIO data"
|
||||
|
||||
string_io_content = sandbox.files.read("writefiles_stringio.txt")
|
||||
assert string_io_content == "StringIO data"
|
||||
|
||||
if debug:
|
||||
for file in files:
|
||||
sandbox.files.remove(file["path"])
|
||||
|
||||
|
||||
def test_write_io_with_octet_stream(sandbox, debug):
|
||||
filename = "test_write_octet_io.bin"
|
||||
text = "Streamed octet-stream upload. " * 10_000
|
||||
content = io.BytesIO(text.encode("utf-8"))
|
||||
|
||||
info = sandbox.files.write(filename, content, use_octet_stream=True)
|
||||
assert info.path == f"/home/user/{filename}"
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == text
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_text_io_with_octet_stream(sandbox, debug):
|
||||
filename = "test_write_octet_text_io.txt"
|
||||
text = "Streamed text octet-stream upload."
|
||||
|
||||
sandbox.files.write(filename, io.StringIO(text), use_octet_stream=True)
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == text
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
|
||||
|
||||
def test_write_io_with_octet_stream_and_gzip(sandbox, debug):
|
||||
filename = "test_write_octet_io_gzip.bin"
|
||||
text = "Streamed gzipped octet-stream upload. " * 10_000
|
||||
content = io.BytesIO(text.encode("utf-8"))
|
||||
|
||||
sandbox.files.write(filename, content, use_octet_stream=True, gzip=True)
|
||||
|
||||
read_content = sandbox.files.read(filename)
|
||||
assert read_content == text
|
||||
|
||||
if debug:
|
||||
sandbox.files.remove(filename)
|
||||
Reference in New Issue
Block a user