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

323 lines
10 KiB
Python

from datetime import datetime, timezone
import pytest
from mirage.commands.builtin.generic.ls import (format_simple, get_extension,
ls, walk)
from mirage.types import FileStat, FileType, LsSortBy, PathSpec
def _spec(path: str) -> PathSpec:
return PathSpec(virtual=path,
directory=path,
resource_path=path.strip("/"))
def _make_fs_backend(tree: dict[str, FileStat]):
"""Build (readdir, stat) callables over an in-memory entry tree.
`tree` maps absolute path → FileStat. Directories are entries whose
type == FileType.DIRECTORY. readdir lists direct children of the path.
"""
async def stat(p: PathSpec, index=None) -> FileStat:
if p.virtual not in tree:
raise FileNotFoundError(p.virtual)
return tree[p.virtual]
async def readdir(p: PathSpec, _index=None) -> list[str]:
if p.virtual not in tree:
raise FileNotFoundError(p.virtual)
if tree[p.virtual].type != FileType.DIRECTORY:
raise ValueError(f"not a directory: {p.virtual}")
prefix = p.virtual.rstrip("/") + "/"
children: list[str] = []
for key in tree:
if key == p.virtual:
continue
if key.startswith(prefix):
remainder = key[len(prefix):]
if "/" not in remainder:
children.append(key)
return sorted(children)
return readdir, stat
def _file(name: str, size: int = 0, modified: str | None = None) -> FileStat:
return FileStat(name=name,
size=size,
modified=modified,
type=FileType.TEXT)
def _dir(name: str) -> FileStat:
return FileStat(name=name, size=None, type=FileType.DIRECTORY)
def test_get_extension_simple():
assert get_extension("file.txt") == ".txt"
def test_get_extension_no_dot_returns_none():
assert get_extension("Makefile") is None
def test_get_extension_dot_in_path_only_not_in_basename():
"""`a.b/c` has no extension on `c`."""
assert get_extension("a.b/c") is None
def test_format_simple_default_lists_names():
out = format_simple([_file("a.txt"), _file("b.txt")])
assert out == ["a.txt", "b.txt"]
def test_format_simple_classify_marks_dirs_with_slash():
out = format_simple([_file("a.txt"), _dir("sub")], classify=True)
assert out == ["a.txt", "sub/"]
@pytest.mark.asyncio
async def test_walk_lists_immediate_children():
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt", 3),
"/dir/b.txt": _file("b.txt", 2),
}
readdir, stat = _make_fs_backend(tree)
entries, warnings = await walk(_spec("/dir"), readdir=readdir, stat=stat)
assert [e.name for e in entries] == ["a.txt", "b.txt"]
assert warnings == []
@pytest.mark.asyncio
async def test_walk_skips_dotfiles_unless_all_files():
tree = {
"/dir": _dir("dir"),
"/dir/.hidden": _file(".hidden", 1),
"/dir/visible.txt": _file("visible.txt", 2),
}
readdir, stat = _make_fs_backend(tree)
entries, _ = await walk(_spec("/dir"), readdir=readdir, stat=stat)
assert [e.name for e in entries] == ["visible.txt"]
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
all_files=True)
assert sorted(e.name for e in entries) == [".hidden", "visible.txt"]
@pytest.mark.asyncio
async def test_walk_sort_by_size():
tree = {
"/dir": _dir("dir"),
"/dir/big.txt": _file("big.txt", 1000),
"/dir/small.txt": _file("small.txt", 1),
}
readdir, stat = _make_fs_backend(tree)
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
sort_by=LsSortBy.SIZE)
assert [e.name for e in entries] == ["big.txt", "small.txt"]
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
sort_by=LsSortBy.SIZE,
reverse=True)
assert [e.name for e in entries] == ["small.txt", "big.txt"]
@pytest.mark.asyncio
async def test_walk_sort_by_time():
older = datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat()
newer = datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat()
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt", 1, modified=older),
"/dir/b.txt": _file("b.txt", 1, modified=newer),
}
readdir, stat = _make_fs_backend(tree)
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
sort_by=LsSortBy.TIME)
assert [e.name for e in entries] == ["b.txt", "a.txt"]
@pytest.mark.asyncio
async def test_walk_recursive_descends_into_dirs():
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt"),
"/dir/sub": _dir("sub"),
"/dir/sub/b.txt": _file("b.txt"),
}
readdir, stat = _make_fs_backend(tree)
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
recursive=True)
names = [e.name for e in entries]
assert "a.txt" in names
assert "sub" in names
assert "b.txt" in names
@pytest.mark.asyncio
async def test_walk_list_dir_returns_only_self():
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt"),
}
readdir, stat = _make_fs_backend(tree)
entries, _ = await walk(_spec("/dir"),
readdir=readdir,
stat=stat,
list_dir=True)
# GNU ls -d prints the operand as given.
assert [e.name for e in entries] == ["/dir"]
@pytest.mark.asyncio
async def test_walk_missing_path_collects_warning():
readdir, stat = _make_fs_backend({})
entries, warnings = await walk(_spec("/nope"), readdir=readdir, stat=stat)
assert entries == []
assert any("/nope" in w for w in warnings)
@pytest.mark.asyncio
async def test_ls_short_output_terminates_record():
tree = {"/dir": _dir("dir"), "/dir/a.txt": _file("a.txt")}
readdir, stat = _make_fs_backend(tree)
output, io = await ls([_spec("/dir")], readdir=readdir, stat=stat)
assert output == b"a.txt\n"
assert io.exit_code == 0
@pytest.mark.asyncio
async def test_ls_long_format_renders_via_format_ls_long():
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt", 42),
}
readdir, stat = _make_fs_backend(tree)
output, _ = await ls([_spec("/dir")],
readdir=readdir,
stat=stat,
long=True)
decoded = output.decode()
assert "a.txt" in decoded
assert "42" in decoded
@pytest.mark.asyncio
async def test_ls_one_per_line_overrides_long():
tree = {
"/dir": _dir("dir"),
"/dir/a.txt": _file("a.txt", 42),
}
readdir, stat = _make_fs_backend(tree)
out_long, _ = await ls([_spec("/dir")],
readdir=readdir,
stat=stat,
long=True,
one_per_line=True)
assert out_long == b"a.txt\n"
@pytest.mark.asyncio
async def test_ls_classify_appends_slash_for_dirs():
tree = {
"/dir": _dir("dir"),
"/dir/sub": _dir("sub"),
"/dir/a.txt": _file("a.txt"),
}
readdir, stat = _make_fs_backend(tree)
output, _ = await ls([_spec("/dir")],
readdir=readdir,
stat=stat,
classify=True)
decoded = output.decode().splitlines()
assert "sub/" in decoded
assert "a.txt" in decoded
@pytest.mark.asyncio
async def test_ls_missing_path_returns_warning_and_exit_1():
readdir, stat = _make_fs_backend({})
output, io = await ls([_spec("/nope")], readdir=readdir, stat=stat)
assert output == b""
assert io.exit_code == 1
assert b"/nope" in (io.stderr or b"")
@pytest.mark.asyncio
async def test_walk_single_file_lists_itself():
tree = {"/dir/a.parquet": _file("a.parquet", 5)}
readdir, stat = _make_fs_backend(tree)
entries, warnings = await walk(_spec("/dir/a.parquet"),
readdir=readdir,
stat=stat)
# GNU ls prints a file operand as given.
assert [e.name for e in entries] == ["/dir/a.parquet"]
assert warnings == []
@pytest.mark.asyncio
async def test_walk_empty_readdir_falls_back_to_file():
"""Object stores (e.g. s3) return [] for a file key instead of raising."""
fstat = _file("a.parquet", 5)
async def stat(p, index=None):
if p.virtual == "/data/a.parquet":
return fstat
raise FileNotFoundError(p.virtual)
async def readdir(p, _index=None):
return []
entries, warnings = await walk(_spec("/data/a.parquet"),
readdir=readdir,
stat=stat)
assert [e.name for e in entries] == ["/data/a.parquet"]
assert warnings == []
@pytest.mark.asyncio
async def test_walk_empty_dir_stays_empty():
tree = {"/empty": _dir("empty")}
readdir, stat = _make_fs_backend(tree)
entries, warnings = await walk(_spec("/empty"), readdir=readdir, stat=stat)
assert entries == []
assert warnings == []
@pytest.mark.asyncio
async def test_ls_file_argument_lists_the_file():
tree = {"/dir/a.json": _file("a.json", 5)}
readdir, stat = _make_fs_backend(tree)
output, io = await ls([_spec("/dir/a.json")], readdir=readdir, stat=stat)
assert output == b"/dir/a.json\n"
assert io.exit_code == 0
@pytest.mark.asyncio
async def test_ls_l_no_filetype_enrichment():
tree = {
"/dir": _dir("dir"),
"/dir/data.parquet": _file("data.parquet", 999),
}
readdir, stat = _make_fs_backend(tree)
output, _ = await ls(
[_spec("/dir")],
readdir=readdir,
stat=stat,
long=True,
)
decoded = output.decode()
assert "data.parquet" in decoded