blob: 78ffdf878b29a11cc515d164de0ee3750efe65b5 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
"""Tests for `check-shared-blocks.py`, the generator that owns every shared
prose block: the one **auto** block (`preflight`, inserted into every
non-`setup` `SKILL.md`, exactly as `check-skill-preflight.py` did) plus any
number of **declared** blocks (filled into a region a target already
carries, never inserted, sourced from `tools/dev/blocks/<name>.md`)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
from types import ModuleType
REPO = Path(__file__).resolve().parents[3]
def _load() -> ModuleType:
spec = importlib.util.spec_from_file_location(
"check_shared_blocks", REPO / "tools" / "dev" / "check-shared-blocks.py"
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
MOD = _load()
NON_EXEMPT_SKILL = """---
name: magpie-demo
family: issue
description: |
A demo skill.
license: Apache-2.0
---
# Demo skill
## Step 1 — gather
Some prose.
"""
EXEMPT_SKILL = """---
name: magpie-setup-demo
family: setup
description: |
A demo setup skill.
license: Apache-2.0
---
# Demo setup skill
## Step 1 — gather
Some prose.
"""
# --- the auto block: preflight ----------------------------------------------------
def test_auto_block_lands_in_non_exempt_skill(tmp_path: Path) -> None:
skill = tmp_path / "skills" / "demo" / "SKILL.md"
skill.parent.mkdir(parents=True)
skill.write_text(NON_EXEMPT_SKILL)
block = MOD.preflight_block_text(REPO / "tools" / "dev" / "preflight-block.md")
changed, error = MOD.apply_preflight(skill, block)
assert (changed, error) == (True, None)
text = skill.read_text()
assert MOD.PREFLIGHT_BEGIN in text
assert MOD.PREFLIGHT_END in text
assert "Pre-flight — is this project set up?" in text
def test_auto_block_is_absent_from_exempt_skill(tmp_path: Path) -> None:
"""The exempt-family removal path: a `setup`-family skill must never
carry the block, even if one was hand-added before it became exempt."""
skill = tmp_path / "skills" / "setup-demo" / "SKILL.md"
skill.parent.mkdir(parents=True)
block = MOD.preflight_block_text(REPO / "tools" / "dev" / "preflight-block.md")
# Simulate a stale block already present in an exempt skill.
stale = EXEMPT_SKILL.replace(
"## Step 1 — gather",
f"{block}\n## Step 1 — gather",
)
skill.write_text(stale)
assert MOD.family_of(skill.read_text()) in MOD.EXEMPT_FAMILIES
assert MOD.PREFLIGHT_RE.search(skill.read_text())
new_text = MOD.PREFLIGHT_RE.sub("", skill.read_text())
assert MOD.PREFLIGHT_BEGIN not in new_text
def test_auto_block_apply_is_idempotent(tmp_path: Path) -> None:
skill = tmp_path / "skills" / "demo" / "SKILL.md"
skill.parent.mkdir(parents=True)
skill.write_text(NON_EXEMPT_SKILL)
block = MOD.preflight_block_text(REPO / "tools" / "dev" / "preflight-block.md")
assert MOD.apply_preflight(skill, block) == (True, None)
first = skill.read_text()
assert MOD.apply_preflight(skill, block) == (False, None)
assert skill.read_text() == first
def test_every_live_preflight_copy_is_byte_identical_to_regenerated() -> None:
"""The load-bearing guarantee: regenerating the auto block for every one
of the live skills must reproduce exactly the text already committed —
not merely 'the same after re-fixing', but identical to what is already
there, since this test never writes anything."""
block = MOD.preflight_block_text(REPO / "tools" / "dev" / "preflight-block.md")
mismatches = []
for path in sorted((REPO / "skills").glob("*/SKILL.md")):
text = path.read_text()
if MOD.family_of(text) in MOD.EXEMPT_FAMILIES:
continue
found = MOD.PREFLIGHT_RE.search(text)
if not found or found.group(0).rstrip("\n") != block.rstrip("\n"):
mismatches.append(path)
assert mismatches == []
# --- declared blocks ----------------------------------------------------------------
def _declared_region(name: str, body: str = "") -> str:
begin = f"<!-- BEGIN MAGPIE BLOCK: {name} — generated from tools/dev/blocks/{name}.md -->"
end = f"<!-- END MAGPIE BLOCK: {name} -->"
return f"{begin}\n{body}{end}\n"
def test_declared_region_is_filled(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body text.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text(f"# Detail\n\n{_declared_region('widget')}\n## Next\n")
new_text, errors = MOD.fill_declared(target.read_text(), blocks_dir=blocks_dir)
assert errors == []
assert "Widget body text." in new_text
assert "## Next" in new_text
def test_declared_region_fill_is_idempotent(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body text.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text(f"# Detail\n\n{_declared_region('widget')}\n## Next\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed, errors) == (True, [])
first = target.read_text()
changed2, errors2 = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed2, errors2) == (False, [])
assert target.read_text() == first
def test_unknown_block_name_is_an_error_not_a_silent_skip(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir() # no "made-up.md" inside
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text(f"# Detail\n\n{_declared_region('made-up')}\n")
new_text, errors = MOD.fill_declared(target.read_text(), blocks_dir=blocks_dir)
assert len(errors) == 1
assert "made-up" in errors[0]
# Left untouched — an unknown block is an error, not a silent skip that
# quietly keeps whatever placeholder text was already there.
assert new_text == target.read_text()
def test_declared_region_whose_source_is_missing_is_an_error(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text(f"# Detail\n\n{_declared_region('ghost')}\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert changed is False
assert errors and "ghost" in errors[0]
# --fix must not write anything when it cannot resolve the source.
assert "<!-- END MAGPIE BLOCK: ghost -->" in target.read_text()
def test_removing_a_declared_source_fails_rather_than_leaving_stale_text(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Original widget body.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text(f"# Detail\n\n{_declared_region('widget')}\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed, errors) == (True, [])
filled = target.read_text()
assert "Original widget body." in filled
(blocks_dir / "widget.md").unlink()
changed2, errors2 = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert changed2 is False
assert errors2 and "widget" in errors2[0]
# The already-filled text stays exactly as it was — removing the source
# must fail loudly, not silently leave (or silently blank) stale text.
assert target.read_text() == filled
def test_target_outside_allowed_roots_is_rejected(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body.\n")
outside = tmp_path / "docs" / "notes.md"
outside.parent.mkdir(parents=True)
outside.write_text(f"# Notes\n\n{_declared_region('widget')}\n")
changed, errors = MOD.process_declared(
outside, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert changed is False
assert errors and "allowed roots" in errors[0]
# Untouched: the region must not have been filled from outside the root.
assert "Widget body." not in outside.read_text()
def test_file_with_no_declared_region_is_a_no_op(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
target.write_text("# Detail\n\nNothing declared here.\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed, errors) == (False, [])
def test_declared_block_report_mode_does_not_write(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
original = f"# Detail\n\n{_declared_region('widget')}\n"
target.write_text(original)
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=False
)
assert changed is True
assert errors and "differ" in errors[0]
assert target.read_text() == original
# --- is_allowed_target: symlinked skill dirs (Task A's own self-adoption shape) -----
def test_is_allowed_target_follows_symlinked_skill_dir(tmp_path: Path) -> None:
"""This repo's own self-adoption layout: `skills/<name>/` is a symlink
into `plugins/<family>/skills/<name>/`. `.resolve()` walks that symlink
to its real location — outside the `skills/` root — and would reject
every legitimate target here; `.absolute()` must not. This is the exact
regression the `.resolve()` -> `.absolute()` fix exists to prevent, so
it earns its own end-to-end test through `process_declared`, not just
`is_allowed_target` in isolation."""
real_dir = tmp_path / "real" / "demo"
real_dir.mkdir(parents=True)
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
(skills_dir / "demo").symlink_to(real_dir, target_is_directory=True)
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body text.\n")
target = skills_dir / "demo" / "detail.md"
target.write_text(f"# Detail\n\n{_declared_region('widget')}\n## Next\n")
assert MOD.is_allowed_target(target, roots=(skills_dir,))
changed, errors = MOD.process_declared(target, blocks_dir=blocks_dir, roots=(skills_dir,), fix=True)
assert (changed, errors) == (True, [])
assert "Widget body text." in target.read_text()
# The write landed through the symlink, at the real file.
assert "Widget body text." in (real_dir / "detail.md").read_text()
def test_is_allowed_target_rejects_dotdot_traversal(tmp_path: Path) -> None:
"""`.absolute()` alone does not collapse `..` segments, so a path like
`skills/../docs/notes.md` would pass a naive `relative_to` prefix check
even though it walks straight back out of `skills/`. `normpath` must
close that gap."""
skills_dir = tmp_path / "skills"
skills_dir.mkdir()
(tmp_path / "docs").mkdir()
traversal_path = skills_dir / ".." / "docs" / "notes.md"
assert not MOD.is_allowed_target(traversal_path, roots=(skills_dir,))
# --- declared blocks: indentation is preserved, not just filled ---------------------
def test_declared_region_preserves_marker_indentation(tmp_path: Path) -> None:
"""A region nested under a list item (BEGIN/END indented 3 spaces, the
`install.md` shape) must come back indented — not flush left, which
would break the list it lives inside."""
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Line one.\n\nLine two.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
region = (
" <!-- BEGIN MAGPIE BLOCK: widget — generated from tools/dev/blocks/widget.md -->\n"
" <!-- END MAGPIE BLOCK: widget -->\n"
)
target.write_text(f"2. Item.\n\n{region}\n3. Next.\n")
new_text, errors = MOD.fill_declared(target.read_text(), blocks_dir=blocks_dir)
assert errors == []
lines = new_text.splitlines()
assert any(line.startswith(" <!-- BEGIN MAGPIE BLOCK: widget") for line in lines)
assert " Line one." in lines
assert " Line two." in lines
assert " <!-- END MAGPIE BLOCK: widget -->" in lines
def test_declared_region_blank_lines_stay_truly_blank(tmp_path: Path) -> None:
"""Blank lines inside an indented region must never carry the indent as
trailing whitespace — `trailing-whitespace` would strip it right back
out on the next hook, and `--fix` would then report a change forever."""
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Line one.\n\nLine two.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
region = (
" <!-- BEGIN MAGPIE BLOCK: widget — generated from tools/dev/blocks/widget.md -->\n"
" <!-- END MAGPIE BLOCK: widget -->\n"
)
target.write_text(f"2. Item.\n\n{region}\n")
new_text, errors = MOD.fill_declared(target.read_text(), blocks_dir=blocks_dir)
assert errors == []
for line in new_text.splitlines():
if line.strip() == "":
assert line == "", f"blank line carries trailing whitespace: {line!r}"
def test_declared_region_indentation_is_idempotent(tmp_path: Path) -> None:
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Line one.\n\nLine two.\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
region = (
" <!-- BEGIN MAGPIE BLOCK: widget — generated from tools/dev/blocks/widget.md -->\n"
" <!-- END MAGPIE BLOCK: widget -->\n"
)
target.write_text(f"2. Item.\n\n{region}\n3. Next.\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed, errors) == (True, [])
first = target.read_text()
changed2, errors2 = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(tmp_path / "skills",), fix=True
)
assert (changed2, errors2) == (False, [])
assert target.read_text() == first
def test_declared_region_fence_near_indented_code_threshold(tmp_path: Path) -> None:
"""A 3-space-indented region containing a fenced code block sits one
space shy of CommonMark's 4-space indented-code-block threshold — the
fence must still render as a fence, not collapse into an indented code
block, once the generator's own indent is added on top."""
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("```bash\necho hi\n```\n")
target = tmp_path / "skills" / "demo" / "detail.md"
target.parent.mkdir(parents=True)
region = (
" <!-- BEGIN MAGPIE BLOCK: widget — generated from tools/dev/blocks/widget.md -->\n"
" <!-- END MAGPIE BLOCK: widget -->\n"
)
target.write_text(f"2. Item.\n\n{region}\n")
new_text, errors = MOD.fill_declared(target.read_text(), blocks_dir=blocks_dir)
assert errors == []
assert " ```bash" in new_text.splitlines()
assert " echo hi" in new_text.splitlines()
assert " ```" in new_text.splitlines()
# Not 4+ spaces anywhere the fence content landed — that would be an
# indented code block instead of a fenced one once rendered.
assert " echo hi" not in new_text
# --- strip_generated_regions: indented declared regions (skill-surface-hash.py) -----
def test_strip_generated_regions_strips_indented_declared_block() -> None:
"""`skill-surface-hash.py` calls this function directly to keep a
declared block's contents out of a skill's reconciliation fingerprint.
It must strip an *indented* region — indent on the BEGIN line included —
exactly as cleanly as a flush-left one, or a nested block's text (and
any heading inside it) leaks into the hashed surface."""
text = (
"1. Item.\n\n"
" <!-- BEGIN MAGPIE BLOCK: widget — generated from tools/dev/blocks/widget.md -->\n\n"
" Some generated text.\n\n"
" <!-- END MAGPIE BLOCK: widget -->\n\n"
"2. Next.\n"
)
stripped = MOD.strip_generated_regions(text)
assert "widget" not in stripped
assert "Some generated text." not in stripped
assert "1. Item." in stripped
assert "2. Next." in stripped
# --- declared blocks: the {override_name} per-target parameter ---------------------
def _plugin_layout(tmp_path: Path) -> tuple[Path, Path, Path]:
"""Mirror this repo's self-adoption shape: the real skill directory
lives under `plugins/<family>/skills/<dir>/` and the repo-root
`skills/<name>` symlink — whose name differs from `<dir>` — points at
it. Returns `(skills_root, real_dir, blocks_dir)`."""
real_dir = tmp_path / "plugins" / "magpie-issue" / "skills" / "triage"
real_dir.mkdir(parents=True)
skills_root = tmp_path / "skills"
skills_root.mkdir()
(skills_root / "issue-triage").symlink_to(real_dir, target_is_directory=True)
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "adopter-overrides.md").write_text(
"Consult `.apache-magpie-overrides/{override_name}.md`.\n"
)
return skills_root, real_dir, blocks_dir
def test_override_name_is_the_skills_symlink_name_not_the_plugin_dir(tmp_path: Path) -> None:
skills_root, real_dir, blocks_dir = _plugin_layout(tmp_path)
target = skills_root / "issue-triage" / "SKILL.md"
target.write_text(f"# Triage\n\n{_declared_region('adopter-overrides')}\n")
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(skills_root,), skills_root=skills_root, fix=True
)
assert (changed, errors) == (True, [])
text = (real_dir / "SKILL.md").read_text()
assert "`.apache-magpie-overrides/issue-triage.md`" in text
assert "{override_name}" not in text
assert "triage.md`" not in text.replace("issue-triage.md`", "")
# Idempotent: the substituted text is what the next run regenerates.
assert MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(skills_root,), skills_root=skills_root, fix=True
) == (False, [])
def test_resolve_override_name_maps_plugin_dir_to_symlink(tmp_path: Path) -> None:
skills_root, real_dir, _ = _plugin_layout(tmp_path)
assert MOD.resolve_override_name(real_dir / "SKILL.md", skills_root) == ("issue-triage", None)
assert MOD.resolve_override_name(skills_root / "issue-triage" / "SKILL.md", skills_root) == (
"issue-triage",
None,
)
def test_unresolvable_override_name_is_an_error_not_a_silent_skip(tmp_path: Path) -> None:
skills_root, _, blocks_dir = _plugin_layout(tmp_path)
# A skill directory no `skills/<name>` entry points at.
orphan_dir = skills_root / "orphan"
orphan_dir.mkdir()
target = orphan_dir / "SKILL.md"
original = f"# Orphan\n\n{_declared_region('adopter-overrides')}\n"
target.write_text(original)
changed, errors = MOD.process_declared(
target, blocks_dir=blocks_dir, roots=(skills_root,), skills_root=tmp_path / "elsewhere", fix=True
)
assert changed is False
assert errors and "{override_name}" in errors[0] and "adopter-overrides" in errors[0]
# --fix must not write the region when it cannot resolve the parameter.
assert target.read_text() == original
def test_ambiguous_override_name_is_an_error(tmp_path: Path) -> None:
skills_root, real_dir, _ = _plugin_layout(tmp_path)
(skills_root / "issue-triage-alias").symlink_to(real_dir, target_is_directory=True)
name, error = MOD.resolve_override_name(real_dir / "SKILL.md", skills_root)
assert name is None
assert error and "issue-triage" in error and "issue-triage-alias" in error
def test_block_without_placeholder_renders_unchanged_and_needs_no_resolution(tmp_path: Path) -> None:
"""A source without `{override_name}` must render byte-identically to
the pre-parameter generator, and must not fail on a target that has no
`skills/<name>` entry — the parameter is only resolved when used."""
blocks_dir = tmp_path / "blocks"
blocks_dir.mkdir()
(blocks_dir / "widget.md").write_text("Widget body text.\n")
region = _declared_region("widget")
with_name, errors_a = MOD.fill_declared(region, blocks_dir=blocks_dir, override_name="anything")
without_name, errors_b = MOD.fill_declared(
region, blocks_dir=blocks_dir, override_name=None, override_name_error="unresolved"
)
assert errors_a == errors_b == []
assert with_name == without_name == MOD.declared_block_text("widget", blocks_dir)
def test_live_adopter_overrides_copies_use_their_skills_entry_name() -> None:
"""Every live `adopter-overrides` region names the override file after
the `skills/<name>` entry it is reached through."""
import os
old = os.getcwd()
os.chdir(REPO)
try:
for path in sorted(Path("skills").glob("*/SKILL.md")):
text = path.read_text()
if "<!-- BEGIN MAGPIE BLOCK: adopter-overrides" not in text:
continue
name = path.parent.name
assert f"`{name}.md` in the personal layer" in text, path
assert f"`.apache-magpie-overrides/{name}.md`" in text, path
finally:
os.chdir(old)