Files
strukto-ai--mirage/python/tests/commands/builtin/helpers/test_grep_helper.py
T
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

309 lines
9.8 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 functools import partial
import pytest
from mirage.commands.builtin.grep_helper import (compile_pattern,
get_extension,
grep_files_only, grep_lines,
grep_recursive)
from mirage.core.ram.mkdir import mkdir
from mirage.core.ram.read import read_bytes
from mirage.core.ram.readdir import readdir
from mirage.core.ram.stat import stat
from mirage.core.ram.write import write_bytes as _async_write_bytes
async def _write(backend, path, content):
accessor = backend.accessor
await _async_write_bytes(accessor, path, content.encode())
async def _mkdir(backend, path):
accessor = backend.accessor
await mkdir(accessor, path, parents=True)
def _bind(backend):
accessor = backend.accessor
index = backend.index
async def _readdir(path):
return await readdir(accessor, path, index)
return (
_readdir,
partial(stat, accessor),
partial(read_bytes, accessor),
)
async def grep(backend, path, pattern, **kwargs):
rd, st, rb = _bind(backend)
recursive = kwargs.pop("recursive", False)
ignore_case = kwargs.pop("ignore_case", False)
invert = kwargs.pop("invert", False)
line_numbers = kwargs.pop("line_numbers", False)
count_only = kwargs.pop("count_only", False)
files_only = kwargs.pop("files_only", False)
fixed_string = kwargs.pop("fixed_string", False)
only_matching = kwargs.pop("only_matching", False)
max_count = kwargs.pop("max_count", None)
whole_word = kwargs.pop("whole_word", False)
show_filename = kwargs.pop("show_filename", None)
warnings = kwargs.pop("warnings", None)
compiled = compile_pattern(pattern, ignore_case, fixed_string, whole_word)
if recursive:
results = await grep_recursive(
rd,
st,
rb,
path,
compiled,
invert,
line_numbers,
count_only,
files_only,
only_matching,
max_count,
warnings,
)
if show_filename is False and not count_only and not files_only:
stripped = []
for r in results:
colon_idx = r.find(":")
stripped.append(r[colon_idx + 1:] if colon_idx != -1 else r)
return stripped
return results
return await grep_files_only(
rd,
st,
rb,
path,
pattern,
recursive=False,
ignore_case=ignore_case,
invert=invert,
line_numbers=line_numbers,
count_only=count_only,
fixed_string=fixed_string,
only_matching=only_matching,
max_count=max_count,
whole_word=whole_word,
warnings=warnings,
)
class TestCompilePattern:
def test_basic(self):
pat = compile_pattern("hello")
assert pat.search("hello world")
def test_ignore_case(self):
pat = compile_pattern("hello", ignore_case=True)
assert pat.search("HELLO")
def test_fixed_string(self):
pat = compile_pattern("a.b", fixed_string=True)
assert not pat.search("axb")
assert pat.search("a.b")
def test_whole_word(self):
pat = compile_pattern("foo", whole_word=True)
assert not pat.search("foobar")
assert pat.search("foo bar")
class TestGetExtension:
def test_normal(self):
assert get_extension("file.txt") == ".txt"
def test_no_ext(self):
assert get_extension("file") is None
def test_directory_dot(self):
assert get_extension("dir.d/file") is None
class TestGrepLines:
def test_basic(self):
compiled = compile_pattern("hello")
result = grep_lines("/f.txt", ["hello world", "foo"], compiled, False,
False, False, False, False, None)
assert result == ["hello world"]
class TestBasicMatching:
@pytest.mark.anyio
async def test_match_found(self, backend):
await _write(backend, "/tmp/a.txt",
"hello world\nfoo bar\nhello again")
result = await grep(backend, "/tmp/a.txt", "hello")
assert result == ["/tmp/a.txt"]
@pytest.mark.anyio
async def test_no_match(self, backend):
await _write(backend, "/tmp/a.txt", "hello world\nfoo bar")
result = await grep(backend, "/tmp/a.txt", "xyz")
assert result == []
@pytest.mark.anyio
async def test_empty_file(self, backend):
await _write(backend, "/tmp/a.txt", "")
result = await grep(backend, "/tmp/a.txt", "hello")
assert result == []
class TestIgnoreCase:
@pytest.mark.anyio
async def test_ignore_case_matches(self, backend):
await _write(backend, "/tmp/a.txt", "Hello World\nhello world\nHELLO")
result = await grep(backend,
"/tmp/a.txt",
"hello",
ignore_case=True,
files_only=True)
assert result == ["/tmp/a.txt"]
class TestInvert:
@pytest.mark.anyio
async def test_invert_match(self, backend):
await _write(backend, "/tmp/a.txt", "hello\nworld\nhello again")
result = await grep(backend,
"/tmp/a.txt",
"hello",
invert=True,
files_only=True)
assert result == ["/tmp/a.txt"]
class TestCountOnly:
@pytest.mark.anyio
async def test_count_only(self, backend):
await _write(backend, "/tmp/a.txt", "foo\nbar\nfoo baz")
result = await grep(backend, "/tmp/a.txt", "foo", count_only=True)
assert result == ["2"]
class TestRecursive:
@pytest.mark.anyio
async def test_recursive_basic(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/a.txt", "hello")
await _write(backend, "/tmp/sub/b.txt", "hello world")
result = await grep(backend, "/tmp", "hello", recursive=True)
assert "/tmp/a.txt:hello" in result
assert "/tmp/sub/b.txt:hello world" in result
@pytest.mark.anyio
async def test_recursive_with_line_numbers(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/sub/b.txt", "x\nhello\ny")
result = await grep(backend,
"/tmp",
"hello",
recursive=True,
line_numbers=True)
assert "/tmp/sub/b.txt:2:hello" in result
@pytest.mark.anyio
async def test_recursive_with_files_only(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/a.txt", "hello")
await _write(backend, "/tmp/sub/b.txt", "world")
result = await grep(backend,
"/tmp",
"hello",
recursive=True,
files_only=True)
assert "/tmp/a.txt" in result
assert "/tmp/sub/b.txt" not in result
@pytest.mark.anyio
async def test_recursive_with_count_only(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/a.txt", "hello\nhello")
await _write(backend, "/tmp/sub/b.txt", "hello")
result = await grep(backend,
"/tmp",
"hello",
recursive=True,
count_only=True)
assert len(result) > 0
class TestMixedFlags:
@pytest.mark.anyio
async def test_recursive_ignore_case_line_numbers(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/sub/a.txt", "Hello\nworld")
result = await grep(backend,
"/tmp",
"hello",
recursive=True,
ignore_case=True,
line_numbers=True)
assert "/tmp/sub/a.txt:1:Hello" in result
class TestShowFilename:
@pytest.mark.anyio
async def test_grep_hide_filename_recursive(self, backend):
await _mkdir(backend, "/tmp/sub")
await _write(backend, "/tmp/sub/a.txt", "needle")
result = await grep(backend,
"/tmp/sub/",
"needle",
recursive=True,
show_filename=False)
assert result == ["needle"]
class TestWarnings:
@pytest.mark.anyio
async def test_warnings_on_missing_file(self, backend):
warnings = []
result = await grep(backend,
"/tmp/nonexistent.txt",
"foo",
warnings=warnings)
assert result == []
assert len(warnings) > 0
assert "nonexistent" in warnings[0]
@pytest.mark.anyio
async def test_warnings_none_does_not_error(self, backend):
result = await grep(backend,
"/tmp/nonexistent.txt",
"foo",
warnings=None)
assert result == []