fix(tester): discover catalogs in subprojects
This commit is contained in:
@@ -55,6 +55,55 @@ def catalog_dir(root: Path) -> Path:
|
||||
return root / ".pouch" / "tester"
|
||||
|
||||
|
||||
def catalog_path(root: Path) -> Path:
|
||||
return catalog_dir(root) / "catalog.yaml"
|
||||
|
||||
|
||||
def discover_catalog_roots(root: Path) -> list[Path]:
|
||||
"""Find tester projects owned by a repository and its Git submodules.
|
||||
|
||||
A monorepo or a parent repository may contain more than one independent
|
||||
tester catalog. Cases must stay scoped to the project that owns them; this
|
||||
helper only discovers roots and never merges their cases.
|
||||
"""
|
||||
root = root.resolve()
|
||||
candidates: list[Path] = []
|
||||
if catalog_path(root).is_file():
|
||||
candidates.append(root)
|
||||
|
||||
# Prefer the authoritative submodule list so generated directories and
|
||||
# vendored dependencies are not accidentally treated as test projects.
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["git", "-C", str(root), "config", "--file", ".gitmodules", "--get-regexp", r"^submodule\..*\.path$"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
for line in proc.stdout.splitlines():
|
||||
_, _, raw_path = line.partition(" ")
|
||||
subroot = (root / raw_path.strip()).resolve()
|
||||
if raw_path.strip() and catalog_path(subroot).is_file() and subroot not in candidates:
|
||||
candidates.append(subroot)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Also support a plain nested package that is not declared as a submodule.
|
||||
# Only inspect a shallow set of project directories; callers can pass that
|
||||
# directory explicitly when a deeper layout is intentional.
|
||||
ignored = {".git", ".pouch", "node_modules", "vendor", "third_party", "dist", "build"}
|
||||
try:
|
||||
children = list(root.iterdir())
|
||||
except OSError:
|
||||
children = []
|
||||
for child in children:
|
||||
if not child.is_dir() or child.name in ignored or child.name.startswith("."):
|
||||
continue
|
||||
if catalog_path(child).is_file() and child.resolve() not in candidates:
|
||||
candidates.append(child.resolve())
|
||||
return sorted(candidates)
|
||||
|
||||
|
||||
def load_yaml(path: Path) -> Any:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
data = yaml.safe_load(text)
|
||||
@@ -829,6 +878,29 @@ def cmd_export_csv(root: Path) -> int:
|
||||
return run_export_csv(root)
|
||||
|
||||
|
||||
def cmd_roots(root: Path) -> int:
|
||||
"""List independent tester catalog roots below a repository."""
|
||||
roots = discover_catalog_roots(root)
|
||||
entries: list[dict[str, str]] = []
|
||||
for item in roots:
|
||||
project = ""
|
||||
try:
|
||||
meta = load_catalog_meta(item)
|
||||
project = str((meta.get("project") or {}).get("name") or "")
|
||||
except (SystemExit, AttributeError):
|
||||
pass
|
||||
entries.append(
|
||||
{
|
||||
"root": str(item),
|
||||
"catalog": str(catalog_path(item)),
|
||||
"project": project,
|
||||
"relative": str(item.relative_to(root.resolve())) if item != root.resolve() else ".",
|
||||
}
|
||||
)
|
||||
print(json.dumps({"base": str(root.resolve()), "count": len(entries), "roots": entries}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_next_id(root: Path, category: str) -> int:
|
||||
meta = load_catalog_meta(root)
|
||||
cat_ids = [c.get("id") for c in meta.get("categories") or [] if isinstance(c, dict)]
|
||||
@@ -988,6 +1060,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
p_show = sub.add_parser("show", help="打印一条用例", parents=[parent])
|
||||
p_show.add_argument("--id", required=True, dest="case_id")
|
||||
sub.add_parser("validate", help="校验目录", parents=[parent])
|
||||
sub.add_parser("roots", help="发现当前仓库及子项目的 tester 根目录", parents=[parent])
|
||||
sub.add_parser("export-csv", help="写出给人看的 cases.csv", parents=[parent])
|
||||
p_next = sub.add_parser("next-id", help="分配下一个 TC-<CAT>-NNN", parents=[parent])
|
||||
p_next.add_argument("--category", required=True)
|
||||
@@ -1008,6 +1081,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return cmd_show(root, args.case_id)
|
||||
if args.cmd == "validate":
|
||||
return cmd_validate(root)
|
||||
if args.cmd == "roots":
|
||||
return cmd_roots(root)
|
||||
if args.cmd == "export-csv":
|
||||
return cmd_export_csv(root)
|
||||
if args.cmd == "next-id":
|
||||
|
||||
Reference in New Issue
Block a user