Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Create 15 new directories with __init__.py files

Execute 15 pure git mv moves

Execute 3 renames (git mv)

Split test_configs.py into object_storage tests

Split test_default.py into fuse/backend tests

Split test_format_ops.py into csv and parquet tests

Split server/test_registry.py into commands/custom + server/constants

Merge types test files into tests/test_types.py

Merge workspace test files into tests/workspace/test_init.py

Cleanup empty directories and stale files

Run tests and pre-commit to verify


[Request interrupted by user]


Thoroughly explore the current test directory structure and source directory structure for this project. I need:

1. Full recursive listing of all files under tests/ (including __init__.py files)
2. Full recursive listing of all .py files under mirage/ (the source)
3. For each test file, identify which source file(s) it tests by looking at its imports

Use glob patterns like "tests/**/*.py" and "mirage/**/*.py" to get complete listings. Then read a few key test files to understand which source files they cover, especially:
- tests/backend/ files
- tests/fuse/ files
- tests/test_workspace.py or similar
- tests/test_types.py or similar
- Any test files that seem to cover multiple source files

Be very thorough - I need the complete picture.

Prompt:
Thoroughly explore the current test directory structure and source directory structure for this project. I need:

1. Full recursive listing of all files under tests/ (including __init__.py files)
2. Full recursive listing of all .py files under mirage/ (the source)
3. For each test file, identify which source file(s) it tests by looking at its imports

Use glob patterns like "tests/**/*.py" and "mirage/**/*.py" to get complete listings. Then read a few key test files to understand which source files they cover, especially:
- tests/backend/ files
- tests/fuse/ files
- tests/test_workspace.py or similar
- tests/test_types.py or similar
- Any test files that seem to cover multiple source files

Be very thorough - I need the complete picture.


/Users/agent/strukto/mirage/tests/__init__.py
/Users/agent/strukto/mirage/tests/cli/__init__.py
/Users/agent/strukto/mirage/tests/commands/__init__.py
/Users/agent/strukto/mirage/tests/server/__init__.py
/Users/agent/strukto/mirage/tests/cli/test_client.py
/Users/agent/strukto/mirage/tests/workspace/__init__.py
/Users/agent/strukto/mirage/tests/workspace/cache/__init__.py
/Users/agent/strukto/mirage/tests/workspace/cache/test_store.py
/Users/agent/strukto/mirage/tests/backend/__init__.py
/Users/agent/strukto/mirage/tests/runtime/__init__.py
/Users/agent/strukto/mirage/tests/workspace/cache/test_policy.py
/Users/agent/strukto/mirage/tests/observe/__init__.py
/Users/agent/strukto/mirage/tests/observe/test_session_log.py
/Users/agent/strukto/mirage/tests/test_types.py
/Users/agent/strukto/mirage/tests/workspace/test_cache_entry.py
/Users/agent/strukto/mirage/tests/observe/test_entries.py
/Users/agent/strukto/mirage/tests/fuse/__init__.py
/Users/agent/strukto/mirage/tests/types/__init__.py
/Users/agent/strukto/mirage/tests/integration/__init__.py
/Users/agent/strukto/mirage/tests/backend/test_memory.py
/Users/agent/strukto/mirage/tests/types/test_types.py
/Users/agent/strukto/mirage/tests/server/test_app.py
/Users/agent/strukto/mirage/tests/server/test_file_cache.py
/Users/agent/strukto/mirage/tests/commands/test_nav.py
/Users/agent/strukto/mirage/tests/commands/test_search.py
/Users/agent/strukto/mirage/tests/commands/test_client.py
/Users/agent/strukto/mirage/tests/observe/test_api.py
/Users/agent/strukto/mirage/tests/server/test_io_router.py
/Users/agent/strukto/mirage/tests/server/test_service.py
/Users/agent/strukto/mirage/tests/server/conftest.py
/Users/agent/strukto/mirage/tests/commands/test_handlers.py
/Users/agent/strukto/mirage/tests/commands/test_write.py
/Users/agent/strukto/mirage/tests/runtime/test_remote.py
/Users/agent/strukto/mirage/tests/runtime/test_local.py
/Users/agent/strukto/mirage/tests/backend/test_local.py
/Users/agent/strukto/mirage/tests/backend/test_proxy.py
/Users/agent/strukto/mirage/tests/backend/test_configs.py
/Users/agent/strukto/mirage/tests/observe/test_inspector.py
/Users/agent/strukto/mirage/tests/cli/test_config.py
/Users/agent/strukto/mirage/tests/integration/test_remote_transfer.py
/Users/agent/strukto/mirage/tests/workspace/test_lock.py
/Users/agent/strukto/mirage/tests/workspace/test_init.py
/Users/agent/strukto/mirage/tests/workspace/test_sync.py
/Users/agent/strukto/mirage/tests/workspace/test_write_ops.py
/Users/agent/strukto/mirage/tests/workspace/test_mount.py
/Users/agent/strukto/mirage/tests/server/test_arg_resolver.py
/Users/agent/strukto/mirage/tests/server/test_registry.py
/Users/agent/strukto/mirage/tests/runtime/test_runtime.py
/Users/agent/strukto/mirage/tests/runtime/test_memory.py
/Users/agent/strukto/mirage/tests/commands/test_init.py
/Users/agent/strukto/mirage/tests/commands/test_read.py
/Users/agent/strukto/mirage/tests/conftest.py
/Users/agent/strukto/mirage/tests/fuse/test_remote.py
/Users/agent/strukto/mirage/tests/fuse/test_format_ops.py
/Users/agent/strukto/mirage/tests/fuse/test_registry.py
/Users/agent/strukto/mirage/tests/runtime/test_accessors.py
/Users/agent/strukto/mirage/tests/backend/test_loader.py
/Users/agent/strukto/mirage/tests/fuse/test_fs.py
/Users/agent/strukto/mirage/tests/cli/test_app.py
/Users/agent/strukto/mirage/tests/fuse/test_default.py
/Users/agent/strukto/mirage/tests/fuse/test_loader.py


Bash

find /Users/agent/strukto/mirage/mirage -name "*.py" | sort

Complete recursive listing of all .py files under mirage/


/Users/agent/strukto/mirage/mirage/__init__.py
/Users/agent/strukto/mirage/mirage/backend/__init__.py
/Users/agent/strukto/mirage/mirage/backend/base.py
/Users/agent/strukto/mirage/mirage/backend/filetype.py
/Users/agent/strukto/mirage/mirage/backend/loader.py
/Users/agent/strukto/mirage/mirage/backend/local/__init__.py
/Users/agent/strukto/mirage/mirage/backend/local/local.py
/Users/agent/strukto/mirage/mirage/backend/local/memory.py
/Users/agent/strukto/mirage/mirage/backend/network/__init__.py
/Users/agent/strukto/mirage/mirage/backend/network/http.py
/Users/agent/strukto/mirage/mirage/backend/network/proxy.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/__init__.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/r2.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/s3.py
/Users/agent/strukto/mirage/mirage/cli/__init__.py
/Users/agent/strukto/mirage/mirage/cli/app.py
/Users/agent/strukto/mirage/mirage/cli/client.py
/Users/agent/strukto/mirage/mirage/cli/commands/__init__.py
/Users/agent/strukto/mirage/mirage/cli/commands/command.py
/Users/agent/strukto/mirage/mirage/cli/commands/observe.py
/Users/agent/strukto/mirage/mirage/cli/commands/server.py
/Users/agent/strukto/mirage/mirage/cli/commands/workspace.py
/Users/agent/strukto/mirage/mirage/cli/config.py
/Users/agent/strukto/mirage/mirage/cli/helpers.py
/Users/agent/strukto/mirage/mirage/commands/__init__.py
/Users/agent/strukto/mirage/mirage/commands/builtin/__init__.py
/Users/agent/strukto/mirage/mirage/commands/builtin/handlers.py
/Users/agent/strukto/mirage/mirage/commands/builtin/nav.py
/Users/agent/strukto/mirage/mirage/commands/builtin/net.py
/Users/agent/strukto/mirage/mirage/commands/builtin/read.py
/Users/agent/strukto/mirage/mirage/commands/builtin/search.py
/Users/agent/strukto/mirage/mirage/commands/builtin/write.py
/Users/agent/strukto/mirage/mirage/commands/custom/__init__.py
/Users/agent/strukto/mirage/mirage/commands/custom/arg_resolver.py
/Users/agent/strukto/mirage/mirage/commands/custom/executor.py
/Users/agent/strukto/mirage/mirage/commands/custom/registry.py
/Users/agent/strukto/mirage/mirage/commands/remote/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/auth.py
/Users/agent/strukto/mirage/mirage/commands/remote/client.py
/Users/agent/strukto/mirage/mirage/commands/remote/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/fastapi/app.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/command_pb2.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/command_pb2_grpc.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/server.py
/Users/agent/strukto/mirage/mirage/fuse/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/default.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/local.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/memory.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/s3.py
/Users/agent/strukto/mirage/mirage/fuse/base.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/csv.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/parquet.py
/Users/agent/strukto/mirage/mirage/fuse/fs.py
/Users/agent/strukto/mirage/mirage/fuse/loader.py
/Users/agent/strukto/mirage/mirage/fuse/registry.py
/Users/agent/strukto/mirage/mirage/fuse/remote/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/remote/remote.py
/Users/agent/strukto/mirage/mirage/observe/__init__.py
/Users/agent/strukto/mirage/mirage/observe/entries.py
/Users/agent/strukto/mirage/mirage/observe/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/observe/fastapi/router.py
/Users/agent/strukto/mirage/mirage/observe/inspector.py
/Users/agent/strukto/mirage/mirage/observe/session_log.py
/Users/agent/strukto/mirage/mirage/runtime/__init__.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/__init__.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/base.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/local.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/remote.py
/Users/agent/strukto/mirage/mirage/runtime/base.py
/Users/agent/strukto/mirage/mirage/runtime/local.py
/Users/agent/strukto/mirage/mirage/runtime/memory.py
/Users/agent/strukto/mirage/mirage/runtime/remote.py
/Users/agent/strukto/mirage/mirage/server/__init__.py
/Users/agent/strukto/mirage/mirage/server/constants.py
/Users/agent/strukto/mirage/mirage/server/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/server/fastapi/app.py
/Users/agent/strukto/mirage/mirage/server/fastapi/config.py
/Users/agent/strukto/mirage/mirage/server/fastapi/daemon.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/__init__.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/command_router.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/io_router.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/workspace_router.py
/Users/agent/strukto/mirage/mirage/server/routes.py
/Users/agent/strukto/mirage/mirage/server/service/__init__.py
/Users/agent/strukto/mirage/mirage/server/service/base.py
/Users/agent/strukto/mirage/mirage/server/service/cache.py
/Users/agent/strukto/mirage/mirage/server/service/local.py
/Users/agent/strukto/mirage/mirage/types.py
/Users/agent/strukto/mirage/mirage/workspace/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/cache/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/cache/entry.py
/Users/agent/strukto/mirage/mirage/workspace/cache/policy.py
/Users/agent/strukto/mirage/mirage/workspace/cache/store.py
/Users/agent/strukto/mirage/mirage/workspace/mount.py
/Users/agent/strukto/mirage/mirage/workspace/sync.py


     1→import pytest
     2→
     3→from mirage.types import FileType
     4→
     5→
     6→def test_put_and_cat(memory_backend):
     7→    memory_backend.create("/f.txt")
     8→    memory_backend.write("/f.txt", 0, b"hello")
     9→    assert memory_backend.cat("/f.txt") == b"hello"
    10→
    11→
    12→def test_create_and_write(memory_backend):
    13→    memory_backend.create("/a.txt")
    14→    memory_backend.write("/a.txt", 0, b"data")
    15→    assert memory_backend.cat("/a.txt") == b"data"
    16→
    17→
    18→def test_read_partial_with_offset(memory_backend):
    19→    memory_backend.create("/f.txt")
    20→    memory_backend.write("/f.txt", 0, b"abcdef")
    21→    assert memory_backend.read("/f.txt", 2, 3) == b"cde"
    22→
    23→
    24→def test_read_missing_raises(memory_backend):
    25→    with pytest.raises(FileNotFoundError):
    26→        memory_backend.read("/missing.txt", 0, 10)
    27→
    28→
    29→def test_mkdir_and_readdir(memory_backend):
    30→    memory_backend.mkdir("/mydir")
    31→    entries, _ = memory_backend.readdir("/mydir")
    32→    assert entries == []
    33→
    34→
    35→def test_mkdir_missing_parent_raises(memory_backend):
    36→    with pytest.raises(FileNotFoundError):
    37→        memory_backend.mkdir("/a/b/c")
    38→
    39→
    40→def test_rmdir_empty(memory_backend):
    41→    memory_backend.mkdir("/emptydir")
    42→    memory_backend.rmdir("/emptydir")
    43→    with pytest.raises(FileNotFoundError):
    44→        memory_backend.stat("/emptydir")
    45→
    46→
    47→def test_rmdir_nonempty_raises(memory_backend):
    48→    memory_backend.mkdir("/dir")
    49→    memory_backend.create("/dir/file.txt")
    50→    with pytest.raises(OSError):
    51→        memory_backend.rmdir("/dir")
    52→
    53→
    54→def test_unlink(memory_backend):
    55→    memory_backend.create("/del.txt")
    56→    memory_backend.unlink("/del.txt")
    57→    with pytest.raises(FileNotFoundError):
    58→        memory_backend.cat("/del.txt")
    59→
    60→
    61→def test_unlink_missing_raises(memory_backend):
    62→    with pytest.raises(FileNotFoundError):
    63→        memory_backend.unlink("/nope.txt")
    64→
    65→
    66→def test_rename_file(memory_backend):
    67→    memory_backend.create("/old.txt")
    68→    memory_backend.write("/old.txt", 0, b"content")
    69→    memory_backend.rename("/old.txt", "/new.txt")
    70→    assert memory_backend.cat("/new.txt") == b"content"
    71→    with pytest.raises(FileNotFoundError):
    72→        memory_backend.cat("/old.txt")
    73→
    74→
    75→def test_truncate_shrink(memory_backend):
    76→    memory_backend.create("/f.txt")
    77→    memory_backend.write("/f.txt", 0, b"abcdef")
    78→    memory_backend.truncate("/f.txt", 3)
    79→    assert memory_backend.cat("/f.txt") == b"abc"
    80→
    81→
    82→def test_truncate_grow(memory_backend):
    83→    memory_backend.create("/f.txt")
    84→    memory_backend.write("/f.txt", 0, b"ab")
    85→    memory_backend.truncate("/f.txt", 5)
    86→    assert memory_backend.cat("/f.txt") == b"ab\x00\x00\x00"
    87→
    88→
    89→def test_stat_file(memory_backend):
    90→    memory_backend.create("/f.txt")
    91→    memory_backend.write("/f.txt", 0, b"hello")
    92→    s = memory_backend.stat("/f.txt")
    93→    assert s.name == "f.txt"
    94→    assert s.size == 5
    95→    assert s.type == "text"
    96→
    97→
    98→def test_stat_directory(memory_backend):
    99→    memory_backend.mkdir("/mydir")
   100→    s = memory_backend.stat("/mydir")
   101→    assert s.type == FileType.DIRECTORY
   102→    assert s.size is None
   103→
   104→
   105→def test_stat_missing_raises(memory_backend):
   106→    with pytest.raises(FileNotFoundError):
   107→        memory_backend.stat("/missing.txt")
   108→
   109→
   110→def test_exists_true(memory_backend):
   111→    memory_backend.create("/f.txt")
   112→    assert memory_backend.exists("/f.txt")
   113→
   114→
   115→def test_exists_false(memory_backend):
   116→    assert not memory_backend.exists("/nope.txt")
   117→
   118→
   119→def test_checksum_deterministic(memory_backend):
   120→    memory_backend.create("/f.txt")
   121→    memory_backend.write("/f.txt", 0, b"data")
   122→    c1 = memory_backend.checksum("/f.txt")
   123→    c2 = memory_backend.checksum("/f.txt")
   124→    assert c1 == c2
   125→    assert len(c1) == 32
   126→
   127→
   128→def test_readdir_lists_direct_children(memory_backend):
   129→    memory_backend.mkdir("/parent")
   130→    memory_backend.mkdir("/parent/child1")
   131→    memory_backend.create("/parent/file.txt")
   132→    entries, _ = memory_backend.readdir("/parent")
   133→    assert any("child1" in e for e in entries)
   134→    assert any("file.txt" in e for e in entries)
   135→


     1→import pytest
     2→
     3→from mirage.backend.local.local import LocalBackend
     4→from mirage.types import FileType
     5→
     6→
     7→@pytest.fixture
     8→def local_backend(tmp_path):
     9→    return LocalBackend(str(tmp_path))
    10→
    11→
    12→def test_create_and_cat(local_backend):
    13→    local_backend.create("/hello.txt")
    14→    local_backend.write("/hello.txt", 0, b"hello")
    15→    assert local_backend.cat("/hello.txt") == b"hello"
    16→
    17→
    18→def test_put_and_cat(local_backend):
    19→    local_backend.put("/f.txt", b"data")
    20→    assert local_backend.cat("/f.txt") == b"data"
    21→
    22→
    23→def test_read_partial(local_backend):
    24→    local_backend.put("/f.txt", b"abcdef")
    25→    assert local_backend.read("/f.txt", 2, 3) == b"cde"
    26→
    27→
    28→def test_mkdir_and_readdir(local_backend):
    29→    local_backend.mkdir("/mydir")
    30→    entries, _ = local_backend.readdir("/mydir")
    31→    assert entries == []
    32→
    33→
    34→def test_rmdir_empty(local_backend):
    35→    local_backend.mkdir("/emptydir")
    36→    local_backend.rmdir("/emptydir")
    37→    with pytest.raises(FileNotFoundError):
    38→        local_backend.stat("/emptydir")
    39→
    40→
    41→def test_unlink(local_backend):
    42→    local_backend.put("/del.txt", b"x")
    43→    local_backend.unlink("/del.txt")
    44→    with pytest.raises(FileNotFoundError):
    45→        local_backend.stat("/del.txt")
    46→
    47→
    48→def test_rename(local_backend):
    49→    local_backend.put("/old.txt", b"content")
    50→    local_backend.rename("/old.txt", "/new.txt")
    51→    assert local_backend.cat("/new.txt") == b"content"
    52→    with pytest.raises(FileNotFoundError):
    53→        local_backend.stat("/old.txt")
    54→
    55→
    56→def test_stat_file(local_backend):
    57→    local_backend.put("/f.txt", b"hello")
    58→    s = local_backend.stat("/f.txt")
    59→    assert s.name == "f.txt"
    60→    assert s.size == 5
    61→    assert s.type == "text"
    62→
    63→
    64→def test_stat_directory(local_backend):
    65→    local_backend.mkdir("/mydir")
    66→    s = local_backend.stat("/mydir")
    67→    assert s.type == FileType.DIRECTORY
    68→    assert s.size is None
    69→
    70→
    71→def test_stat_missing_raises(local_backend):
    72→    with pytest.raises(FileNotFoundError):
    73→        local_backend.stat("/missing.txt")
    74→
    75→
    76→def test_write_at_offset(local_backend):
    77→    local_backend.put("/f.txt", b"hello world")
    78→    local_backend.write("/f.txt", 6, b"earth")
    79→    assert local_backend.cat("/f.txt") == b"hello earth"
    80→
    81→
    82→def test_truncate_shrink(local_backend):
    83→    local_backend.put("/f.txt", b"abcdef")
    84→    local_backend.truncate("/f.txt", 3)
    85→    assert local_backend.cat("/f.txt") == b"abc"
    86→
    87→
    88→def test_truncate_grow(local_backend):
    89→    local_backend.put("/f.txt", b"ab")
    90→    local_backend.truncate("/f.txt", 5)
    91→    assert len(local_backend.cat("/f.txt")) == 5
    92→
    93→
    94→def test_path_traversal_raises(local_backend):
    95→    with pytest.raises(ValueError):
    96→        local_backend.stat("/../etc/passwd")
    97→


     1→import pytest
     2→from fastapi.testclient import TestClient
     3→
     4→from mirage.backend.network.proxy import ProxyBackend
     5→from mirage.observe import session_log as observe_session_module
     6→from mirage.server.fastapi import app as server_app_module
     7→from mirage.server.fastapi.app import app
     8→
     9→
    10→@pytest.fixture
    11→def proxy():
    12→    server_app_module._sessions.clear()
    13→    observe_session_module._session_logs.clear()
    14→    client = TestClient(app)
    15→    resp = client.post("/workspaces", json={"mounts": {"/": "memory"}})
    16→    ws_id = resp.json()["id"]
    17→    backend = ProxyBackend("http://testserver", ws_id, http_client=client)
    18→    yield backend
    19→    server_app_module._sessions.clear()
    20→    observe_session_module._session_logs.clear()
    21→
    22→
    23→def test_create_stat(proxy):
    24→    proxy.create("/f.txt")
    25→    st = proxy.stat("/f.txt")
    26→    assert st.name is not None
    27→    assert st.size == 0
    28→
    29→
    30→def test_write_read(proxy):
    31→    proxy.create("/f.txt")
    32→    written = proxy.write("/f.txt", 0, b"hello world")
    33→    assert written == 11
    34→    data = proxy.read("/f.txt", 0, 11)
    35→    assert data == b"hello world"
    36→
    37→
    38→def test_cat(proxy):
    39→    proxy.create("/f.txt")
    40→    proxy.write("/f.txt", 0, b"hello")
    41→    data = proxy.cat("/f.txt")
    42→    assert data == b"hello"
    43→
    44→
    45→def test_mkdir_readdir(proxy):
    46→    proxy.mkdir("/sub")
    47→    entries = proxy.ls("/")
    48→    names = [e.rsplit("/", 1)[-1] for e in entries]
    49→    assert "sub" in names
    50→
    51→
    52→def test_unlink(proxy):
    53→    proxy.create("/f.txt")
    54→    proxy.unlink("/f.txt")
    55→    with pytest.raises(FileNotFoundError):
    56→        proxy.stat("/f.txt")
    57→
    58→
    59→def test_rename(proxy):
    60→    proxy.create("/a.txt")
    61→    proxy.write("/a.txt", 0, b"data")
    62→    proxy.rename("/a.txt", "/b.txt")
    63→    with pytest.raises(FileNotFoundError):
    64→        proxy.stat("/a.txt")
    65→    st = proxy.stat("/b.txt")
    66→    assert st.size == 4
    67→
    68→
    69→def test_truncate(proxy):
    70→    proxy.create("/f.txt")
    71→    proxy.write("/f.txt", 0, b"hello world")
    72→    proxy.truncate("/f.txt", 5)
    73→    st = proxy.stat("/f.txt")
    74→    assert st.size == 5
    75→
    76→
    77→def test_stat_missing(proxy):
    78→    with pytest.raises(FileNotFoundError):
    79→        proxy.stat("/missing.txt")
    80→
    81→
    82→def test_ls(proxy):
    83→    proxy.create("/a.txt")
    84→    proxy.create("/b.txt")
    85→    entries = proxy.ls("/")
    86→    names = [e.rsplit("/", 1)[-1] for e in entries]
    87→    assert "a.txt" in names
    88→    assert "b.txt" in names
    89→
    90→
    91→def test_fuse_cat(proxy):
    92→    proxy.create("/f.txt")
    93→    proxy.write("/f.txt", 0, b"content")
    94→    data = proxy.fuse_cat("/f.txt")
    95→    assert data == b"content"
    96→
    97→
    98→def test_put(proxy):
    99→    proxy.put("/f.txt", b"hello")
   100→    data = proxy.cat("/f.txt")
   101→    assert data == b"hello"
   102→


     1→import pytest
     2→from pydantic import ValidationError
     3→
     4→from mirage.backend.object_storage.r2 import R2Config
     5→from mirage.backend.object_storage.s3 import S3Config
     6→from mirage.commands.custom.registry import ExternalCommand
     7→
     8→
     9→def test_s3config_defaults():
    10→    c = S3Config(bucket="my-bucket")
    11→    assert c.region is None
    12→    assert c.timeout == 30
    13→    assert c.select_threshold_bytes == 1024 * 1024
    14→
    15→
    16→def test_s3config_immutable():
    17→    c = S3Config(bucket="x")
    18→    with pytest.raises(ValidationError):
    19→        c.bucket = "y"
    20→
    21→
    22→def test_r2config_fields():
    23→    c = R2Config(account_id="acc",
    24→                 bucket="bkt",
    25→                 access_key_id="k",
    26→                 secret_access_key="s")
    27→    assert c.region == "auto"
    28→    assert c.timeout == 30
    29→
    30→
    31→def test_r2config_immutable():
    32→    c = R2Config(account_id="a",
    33→                 bucket="b",
    34→                 access_key_id="k",
    35→                 secret_access_key="s")
    36→    with pytest.raises(ValidationError):
    37→        c.bucket = "other"
    38→
    39→
    40→def test_external_command_fields():
    41→    cmd = ExternalCommand(name="linecount", exec="python lc.py {file}")
    42→    assert cmd.description == ""
    43→
    44→
    45→def test_external_command_immutable():
    46→    cmd = ExternalCommand(name="x", exec="echo {file}")
    47→    with pytest.raises(ValidationError):
    48→        cmd.name = "y"
    49→


     1→import pytest
     2→
     3→from mirage.backend.loader import load_backend_class
     4→
     5→
     6→def test_load_from_module():
     7→    cls = load_backend_class("mirage.backend.local.memory:MemoryBackend")
     8→    from mirage.backend.local.memory import MemoryBackend
     9→    assert cls is MemoryBackend
    10→
    11→
    12→def test_load_from_script_file(tmp_path):
    13→    script = tmp_path / "custom_backend.py"
    14→    script.write_text("from mirage.backend.local.memory import MemoryBackend\n"
    15→                      "class CustomBackend(MemoryBackend):\n"
    16→                      "    pass\n")
    17→    cls = load_backend_class(f"{script}:CustomBackend")
    18→    assert cls.__name__ == "CustomBackend"
    19→    instance = cls()
    20→    instance.put("/test.txt", b"hello")
    21→    assert instance.cat("/test.txt") == b"hello"
    22→
    23→
    24→def test_load_invalid_spec_no_colon():
    25→    with pytest.raises(ValueError, match="invalid backend spec"):
    26→        load_backend_class("mirage.backend.memory")
    27→
    28→
    29→def test_load_missing_file():
    30→    with pytest.raises(ValueError, match="cannot load script"):
    31→        load_backend_class("/nonexistent/path.py:Cls")
    32→
    33→
    34→def test_load_missing_class():
    35→    with pytest.raises(ValueError, match="not found"):
    36→        load_backend_class("mirage.backend.local.memory:DoesNotExist")
    37→


     1→import errno
     2→import os
     3→import stat
     4→import subprocess
     5→import sys
     6→import tempfile
     7→
     8→import fuse
     9→import pytest
    10→
    11→from mirage.backend.local.memory import MemoryBackend
    12→from mirage.fuse.fs import _MIRAGE_DIR, _MIRAGE_WHOAMI, MirageFS
    13→from mirage.types import MountMode
    14→from mirage.workspace import Workspace
    15→
    16→_fuse_available = sys.platform in ("linux", "darwin")
    17→
    18→
    19→@pytest.fixture
    20→def seed_ws():
    21→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    22→    ws.write("/a.txt", b"hello world")
    23→    ws.write("/sub/b.txt", b"nested")
    24→    return ws
    25→
    26→
    27→@pytest.fixture
    28→def rw_ws():
    29→    return Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    30→
    31→
    32→def test_getattr_root(seed_ws):
    33→    fs = MirageFS(seed_ws)
    34→    attrs = fs.getattr("/")
    35→    assert attrs["st_mode"] & stat.S_IFDIR
    36→
    37→
    38→def test_getattr_file(seed_ws):
    39→    fs = MirageFS(seed_ws)
    40→    attrs = fs.getattr("/a.txt")
    41→    assert attrs["st_mode"] & stat.S_IFREG
    42→    assert attrs["st_size"] == len(b"hello world")
    43→
    44→
    45→def test_getattr_dir(seed_ws):
    46→    fs = MirageFS(seed_ws)
    47→    attrs = fs.getattr("/sub")
    48→    assert attrs["st_mode"] & stat.S_IFDIR
    49→
    50→
    51→def test_getattr_missing(seed_ws):
    52→    fs = MirageFS(seed_ws)
    53→    with pytest.raises(fuse.FuseOSError) as exc:
    54→        fs.getattr("/no_such_file.txt")
    55→    assert exc.value.errno == errno.ENOENT
    56→
    57→
    58→def test_readdir_root(seed_ws):
    59→    fs = MirageFS(seed_ws)
    60→    entries = fs.readdir("/", None)
    61→    assert "." in entries
    62→    assert ".." in entries
    63→    assert "a.txt" in entries
    64→    assert "sub" in entries
    65→
    66→
    67→def test_readdir_subdir(seed_ws):
    68→    fs = MirageFS(seed_ws)
    69→    entries = fs.readdir("/sub", None)
    70→    assert "b.txt" in entries
    71→
    72→
    73→def test_read_full(seed_ws):
    74→    fs = MirageFS(seed_ws)
    75→    fh = fs.open("/a.txt", os.O_RDONLY)
    76→    data = fs.read("/a.txt", 1024, 0, fh)
    77→    assert data == b"hello world"
    78→
    79→
    80→def test_read_offset(seed_ws):
    81→    fs = MirageFS(seed_ws)
    82→    fh = fs.open("/a.txt", os.O_RDONLY)
    83→    data = fs.read("/a.txt", 5, 6, fh)
    84→    assert data == b"world"
    85→
    86→
    87→def test_create_and_write(rw_ws):
    88→    fs = MirageFS(rw_ws)
    89→    fh = fs.create("/new.txt", 0o644)
    90→    fs.write("/new.txt", b"data", 0, fh)
    91→    fs.flush("/new.txt", fh)
    92→    assert rw_ws.cat("/new.txt") == b"data"
    93→
    94→
    95→def test_mkdir(rw_ws):
    96→    fs = MirageFS(rw_ws)
    97→    fs.mkdir("/newdir", 0o755)
    98→    entries = fs.readdir("/newdir", None)
    99→    assert "." in entries
   100→
   101→
   102→def test_unlink(rw_ws):
   103→    rw_ws.write("/todel.txt", b"bye")
   104→    fs = MirageFS(rw_ws)
   105→    fs.unlink("/todel.txt")
   106→    with pytest.raises(fuse.FuseOSError) as exc:
   107→        fs.getattr("/todel.txt")
   108→    assert exc.value.errno == errno.ENOENT
   109→
   110→
   111→def test_rename(rw_ws):
   112→    rw_ws.write("/old.txt", b"content")
   113→    fs = MirageFS(rw_ws)
   114→    fs.rename("/old.txt", "/new.txt")
   115→    assert rw_ws.cat("/new.txt") == b"content"
   116→
   117→
   118→def test_truncate(rw_ws):
   119→    rw_ws.write("/f.txt", b"hello world")
   120→    fs = MirageFS(rw_ws)
   121→    fs.truncate("/f.txt", 5)
   122→    assert rw_ws.cat("/f.txt") == b"hello"
   123→
   124→
   125→def test_write_at_offset(rw_ws):
   126→    rw_ws.write("/f.txt", b"hello world")
   127→    fs = MirageFS(rw_ws)
   128→    fh = fs.open("/f.txt", os.O_RDWR)
   129→    fs.write("/f.txt", b"WORLD", 6, fh)
   130→    fs.flush("/f.txt", fh)
   131→    assert rw_ws.cat("/f.txt") == b"hello WORLD"
   132→
   133→
   134→def test_truncate_extend(rw_ws):
   135→    rw_ws.write("/f.txt", b"hi")
   136→    fs = MirageFS(rw_ws)
   137→    fs.truncate("/f.txt", 5)
   138→    assert rw_ws.cat("/f.txt") == b"hi\x00\x00\x00"
   139→
   140→
   141→def test_mirage_dir_getattr(seed_ws):
   142→    fs = MirageFS(seed_ws)
   143→    attrs = fs.getattr(_MIRAGE_DIR)
   144→    assert attrs["st_mode"] & stat.S_IFDIR
   145→
   146→
   147→def test_mirage_whoami_getattr(seed_ws):
   148→    fs = MirageFS(seed_ws, agent_id="test-agent")
   149→    attrs = fs.getattr(_MIRAGE_WHOAMI)
   150→    assert attrs["st_mode"] & stat.S_IFREG
   151→    assert attrs["st_size"] > 0
   152→
   153→
   154→def test_mirage_dir_in_root_readdir(seed_ws):
   155→    fs = MirageFS(seed_ws)
   156→    entries = fs.readdir("/", None)
   157→    assert ".mirage" in entries
   158→
   159→
   160→def test_mirage_dir_readdir(seed_ws):
   161→    fs = MirageFS(seed_ws)
   162→    entries = fs.readdir(_MIRAGE_DIR, None)
   163→    assert "whoami" in entries
   164→    assert "." in entries
   165→    assert ".." in entries
   166→
   167→
   168→def test_whoami_content_has_agent(seed_ws):
   169→    fs = MirageFS(seed_ws, agent_id="claude")
   170→    fh = fs.open(_MIRAGE_WHOAMI, os.O_RDONLY)
   171→    data = fs.read(_MIRAGE_WHOAMI, 4096, 0, fh)
   172→    text = data.decode()
   173→    assert "agent: claude" in text
   174→    assert "cwd: /" in text
   175→    assert "mounts:" in text
   176→
   177→
   178→def test_whoami_content_offset(seed_ws):
   179→    fs = MirageFS(seed_ws, agent_id="x")
   180→    fh = fs.open(_MIRAGE_WHOAMI, os.O_RDONLY)
   181→    full = fs.read(_MIRAGE_WHOAMI, 4096, 0, fh)
   182→    partial = fs.read(_MIRAGE_WHOAMI, 5, 0, fh)
   183→    assert partial == full[:5]
   184→
   185→
   186→def test_whoami_open(seed_ws):
   187→    fs = MirageFS(seed_ws)
   188→    fh = fs.open(_MIRAGE_WHOAMI, os.O_RDONLY)
   189→    assert isinstance(fh, int)
   190→    assert fh > 0
   191→
   192→
   193→def test_agent_id_auto_generated(seed_ws):
   194→    fs = MirageFS(seed_ws)
   195→    assert fs.agent_id.startswith("agent-")
   196→
   197→
   198→def test_agent_id_from_param(seed_ws):
   199→    fs = MirageFS(seed_ws, agent_id="my-bot")
   200→    assert fs.agent_id == "my-bot"
   201→
   202→
   203→def test_agent_id_from_env(seed_ws, monkeypatch):
   204→    monkeypatch.setenv("MIRAGE_AGENT_ID", "env-agent")
   205→    fs = MirageFS(seed_ws)
   206→    assert fs.agent_id == "env-agent"
   207→
   208→
   209→def test_whoami_size_matches_content(seed_ws):
   210→    fs = MirageFS(seed_ws, agent_id="claude")
   211→    attrs = fs.getattr(_MIRAGE_WHOAMI)
   212→    fh = fs.open(_MIRAGE_WHOAMI, os.O_RDONLY)
   213→    content = fs.read(_MIRAGE_WHOAMI, 4096, 0, fh)
   214→    assert attrs["st_size"] == len(content)
   215→
   216→
   217→def test_fuse_dispatches_to_registry(seed_ws):
   218→    from mirage.fuse.registry import FuseOpRegistry
   219→    registry = FuseOpRegistry()
   220→    fs = MirageFS(seed_ws, fuse_registry=registry)
   221→    fh = fs.open("/a.txt", os.O_RDONLY)
   222→    data = fs.read("/a.txt", 1024, 0, fh)
   223→    assert data == b"hello world"
   224→
   225→
   226→def test_fuse_write_buffered_flush(rw_ws):
   227→    from mirage.fuse.registry import FuseOpRegistry
   228→    registry = FuseOpRegistry()
   229→    fs = MirageFS(rw_ws, fuse_registry=registry)
   230→    rw_ws.write("/f.txt", b"hello world")
   231→    fh = fs.open("/f.txt", os.O_RDWR)
   232→    fs.write("/f.txt", b"HELLO", 0, fh)
   233→    fs.flush("/f.txt", fh)
   234→    assert rw_ws.cat("/f.txt") == b"HELLO world"
   235→
   236→
   237→def test_fuse_release_cleans_handles(seed_ws):
   238→    fs = MirageFS(seed_ws)
   239→    fh = fs.open("/a.txt", os.O_RDONLY)
   240→    assert fh in fs._handles
   241→    fs.release("/a.txt", fh)
   242→    assert fh not in fs._handles
   243→
   244→
   245→def test_open_returns_unique_handles(seed_ws):
   246→    fs = MirageFS(seed_ws)
   247→    fh1 = fs.open("/a.txt", os.O_RDONLY)
   248→    fh2 = fs.open("/a.txt", os.O_RDONLY)
   249→    assert fh1 != fh2
   250→
   251→
   252→@pytest.mark.skipif(not _fuse_available,
   253→                    reason="FUSE not available on this platform")
   254→def test_mount_background_readable():
   255→    from mirage.fuse.fs import mount_background
   256→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
   257→    ws.write("/hello.txt", b"hi from memory")
   258→    with tempfile.TemporaryDirectory() as mountpoint:
   259→        t = mount_background(ws, mountpoint, agent_id="test-agent")
   260→        try:
   261→            path = os.path.join(mountpoint, "hello.txt")
   262→            assert os.path.exists(path)
   263→            with open(path, "rb") as f:
   264→                assert f.read() == b"hi from memory"
   265→        finally:
   266→            if sys.platform == "darwin":
   267→                subprocess.run(["diskutil", "unmount", "force", mountpoint],
   268→                               capture_output=True)
   269→            else:
   270→                subprocess.run(["fusermount", "-u", mountpoint],
   271→                               capture_output=True)
   272→            t.join(timeout=3)
   273→
   274→
   275→@pytest.mark.skipif(not _fuse_available,
   276→                    reason="FUSE not available on this platform")
   277→def test_mount_background_whoami_file():
   278→    from mirage.fuse.fs import mount_background
   279→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
   280→    with tempfile.TemporaryDirectory() as mountpoint:
   281→        t = mount_background(ws, mountpoint, agent_id="claude")
   282→        try:
   283→            whoami_path = os.path.join(mountpoint, ".mirage", "whoami")
   284→            assert os.path.exists(whoami_path)
   285→            content = open(whoami_path).read()
   286→            assert "agent: claude" in content
   287→        finally:
   288→            if sys.platform == "darwin":
   289→                subprocess.run(["diskutil", "unmount", "force", mountpoint],
   290→                               capture_output=True)
   291→            else:
   292→                subprocess.run(["fusermount", "-u", mountpoint],
   293→                               capture_output=True)
   294→            t.join(timeout=3)
   295→


     1→from unittest.mock import MagicMock
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.fuse.remote.remote import RemoteFuseOp
     5→from mirage.types import MountMode, SyncPolicy
     6→from mirage.workspace import Workspace
     7→
     8→
     9→def test_remote_on_open_sends_file():
    10→    mock_client = MagicMock()
    11→    mock_client.on_open.return_value = {"eager": True, "buffer": b"processed"}
    12→
    13→    ws = Workspace({"/data/": MemoryBackend()},
    14→                   mode=MountMode.WRITE,
    15→                   sync_policy=SyncPolicy.IMMEDIATE)
    16→    ws.write("/data/test.txt", b"hello world")
    17→
    18→    op = RemoteFuseOp(client=mock_client)
    19→    ctx = op.on_open(ws, "/data/test.txt", 11)
    20→    assert ctx.get("eager") is True
    21→
    22→    call_kwargs = mock_client.on_open.call_args
    23→    assert call_kwargs.kwargs.get("data") == b"hello world"
    24→    assert call_kwargs.kwargs.get("checksum") is not None
    25→
    26→
    27→def test_remote_read_delegates():
    28→    mock_client = MagicMock()
    29→    mock_client.read.return_value = b"partial"
    30→
    31→    ws = Workspace({"/data/": MemoryBackend()})
    32→    op = RemoteFuseOp(client=mock_client)
    33→    result = op.read(ws, "/data/test.txt", 0, 7, {"eager": False})
    34→    assert result == b"partial"
    35→    mock_client.read.assert_called_once()
    36→
    37→
    38→def test_remote_on_flush_writes_back():
    39→    mock_client = MagicMock()
    40→    mock_client.on_flush.return_value = b"final output"
    41→
    42→    ws = Workspace({"/data/": MemoryBackend()},
    43→                   mode=MountMode.WRITE,
    44→                   sync_policy=SyncPolicy.IMMEDIATE)
    45→    ws.write("/data/test.txt", b"initial")
    46→
    47→    op = RemoteFuseOp(client=mock_client)
    48→    ctx = {"writes": [(0, b"some data")]}
    49→    op.on_flush(ws, "/data/test.txt", ctx)
    50→    mock_client.on_flush.assert_called_once()
    51→    result = ws.cat("/data/test.txt")
    52→    assert result == b"final output"
    53→
    54→
    55→def test_remote_write_delegates():
    56→    mock_client = MagicMock()
    57→    mock_client.write.return_value = 5
    58→
    59→    ws = Workspace({"/data/": MemoryBackend()})
    60→    op = RemoteFuseOp(client=mock_client)
    61→    result = op.write(ws, "/data/test.txt", 0, b"hello", {})
    62→    assert result == 5
    63→    mock_client.write.assert_called_once()
    64→
    65→
    66→def test_remote_on_open_empty_file():
    67→    mock_client = MagicMock()
    68→    mock_client.on_open.return_value = {"eager": True}
    69→
    70→    ws = Workspace({"/data/": MemoryBackend()})
    71→    op = RemoteFuseOp(client=mock_client)
    72→    ctx = op.on_open(ws, "/data/empty.txt", 0)
    73→    assert ctx.get("eager") is True
    74→    call_kwargs = mock_client.on_open.call_args
    75→    assert call_kwargs.kwargs.get("data") == b""
    76→


     1→from mirage.fuse import FUSE_OPS, FuseOp
     2→from mirage.fuse.registry import FuseOpRegistry
     3→
     4→
     5→class ParquetOp(FuseOp):
     6→    name = "parquet"
     7→
     8→
     9→def test_fuse_ops_dict_has_default():
    10→    assert "default" in FUSE_OPS
    11→
    12→
    13→def test_fuse_ops_dict_has_all_backends():
    14→    assert "s3" in FUSE_OPS
    15→    assert "memory" in FUSE_OPS
    16→    assert "local" in FUSE_OPS
    17→
    18→
    19→def test_registry_resolve_default():
    20→    reg = FuseOpRegistry()
    21→    op = reg.resolve("/data/file.txt", 100)
    22→    assert op.name == "default"
    23→
    24→
    25→def test_registry_resolve_custom():
    26→    reg = FuseOpRegistry()
    27→    reg.register("*.parquet", ParquetOp())
    28→    op = reg.resolve("/data/file.parquet", 100)
    29→    assert op.name == "parquet"
    30→
    31→
    32→def test_registry_first_match_wins():
    33→    reg = FuseOpRegistry()
    34→    reg.register("*.parquet", ParquetOp())
    35→    reg.register("*", FuseOp())
    36→    op = reg.resolve("/data/file.parquet", 100)
    37→    assert op.name == "parquet"
    38→
    39→
    40→def test_registry_list_ops():
    41→    reg = FuseOpRegistry()
    42→    reg.register("*.csv", FuseOp())
    43→    ops = reg.list_ops()
    44→    assert len(ops) >= 2
    45→    patterns = [o["pattern"] for o in ops]
    46→    assert "*.csv" in patterns
    47→    assert "*" in patterns
    48→
    49→
    50→def test_registry_set_default():
    51→    reg = FuseOpRegistry()
    52→    reg.set_default(ParquetOp())
    53→    op = reg.resolve("/anything.txt", 100)
    54→    assert op.name == "parquet"
    55→
    56→
    57→def test_fuse_ops_dict_has_format_ops():
    58→    assert "parquet" in FUSE_OPS
    59→    assert "csv" in FUSE_OPS
    60→
    61→
    62→def test_registry_backend_aware_memory():
    63→    from mirage.backend.local.memory import MemoryBackend
    64→    from mirage.types import MountMode
    65→    from mirage.workspace import Workspace
    66→
    67→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    68→    reg = FuseOpRegistry()
    69→    op = reg.resolve("/file.txt", 100, ws)
    70→    assert op.name == "memory"
    71→
    72→
    73→def test_registry_backend_aware_local():
    74→    import tempfile
    75→
    76→    from mirage.backend.local.local import LocalBackend
    77→    from mirage.types import MountMode
    78→    from mirage.workspace import Workspace
    79→
    80→    with tempfile.TemporaryDirectory() as d:
    81→        ws = Workspace({"/": LocalBackend(root=d)}, mode=MountMode.READ)
    82→        reg = FuseOpRegistry()
    83→        op = reg.resolve("/file.txt", 100, ws)
    84→        assert op.name == "local"
    85→
    86→
    87→def test_registry_pattern_overrides_backend():
    88→    from mirage.backend.local.memory import MemoryBackend
    89→    from mirage.types import MountMode
    90→    from mirage.workspace import Workspace
    91→
    92→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    93→    reg = FuseOpRegistry()
    94→    reg.register("*.txt", FUSE_OPS["s3"])
    95→    op = reg.resolve("/file.txt", 100, ws)
    96→    assert op.name == "s3"
    97→
    98→
    99→def test_workspace_read_partial():
   100→    from mirage.backend.local.memory import MemoryBackend
   101→    from mirage.types import MountMode
   102→    from mirage.workspace import Workspace
   103→
   104→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
   105→    ws.write("/f.txt", b"hello world")
   106→    chunk = ws.read("/f.txt", 0, 5)
   107→    assert chunk == b"hello"
   108→
   109→
   110→def test_workspace_read_full():
   111→    from mirage.backend.local.memory import MemoryBackend
   112→    from mirage.types import MountMode
   113→    from mirage.workspace import Workspace
   114→
   115→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
   116→    ws.write("/f.txt", b"hello")
   117→    data = ws.read("/f.txt", 0, 5)
   118→    assert data == b"hello"
   119→


     1→import os
     2→import tempfile
     3→
     4→import pytest
     5→
     6→from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class
     7→
     8→
     9→def test_load_builtin_module():
    10→    cls = load_fuse_op_class("mirage.fuse.backend.default:DefaultFuseOp")
    11→    op = cls()
    12→    assert op.name == "default"
    13→
    14→
    15→def test_load_from_file():
    16→    code = ("from mirage.fuse import FuseOp\n"
    17→            "class CustomOp(FuseOp):\n"
    18→            "    name = 'custom_test'\n")
    19→    with tempfile.NamedTemporaryFile(suffix=".py", mode="w",
    20→                                     delete=False) as f:
    21→        f.write(code)
    22→        f.flush()
    23→        cls = load_fuse_op_class(f"{f.name}:CustomOp")
    24→    op = cls()
    25→    assert op.name == "custom_test"
    26→    os.unlink(f.name)
    27→
    28→
    29→def test_invalid_spec():
    30→    with pytest.raises(ValueError, match="invalid"):
    31→        load_fuse_op_class("no_colon_here")
    32→
    33→
    34→def test_build_registry_builtin():
    35→    registry = build_registry_from_config({"*.parquet": "default"})
    36→    op = registry.resolve("/data/file.parquet", 100)
    37→    assert op.name == "default"
    38→
    39→
    40→def test_build_registry_custom_class():
    41→    registry = build_registry_from_config({
    42→        "*.txt":
    43→        "mirage.fuse.backend.local.memory:MemoryFuseOp",
    44→    })
    45→    op = registry.resolve("/data/file.txt", 100)
    46→    assert op.name == "memory"
    47→
    48→
    49→def test_build_registry_dict_form():
    50→    registry = build_registry_from_config({
    51→        "*.bin": {
    52→            "handler": "s3"
    53→        },
    54→    })
    55→    op = registry.resolve("/data/file.bin", 100)
    56→    assert op.name == "s3"
    57→
    58→
    59→def test_build_registry_empty():
    60→    registry = build_registry_from_config({})
    61→    op = registry.resolve("/data/file.txt", 100)
    62→    assert op.name == "default"
    63→


     1→import io
     2→
     3→import pyarrow as pa
     4→import pyarrow.parquet as pq
     5→
     6→from mirage.backend.local.memory import MemoryBackend
     7→from mirage.fuse.filetype.csv import CsvFuseOp
     8→from mirage.fuse.filetype.parquet import ParquetFuseOp
     9→from mirage.types import MountMode
    10→from mirage.workspace import Workspace
    11→
    12→
    13→def _make_parquet_bytes() -> bytes:
    14→    table = pa.table({
    15→        "name": ["alice", "bob", "charlie"],
    16→        "score": [95, 87, 72],
    17→    })
    18→    buf = io.BytesIO()
    19→    pq.write_table(table, buf)
    20→    return buf.getvalue()
    21→
    22→
    23→def test_parquet_read_returns_text():
    24→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    25→    ws.write("/data.parquet", _make_parquet_bytes())
    26→    op = ParquetFuseOp()
    27→    ctx = op.on_open(ws, "/data.parquet", 1000)
    28→    data = op.read(ws, "/data.parquet", 0, 4096, ctx)
    29→    text = data.decode()
    30→    assert "name" in text
    31→    assert "score" in text
    32→    assert "alice" in text
    33→
    34→
    35→def test_parquet_schema_in_output():
    36→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    37→    ws.write("/data.parquet", _make_parquet_bytes())
    38→    op = ParquetFuseOp()
    39→    ctx = op.on_open(ws, "/data.parquet", 1000)
    40→    data = op.read(ws, "/data.parquet", 0, 8192, ctx)
    41→    text = data.decode()
    42→    assert "int" in text.lower()
    43→
    44→
    45→def test_parquet_row_count():
    46→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    47→    ws.write("/data.parquet", _make_parquet_bytes())
    48→    op = ParquetFuseOp()
    49→    ctx = op.on_open(ws, "/data.parquet", 1000)
    50→    data = op.read(ws, "/data.parquet", 0, 8192, ctx)
    51→    text = data.decode()
    52→    assert "Rows: 3" in text
    53→
    54→
    55→def test_parquet_error_handling():
    56→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    57→    ws.write("/bad.parquet", b"not a parquet file")
    58→    op = ParquetFuseOp()
    59→    ctx = op.on_open(ws, "/bad.parquet", 18)
    60→    data = op.read(ws, "/bad.parquet", 0, 4096, ctx)
    61→    text = data.decode()
    62→    assert "Error" in text
    63→
    64→
    65→def test_csv_small_passthrough():
    66→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    67→    small = b"name,score\nalice,95\nbob,87\n"
    68→    ws.write("/small.csv", small)
    69→    op = CsvFuseOp()
    70→    ctx = op.on_open(ws, "/small.csv", len(small))
    71→    data = op.read(ws, "/small.csv", 0, 4096, ctx)
    72→    assert data == small
    73→
    74→
    75→def test_csv_large_preview():
    76→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    77→    header = "name,score\n"
    78→    rows = "".join(f"user{i},{i}\n" for i in range(5000))
    79→    big = (header + rows).encode()
    80→    ws.write("/big.csv", big)
    81→    op = CsvFuseOp()
    82→    ctx = op.on_open(ws, "/big.csv", len(big))
    83→    data = op.read(ws, "/big.csv", 0, 8192, ctx)
    84→    text = data.decode()
    85→    assert "name" in text
    86→    assert "score" in text
    87→    assert "5001 rows" in text
    88→    assert "2 columns" in text
    89→
    90→
    91→def test_csv_large_has_preview_marker():
    92→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    93→    header = "a,b,c\n"
    94→    rows = "".join(f"{i},{i+1},{i+2}\n" for i in range(5000))
    95→    big = (header + rows).encode()
    96→    ws.write("/big.csv", big)
    97→    op = CsvFuseOp()
    98→    ctx = op.on_open(ws, "/big.csv", len(big))
    99→    data = op.read(ws, "/big.csv", 0, 8192, ctx)
   100→    text = data.decode()
   101→    assert "more rows" in text
   102→


     1→from mirage.backend.local.memory import MemoryBackend
     2→from mirage.fuse.backend.default import DefaultFuseOp
     3→from mirage.fuse.backend.local.memory import MemoryFuseOp
     4→from mirage.types import MountMode
     5→from mirage.workspace import Workspace
     6→
     7→
     8→def test_default_eager_small_file():
     9→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    10→    ws.write("/f.txt", b"hello world")
    11→    op = DefaultFuseOp()
    12→    ctx = op.on_open(ws, "/f.txt", 11)
    13→    assert ctx["eager"] is True
    14→    data = op.read(ws, "/f.txt", 0, 5, ctx)
    15→    assert data == b"hello"
    16→
    17→
    18→def test_default_lazy_large_file():
    19→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    20→    big = b"x" * (2 * 1024 * 1024)
    21→    ws.write("/big.bin", big)
    22→    op = DefaultFuseOp()
    23→    ctx = op.on_open(ws, "/big.bin", len(big))
    24→    assert ctx["eager"] is False
    25→    chunk = op.read(ws, "/big.bin", 0, 100, ctx)
    26→    assert chunk == b"x" * 100
    27→
    28→
    29→def test_memory_always_eager():
    30→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    31→    big = b"y" * (2 * 1024 * 1024)
    32→    ws.write("/big.bin", big)
    33→    op = MemoryFuseOp()
    34→    ctx = op.on_open(ws, "/big.bin", len(big))
    35→    assert ctx["eager"] is True
    36→
    37→
    38→def test_buffered_write_flush():
    39→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    40→    ws.write("/f.txt", b"hello world")
    41→    op = DefaultFuseOp()
    42→    ctx = op.on_open(ws, "/f.txt", 11)
    43→    op.write(ws, "/f.txt", 0, b"HELLO", ctx)
    44→    op.write(ws, "/f.txt", 6, b"WORLD", ctx)
    45→    op.on_flush(ws, "/f.txt", ctx)
    46→    assert ws.cat("/f.txt") == b"HELLO WORLD"
    47→
    48→
    49→def test_buffered_write_new_file():
    50→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    51→    op = DefaultFuseOp()
    52→    ctx = op.on_open(ws, "/new.txt", 0)
    53→    op.write(ws, "/new.txt", 0, b"brand new", ctx)
    54→    op.on_flush(ws, "/new.txt", ctx)
    55→    assert ws.cat("/new.txt") == b"brand new"
    56→
    57→
    58→def test_flush_noop_without_writes():
    59→    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    60→    ws.write("/f.txt", b"original")
    61→    op = DefaultFuseOp()
    62→    ctx = op.on_open(ws, "/f.txt", 8)
    63→    op.on_flush(ws, "/f.txt", ctx)
    64→    assert ws.cat("/f.txt") == b"original"
    65→


     1→import pytest
     2→from pydantic import ValidationError
     3→
     4→from mirage.types import (CommandStats, FileStat, Inode, SyncResult,
     5→                          TransferStats)
     6→
     7→
     8→def test_filestat_defaults():
     9→    fs = FileStat(name="foo.txt")
    10→    assert fs.name == "foo.txt"
    11→    assert fs.size is None
    12→    assert fs.extra == {}
    13→
    14→
    15→def test_filestat_immutable():
    16→    fs = FileStat(name="foo.txt")
    17→    with pytest.raises(ValidationError):
    18→        fs.name = "bar.txt"
    19→
    20→
    21→def test_inode_defaults():
    22→    inode = Inode(virtual_path="/a/b.txt", backend_mount="/a/")
    23→    assert inode.dirty is False
    24→    assert inode.checksum is None
    25→
    26→
    27→def test_inode_mutable():
    28→    inode = Inode(virtual_path="/a/b.txt", backend_mount="/a/")
    29→    inode.dirty = True
    30→    assert inode.dirty is True
    31→
    32→
    33→def test_syncresult_fields():
    34→    r = SyncResult(path="/a/b.txt", op="create", status="ok")
    35→    assert r.op == "create"
    36→    assert r.duration_ms == 0
    37→
    38→
    39→def test_syncresult_immutable():
    40→    r = SyncResult(path="/a/b.txt", op="create", status="ok")
    41→    with pytest.raises(ValidationError):
    42→        r.status = "error"
    43→
    44→
    45→def test_transferstats_mutable():
    46→    ts = TransferStats()
    47→    ts.bytes_read += 100
    48→    assert ts.bytes_read == 100
    49→
    50→
    51→def test_commandstats_defaults():
    52→    cs = CommandStats()
    53→    assert cs.total_commands == 0
    54→    assert cs.command_counts == {}
    55→


     1→from mirage.types import (BackendType, CommandResult, FilePayload,
     2→                          RemoteCommandOptions)
     3→
     4→
     5→def test_command_result_defaults():
     6→    r = CommandResult(stdout="hello")
     7→    assert r.stdout == "hello"
     8→    assert r.output_files == {}
     9→
    10→
    11→def test_command_result_with_files():
    12→    r = CommandResult(stdout="done", output_files={"/out.csv": b"a,b"})
    13→    assert r.output_files["/out.csv"] == b"a,b"
    14→
    15→
    16→def test_file_payload_with_data():
    17→    p = FilePayload(checksum="abc123", data=b"hello")
    18→    assert p.checksum == "abc123"
    19→    assert p.data == b"hello"
    20→
    21→
    22→def test_file_payload_checksum_only():
    23→    p = FilePayload(checksum="abc123")
    24→    assert p.data is None
    25→
    26→
    27→def test_remote_command_options_defaults():
    28→    opts = RemoteCommandOptions()
    29→    assert opts.max_file_size == "100MB"
    30→    assert opts.cache == "enabled"
    31→
    32→
    33→def test_remote_command_options_custom():
    34→    opts = RemoteCommandOptions(max_file_size="500MB", cache="disabled")
    35→    assert opts.max_file_size == "500MB"
    36→    assert opts.cache == "disabled"
    37→
    38→
    39→def test_remote_command_options_only_two_fields():
    40→    opts = RemoteCommandOptions()
    41→    assert opts.max_file_size == "100MB"
    42→    assert opts.cache == "enabled"
    43→    assert set(RemoteCommandOptions.model_fields.keys()) == {
    44→        "max_file_size", "cache"
    45→    }
    46→
    47→
    48→def test_backend_type_values():
    49→    assert BackendType.MEMORY == "memory"
    50→    assert BackendType.LOCAL == "local"
    51→    assert BackendType.S3 == "s3"
    52→    assert BackendType.R2 == "r2"
    53→    assert BackendType.HTTP == "http"
    54→    assert BackendType.HTTPS == "https"
    55→
    56→
    57→def test_backend_type_string_equality():
    58→    assert BackendType.MEMORY.value == "memory"
    59→    assert BackendType.LOCAL.value == "local"
    60→    assert BackendType.S3.value == "s3"
    61→    assert BackendType.R2.value == "r2"
    62→    assert BackendType.HTTP.value == "http"
    63→    assert BackendType.HTTPS.value == "https"
    64→    assert BackendType("memory") is BackendType.MEMORY
    65→    assert BackendType("s3") is BackendType.S3
    66→
    67→
    68→def test_backend_type_members():
    69→    assert set(BackendType) == {
    70→        BackendType.MEMORY,
    71→        BackendType.LOCAL,
    72→        BackendType.S3,
    73→        BackendType.R2,
    74→        BackendType.HTTP,
    75→        BackendType.HTTPS,
    76→    }
    77→


     1→import time
     2→
     3→from mirage.workspace.cache.entry import CacheEntry
     4→
     5→
     6→def test_cache_entry_creation():
     7→    entry = CacheEntry(data=b"hello", cached_at=time.monotonic())
     8→    assert entry.data == b"hello"
     9→    assert entry.etag is None
    10→    assert entry.size is None
    11→
    12→
    13→def test_cache_entry_mutable():
    14→    entry = CacheEntry(data=b"x", cached_at=100.0)
    15→    entry.cached_at -= 20
    16→    assert entry.cached_at == 80.0
    17→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.types import MountMode, SyncPolicy
     5→from mirage.workspace import Workspace
     6→
     7→
     8→def test_lock_paths_blocks_write():
     9→    ws = Workspace({"/data/": MemoryBackend()},
    10→                   mode=MountMode.WRITE,
    11→                   sync_policy=SyncPolicy.IMMEDIATE)
    12→    ws.write("/data/file.txt", b"hello")
    13→    with ws.lock_paths(["/data/file.txt"]):
    14→        with pytest.raises(PermissionError, match="locked"):
    15→            ws.write("/data/file.txt", b"world")
    16→
    17→
    18→def test_lock_paths_unlocks_on_exit():
    19→    ws = Workspace({"/data/": MemoryBackend()},
    20→                   mode=MountMode.WRITE,
    21→                   sync_policy=SyncPolicy.IMMEDIATE)
    22→    ws.write("/data/file.txt", b"hello")
    23→    with ws.lock_paths(["/data/file.txt"]):
    24→        pass
    25→    ws.write("/data/file.txt", b"world")
    26→    assert ws.cat("/data/file.txt") == b"world"
    27→
    28→
    29→def test_lock_paths_unlocks_on_exception():
    30→    ws = Workspace({"/data/": MemoryBackend()},
    31→                   mode=MountMode.WRITE,
    32→                   sync_policy=SyncPolicy.IMMEDIATE)
    33→    ws.write("/data/file.txt", b"hello")
    34→    with pytest.raises(RuntimeError):
    35→        with ws.lock_paths(["/data/file.txt"]):
    36→            raise RuntimeError("boom")
    37→    ws.write("/data/file.txt", b"world")
    38→    assert ws.cat("/data/file.txt") == b"world"
    39→
    40→
    41→def test_lock_allows_read():
    42→    ws = Workspace({"/data/": MemoryBackend()},
    43→                   mode=MountMode.WRITE,
    44→                   sync_policy=SyncPolicy.IMMEDIATE)
    45→    ws.write("/data/file.txt", b"hello")
    46→    with ws.lock_paths(["/data/file.txt"]):
    47→        assert ws.cat("/data/file.txt") == b"hello"
    48→
    49→
    50→def test_lock_multiple_paths():
    51→    ws = Workspace({"/data/": MemoryBackend()},
    52→                   mode=MountMode.WRITE,
    53→                   sync_policy=SyncPolicy.IMMEDIATE)
    54→    ws.write("/data/a.txt", b"a")
    55→    ws.write("/data/b.txt", b"b")
    56→    with ws.lock_paths(["/data/a.txt", "/data/b.txt"]):
    57→        with pytest.raises(PermissionError, match="locked"):
    58→            ws.write("/data/a.txt", b"x")
    59→        with pytest.raises(PermissionError, match="locked"):
    60→            ws.write("/data/b.txt", b"x")
    61→
    62→
    63→def test_unlock_paths_force():
    64→    ws = Workspace({"/data/": MemoryBackend()},
    65→                   mode=MountMode.WRITE,
    66→                   sync_policy=SyncPolicy.IMMEDIATE)
    67→    ws.write("/data/file.txt", b"hello")
    68→    ws._lock_path("/data/file.txt")
    69→    with pytest.raises(PermissionError, match="locked"):
    70→        ws.write("/data/file.txt", b"world")
    71→    ws.unlock_paths(["/data/file.txt"])
    72→    ws.write("/data/file.txt", b"world")
    73→


     1→from datetime import datetime, timezone
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.types import ConsistencyPolicy, MountMode
     5→from mirage.workspace import Workspace
     6→
     7→
     8→def _make_ws(consistency=ConsistencyPolicy.NONE, ttl_seconds=300):
     9→    backend = MemoryBackend()
    10→    backend.put("/file.txt", b"original")
    11→    ws = Workspace(
    12→        {"/data/": backend},
    13→        mode=MountMode.WRITE,
    14→        consistency=consistency,
    15→        ttl_seconds=ttl_seconds,
    16→    )
    17→    return ws, backend
    18→
    19→
    20→def test_consistency_none_caches_forever():
    21→    ws, backend = _make_ws(ConsistencyPolicy.NONE)
    22→    assert ws.cat("/data/file.txt") == b"original"
    23→    backend._files["/file.txt"] = b"updated"
    24→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    25→    assert ws.cat("/data/file.txt") == b"original"
    26→
    27→
    28→def test_consistency_always_refetches():
    29→    ws, backend = _make_ws(ConsistencyPolicy.ALWAYS)
    30→    assert ws.cat("/data/file.txt") == b"original"
    31→    backend._files["/file.txt"] = b"updated"
    32→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    33→    assert ws.cat("/data/file.txt") == b"updated"
    34→
    35→
    36→def test_consistency_ttl_fresh_within_window():
    37→    ws, backend = _make_ws(ConsistencyPolicy.TTL, ttl_seconds=300)
    38→    assert ws.cat("/data/file.txt") == b"original"
    39→    backend._files["/file.txt"] = b"updated"
    40→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    41→    assert ws.cat("/data/file.txt") == b"original"
    42→
    43→
    44→def test_consistency_ttl_stale_after_expiry():
    45→    ws, backend = _make_ws(ConsistencyPolicy.TTL, ttl_seconds=10)
    46→    assert ws.cat("/data/file.txt") == b"original"
    47→    backend._files["/file.txt"] = b"updated"
    48→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    49→    entry = ws._cache.get("/data/file.txt")
    50→    entry.cached_at -= 20
    51→    assert ws.cat("/data/file.txt") == b"updated"
    52→
    53→
    54→def test_consistency_lazy_unchanged():
    55→    ws, backend = _make_ws(ConsistencyPolicy.LAZY)
    56→    assert ws.cat("/data/file.txt") == b"original"
    57→    assert ws.cat("/data/file.txt") == b"original"
    58→
    59→
    60→def test_consistency_lazy_changed():
    61→    ws, backend = _make_ws(ConsistencyPolicy.LAZY)
    62→    assert ws.cat("/data/file.txt") == b"original"
    63→    backend._files["/file.txt"] = b"updated-content"
    64→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    65→    assert ws.cat("/data/file.txt") == b"updated-content"
    66→
    67→
    68→def test_dirty_file_skips_revalidation():
    69→    ws, backend = _make_ws(ConsistencyPolicy.ALWAYS)
    70→    ws.write("/data/file.txt", b"local-edit")
    71→    backend._files["/file.txt"] = b"remote-change"
    72→    assert ws.cat("/data/file.txt") == b"local-edit"
    73→
    74→
    75→def test_per_mount_consistency():
    76→    be_none = MemoryBackend()
    77→    be_none.put("/f.txt", b"v1")
    78→    be_always = MemoryBackend()
    79→    be_always.put("/f.txt", b"v1")
    80→    ws = Workspace(
    81→        {
    82→            "/a/": be_none,
    83→            "/b/": (be_always, MountMode.READ, ConsistencyPolicy.ALWAYS),
    84→        },
    85→        mode=MountMode.READ,
    86→        consistency=ConsistencyPolicy.NONE,
    87→    )
    88→    assert ws.cat("/a/f.txt") == b"v1"
    89→    assert ws.cat("/b/f.txt") == b"v1"
    90→    be_none._files["/f.txt"] = b"v2"
    91→    be_none._modified["/f.txt"] = datetime.now(timezone.utc).isoformat()
    92→    be_always._files["/f.txt"] = b"v2"
    93→    be_always._modified["/f.txt"] = datetime.now(timezone.utc).isoformat()
    94→    assert ws.cat("/a/f.txt") == b"v1"
    95→    assert ws.cat("/b/f.txt") == b"v2"
    96→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.workspace.sync import DirtyTracker
     5→
     6→
     7→@pytest.fixture
     8→def tracker():
     9→    return DirtyTracker()
    10→
    11→
    12→@pytest.fixture
    13→def backend():
    14→    return MemoryBackend()
    15→
    16→
    17→def test_on_read_records_clean_inode(tracker):
    18→    tracker.on_read("/f.txt", b"data", "/tmp/")
    19→    inode = tracker.get("/f.txt")
    20→    assert inode is not None
    21→    assert inode.dirty is False
    22→    assert inode.checksum is not None
    23→
    24→
    25→def test_on_read_updates_existing(tracker):
    26→    tracker.on_read("/f.txt", b"old", "/tmp/")
    27→    tracker.on_read("/f.txt", b"new", "/tmp/")
    28→    inode = tracker.get("/f.txt")
    29→    assert inode.size == 3
    30→
    31→
    32→def test_on_write_new_file_marks_dirty(tracker):
    33→    tracker.on_write("/f.txt", b"data", "/tmp/")
    34→    inode = tracker.get("/f.txt")
    35→    assert inode.dirty is True
    36→    assert inode.remote_checksum is None
    37→
    38→
    39→def test_on_write_existing_file_updates_checksum(tracker):
    40→    tracker.on_read("/f.txt", b"old", "/tmp/")
    41→    old_checksum = tracker.get("/f.txt").checksum
    42→    tracker.on_write("/f.txt", b"new data", "/tmp/")
    43→    inode = tracker.get("/f.txt")
    44→    assert inode.dirty is True
    45→    assert inode.checksum != old_checksum
    46→    assert inode.size == 8
    47→
    48→
    49→def test_on_delete_marks_dirty_null_checksum(tracker):
    50→    tracker.on_read("/f.txt", b"data", "/tmp/")
    51→    tracker.on_delete("/f.txt")
    52→    inode = tracker.get("/f.txt")
    53→    assert inode.dirty is True
    54→    assert inode.checksum is None
    55→    assert inode.size is None
    56→
    57→
    58→def test_on_sync_uploads_data(tracker, backend):
    59→    tracker.on_write("/f.txt", b"hello", "/tmp/")
    60→    backend.create("/f.txt")
    61→    result = tracker.on_sync("/f.txt", "/f.txt", b"hello", backend)
    62→    assert result.status == "ok"
    63→    assert result.bytes == 5
    64→
    65→
    66→def test_on_sync_clears_dirty_flag(tracker, backend):
    67→    tracker.on_write("/f.txt", b"hello", "/tmp/")
    68→    backend.create("/f.txt")
    69→    tracker.on_sync("/f.txt", "/f.txt", b"hello", backend)
    70→    inode = tracker.get("/f.txt")
    71→    assert inode.dirty is False
    72→
    73→
    74→def test_on_sync_delete_calls_unlink(tracker, backend):
    75→    backend.create("/f.txt")
    76→    tracker.on_read("/f.txt", b"data", "/tmp/")
    77→    tracker.on_delete("/f.txt")
    78→    result = tracker.on_sync("/f.txt", "/f.txt", None, backend)
    79→    assert result.status == "ok"
    80→    assert result.op == "delete"
    81→
    82→
    83→def test_on_sync_error_returns_error_result(tracker):
    84→
    85→    class FailingBackend:
    86→
    87→        def put(self, path, data):
    88→            raise RuntimeError("upload failed")
    89→
    90→        def unlink(self, path):
    91→            raise RuntimeError("delete failed")
    92→
    93→    tracker.on_write("/f.txt", b"data", "/tmp/")
    94→    result = tracker.on_sync("/f.txt", "/f.txt", b"data", FailingBackend())
    95→    assert result.status == "error"
    96→    assert result.error is not None
    97→
    98→
    99→def test_dirty_returns_only_dirty_inodes(tracker):
   100→    tracker.on_read("/clean.txt", b"data", "/tmp/")
   101→    tracker.on_write("/dirty.txt", b"data", "/tmp/")
   102→    dirty = tracker.dirty()
   103→    assert len(dirty) == 1
   104→    assert dirty[0].virtual_path == "/dirty.txt"
   105→
   106→
   107→def test_status_returns_all_inodes(tracker):
   108→    tracker.on_read("/a.txt", b"a", "/tmp/")
   109→    tracker.on_write("/b.txt", b"b", "/tmp/")
   110→    assert len(tracker.status()) == 2
   111→
   112→
   113→def test_get_existing_inode(tracker):
   114→    tracker.on_write("/f.txt", b"x", "/tmp/")
   115→    assert tracker.get("/f.txt") is not None
   116→
   117→
   118→def test_get_missing_returns_none(tracker):
   119→    assert tracker.get("/nope.txt") is None
   120→
   121→
   122→def test_reset_clears_all(tracker):
   123→    tracker.on_write("/f.txt", b"data", "/tmp/")
   124→    tracker.reset()
   125→    assert tracker.status() == []
   126→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.types import MountMode, SyncPolicy
     5→from mirage.workspace import Workspace
     6→
     7→
     8→def _make_ws(mode=MountMode.WRITE, sync_policy=SyncPolicy.STAGED):
     9→    backend = MemoryBackend()
    10→    return Workspace({"/data/": backend}, mode=mode,
    11→                     sync_policy=sync_policy), backend
    12→
    13→
    14→def test_write_and_cat():
    15→    ws, _ = _make_ws()
    16→    ws.write("/data/hello.txt", b"hello world")
    17→    assert ws.cat("/data/hello.txt") == b"hello world"
    18→
    19→
    20→def test_write_marks_dirty():
    21→    ws, _ = _make_ws()
    22→    ws.write("/data/file.txt", b"x")
    23→    assert any(i.virtual_path == "/data/file.txt" and i.dirty
    24→               for i in ws.status())
    25→
    26→
    27→def test_rm_staged_only():
    28→    ws, _ = _make_ws()
    29→    ws.write("/data/new.txt", b"staged")
    30→    ws.rm("/data/new.txt")
    31→    assert not ws.exists("/data/new.txt")
    32→
    33→
    34→def test_rm_persisted():
    35→    ws, backend = _make_ws()
    36→    backend.put("/remote.txt", b"remote")
    37→    ws.rm("/data/remote.txt")
    38→    assert not ws.exists("/data/remote.txt")
    39→
    40→
    41→def test_mv_staged_only():
    42→    ws, _ = _make_ws()
    43→    ws.write("/data/a.txt", b"content")
    44→    ws.mv("/data/a.txt", "/data/b.txt")
    45→    assert ws.cat("/data/b.txt") == b"content"
    46→    assert not ws.exists("/data/a.txt")
    47→
    48→
    49→def test_cp_creates_copy():
    50→    ws, backend = _make_ws()
    51→    backend.put("/src.txt", b"original")
    52→    ws.cp("/data/src.txt", "/data/copy.txt")
    53→    assert ws.cat("/data/copy.txt") == b"original"
    54→
    55→
    56→def test_write_read_only_raises():
    57→    ws, _ = _make_ws(mode=MountMode.READ)
    58→    with pytest.raises(PermissionError):
    59→        ws.write("/data/x.txt", b"x")
    60→
    61→
    62→def test_sync_staged_writes_to_backend():
    63→    ws, backend = _make_ws(sync_policy=SyncPolicy.STAGED)
    64→    ws.write("/data/out.txt", b"synced")
    65→    results = ws.sync()
    66→    assert any(r.path == "/data/out.txt" and r.status == "ok" for r in results)
    67→    assert backend.cat("/out.txt") == b"synced"
    68→
    69→
    70→def test_sync_immediate_writes_through():
    71→    ws, backend = _make_ws(sync_policy=SyncPolicy.IMMEDIATE)
    72→    ws.write("/data/live.txt", b"live data")
    73→    assert backend.cat("/live.txt") == b"live data"
    74→    assert not any(i.dirty for i in ws.status())
    75→
    76→
    77→def test_reset_clears_dirty():
    78→    ws, _ = _make_ws()
    79→    ws.write("/data/x.txt", b"x")
    80→    ws.reset()
    81→    assert ws.status() == []
    82→
    83→
    84→def test_stat_staged_file():
    85→    ws, _ = _make_ws()
    86→    ws.write("/data/stat.txt", b"hello")
    87→    stat = ws.stat("/data/stat.txt")
    88→    assert stat.size == 5
    89→
    90→
    91→def test_exists_staged_file():
    92→    ws, _ = _make_ws()
    93→    ws.write("/data/exists.txt", b"y")
    94→    assert ws.exists("/data/exists.txt")
    95→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.types import MountMode
     5→from mirage.workspace import Workspace
     6→from mirage.workspace.mount import MountRegistry
     7→
     8→
     9→def test_duplicate_prefix_raises():
    10→    reg = MountRegistry()
    11→    reg.mount("/data/", MemoryBackend())
    12→    with pytest.raises(ValueError, match="duplicate mount prefix"):
    13→        reg.mount("/data/", MemoryBackend())
    14→
    15→
    16→def test_duplicate_different_normalization():
    17→    reg = MountRegistry()
    18→    reg.mount("/data", MemoryBackend())
    19→    with pytest.raises(ValueError, match="duplicate mount prefix"):
    20→        reg.mount("/data/", MemoryBackend())
    21→
    22→
    23→def test_nested_prefixes_allowed():
    24→    reg = MountRegistry()
    25→    reg.mount("/data/", MemoryBackend())
    26→    reg.mount("/data/sub/", MemoryBackend())
    27→    assert len(reg.mounts()) == 2
    28→
    29→
    30→def test_per_mount_mode_resolve():
    31→    reg = MountRegistry()
    32→    reg.mount("/ro/", MemoryBackend(), mode=MountMode.READ)
    33→    reg.mount("/rw/", MemoryBackend(), mode=MountMode.WRITE)
    34→    _, _, mode_ro = reg.resolve("/ro/file.txt")
    35→    _, _, mode_rw = reg.resolve("/rw/file.txt")
    36→    assert mode_ro == MountMode.READ
    37→    assert mode_rw == MountMode.WRITE
    38→
    39→
    40→def test_workspace_per_mount_mode():
    41→    ro_backend = MemoryBackend()
    42→    rw_backend = MemoryBackend()
    43→    ws = Workspace(
    44→        {
    45→            "/ro/": ro_backend,
    46→            "/rw/": (rw_backend, MountMode.WRITE),
    47→        },
    48→        mode=MountMode.READ,
    49→    )
    50→    ws.write("/rw/test.txt", b"hello")
    51→    assert ws.cat("/rw/test.txt") == b"hello"
    52→    with pytest.raises(PermissionError):
    53→        ws.write("/ro/test.txt", b"fail")
    54→
    55→
    56→def test_workspace_tuple_mode_override():
    57→    ws = Workspace(
    58→        {"/a/": (MemoryBackend(), MountMode.WRITE)},
    59→        mode=MountMode.READ,
    60→    )
    61→    ws.write("/a/test.txt", b"ok")
    62→    assert ws.cat("/a/test.txt") == b"ok"
    63→
    64→
    65→def test_workspace_duplicate_prefix_raises():
    66→    with pytest.raises(ValueError, match="duplicate mount prefix"):
    67→        Workspace({
    68→            "/data/": MemoryBackend(),
    69→            "/data": MemoryBackend(),
    70→        })
    71→


     1→from mirage.workspace.cache.store import LRUCache
     2→
     3→
     4→def test_cache_entry_eviction():
     5→    cache = LRUCache(limit=100)
     6→    cache.put("a", b"x" * 60)
     7→    cache.put("b", b"y" * 60)
     8→    assert cache.get("a") is None
     9→    entry_b = cache.get("b")
    10→    assert entry_b is not None
    11→    assert entry_b.data == b"y" * 60
    12→
    13→
    14→def test_cache_entry_metadata_stored():
    15→    cache = LRUCache()
    16→    cache.put("f", b"data", etag="e1", modified="m1", size=4)
    17→    entry = cache.get("f")
    18→    assert entry is not None
    19→    assert entry.data == b"data"
    20→    assert entry.etag == "e1"
    21→    assert entry.modified == "m1"
    22→    assert entry.size == 4
    23→    assert entry.cached_at > 0
    24→


     1→from mirage.types import FileStat
     2→from mirage.workspace.cache.entry import CacheEntry
     3→from mirage.workspace.cache.policy import metadata_matches
     4→
     5→
     6→def test_metadata_matches_etag_priority():
     7→    entry = CacheEntry(data=b"x",
     8→                       cached_at=0,
     9→                       etag="abc",
    10→                       modified="old",
    11→                       size=1)
    12→    remote = FileStat(name="f",
    13→                      size=999,
    14→                      modified="new",
    15→                      extra={"etag": "abc"})
    16→    assert metadata_matches(entry, remote) is True
    17→
    18→
    19→def test_metadata_matches_fallback_to_modified():
    20→    entry = CacheEntry(data=b"x", cached_at=0, modified="2024-01-01", size=1)
    21→    remote = FileStat(name="f", size=999, modified="2024-01-01")
    22→    assert metadata_matches(entry, remote) is True
    23→    remote2 = FileStat(name="f", size=999, modified="2024-02-01")
    24→    assert metadata_matches(entry, remote2) is False
    25→
    26→
    27→def test_metadata_matches_fallback_to_size():
    28→    entry = CacheEntry(data=b"x", cached_at=0, size=100)
    29→    remote = FileStat(name="f", size=100)
    30→    assert metadata_matches(entry, remote) is True
    31→    remote2 = FileStat(name="f", size=200)
    32→    assert metadata_matches(entry, remote2) is False
    33→


     1→import json
     2→
     3→from mirage.observe.entries import CommandEntry, SessionEntry
     4→from mirage.observe.session_log import SessionLog
     5→
     6→
     7→def test_write_session(tmp_path):
     8→    log = SessionLog("ws-test", base_dir=str(tmp_path))
     9→    entry = SessionEntry(timestamp="2026-01-01T00:00:00Z")
    10→    log.write_session(entry)
    11→    results = log.read_last(10)
    12→    assert len(results) == 1
    13→    assert results[0]["type"] == "session"
    14→
    15→
    16→def test_write_command_auto_id(tmp_path):
    17→    log = SessionLog("ws-test", base_dir=str(tmp_path))
    18→    log.write_command(CommandEntry(raw="ls /"))
    19→    log.write_command(CommandEntry(raw="cat /file"))
    20→    results = log.read_last(10)
    21→    assert len(results) == 2
    22→    assert results[0]["id"] == "c001"
    23→    assert results[1]["id"] == "c002"
    24→
    25→
    26→def test_write_command_explicit_id(tmp_path):
    27→    log = SessionLog("ws-test", base_dir=str(tmp_path))
    28→    log.write_command(CommandEntry(id="custom-1", raw="ls /"))
    29→    results = log.read_last(10)
    30→    assert results[0]["id"] == "custom-1"
    31→
    32→
    33→def test_read_last_limits(tmp_path):
    34→    log = SessionLog("ws-test", base_dir=str(tmp_path))
    35→    for i in range(10):
    36→        log.write_command(CommandEntry(raw=f"cmd {i}"))
    37→    results = log.read_last(3)
    38→    assert len(results) == 3
    39→    assert results[-1]["id"] == "c010"
    40→
    41→
    42→def test_read_last_empty(tmp_path):
    43→    log = SessionLog("ws-test", base_dir=str(tmp_path))
    44→    results = log.read_last(10)
    45→    assert results == []
    46→
    47→
    48→def test_jsonl_format(tmp_path):
    49→    log = SessionLog("ws-test", base_dir=str(tmp_path))
    50→    log.write_session(SessionEntry(timestamp="2026-01-01T00:00:00Z"))
    51→    log.write_command(CommandEntry(raw="ls /"))
    52→    lines = log.path.read_text().strip().splitlines()
    53→    assert len(lines) == 2
    54→    for line in lines:
    55→        json.loads(line)
    56→
    57→
    58→def test_session_dir_created(tmp_path):
    59→    log = SessionLog("ws-abc", base_dir=str(tmp_path))
    60→    assert (tmp_path / "ws-abc").is_dir()
    61→    assert log.path.parent == tmp_path / "ws-abc"
    62→


     1→from mirage.observe.entries import (CommandEntry, ParsedCommand, SessionEntry,
     2→                                    WorkspaceInfo)
     3→from mirage.types import RuntimeType
     4→
     5→
     6→def test_session_entry_unique_ids():
     7→    e1 = SessionEntry()
     8→    e2 = SessionEntry()
     9→    assert e1.id != e2.id
    10→    assert len(e1.id) == 32
    11→
    12→
    13→def test_session_entry_to_dict_no_workspace():
    14→    e = SessionEntry()
    15→    d = e.to_dict()
    16→    assert "workspace" not in d
    17→    assert d["type"] == "session"
    18→
    19→
    20→def test_session_entry_to_dict_with_workspace():
    21→    ws = WorkspaceInfo(mounts={"/data/": {}},
    22→                       cache_limit="512MB",
    23→                       runtime_type=RuntimeType.MEMORY)
    24→    e = SessionEntry(workspace=ws)
    25→    d = e.to_dict()
    26→    assert "workspace" in d
    27→    assert d["workspace"]["runtime_type"] == "memory"
    28→
    29→
    30→def test_command_entry_to_dict_excludes_none():
    31→    e = CommandEntry(id="abc", raw="ls /")
    32→    d = e.to_dict()
    33→    assert "parsed" not in d
    34→    assert "result" not in d
    35→
    36→
    37→def test_command_entry_to_dict_includes_parsed():
    38→    e = CommandEntry(id="abc",
    39→                     raw="ls /",
    40→                     parsed=ParsedCommand(pipeline=["ls"]))
    41→    d = e.to_dict()
    42→    assert "parsed" in d
    43→    assert d["parsed"]["pipeline"] == ["ls"]
    44→
    45→
    46→def test_workspace_info_runtime_type_enum():
    47→    ws = WorkspaceInfo(runtime_type=RuntimeType.LOCAL)
    48→    assert ws.runtime_type == RuntimeType.LOCAL
    49→


     1→import pytest
     2→from fastapi.testclient import TestClient
     3→
     4→from mirage.observe import session_log as observe_session_module
     5→from mirage.server.fastapi import app as server_app_module
     6→from mirage.server.fastapi.app import app
     7→
     8→
     9→@pytest.fixture
    10→def client():
    11→    return TestClient(app)
    12→
    13→
    14→@pytest.fixture(autouse=True)
    15→def clear_state():
    16→    server_app_module._sessions.clear()
    17→    observe_session_module._session_logs.clear()
    18→    yield
    19→    server_app_module._sessions.clear()
    20→    observe_session_module._session_logs.clear()
    21→
    22→
    23→def test_global_status(client):
    24→    resp = client.get("/observe/status")
    25→    assert resp.status_code == 200
    26→    data = resp.json()
    27→    assert "rss_bytes" in data
    28→    assert "total_sessions" in data
    29→    assert "total_cache_bytes" in data
    30→    assert "uptime_seconds" in data
    31→
    32→
    33→def test_global_sessions_empty(client):
    34→    resp = client.get("/observe/sessions")
    35→    assert resp.status_code == 200
    36→    assert resp.json() == []
    37→
    38→
    39→def test_global_sessions_with_workspace(client):
    40→    client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    41→    resp = client.get("/observe/sessions")
    42→    data = resp.json()
    43→    assert len(data) == 1
    44→    assert "cache_used" in data[0]
    45→    assert "total_commands" in data[0]
    46→
    47→
    48→def test_ws_status(client):
    49→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    50→    ws_id = ws_resp.json()["id"]
    51→    resp = client.get(f"/workspaces/{ws_id}/observe/status")
    52→    assert resp.status_code == 200
    53→    data = resp.json()
    54→    assert "cache_used" in data
    55→    assert "cache_limit" in data
    56→    assert "dirty_inodes" in data
    57→    assert "backends" in data
    58→    assert "total_commands" in data
    59→
    60→
    61→def test_ws_commands(client):
    62→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    63→    ws_id = ws_resp.json()["id"]
    64→    resp = client.get(f"/workspaces/{ws_id}/observe/commands")
    65→    assert resp.status_code == 200
    66→    cmds = resp.json()
    67→    assert len(cmds) >= 20
    68→    names = {c["name"] for c in cmds}
    69→    assert "cat" in names
    70→    assert "ls" in names
    71→
    72→
    73→def test_ws_memory(client):
    74→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    75→    ws_id = ws_resp.json()["id"]
    76→    resp = client.get(f"/workspaces/{ws_id}/observe/memory")
    77→    assert resp.status_code == 200
    78→    data = resp.json()
    79→    assert "cache" in data
    80→    assert "dirty_tracker" in data
    81→    assert "backend_memory" in data
    82→
    83→
    84→def test_ws_history_after_exec(client):
    85→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    86→    ws_id = ws_resp.json()["id"]
    87→    client.post(f"/workspaces/{ws_id}/exec", json={"command": "ls /tmp/"})
    88→    resp = client.get(f"/workspaces/{ws_id}/observe/history")
    89→    assert resp.status_code == 200
    90→    data = resp.json()
    91→    assert len(data) >= 2
    92→    types = {e["type"] for e in data}
    93→    assert "session" in types
    94→    assert "command" in types
    95→
    96→
    97→def test_ws_history_empty(client):
    98→    resp = client.get("/workspaces/nonexistent/observe/history")
    99→    assert resp.status_code == 200
   100→    assert resp.json() == []
   101→
   102→
   103→def test_ws_transfers(client):
   104→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
   105→    ws_id = ws_resp.json()["id"]
   106→    resp = client.get(f"/workspaces/{ws_id}/observe/transfers")
   107→    assert resp.status_code == 200
   108→    assert isinstance(resp.json(), dict)
   109→
   110→
   111→def test_ws_status_404(client):
   112→    resp = client.get("/workspaces/bad-id/observe/status")
   113→    assert resp.status_code == 404
   114→
   115→
   116→def test_fuse_ws_status(client):
   117→    resp = client.post("/workspaces/fuse",
   118→                       json={
   119→                           "mountpoint": "/tmp/ws",
   120→                           "pid": 12345,
   121→                           "mount_specs": ["memory:/data/"],
   122→                       })
   123→    ws_id = resp.json()["id"]
   124→    resp = client.get(f"/workspaces/{ws_id}/observe/status")
   125→    assert resp.status_code == 200
   126→    data = resp.json()
   127→    assert data["cache_used"] == 0
   128→    assert data["cache_limit"] == 0
   129→    assert data["dirty_inodes"] == 0
   130→    assert data["total_commands"] == 0
   131→    assert data["total_errors"] == 0
   132→    assert data["last_command"] is None
   133→    assert len(data["backends"]) == 1
   134→    assert data["backends"][0]["mount"] == "/data/"
   135→    assert data["backends"][0]["backend"] == "memory"
   136→
   137→
   138→def test_fuse_ws_history(client):
   139→    resp = client.post("/workspaces/fuse",
   140→                       json={
   141→                           "mountpoint": "/tmp/ws-hist",
   142→                           "pid": 12345,
   143→                           "mount_specs": ["memory:/data/"],
   144→                       })
   145→    ws_id = resp.json()["id"]
   146→    resp = client.get(f"/workspaces/{ws_id}/observe/history?n=50")
   147→    assert resp.status_code == 200
   148→    entries = resp.json()
   149→    assert len(entries) >= 1
   150→    assert entries[0]["type"] == "session"
   151→
   152→
   153→def test_fuse_ws_transfers(client):
   154→    resp = client.post("/workspaces/fuse",
   155→                       json={
   156→                           "mountpoint": "/tmp/ws",
   157→                           "pid": 12345,
   158→                       })
   159→    ws_id = resp.json()["id"]
   160→    resp = client.get(f"/workspaces/{ws_id}/observe/transfers")
   161→    assert resp.status_code == 200
   162→    assert resp.json() == {}
   163→
   164→
   165→def test_fuse_report_updates_status(client):
   166→    resp = client.post("/workspaces/fuse",
   167→                       json={
   168→                           "mountpoint": "/tmp/ws",
   169→                           "pid": 12345,
   170→                           "mount_specs": ["memory:/data/"],
   171→                       })
   172→    ws_id = resp.json()["id"]
   173→    reported = {
   174→        "cache_used": 1024,
   175→        "cache_limit": 10485760,
   176→        "dirty_inodes": 2,
   177→        "backends": [{
   178→            "mount": "/data/",
   179→            "backend": "MemoryBackend"
   180→        }],
   181→        "last_command": None,
   182→        "total_commands": 0,
   183→        "total_errors": 0,
   184→    }
   185→    resp = client.post("/workspaces/fuse/report",
   186→                       json={
   187→                           "mountpoint": "/tmp/ws",
   188→                           "status": reported,
   189→                           "transfers": {
   190→                               "/data/": {
   191→                                   "bytes_read": 512,
   192→                                   "bytes_written": 256,
   193→                                   "read_ops": 3,
   194→                                   "write_ops": 1,
   195→                                   "avg_read_latency_ms": 0.5,
   196→                                   "cache_hit_rate": 0.667,
   197→                               }
   198→                           },
   199→                       })
   200→    assert resp.status_code == 200
   201→    status = client.get(f"/workspaces/{ws_id}/observe/status").json()
   202→    assert status["cache_used"] == 1024
   203→    assert status["dirty_inodes"] == 2
   204→    assert status["backends"][0]["backend"] == "MemoryBackend"
   205→    transfers = client.get(f"/workspaces/{ws_id}/observe/transfers").json()
   206→    assert transfers["/data/"]["bytes_read"] == 512
   207→
   208→
   209→def test_fuse_report_ops_written_to_history(client):
   210→    resp = client.post("/workspaces/fuse",
   211→                       json={
   212→                           "mountpoint": "/tmp/ws-ops",
   213→                           "pid": 12345,
   214→                           "mount_specs": ["memory:/data/"],
   215→                       })
   216→    ws_id = resp.json()["id"]
   217→    ops = [
   218→        {
   219→            "type": "fuse_op",
   220→            "op": "create",
   221→            "path": "/data/a.py",
   222→            "timestamp": "2026-03-15T00:00:00+00:00",
   223→            "raw": "create /data/a.py",
   224→        },
   225→        {
   226→            "type": "fuse_op",
   227→            "op": "write",
   228→            "path": "/data/a.py",
   229→            "timestamp": "2026-03-15T00:00:01+00:00",
   230→            "raw": "write /data/a.py",
   231→        },
   232→    ]
   233→    resp = client.post("/workspaces/fuse/report",
   234→                       json={
   235→                           "mountpoint": "/tmp/ws-ops",
   236→                           "status": {},
   237→                           "ops": ops,
   238→                       })
   239→    assert resp.status_code == 200
   240→    history = client.get(f"/workspaces/{ws_id}/observe/history?n=50").json()
   241→    assert len(history) >= 3
   242→    assert history[0]["type"] == "session"
   243→    fuse_ops = [e for e in history if e["type"] == "fuse_op"]
   244→    assert len(fuse_ops) == 2
   245→    assert fuse_ops[0]["op"] == "create"
   246→    assert fuse_ops[1]["raw"] == "write /data/a.py"
   247→
   248→
   249→def test_fuse_report_not_found(client):
   250→    resp = client.post("/workspaces/fuse/report",
   251→                       json={
   252→                           "mountpoint": "/nonexistent",
   253→                           "status": {},
   254→                       })
   255→    assert resp.status_code == 404
   256→
   257→
   258→def test_command_stats_tracked(client):
   259→    ws_resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
   260→    ws_id = ws_resp.json()["id"]
   261→    client.post(f"/workspaces/{ws_id}/exec", json={"command": "ls /tmp/"})
   262→    client.post(f"/workspaces/{ws_id}/exec", json={"command": "ls /tmp/"})
   263→    resp = client.get(f"/workspaces/{ws_id}/observe/status")
   264→    data = resp.json()
   265→    assert data["total_commands"] == 2
   266→    assert data["last_command"] == "ls /tmp/"
   267→


     1→from dataclasses import dataclass, field
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.commands import COMMANDS
     5→from mirage.commands.custom.registry import CommandRegistry
     6→from mirage.observe import inspector
     7→from mirage.types import CommandStats, MountMode, SyncPolicy
     8→from mirage.workspace import Workspace
     9→
    10→
    11→@dataclass
    12→class _FakeSession:
    13→    id: str = "test-ws"
    14→    ws: Workspace | None = None
    15→    registry: CommandRegistry | None = None
    16→    cwd: str = "/"
    17→    fuse_mounted: bool = False
    18→    mode: str = "write"
    19→    mountpoint: str | None = None
    20→    pid: int | None = None
    21→    mounts: list[dict] = field(default_factory=list)
    22→    command_stats: CommandStats = field(default_factory=CommandStats)
    23→
    24→
    25→def _make_session() -> _FakeSession:
    26→    ws = Workspace(
    27→        {"/tmp/": MemoryBackend()},
    28→        mode=MountMode.WRITE,
    29→        sync_policy=SyncPolicy.STAGED,
    30→    )
    31→    return _FakeSession(ws=ws, registry=CommandRegistry())
    32→
    33→
    34→def test_global_status():
    35→    session = _make_session()
    36→    sessions = {"ws1": session}
    37→    result = inspector.global_status(sessions)
    38→    assert "rss_bytes" in result
    39→    assert result["total_sessions"] == 1
    40→    assert result["total_cache_bytes"] == 0
    41→    assert "uptime_seconds" in result
    42→
    43→
    44→def test_list_sessions():
    45→    session = _make_session()
    46→    sessions = {"ws1": session}
    47→    result = inspector.list_sessions(sessions)
    48→    assert len(result) == 1
    49→    assert result[0]["id"] == "test-ws"
    50→    assert result[0]["cache_used"] == 0
    51→    assert result[0]["total_commands"] == 0
    52→
    53→
    54→def test_workspace_status():
    55→    session = _make_session()
    56→    session.command_stats.total_commands = 5
    57→    session.command_stats.total_errors = 1
    58→    session.command_stats.last_command = "cat /tmp/test"
    59→    result = inspector.workspace_status(session)
    60→    assert result["cache_used"] == 0
    61→    assert result["dirty_inodes"] == 0
    62→    assert result["total_commands"] == 5
    63→    assert result["total_errors"] == 1
    64→    assert result["last_command"] == "cat /tmp/test"
    65→    assert len(result["backends"]) == 1
    66→    assert result["backends"][0]["backend"] == "MemoryBackend"
    67→
    68→
    69→def test_workspace_commands():
    70→    session = _make_session()
    71→    result = inspector.workspace_commands(session)
    72→    names = {c["name"] for c in result}
    73→    assert "cat" in names
    74→    assert "ls" in names
    75→    assert "grep" in names
    76→    assert len(result) >= len(COMMANDS)
    77→    for cmd in result:
    78→        assert "source" in cmd
    79→        assert "cost_level" in cmd
    80→
    81→
    82→def test_workspace_memory():
    83→    session = _make_session()
    84→    session.ws.write("/tmp/hello.txt", b"hello world")
    85→    result = inspector.workspace_memory(session)
    86→    assert result["cache"]["used"] > 0
    87→    assert result["cache"]["entry_count"] == 1
    88→    assert "/tmp/hello.txt" in result["cache"]["entries"]
    89→    assert result["dirty_tracker"]["total_inodes"] == 1
    90→    assert "/tmp/hello.txt" in result["dirty_tracker"]["dirty_paths"]
    91→    assert "/tmp/" in result["backend_memory"]
    92→    assert result["backend_memory"]["/tmp/"]["file_count"] == 0
    93→
    94→
    95→def test_workspace_memory_with_synced_file():
    96→    ws = Workspace(
    97→        {"/tmp/": MemoryBackend()},
    98→        mode=MountMode.WRITE,
    99→        sync_policy=SyncPolicy.IMMEDIATE,
   100→    )
   101→    session = _FakeSession(ws=ws, registry=CommandRegistry())
   102→    ws.write("/tmp/hello.txt", b"hello")
   103→    result = inspector.workspace_memory(session)
   104→    assert result["backend_memory"]["/tmp/"]["file_count"] == 1
   105→
   106→
   107→def test_workspace_transfers():
   108→    session = _make_session()
   109→    session.ws.write("/tmp/a.txt", b"data")
   110→    session.ws.cat("/tmp/a.txt")
   111→    session.ws.cat("/tmp/a.txt")
   112→    result = inspector.workspace_transfers(session)
   113→    assert "/tmp/" in result
   114→    stats = result["/tmp/"]
   115→    assert stats["write_ops"] >= 1
   116→    assert stats["read_ops"] >= 2
   117→    assert stats["bytes_read"] > 0
   118→    assert stats["cache_hit_rate"] > 0
   119→