[wip] introduce BuildLibrary and make overlay first-class
This commit is contained in:
parent
e108199bcd
commit
6d494142fe
7 changed files with 1335 additions and 98 deletions
315
masque/test/test_build_library.py
Normal file
315
masque/test/test_build_library.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
import pytest
|
||||
|
||||
from ..builder import Pather
|
||||
from ..error import BuildError
|
||||
from ..library import BuildLibrary, BuiltLibrary, Library, cell
|
||||
from ..pattern import Pattern
|
||||
from ..ports import Port
|
||||
|
||||
|
||||
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 = builder.build()
|
||||
|
||||
assert "parent" in built
|
||||
assert "child" in built
|
||||
assert built.build_report.dependency_graph["parent"] == frozenset({"child"})
|
||||
assert built.build_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 = builder.build()
|
||||
report = built.build_report
|
||||
|
||||
helpers = [
|
||||
prov for prov in report.provenance.values()
|
||||
if prov.owner_declared_name == "top" and prov.kind == "helper"
|
||||
]
|
||||
|
||||
assert "top" in report.owned_cells["top"]
|
||||
assert len(helpers) == 2
|
||||
assert all(prov.emitted_via == "tree_merge" for prov in helpers)
|
||||
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 = builder.build(output="library")
|
||||
|
||||
assert isinstance(built, BuiltLibrary)
|
||||
assert built.build_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_check_on_register_rolls_back_failed_declarations() -> None:
|
||||
builder = BuildLibrary(check_on_register=True)
|
||||
|
||||
def make_parent(lib: BuildLibrary) -> Pattern:
|
||||
pat = Pattern()
|
||||
pat.ref("child")
|
||||
lib.abstract("child")
|
||||
return pat
|
||||
|
||||
with pytest.raises(BuildError, match='Failed while building declared cell "parent"'):
|
||||
builder.cells.parent = cell(make_parent)(builder)
|
||||
|
||||
assert "parent" not in builder
|
||||
|
||||
|
||||
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_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 = builder.build()
|
||||
|
||||
helper_prov = built.build_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 = builder.build()
|
||||
|
||||
assert "src" in built
|
||||
assert built.build_report.provenance["src"].kind == "source"
|
||||
assert built.build_report.provenance["src"].emitted_via == "source_import"
|
||||
|
||||
|
||||
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 = builder.build()
|
||||
|
||||
assert "renamed_child" in built
|
||||
assert "child" not in built
|
||||
assert "renamed_child" in built["parent"].refs
|
||||
assert built.build_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_and_owned_cells() -> 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 = builder.build()
|
||||
report = built.build_report
|
||||
|
||||
assert "final_helper" in built
|
||||
assert "_helper" not in built
|
||||
assert "final_helper" in report.owned_cells["top"]
|
||||
assert "_helper" not in report.owned_cells["top"]
|
||||
prov = report.provenance["final_helper"]
|
||||
assert prov.kind == "helper"
|
||||
assert prov.requested_name == "_helper"
|
||||
assert prov.renamed_from == "_helper"
|
||||
assert prov.final_name == "final_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 = builder.build()
|
||||
report = built.build_report
|
||||
|
||||
assert "_helper" not in built
|
||||
assert "_helper" not in report.provenance
|
||||
assert report.owned_cells["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 = builder.build()
|
||||
report = built.build_report
|
||||
|
||||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue