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
384 lines
12 KiB
Python
384 lines
12 KiB
Python
import pytest
|
|
|
|
from mirage.commands.builtin.generic.diff import diff
|
|
from mirage.commands.builtin.generic.jq import jq
|
|
from mirage.commands.builtin.generic.patch import patch
|
|
from mirage.commands.builtin.generic.tar import tar
|
|
from mirage.commands.builtin.generic.tsort import tsort
|
|
from mirage.commands.builtin.generic.unzip import unzip
|
|
from mirage.commands.builtin.generic.zip_cmd import zip_cmd
|
|
from mirage.types import PathSpec
|
|
from mirage.utils.key_prefix import mount_key
|
|
|
|
|
|
def _spec(path: str, prefix: str = "") -> PathSpec:
|
|
return PathSpec(resource_path=mount_key(path, prefix),
|
|
virtual=path,
|
|
directory=path,
|
|
resolved=True)
|
|
|
|
|
|
def _make_backend(files: dict[str, bytes]):
|
|
store = dict(files)
|
|
|
|
async def read_bytes(accessor, path, index=None):
|
|
key = path.virtual if isinstance(path, PathSpec) else path
|
|
if key not in store:
|
|
raise FileNotFoundError(key)
|
|
return store[key]
|
|
|
|
async def write_bytes(accessor, path, data, index=None):
|
|
key = path.virtual if isinstance(path, PathSpec) else path
|
|
store[key] = data
|
|
|
|
async def read_stream(accessor, path, index=None):
|
|
key = path.virtual if isinstance(path, PathSpec) else path
|
|
if key not in store:
|
|
raise FileNotFoundError(key)
|
|
yield store[key]
|
|
|
|
async def mkdir_fn(accessor, path, parents=False):
|
|
pass
|
|
|
|
return read_bytes, write_bytes, read_stream, mkdir_fn, store
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tsort_basic():
|
|
rb, _, _, _, _ = _make_backend({"deps": b"a b\nb c\n"})
|
|
out, io = await tsort([_spec("deps")], read_bytes=rb)
|
|
assert out == b"a\nb\nc\n"
|
|
assert io.exit_code == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tsort_cycle_detection():
|
|
rb, _, _, _, _ = _make_backend({"deps": b"a b\nb a\n"})
|
|
out, io = await tsort([_spec("deps")], read_bytes=rb)
|
|
assert b"cycle" in out
|
|
assert io.exit_code == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tsort_odd_tokens():
|
|
rb, _, _, _, _ = _make_backend({"deps": b"a b c\n"})
|
|
out, io = await tsort([_spec("deps")], read_bytes=rb)
|
|
assert b"odd number" in out
|
|
assert io.exit_code == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tsort_stdin():
|
|
rb, _, _, _, _ = _make_backend({})
|
|
out, io = await tsort([], read_bytes=rb, stdin=b"x y\ny z\n")
|
|
assert out == b"x\ny\nz\n"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_jq_simple_object():
|
|
rb, _, rs, _, _ = _make_backend({"a.json": b'{"name":"alice","age":30}'})
|
|
out, _ = await jq([_spec("a.json")],
|
|
".name",
|
|
read_bytes=rb,
|
|
read_stream=rs)
|
|
assert b'"alice"' in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_jq_raw_output():
|
|
rb, _, rs, _, _ = _make_backend({"a.json": b'{"name":"alice"}'})
|
|
out, _ = await jq([_spec("a.json")],
|
|
".name",
|
|
read_bytes=rb,
|
|
read_stream=rs,
|
|
r=True)
|
|
assert b"alice" in out
|
|
assert b'"' not in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_jq_stdin():
|
|
rb, _, rs, _, _ = _make_backend({})
|
|
out, _ = await jq([],
|
|
".x",
|
|
read_bytes=rb,
|
|
read_stream=rs,
|
|
stdin=b'{"x":42}')
|
|
assert b"42" in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_jq_missing_expression_defaults_dot():
|
|
rb, _, rs, _, _ = _make_backend({})
|
|
out, io = await jq([], read_bytes=rb, read_stream=rs, stdin=b'{"x":42}')
|
|
assert b"42" in out
|
|
assert io.exit_code == 0
|
|
out, io = await jq([], read_bytes=rb, read_stream=rs)
|
|
assert out is None
|
|
assert io.exit_code == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_jq_no_input():
|
|
rb, _, rs, _, _ = _make_backend({})
|
|
out, io = await jq([], ".x", read_bytes=rb, read_stream=rs)
|
|
assert out is None
|
|
assert io.exit_code == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zip_basic():
|
|
rb, wb, _, _, store = _make_backend({
|
|
"f1.txt": b"hello",
|
|
"f2.txt": b"world"
|
|
})
|
|
out, io = await zip_cmd(
|
|
[_spec("out.zip"), _spec("f1.txt"),
|
|
_spec("f2.txt")],
|
|
read_bytes=rb,
|
|
write_bytes=wb)
|
|
assert b"adding" in out
|
|
assert "out.zip" in store
|
|
archive = store["out.zip"]
|
|
assert archive.startswith(b"PK")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zip_quiet():
|
|
rb, wb, _, _, _ = _make_backend({"f.txt": b"x"})
|
|
out, _ = await zip_cmd([_spec("o.zip"), _spec("f.txt")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
q=True)
|
|
assert out is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zip_too_few_paths():
|
|
rb, wb, _, _, _ = _make_backend({})
|
|
with pytest.raises(ValueError, match="usage"):
|
|
await zip_cmd([_spec("only.zip")], read_bytes=rb, write_bytes=wb)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unzip_extracts():
|
|
import io as _io
|
|
import zipfile
|
|
buf = _io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w") as zf:
|
|
zf.writestr("a.txt", b"hello")
|
|
zf.writestr("sub/b.txt", b"world")
|
|
rb, wb, _, mk, store = _make_backend({"a.zip": buf.getvalue()})
|
|
out, io_res = await unzip([_spec("a.zip")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk)
|
|
assert b"inflating" in out
|
|
assert "/a.txt" in io_res.writes
|
|
assert "/sub/b.txt" in io_res.writes
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unzip_list():
|
|
import io as _io
|
|
import zipfile
|
|
buf = _io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w") as zf:
|
|
zf.writestr("file.txt", b"data")
|
|
rb, wb, _, mk, _ = _make_backend({"a.zip": buf.getvalue()})
|
|
out, _ = await unzip([_spec("a.zip")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
args_l=True)
|
|
assert b"file.txt" in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unzip_test_mode():
|
|
import io as _io
|
|
import zipfile
|
|
buf = _io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w") as zf:
|
|
zf.writestr("ok.txt", b"x")
|
|
rb, wb, _, mk, _ = _make_backend({"a.zip": buf.getvalue()})
|
|
out, _ = await unzip([_spec("a.zip")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
t=True)
|
|
assert b"No errors" in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unzip_pipe_mode():
|
|
import io as _io
|
|
import zipfile
|
|
buf = _io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w") as zf:
|
|
zf.writestr("a.txt", b"hello")
|
|
rb, wb, _, mk, _ = _make_backend({"a.zip": buf.getvalue()})
|
|
out, _ = await unzip([_spec("a.zip")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
p=True)
|
|
assert out == b"hello"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tar_create_and_list():
|
|
rb, wb, _, mk, store = _make_backend({"a.txt": b"alpha", "b.txt": b"beta"})
|
|
_, io_res = await tar([_spec("a.txt"), _spec("b.txt")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
c=True,
|
|
f=_spec("out.tar"))
|
|
assert "/out.tar" in io_res.writes
|
|
out, _ = await tar([],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
t=True,
|
|
f=_spec("out.tar"))
|
|
assert b"a.txt" in out
|
|
assert b"b.txt" in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tar_extract():
|
|
rb, wb, _, mk, _ = _make_backend({"x.txt": b"data"})
|
|
await tar([_spec("x.txt")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
c=True,
|
|
f=_spec("a.tar"))
|
|
_, io_res = await tar([],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
x=True,
|
|
f=_spec("a.tar"),
|
|
C=_spec("/out"))
|
|
assert any("x.txt" in p for p in io_res.writes)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tar_requires_mode():
|
|
rb, wb, _, mk, _ = _make_backend({})
|
|
with pytest.raises(ValueError, match="-c, -x, or -t"):
|
|
await tar([], read_bytes=rb, write_bytes=wb, mkdir_fn=mk)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tar_requires_f():
|
|
rb, wb, _, mk, _ = _make_backend({"a.txt": b"x"})
|
|
with pytest.raises(ValueError, match="-f is required"):
|
|
await tar([_spec("a.txt")],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
mkdir_fn=mk,
|
|
c=True)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_identical_files():
|
|
rb, _, _, _, _ = _make_backend({"a": b"hello\n", "b": b"hello\n"})
|
|
|
|
async def rd(accessor, path, index=None):
|
|
return []
|
|
|
|
out, io_res = await diff([_spec("a"), _spec("b")],
|
|
read_bytes=rb,
|
|
readdir_fn=rd)
|
|
assert out == b""
|
|
assert io_res.exit_code == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_quiet_differ():
|
|
rb, _, _, _, _ = _make_backend({"a": b"x\n", "b": b"y\n"})
|
|
|
|
async def rd(accessor, path, index=None):
|
|
return []
|
|
|
|
out, io_res = await diff([_spec("a"), _spec("b")],
|
|
read_bytes=rb,
|
|
readdir_fn=rd,
|
|
q=True)
|
|
assert b"differ" in out
|
|
assert io_res.exit_code == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_unified():
|
|
rb, _, _, _, _ = _make_backend({
|
|
"a": b"hello\nworld\n",
|
|
"b": b"hello\nuniverse\n"
|
|
})
|
|
|
|
async def rd(accessor, path, index=None):
|
|
return []
|
|
|
|
out, _ = await diff([_spec("a"), _spec("b")],
|
|
read_bytes=rb,
|
|
readdir_fn=rd,
|
|
u=True)
|
|
assert b"-world" in out
|
|
assert b"+universe" in out
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_too_few_paths():
|
|
rb, _, _, _, _ = _make_backend({"a": b"x\n"})
|
|
|
|
async def rd(accessor, path, index=None):
|
|
return []
|
|
|
|
with pytest.raises(ValueError, match="two paths"):
|
|
await diff([_spec("a")], read_bytes=rb, readdir_fn=rd)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_patch_apply():
|
|
diff_text = (b"--- a/hello.txt\n+++ b/hello.txt\n@@ -1,2 +1,2 @@\n"
|
|
b" hello\n-world\n+universe\n")
|
|
rb, wb, _, _, store = _make_backend({"/hello.txt": b"hello\nworld\n"})
|
|
_, io_res = await patch([],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
has_resource=True,
|
|
stdin=diff_text,
|
|
p="1")
|
|
assert b"universe" in store["/hello.txt"]
|
|
assert "/hello.txt" in io_res.writes
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_patch_reverse():
|
|
diff_text = (b"--- a/x.txt\n+++ b/x.txt\n@@ -1,2 +1,2 @@\n"
|
|
b" hello\n-world\n+universe\n")
|
|
rb, wb, _, _, store = _make_backend({"/x.txt": b"hello\nuniverse\n"})
|
|
await patch([],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
has_resource=True,
|
|
stdin=diff_text,
|
|
R=True,
|
|
p="1")
|
|
assert b"world" in store["/x.txt"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_patch_missing_input():
|
|
rb, wb, _, _, store = _make_backend({})
|
|
out, io = await patch([],
|
|
read_bytes=rb,
|
|
write_bytes=wb,
|
|
has_resource=False)
|
|
assert out is None
|
|
assert io.exit_code == 0
|
|
assert not store
|