masque/masque/test/test_build_library.py

365 lines
11 KiB
Python
Raw Normal View History

import pytest
from ..builder import Pather
from ..error import BuildError
from ..library import BuildLibrary, Library, cell
from ..pattern import Pattern
from ..ports import Port
def _owned_by(report, owner: str) -> set[str]:
return {
name for name, prov in report.provenance.items()
if prov.owner_declared_name == owner
}
def test_build_library_traces_declared_dependencies_out_of_order() -> None:
builder = BuildLibrary()
def make_parent(lib: BuildLibrary) -> Pattern:
pat = Pattern()
pat.ref("child")
assert lib.abstract("child").name == "child"
return pat
builder.cells.parent = cell(make_parent)(builder)
builder["child"] = Pattern(ports={"p": Port((0, 0), 0)})
built, report = builder.build()
assert "parent" in built
assert "child" in built
assert report.dependency_graph["parent"] == frozenset({"child"})
assert report.provenance["parent"].kind == "declared"
def test_build_library_tracks_helper_provenance_and_tree_merge_renames() -> None:
builder = BuildLibrary()
def make_top(lib: BuildLibrary) -> Pattern:
tree = Library({"_helper": Pattern()})
name_a = lib << tree
name_b = lib << tree
top = Pattern()
top.ref(name_a)
top.ref(name_b)
return top
builder.cells.top = cell(make_top)(builder)
_built, report = builder.build()
helpers = [
prov for prov in report.provenance.values()
if prov.owner_declared_name == "top" and prov.kind == "helper"
]
assert "top" in _owned_by(report, "top")
assert len(helpers) == 2
assert any(prov.renamed_from == "_helper" for prov in helpers)
def test_build_library_requires_build_session_for_reads_and_freezes_after_build() -> None:
builder = BuildLibrary()
builder["leaf"] = Pattern()
with pytest.raises(BuildError, match="validate\\(\\) or build\\(\\)"):
_ = builder["leaf"]
with pytest.raises(BuildError, match="write-only"):
_ = builder.cells.leaf
built, report = builder.build(output="library")
assert isinstance(built, Library)
assert report.requested_roots == ("leaf",)
with pytest.raises(BuildError, match="frozen"):
builder["later"] = Pattern()
with pytest.raises(BuildError, match="frozen"):
builder.build()
def test_build_library_validate_is_retryable_after_failure() -> None:
builder = BuildLibrary()
def make_parent(lib: BuildLibrary) -> Pattern:
pat = Pattern()
pat.ref("child")
lib.abstract("child")
return pat
builder.cells.parent = cell(make_parent)(builder)
with pytest.raises(BuildError, match='Failed while building declared cell "parent"'):
builder.validate()
builder["child"] = Pattern(ports={"p": Port((0, 0), 0)})
report = builder.validate()
assert report.dependency_graph["parent"] == frozenset({"child"})
def test_build_library_depends_on_supports_hidden_dependencies_for_partial_validation() -> None:
builder = BuildLibrary()
builder["child"] = Pattern()
def make_parent() -> Pattern:
pat = Pattern()
pat.ref("child")
return pat
builder.cells.parent = cell(make_parent)().depends_on("child")
report = builder.validate(names=("parent",))
assert report.requested_roots == ("parent",)
assert report.dependency_graph["parent"] == frozenset({"child"})
def test_build_library_validate_rejects_removed_output_argument() -> None:
builder = BuildLibrary()
builder["leaf"] = Pattern()
with pytest.raises(TypeError):
builder.validate(output="library") # type: ignore[call-arg]
def test_build_library_rejects_unknown_build_output_mode() -> None:
builder = BuildLibrary()
builder["leaf"] = Pattern()
with pytest.raises(ValueError, match="Unknown build output mode"):
builder.build(output="bad") # type: ignore[arg-type]
def test_build_library_allows_helper_writes_via_pather() -> None:
builder = BuildLibrary()
builder["leaf"] = Pattern(ports={"a": Port((0, 0), 0)})
def make_top(lib: BuildLibrary) -> Pattern:
helper = Pather(library=lib, ports="leaf", name="_route")
top = Pattern()
top.ref("_route")
top.ref("leaf")
top.ports.update(helper.pattern.ports)
return top
builder.cells.top = cell(make_top)(builder)
_built, report = builder.build()
helper_prov = report.provenance["_route"]
assert helper_prov.kind == "helper"
assert helper_prov.owner_declared_name == "top"
def test_build_library_preserves_source_cells_and_records_source_provenance() -> None:
source = Library({"src": Pattern()})
builder = BuildLibrary()
builder.add_source(source)
builder.cells.top = cell(lambda: Pattern())()
built, report = builder.build()
assert "src" in built
assert report.provenance["src"].kind == "source"
2026-06-19 22:48:21 -07:00
def test_build_library_add_source_can_rename_every_source_cell() -> None:
source = Library()
source["child"] = Pattern()
parent = Pattern()
parent.ref("child")
source["parent"] = parent
builder = BuildLibrary()
rename_map = builder.add_source(
source,
rename_theirs=lambda _lib, name: f"mapped_{name}",
rename_when="always",
)
built, report = builder.build()
assert rename_map == {
"child": "mapped_child",
"parent": "mapped_parent",
}
assert "mapped_child" in built["mapped_parent"].refs
assert report.provenance["mapped_child"].source_name == "child"
def test_build_library_rejects_source_cells_added_after_add_source() -> None:
source = Library({"src": Pattern()})
builder = BuildLibrary()
builder.add_source(source)
source["late"] = Pattern()
with pytest.raises(BuildError, match="Do not structurally mutate source libraries"):
builder.build()
def test_build_library_rejects_source_cells_removed_after_add_source() -> None:
source = Library({"src": Pattern()})
builder = BuildLibrary()
builder.add_source(source)
del source["src"]
with pytest.raises(BuildError, match="Do not structurally mutate source libraries"):
builder.build()
def test_build_library_rejects_add_source_during_build() -> None:
builder = BuildLibrary()
def make_top(lib: BuildLibrary) -> Pattern:
lib.add_source(Library({"src": Pattern()}))
return Pattern()
builder.cells.top = cell(make_top)(builder)
with pytest.raises(BuildError, match="add_source"):
builder.build()
def test_build_library_can_rename_imported_source_cells_during_authoring() -> None:
source = Library()
source["child"] = Pattern()
parent = Pattern()
parent.ref("child")
source["parent"] = parent
builder = BuildLibrary()
builder.add_source(source)
builder.rename("child", "renamed_child")
built, report = builder.build()
assert "renamed_child" in built
assert "child" not in built
assert "renamed_child" in built["parent"].refs
assert report.provenance["renamed_child"].source_name == "child"
def test_build_library_rejects_move_references_for_source_rename() -> None:
builder = BuildLibrary()
builder.add_source(Library({"src": Pattern()}))
with pytest.raises(BuildError, match="move_references=True"):
builder.rename("src", "renamed_src", move_references=True)
def test_build_library_rejects_renaming_declared_cells_during_authoring() -> None:
builder = BuildLibrary()
builder["declared"] = Pattern()
with pytest.raises(BuildError, match='Cannot rename declared build cell "declared"'):
builder.rename("declared", "renamed_declared")
def test_build_library_helper_rename_updates_provenance_owner() -> None:
builder = BuildLibrary()
def make_top(lib: BuildLibrary) -> Pattern:
lib["_helper"] = Pattern()
lib.rename("_helper", "final_helper")
top = Pattern()
top.ref("final_helper")
return top
builder.cells.top = cell(make_top)(builder)
built, report = builder.build()
assert "final_helper" in built
assert "_helper" not in built
owned = _owned_by(report, "top")
assert "final_helper" in owned
assert "_helper" not in owned
prov = report.provenance["final_helper"]
assert prov.kind == "helper"
assert prov.requested_name == "_helper"
assert prov.renamed_from == "_helper"
def test_build_library_helper_delete_removes_provenance_and_ownership() -> None:
builder = BuildLibrary()
def make_top(lib: BuildLibrary) -> Pattern:
lib["_helper"] = Pattern()
del lib["_helper"]
return Pattern()
builder.cells.top = cell(make_top)(builder)
built, report = builder.build()
assert "_helper" not in built
assert "_helper" not in report.provenance
assert _owned_by(report, "top") == {"top"}
def test_build_library_helper_rename_after_auto_rename_preserves_requested_name() -> None:
builder = BuildLibrary()
def make_top(lib: BuildLibrary) -> Pattern:
tree = Library({"_helper": Pattern()})
_ = lib << tree
renamed = lib << tree
lib.rename(renamed, "final_helper")
top = Pattern()
top.ref("_helper")
top.ref("final_helper")
return top
builder.cells.top = cell(make_top)(builder)
built, report = builder.build()
assert "final_helper" in built
prov = report.provenance["final_helper"]
assert prov.requested_name == "_helper"
assert prov.renamed_from == "_helper"
def test_build_library_rejects_renaming_declared_or_source_cells_during_build() -> None:
declared = BuildLibrary()
declared["leaf"] = Pattern()
def rename_declared(lib: BuildLibrary) -> Pattern:
lib.rename("leaf", "renamed_leaf")
return Pattern()
declared.cells.top = cell(rename_declared)(declared)
with pytest.raises(BuildError, match='Cannot rename declared build cell "leaf"'):
declared.build()
source = BuildLibrary()
source.add_source(Library({"src": Pattern()}))
def rename_source(lib: BuildLibrary) -> Pattern:
lib.rename("src", "renamed_src")
return Pattern()
source.cells.top = cell(rename_source)(source)
with pytest.raises(BuildError, match='Cannot rename imported source cell "src"'):
source.build()
def test_build_library_rejects_deleting_declared_or_source_cells_during_build() -> None:
declared = BuildLibrary()
declared["leaf"] = Pattern()
def delete_declared(lib: BuildLibrary) -> Pattern:
del lib["leaf"]
return Pattern()
declared.cells.top = cell(delete_declared)(declared)
with pytest.raises(BuildError, match='Cannot delete declared build cell "leaf"'):
declared.build()
source = BuildLibrary()
source.add_source(Library({"src": Pattern()}))
def delete_source(lib: BuildLibrary) -> Pattern:
del lib["src"]
return Pattern()
source.cells.top = cell(delete_source)(source)
with pytest.raises(BuildError, match='Cannot delete imported source cell "src"'):
source.build()