chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:44 +08:00
commit bcbd1bdb22
5748 changed files with 562488 additions and 0 deletions
+323
View File
@@ -0,0 +1,323 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import asyncio
from unittest.mock import AsyncMock, patch
import pytest
from mirage.cache.index import IndexEntry
from mirage.fuse.fs import MirageFS
from mirage.ops import Ops
from mirage.ops.config import OpsMount
from mirage.resource.discord import DiscordConfig, DiscordResource
from mirage.types import FileType, MountMode
GUILD = "TestGuild"
CHANNEL = "general"
DATE = "2024-01-15"
FILE = "chat.jsonl"
GUILD_PATH = f"{GUILD}"
CHANNEL_PATH = f"{GUILD}/channels/{CHANNEL}"
DATE_DIR_PATH = f"{GUILD}/channels/{CHANNEL}/{DATE}"
FILE_PATH = f"{GUILD}/channels/{CHANNEL}/{DATE}/{FILE}"
MEMBER_PATH = f"{GUILD}/members/alice.json"
PREFIX = "/discord"
FAKE_JSONL = (b'{"id":"1","content":"hello","author":{"username":"alice"}}\n'
b'{"id":"2","content":"world","author":{"username":"bob"}}\n')
FAKE_MEMBER = b'{"user":{"id":"U1","username":"alice"},"roles":[]}'
def _run(coro):
return asyncio.run(coro)
def _make_resource() -> DiscordResource:
config = DiscordConfig(token="test-token")
resource = DiscordResource(config=config)
index = resource.index
_run(
index.put(
f"{PREFIX}/{GUILD}",
IndexEntry(id="G1",
name=GUILD,
resource_type="discord/guild",
vfs_name=GUILD)))
_run(
index.put(
f"{PREFIX}/{CHANNEL_PATH}",
IndexEntry(id="C1",
name=CHANNEL,
resource_type="discord/channel",
vfs_name=CHANNEL)))
_run(
index.put(
f"{PREFIX}/{DATE_DIR_PATH}",
IndexEntry(id="C1:2024-01-15",
name="2024-01-15",
resource_type="discord/history",
vfs_name=DATE)))
_run(
index.put(
f"{PREFIX}/{FILE_PATH}",
IndexEntry(id="C1:2024-01-15:chat",
name="chat.jsonl",
resource_type="discord/chat_jsonl",
vfs_name=FILE)))
_run(
index.put(
f"{PREFIX}/{MEMBER_PATH}",
IndexEntry(id="U1",
name="alice",
resource_type="discord/member",
vfs_name="alice.json")))
_run(
index.put(
f"{PREFIX}/{GUILD}/members",
IndexEntry(id="G1:members",
name="members",
resource_type="discord/virtual_dir")))
_run(
index.set_dir(f"{PREFIX}/{GUILD}/channels", [
(CHANNEL,
IndexEntry(id="C1",
name=CHANNEL,
resource_type="discord/channel",
vfs_name=CHANNEL)),
]))
_run(
index.set_dir(f"{PREFIX}/{CHANNEL_PATH}", [
(DATE,
IndexEntry(id="C1:2024-01-15",
name="2024-01-15",
resource_type="discord/history",
vfs_name=DATE)),
]))
_run(
index.set_dir(f"{PREFIX}/{DATE_DIR_PATH}", [
(FILE,
IndexEntry(id="C1:2024-01-15:chat",
name="chat.jsonl",
resource_type="discord/chat_jsonl",
vfs_name=FILE)),
]))
_run(
index.set_dir(f"{PREFIX}/{GUILD}/members", [
("alice.json",
IndexEntry(id="U1",
name="alice",
resource_type="discord/member",
vfs_name="alice.json")),
]))
return resource
def _make_ops(resource: DiscordResource) -> Ops:
mount = OpsMount(
prefix=f"{PREFIX}/",
resource_type=resource.name,
accessor=resource.accessor,
index=resource.index,
mode=MountMode.READ,
ops=resource.ops_list(),
)
return Ops([mount])
@pytest.fixture
def resource():
return _make_resource()
@pytest.fixture
def ops(resource):
return _make_ops(resource)
@pytest.fixture
def fs(ops):
return MirageFS(ops)
# ── ops.readdir ──────────────────────────────────
def test_ops_readdir_guild(ops):
entries = _run(ops.readdir(f"{PREFIX}/{GUILD}/"))
names = [e.rsplit("/", 1)[-1] for e in entries]
assert "channels" in names
assert "members" in names
def test_ops_readdir_channels(ops):
entries = _run(ops.readdir(f"{PREFIX}/{GUILD}/channels/"))
names = [e.rsplit("/", 1)[-1] for e in entries]
assert CHANNEL in names
def test_ops_readdir_dates(ops):
entries = _run(ops.readdir(f"{PREFIX}/{CHANNEL_PATH}/"))
names = [e.rsplit("/", 1)[-1] for e in entries]
assert DATE in names
def test_ops_readdir_members(ops):
entries = _run(ops.readdir(f"{PREFIX}/{GUILD}/members/"))
names = [e.rsplit("/", 1)[-1] for e in entries]
assert "alice.json" in names
# ── ops.stat ─────────────────────────────────────
def test_ops_stat_guild(ops):
s = _run(ops.stat(f"{PREFIX}/{GUILD}"))
assert s.type == FileType.DIRECTORY
def test_ops_stat_channel(ops):
s = _run(ops.stat(f"{PREFIX}/{CHANNEL_PATH}"))
assert s.type == FileType.DIRECTORY
def test_ops_stat_file(ops):
s = _run(ops.stat(f"{PREFIX}/{FILE_PATH}"))
assert s.type == FileType.TEXT
assert s.name == FILE
# ── ops.read ─────────────────────────────────────
def test_ops_read_jsonl(ops):
with patch("mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=FAKE_JSONL):
data = _run(ops.read(f"{PREFIX}/{FILE_PATH}"))
assert data == FAKE_JSONL
assert b"hello" in data
def test_ops_read_member(ops):
with patch("mirage.core.discord.read.list_members",
new_callable=AsyncMock,
return_value=[{
"user": {
"id": "U1",
"username": "alice"
}
}]):
data = _run(ops.read(f"{PREFIX}/{MEMBER_PATH}"))
assert b"alice" in data
# ── ops.read prefix in index keys ────────────────
def test_ops_read_uses_prefix_for_index(ops):
with patch("mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=b"data") as mock_get:
data = _run(ops.read(f"{PREFIX}/{FILE_PATH}"))
assert data == b"data"
mock_get.assert_called_once()
# ── FUSE getattr ─────────────────────────────────
def test_fuse_getattr_root(fs):
attr = fs.getattr("/")
assert attr["st_mode"] & 0o170000 == 0o040000
def test_fuse_getattr_mount_prefix(fs):
attr = fs.getattr("/discord")
assert attr["st_mode"] & 0o170000 == 0o040000
def test_fuse_getattr_guild(fs):
attr = fs.getattr(f"{PREFIX}/{GUILD}")
assert attr["st_mode"] & 0o170000 == 0o040000
def test_fuse_getattr_file(fs):
attr = fs.getattr(f"{PREFIX}/{FILE_PATH}")
assert attr["st_mode"] & 0o170000 == 0o100000
def test_fuse_getattr_unknown_size(fs):
# Unknown size before open() — direct_io ensures read() still works
attr = fs.getattr(f"{PREFIX}/{FILE_PATH}")
assert attr["st_size"] == 0
# ── FUSE readdir ─────────────────────────────────
def test_fuse_readdir_root(fs):
entries = fs.readdir("/", None)
assert "discord" in entries
def test_fuse_readdir_guild(fs):
entries = fs.readdir(f"{PREFIX}/{GUILD}", None)
assert "channels" in entries
assert "members" in entries
def test_fuse_readdir_channels(fs):
entries = fs.readdir(f"{PREFIX}/{GUILD}/channels", None)
assert CHANNEL in entries
def test_fuse_readdir_dates(fs):
entries = fs.readdir(f"{PREFIX}/{CHANNEL_PATH}", None)
assert DATE in entries
# ── FUSE open + read ─────────────────────────────
def test_fuse_read_file(fs):
with patch("mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=FAKE_JSONL):
fh = fs.open(f"{PREFIX}/{FILE_PATH}", 0)
data = fs.read(f"{PREFIX}/{FILE_PATH}", 4096, 0, fh)
fs.release(f"{PREFIX}/{FILE_PATH}", fh)
assert data == FAKE_JSONL
def test_fuse_read_offset(fs):
with patch("mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=FAKE_JSONL):
fh = fs.open(f"{PREFIX}/{FILE_PATH}", 0)
data = fs.read(f"{PREFIX}/{FILE_PATH}", 10, 0, fh)
fs.release(f"{PREFIX}/{FILE_PATH}", fh)
assert data == FAKE_JSONL[:10]
def test_fuse_read_beyond_eof(fs):
with patch("mirage.core.discord.read.get_history_jsonl",
new_callable=AsyncMock,
return_value=FAKE_JSONL):
fh = fs.open(f"{PREFIX}/{FILE_PATH}", 0)
data = fs.read(f"{PREFIX}/{FILE_PATH}", 4096, len(FAKE_JSONL), fh)
fs.release(f"{PREFIX}/{FILE_PATH}", fh)
assert data == b""
+564
View File
@@ -0,0 +1,564 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import errno
import os
import stat
import subprocess
import sys
import tempfile
import pytest
import pytest_asyncio
from mirage.fuse.fs import MirageFS
from mirage.resource.ram import RAMResource
from mirage.types import MountMode
from mirage.workspace import Workspace
_fuse_available = sys.platform in ("linux", "darwin")
@pytest_asyncio.fixture
async def seed_ws():
ws = Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
await ws.execute("tee /a.txt", stdin=b"hello world")
await ws.execute("mkdir /sub")
await ws.execute("tee /sub/b.txt", stdin=b"nested")
return ws
@pytest.fixture
def rw_ws():
return Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
@pytest.mark.asyncio
async def test_getattr_root(seed_ws):
fs = MirageFS(seed_ws.ops)
attrs = fs.getattr("/")
assert attrs["st_mode"] & stat.S_IFDIR
@pytest.mark.asyncio
async def test_getattr_file(seed_ws):
fs = MirageFS(seed_ws.ops)
attrs = fs.getattr("/a.txt")
assert attrs["st_mode"] & stat.S_IFREG
assert attrs["st_size"] == len(b"hello world")
@pytest.mark.asyncio
async def test_getattr_dir(seed_ws):
fs = MirageFS(seed_ws.ops)
attrs = fs.getattr("/sub")
assert attrs["st_mode"] & stat.S_IFDIR
@pytest.mark.asyncio
async def test_getattr_missing(seed_ws):
fs = MirageFS(seed_ws.ops)
with pytest.raises(OSError) as exc:
fs.getattr("/no_such_file.txt")
assert exc.value.errno == errno.ENOENT
@pytest.mark.asyncio
async def test_getattr_empty_readdir_not_ghost_dir():
ws = Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
await ws.execute("mkdir /emptydir")
fs = MirageFS(ws.ops)
with pytest.raises(OSError) as exc:
fs.getattr("/typo_command")
assert exc.value.errno == errno.ENOENT
@pytest.mark.asyncio
async def test_readdir_root(seed_ws):
fs = MirageFS(seed_ws.ops)
entries = fs.readdir("/", None)
assert "." in entries
assert ".." in entries
assert "a.txt" in entries
assert "sub" in entries
@pytest.mark.asyncio
async def test_readdir_subdir(seed_ws):
fs = MirageFS(seed_ws.ops)
entries = fs.readdir("/sub", None)
assert "b.txt" in entries
@pytest.mark.asyncio
async def test_readdir_missing(seed_ws):
fs = MirageFS(seed_ws.ops)
with pytest.raises(OSError) as exc:
fs.readdir("/nope", None)
assert exc.value.errno == errno.ENOENT
@pytest.mark.asyncio
async def test_read_full(seed_ws):
fs = MirageFS(seed_ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
data = fs.read("/a.txt", 1024, 0, fh)
assert data == b"hello world"
@pytest.mark.asyncio
async def test_read_offset(seed_ws):
fs = MirageFS(seed_ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
data = fs.read("/a.txt", 5, 6, fh)
assert data == b"world"
@pytest.mark.asyncio
async def test_open_missing(seed_ws):
fs = MirageFS(seed_ws.ops)
with pytest.raises(OSError) as exc:
fs.open("/missing.txt", os.O_RDONLY)
assert exc.value.errno == errno.ENOENT
@pytest.mark.asyncio
async def test_create_and_write(rw_ws):
fs = MirageFS(rw_ws.ops)
fh = fs.create("/new.txt", 0o644)
fs.write("/new.txt", b"data", 0, fh)
fs.flush("/new.txt", fh)
result = await rw_ws.execute("cat /new.txt")
assert result.stdout == b"data"
@pytest.mark.asyncio
async def test_mkdir(rw_ws):
fs = MirageFS(rw_ws.ops)
fs.mkdir("/newdir", 0o755)
entries = fs.readdir("/newdir", None)
assert "." in entries
@pytest.mark.asyncio
async def test_unlink(rw_ws):
await rw_ws.execute("tee /todel.txt", stdin=b"bye")
fs = MirageFS(rw_ws.ops)
fs.unlink("/todel.txt")
with pytest.raises(OSError) as exc:
fs.getattr("/todel.txt")
assert exc.value.errno == errno.ENOENT
@pytest.mark.asyncio
async def test_rename(rw_ws):
await rw_ws.execute("tee /old.txt", stdin=b"content")
fs = MirageFS(rw_ws.ops)
fs.rename("/old.txt", "/new.txt")
result = await rw_ws.execute("cat /new.txt")
assert result.stdout == b"content"
@pytest.mark.asyncio
async def test_rmdir_empty(rw_ws):
await rw_ws.execute("mkdir /emptydir")
fs = MirageFS(rw_ws.ops)
fs.rmdir("/emptydir")
@pytest.mark.asyncio
async def test_rmdir_nonempty(rw_ws):
await rw_ws.execute("mkdir /nonempty")
await rw_ws.execute("tee /nonempty/file.txt", stdin=b"x")
fs = MirageFS(rw_ws.ops)
with pytest.raises(OSError) as exc:
fs.rmdir("/nonempty")
assert exc.value.errno == errno.ENOTEMPTY
@pytest.mark.asyncio
async def test_truncate(rw_ws):
await rw_ws.execute("tee /f.txt", stdin=b"hello world")
fs = MirageFS(rw_ws.ops)
fs.truncate("/f.txt", 5)
result = await rw_ws.execute("cat /f.txt")
assert result.stdout == b"hello"
@pytest.mark.asyncio
async def test_truncate_extend(rw_ws):
await rw_ws.execute("tee /f.txt", stdin=b"hi")
fs = MirageFS(rw_ws.ops)
fs.truncate("/f.txt", 5)
result = await rw_ws.execute("cat /f.txt")
assert result.stdout == b"hi\x00\x00\x00"
@pytest.mark.asyncio
async def test_write_at_offset(rw_ws):
await rw_ws.execute("tee /f.txt", stdin=b"hello world")
fs = MirageFS(rw_ws.ops)
fh = fs.open("/f.txt", os.O_RDWR)
fs.write("/f.txt", b"WORLD", 6, fh)
fs.flush("/f.txt", fh)
result = await rw_ws.execute("cat /f.txt")
assert result.stdout == b"hello WORLD"
@pytest.mark.asyncio
async def test_statfs(seed_ws):
fs = MirageFS(seed_ws.ops)
result = fs.statfs("/")
assert "f_bsize" in result
assert "f_blocks" in result
assert result["f_bsize"] > 0
@pytest.mark.asyncio
async def test_chmod_does_not_raise(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.chmod("/a.txt", 0o644)
@pytest.mark.asyncio
async def test_chown_does_not_raise(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.chown("/a.txt", os.getuid(), os.getgid())
@pytest.mark.asyncio
async def test_utimens_does_not_raise(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.utimens("/a.txt", None)
@pytest.mark.asyncio
async def test_access_does_not_raise(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.access("/a.txt", os.R_OK)
@pytest.mark.asyncio
async def test_fsync_delegates_to_flush(rw_ws):
await rw_ws.execute("tee /f.txt", stdin=b"before")
fs = MirageFS(rw_ws.ops)
fh = fs.open("/f.txt", os.O_RDWR)
fs.write("/f.txt", b"after!", 0, fh)
fs.fsync("/f.txt", 0, fh)
result = await rw_ws.execute("cat /f.txt")
assert result.stdout == b"after!"
@pytest.mark.asyncio
async def test_open_returns_unique_handles(seed_ws):
fs = MirageFS(seed_ws.ops)
fh1 = fs.open("/a.txt", os.O_RDONLY)
fh2 = fs.open("/a.txt", os.O_RDONLY)
assert fh1 != fh2
@pytest.mark.asyncio
async def test_release_cleans_handles(seed_ws):
fs = MirageFS(seed_ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
assert fh in fs._handles
fs.release("/a.txt", fh)
assert fh not in fs._handles
@pytest.mark.asyncio
async def test_drain_ops_returns_and_clears(rw_ws):
await rw_ws.execute("tee /track.txt", stdin=b"x")
fs = MirageFS(rw_ws.ops)
fh = fs.create("/new.txt", 0o644)
fs.write("/new.txt", b"y", 0, fh)
fs.flush("/new.txt", fh)
ops = fs.drain_ops()
assert any(o["op"] == "create" for o in ops)
assert any(o["op"] == "write" for o in ops)
assert len(fs.drain_ops()) == 0
@pytest.mark.asyncio
async def test_drain_ops_read_deduplication(seed_ws):
fs = MirageFS(seed_ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
fs.read("/a.txt", 1024, 0, fh)
fs.read("/a.txt", 1024, 0, fh)
ops = fs.drain_ops()
read_ops = [o for o in ops if o["op"] == "read" and o["path"] == "/a.txt"]
assert len(read_ops) >= 1
@pytest.mark.asyncio
async def test_fuse_read_uses_cache_when_populated():
mem = RAMResource()
mem.caches_reads = True
mem._store.files["/a.txt"] = b"hello world"
ws = Workspace({"/": mem}, mode=MountMode.WRITE)
await ws.execute("cat /a.txt")
fs = MirageFS(ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
data = fs.read("/a.txt", 5, 0, fh)
assert data == b"hello"
assert (await ws._cache.get("/a.txt")) is not None
@pytest.mark.asyncio
async def test_readdir_logs_ls_op(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.readdir("/", None)
ops = fs.drain_ops()
assert any(o["op"] == "readdir" and o["path"] == "/" for o in ops)
@pytest.mark.asyncio
async def test_total_ops_persists_across_drains(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.readdir("/", None)
first = fs.drain_ops()
fs.readdir("/sub", None)
second = fs.drain_ops()
assert len(first) >= 1
assert len(second) >= 1
@pytest.mark.asyncio
async def test_total_ops_counts_reads_and_writes(rw_ws):
await rw_ws.execute("tee /f.txt", stdin=b"x")
fs = MirageFS(rw_ws.ops)
fs._ops.records.clear()
fh = fs.open("/f.txt", os.O_RDONLY)
fs.read("/f.txt", 1024, 0, fh)
fh2 = fs.create("/g.txt", 0o644)
fs.write("/g.txt", b"y", 0, fh2)
fs.flush("/g.txt", fh2)
ops = fs.drain_ops()
assert len(ops) >= 3
def test_permission_error_logged_on_create():
ro_ws = Workspace({"/": RAMResource()}, mode=MountMode.READ)
fs = MirageFS(ro_ws.ops)
with pytest.raises(Exception):
fs.create("/new.txt", 0o644)
def test_permission_error_not_counted_as_op():
ro_ws = Workspace({"/": RAMResource()}, mode=MountMode.READ)
fs = MirageFS(ro_ws.ops)
fs._ops.records.clear()
with pytest.raises(Exception):
fs.create("/new.txt", 0o644)
ops = fs.drain_ops()
assert len(ops) == 0
@pytest.mark.asyncio
async def test_fuse_dispatches_to_backend_hooks(seed_ws):
fs = MirageFS(seed_ws.ops)
fh = fs.open("/a.txt", os.O_RDONLY)
data = fs.read("/a.txt", 1024, 0, fh)
assert data == b"hello world"
@pytest.mark.asyncio
async def test_fuse_write_buffered_flush(rw_ws):
fs = MirageFS(rw_ws.ops)
await rw_ws.execute("tee /f.txt", stdin=b"hello world")
fh = fs.open("/f.txt", os.O_RDWR)
fs.write("/f.txt", b"HELLO", 0, fh)
fs.flush("/f.txt", fh)
result = await rw_ws.execute("cat /f.txt")
assert result.stdout == b"HELLO world"
@pytest.mark.skipif(not _fuse_available,
reason="FUSE not available on this platform")
@pytest.mark.asyncio
async def test_mount_background_readable():
from mirage.fuse.mount import mount_background
ws = Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
await ws.execute("tee /hello.txt", stdin=b"hi from memory")
with tempfile.TemporaryDirectory() as mountpoint:
t = mount_background(ws.ops, mountpoint, agent_id="test-agent")
try:
import time
time.sleep(1)
path = os.path.join(mountpoint, "hello.txt")
assert os.path.exists(path)
with open(path, "rb") as f:
assert f.read() == b"hi from memory"
finally:
if sys.platform == "darwin":
subprocess.run(["diskutil", "unmount", "force", mountpoint],
capture_output=True)
else:
subprocess.run(["fusermount", "-u", mountpoint],
capture_output=True)
t.join(timeout=3)
@pytest.mark.asyncio
async def test_xattr_set_get_roundtrip(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.setxattr("/a.txt", "user.test", b"value", 0)
assert fs.getxattr("/a.txt", "user.test") == b"value"
@pytest.mark.asyncio
async def test_xattr_get_missing_raises(seed_ws):
fs = MirageFS(seed_ws.ops)
with pytest.raises(OSError) as exc:
fs.getxattr("/a.txt", "user.absent")
assert exc.value.errno in (errno.ENODATA,
getattr(errno, "ENOATTR", errno.ENODATA))
@pytest.mark.asyncio
async def test_xattr_list_and_remove(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.setxattr("/a.txt", "user.one", b"1", 0)
fs.setxattr("/a.txt", "user.two", b"2", 0)
assert sorted(fs.listxattr("/a.txt")) == ["user.one", "user.two"]
fs.removexattr("/a.txt", "user.one")
assert fs.listxattr("/a.txt") == ["user.two"]
@pytest.mark.asyncio
async def test_xattr_probe_succeeds(seed_ws):
fs = MirageFS(seed_ws.ops)
assert fs.setxattr("/a.txt", "user.containers._probe", b"x", 0) == 0
assert fs.removexattr("/a.txt", "user.containers._probe") == 0
@pytest.mark.asyncio
async def test_xattr_cleared_on_unlink(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.setxattr("/a.txt", "user.keep", b"v", 0)
fs.unlink("/a.txt")
assert fs.listxattr("/sub") == []
assert "/a.txt" not in fs._xattrs
@pytest.mark.asyncio
async def test_xattr_follows_rename(seed_ws):
fs = MirageFS(seed_ws.ops)
fs.setxattr("/a.txt", "user.keep", b"v", 0)
fs.rename("/a.txt", "/renamed.txt")
assert fs.getxattr("/renamed.txt", "user.keep") == b"v"
assert "/a.txt" not in fs._xattrs
class _SizelessOps:
def __init__(self, ops):
self._inner = ops
self.read_calls = 0
def __getattr__(self, name):
return getattr(self._inner, name)
async def stat(self, path):
s = await self._inner.stat(path)
return s.model_copy(update={"size": None})
async def read(self, path):
self.read_calls += 1
return await self._inner.read(path)
_PAYLOAD = b"payload-bytes"
@pytest_asyncio.fixture
async def sizeless_fs():
ws = Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
await ws.execute("tee /u.json", stdin=_PAYLOAD)
ops = _SizelessOps(ws.ops)
return MirageFS(ops), ops
@pytest.mark.asyncio
async def test_unknown_size_preopen_stats_zero(sizeless_fs):
fs, ops = sizeless_fs
attrs = fs.getattr("/u.json")
assert attrs["st_size"] == 0
assert ops.read_calls == 0
@pytest.mark.asyncio
async def test_unknown_size_fh_stat_returns_real_size(sizeless_fs):
fs, _ = sizeless_fs
fh = fs.open("/u.json", os.O_RDONLY)
attrs = fs.getattr("/u.json", fh)
assert attrs["st_size"] == len(_PAYLOAD)
@pytest.mark.asyncio
async def test_unknown_size_path_stat_uses_open_handle(sizeless_fs):
fs, _ = sizeless_fs
fs.open("/u.json", os.O_RDONLY)
attrs = fs.getattr("/u.json")
assert attrs["st_size"] == len(_PAYLOAD)
@pytest.mark.asyncio
async def test_prefetch_survives_release_within_ttl(sizeless_fs):
fs, ops = sizeless_fs
fh = fs.open("/u.json", os.O_RDONLY)
fs.release("/u.json", fh)
attrs = fs.getattr("/u.json")
assert attrs["st_size"] == len(_PAYLOAD)
assert ops.read_calls == 1
@pytest.mark.asyncio
async def test_prefetch_expires_after_ttl(sizeless_fs):
fs, _ = sizeless_fs
fh = fs.open("/u.json", os.O_RDONLY)
fs.release("/u.json", fh)
data, _ = fs._prefetch["/u.json"]
fs._prefetch["/u.json"] = (data, 0.0)
attrs = fs.getattr("/u.json")
assert attrs["st_size"] == 0
assert "/u.json" not in fs._prefetch
@pytest.mark.asyncio
async def test_open_then_read_does_not_refetch(sizeless_fs):
fs, ops = sizeless_fs
fh = fs.open("/u.json", os.O_RDONLY)
assert fs.read("/u.json", 1024, 0, fh) == _PAYLOAD
assert fs.read("/u.json", 7, 0, fh) == _PAYLOAD[:7]
assert ops.read_calls == 1
@pytest.mark.asyncio
async def test_flush_drops_prefetch(sizeless_fs):
fs, _ = sizeless_fs
fh = fs.open("/u.json", os.O_RDWR)
assert "/u.json" in fs._prefetch
fs.write("/u.json", b"NEW", 0, fh)
fs.flush("/u.json", fh)
assert "/u.json" not in fs._prefetch
@pytest.mark.asyncio
async def test_unlink_drops_prefetch(sizeless_fs):
fs, _ = sizeless_fs
fh = fs.open("/u.json", os.O_RDONLY)
fs.release("/u.json", fh)
fs.unlink("/u.json")
assert "/u.json" not in fs._prefetch
+50
View File
@@ -0,0 +1,50 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import pytest
from mirage.fuse.fs import MirageFS
from mirage.fuse.mount import _run_fuse
from mirage.resource.ram import RAMResource
from mirage.types import MountMode
from mirage.workspace import Workspace
class _CaptureFuse:
kwargs: dict = {}
args: tuple = ()
def __init__(self, *args, **kwargs):
_CaptureFuse.args = args
_CaptureFuse.kwargs = kwargs
@pytest.fixture
def fs():
ws = Workspace({"/": RAMResource()}, mode=MountMode.WRITE)
return MirageFS(ws.ops)
def test_run_fuse_mount_options(monkeypatch, fs):
monkeypatch.setattr("mirage.fuse.mount.fuse.FUSE", _CaptureFuse)
_run_fuse(fs, "/tmp/mp", foreground=True)
assert _CaptureFuse.args == (fs, "/tmp/mp")
assert _CaptureFuse.kwargs["nothreads"] is True
assert _CaptureFuse.kwargs["foreground"] is True
# direct_io keeps reads correct for tools that never fstat; attr_timeout=0
# keeps fstat-based tools (wc -c, BSD cp, tail -c) from clamping at the
# stale pre-open size.
assert _CaptureFuse.kwargs["direct_io"] is True
assert _CaptureFuse.kwargs["attr_timeout"] == 0