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
226 lines
7.2 KiB
Python
226 lines
7.2 KiB
Python
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
|
|
|
from mirage.commands.builtin.find_helper import find as _find_impl
|
|
from mirage.types import FileStat, FileType
|
|
from mirage.utils.filetype import guess_type
|
|
|
|
|
|
def _norm(path):
|
|
return "/" + path.strip("/")
|
|
|
|
|
|
def _write(backend, path, data):
|
|
store = backend.accessor.store
|
|
store.files[_norm(path)] = data
|
|
|
|
|
|
def _mkdir(backend, path):
|
|
store = backend.accessor.store
|
|
store.dirs.add(_norm(path))
|
|
|
|
|
|
def _sync_readdir(store, path):
|
|
p = _norm(path)
|
|
if p not in store.dirs:
|
|
raise FileNotFoundError(p)
|
|
prefix = p.rstrip("/") + "/"
|
|
seen = set()
|
|
for key in list(store.files) + list(store.dirs):
|
|
if key == p:
|
|
continue
|
|
if key.startswith(prefix):
|
|
remainder = key[len(prefix):]
|
|
child = remainder.split("/")[0]
|
|
if child:
|
|
seen.add(prefix + child)
|
|
return sorted(seen)
|
|
|
|
|
|
def _sync_stat(store, path):
|
|
p = _norm(path)
|
|
if p in store.dirs:
|
|
return FileStat(
|
|
name=p.rsplit("/", 1)[-1] or "/",
|
|
size=None,
|
|
modified=store.modified.get(p),
|
|
type=FileType.DIRECTORY,
|
|
)
|
|
if p in store.files:
|
|
data = store.files[p]
|
|
return FileStat(
|
|
name=p.rsplit("/", 1)[-1],
|
|
size=len(data),
|
|
modified=store.modified.get(p),
|
|
type=guess_type(p),
|
|
)
|
|
raise FileNotFoundError(p)
|
|
|
|
|
|
def find(backend, path, **kwargs):
|
|
store = backend.accessor.store
|
|
return _find_impl(lambda p: _sync_readdir(store, p),
|
|
lambda p: _sync_stat(store, p), path, **kwargs)
|
|
|
|
|
|
class TestDefault:
|
|
|
|
def test_all_files_recursively(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"hello")
|
|
_write(backend, "/tmp/b.txt", b"world")
|
|
result = find(backend, "/tmp")
|
|
assert sorted(result) == ["/tmp/a.txt", "/tmp/b.txt"]
|
|
|
|
def test_includes_subdirectories(self, backend):
|
|
_mkdir(backend, "/tmp/sub")
|
|
_write(backend, "/tmp/sub/c.txt", b"nested")
|
|
result = find(backend, "/tmp")
|
|
assert "/tmp/sub" in result
|
|
assert "/tmp/sub/c.txt" in result
|
|
|
|
def test_empty_dir(self, backend):
|
|
result = find(backend, "/tmp")
|
|
assert result == []
|
|
|
|
|
|
class TestName:
|
|
|
|
def test_glob_pattern(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
_write(backend, "/tmp/b.py", b"bbb")
|
|
_write(backend, "/tmp/c.txt", b"ccc")
|
|
result = find(backend, "/tmp", name="*.txt")
|
|
assert sorted(result) == ["/tmp/a.txt", "/tmp/c.txt"]
|
|
|
|
def test_exact_name(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
_write(backend, "/tmp/b.txt", b"bbb")
|
|
result = find(backend, "/tmp", name="a.txt")
|
|
assert result == ["/tmp/a.txt"]
|
|
|
|
|
|
class TestTypeDirectory:
|
|
|
|
def test_type_directory(self, backend):
|
|
_mkdir(backend, "/tmp/sub")
|
|
_write(backend, "/tmp/file.txt", b"data")
|
|
result = find(backend, "/tmp", type=FileType.DIRECTORY)
|
|
assert result == ["/tmp/sub"]
|
|
|
|
def test_type_directory_nested(self, backend):
|
|
_mkdir(backend, "/tmp/a")
|
|
_mkdir(backend, "/tmp/a/b")
|
|
_write(backend, "/tmp/a/b/f.txt", b"x")
|
|
result = find(backend, "/tmp", type=FileType.DIRECTORY)
|
|
assert sorted(result) == ["/tmp/a", "/tmp/a/b"]
|
|
|
|
|
|
class TestTypeFile:
|
|
|
|
def test_type_file(self, backend):
|
|
_mkdir(backend, "/tmp/sub")
|
|
_write(backend, "/tmp/file.txt", b"data")
|
|
_write(backend, "/tmp/sub/nested.py", b"code")
|
|
result = find(backend, "/tmp", type="file")
|
|
assert sorted(result) == ["/tmp/file.txt", "/tmp/sub/nested.py"]
|
|
|
|
|
|
class TestSize:
|
|
|
|
def test_min_size(self, backend):
|
|
_write(backend, "/tmp/small.txt", b"hi")
|
|
_write(backend, "/tmp/big.txt", b"a" * 100)
|
|
result = find(backend, "/tmp", min_size=50)
|
|
assert result == ["/tmp/big.txt"]
|
|
|
|
def test_max_size(self, backend):
|
|
_write(backend, "/tmp/small.txt", b"hi")
|
|
_write(backend, "/tmp/big.txt", b"a" * 100)
|
|
result = find(backend, "/tmp", max_size=50)
|
|
assert result == ["/tmp/small.txt"]
|
|
|
|
def test_size_range(self, backend):
|
|
_write(backend, "/tmp/tiny.txt", b"x")
|
|
_write(backend, "/tmp/mid.txt", b"a" * 50)
|
|
_write(backend, "/tmp/huge.txt", b"a" * 200)
|
|
result = find(backend, "/tmp", min_size=10, max_size=100)
|
|
assert result == ["/tmp/mid.txt"]
|
|
|
|
|
|
class TestMaxdepth:
|
|
|
|
def test_depth_zero(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
result = find(backend, "/tmp", maxdepth=0)
|
|
assert result == ["/tmp/a.txt"]
|
|
|
|
def test_depth_limits_recursion(self, backend):
|
|
_mkdir(backend, "/tmp/d1")
|
|
_mkdir(backend, "/tmp/d1/d2")
|
|
_write(backend, "/tmp/a.txt", b"a")
|
|
_write(backend, "/tmp/d1/b.txt", b"b")
|
|
_write(backend, "/tmp/d1/d2/c.txt", b"c")
|
|
result = find(backend, "/tmp", maxdepth=1)
|
|
assert "/tmp/a.txt" in result
|
|
assert "/tmp/d1" in result
|
|
assert "/tmp/d1/b.txt" in result
|
|
assert "/tmp/d1/d2/c.txt" not in result
|
|
|
|
|
|
class TestNameExclude:
|
|
|
|
def test_exclude_pattern(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
_write(backend, "/tmp/b.pyc", b"bbb")
|
|
_write(backend, "/tmp/c.txt", b"ccc")
|
|
result = find(backend, "/tmp", name_exclude="*.pyc")
|
|
assert sorted(result) == ["/tmp/a.txt", "/tmp/c.txt"]
|
|
|
|
def test_exclude_with_name(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
_write(backend, "/tmp/b.txt", b"bbb")
|
|
_write(backend, "/tmp/c.py", b"ccc")
|
|
result = find(backend, "/tmp", name="*.txt", name_exclude="b*")
|
|
assert result == ["/tmp/a.txt"]
|
|
|
|
|
|
class TestOrNames:
|
|
|
|
def test_or_names(self, backend):
|
|
_write(backend, "/tmp/a.txt", b"aaa")
|
|
_write(backend, "/tmp/b.py", b"bbb")
|
|
_write(backend, "/tmp/c.md", b"ccc")
|
|
result = find(backend, "/tmp", or_names=["*.txt", "*.py"])
|
|
assert sorted(result) == ["/tmp/a.txt", "/tmp/b.py"]
|
|
|
|
|
|
class TestMixed:
|
|
|
|
def test_name_and_min_size(self, backend):
|
|
_write(backend, "/tmp/small.txt", b"hi")
|
|
_write(backend, "/tmp/big.txt", b"a" * 100)
|
|
_write(backend, "/tmp/big.py", b"a" * 100)
|
|
result = find(backend, "/tmp", name="*.txt", min_size=50)
|
|
assert result == ["/tmp/big.txt"]
|
|
|
|
|
|
class TestWarnings:
|
|
|
|
def test_warnings_on_missing_path(self, backend):
|
|
warnings = []
|
|
result = find(backend, "/nonexistent", warnings=warnings)
|
|
assert result == []
|
|
assert len(warnings) == 1
|
|
assert "/nonexistent" in warnings[0]
|