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

143 lines
5.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. =========
import ast
import pathlib
import pytest
BUILTIN = pathlib.Path(
__file__).resolve().parents[3] / "mirage" / "commands" / "builtin"
GUARDED_COMMANDS = ("cat", "head", "tail", "wc", "du", "file", "nl", "md5")
# Backends whose command genuinely operates on a single resource and rejects
# or has no multi-file semantics. Each entry needs a one-line reason so the
# allowlist stays honest. Keyed by (command, backend).
ALLOWLIST = {
("wc", "mongodb"): "counts documents of one collection via count API",
}
def _command_funcs(tree: ast.Module) -> list[ast.AsyncFunctionDef]:
funcs = []
for node in tree.body:
if not isinstance(node, ast.AsyncFunctionDef):
continue
for dec in node.decorator_list:
target = dec.func if isinstance(dec, ast.Call) else dec
name = getattr(target, "id", getattr(target, "attr", None))
if name == "command":
funcs.append(node)
return funcs
def _calls_resolve_glob(func: ast.AST) -> bool:
for node in ast.walk(func):
if isinstance(node, ast.Call):
target = node.func
name = getattr(target, "id", getattr(target, "attr", None))
if name == "resolve_glob":
return True
return False
def _indexes_first(func: ast.AST) -> bool:
for node in ast.walk(func):
if (isinstance(node, ast.Subscript)
and isinstance(node.value, ast.Name)
and node.value.id in ("paths", "resolved")):
idx = node.slice
if isinstance(idx, ast.Constant) and idx.value == 0:
return True
return False
def _loops_paths(func: ast.AST) -> bool:
for node in ast.walk(func):
if isinstance(node, (ast.For, ast.AsyncFor)) and isinstance(
node.iter, ast.Name) and node.iter.id in ("paths", "resolved"):
return True
# comprehension form: {... for p in paths}
for comp in ast.walk(node) if isinstance(node,
(ast.DictComp, ast.ListComp,
ast.SetComp,
ast.GeneratorExp)) else []:
if isinstance(comp, ast.comprehension) and isinstance(
comp.iter,
ast.Name) and comp.iter.id in ("paths", "resolved"):
return True
return False
_MULTI_HELPERS = ("format_multi", "generic_grep", "generic_rg", "grep", "rg",
"generic_du", "generic_file", "file_cmd", "du_multi",
"generic_nl", "generic_md5")
def _passes_full_list(func: ast.AST) -> bool:
"""Heuristic: the resolved list is handed to a *_multi / generic helper.
Covers backends that delegate multi-file handling to a generic routine
(head_multi, tail_multi, format_multi, du_multi, file_cmd, generic_grep,
generic_rg) by passing the whole ``paths``/``resolved`` list as the first
argument.
"""
for node in ast.walk(func):
if not isinstance(node, ast.Call):
continue
target = node.func
name = getattr(target, "id", getattr(target, "attr", "")) or ""
if name.endswith("_multi") or name in _MULTI_HELPERS:
for arg in node.args:
if isinstance(arg,
ast.Name) and arg.id in ("paths", "resolved"):
return True
return False
def _iter_command_files():
for path in sorted(BUILTIN.glob("*/*.py")):
cmd = path.stem
if cmd not in GUARDED_COMMANDS:
continue
backend = path.parent.name
yield cmd, backend, path
@pytest.mark.parametrize(
"cmd,backend,path",
[
pytest.param(c, b, p, id=f"{c}:{b}")
for c, b, p in _iter_command_files()
],
)
def test_command_handles_multiple_files(cmd, backend, path):
if (cmd, backend) in ALLOWLIST:
pytest.skip(ALLOWLIST[(cmd, backend)])
tree = ast.parse(path.read_text())
for func in _command_funcs(tree):
if not _calls_resolve_glob(func):
continue
if _loops_paths(func) or _passes_full_list(func):
continue
if _indexes_first(func):
pytest.fail(
f"{cmd}/{backend}: command resolves a glob but indexes "
f"paths[0]/resolved[0] without looping all paths. This drops "
f"every file after the first (the multi-file bug). Loop all "
f"paths or delegate to a *_multi helper. If this backend is "
f"genuinely single-resource, add it to ALLOWLIST with a "
f"reason.")