Add persistent source generations
This commit is contained in:
parent
3bee200234
commit
ad4f52b239
7 changed files with 770 additions and 231 deletions
|
|
@ -761,7 +761,7 @@ operations = ["create", "update", "move", "delete"]
|
|||
mock.patch.object(
|
||||
race_project,
|
||||
"canonical_source_paths",
|
||||
side_effect=[sources, sources, sources, (*sources, invented)],
|
||||
side_effect=[sources, (*sources, invented)],
|
||||
),
|
||||
self.assertRaisesRegex(DocForgeError, "changed during changeset storage"),
|
||||
):
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from docforge.cli import main # noqa: E402
|
|||
from docforge.context import compile_context # noqa: E402
|
||||
from docforge.errors import DocForgeError # noqa: E402
|
||||
from docforge.index import ProjectIndex # noqa: E402
|
||||
from docforge.models import Edge # noqa: E402
|
||||
from docforge.models import Edge, ProjectState # noqa: E402
|
||||
from docforge.project import Project, validate_graph # noqa: E402
|
||||
|
||||
FIXTURES = ROOT / "tests" / "fixtures"
|
||||
|
|
@ -138,6 +138,7 @@ class DocForgeCoreTests(unittest.TestCase):
|
|||
def test_duplicate_nodes_broken_edges_and_dependency_cycles_fail(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
snapshot = Project.open(root).load()
|
||||
content = root / "docs" / "content"
|
||||
duplicate = content / "duplicate.md"
|
||||
duplicate.write_text((content / "foundation.md").read_text(), encoding="utf-8")
|
||||
|
|
@ -169,6 +170,23 @@ class DocForgeCoreTests(unittest.TestCase):
|
|||
with self.assertRaisesRegex(DocForgeError, "cycle"):
|
||||
Project.open(root).load()
|
||||
|
||||
with self.assertRaises(DocForgeError) as missing_source:
|
||||
validate_graph(
|
||||
snapshot.nodes,
|
||||
(
|
||||
Edge(
|
||||
"missing.source",
|
||||
"depends_on",
|
||||
snapshot.nodes[0].node_id,
|
||||
),
|
||||
),
|
||||
)
|
||||
self.assertEqual("broken_edge", missing_source.exception.code)
|
||||
self.assertEqual(
|
||||
["missing.source"],
|
||||
missing_source.exception.details["sources"],
|
||||
)
|
||||
|
||||
def test_graph_validation_uses_a_bounded_number_of_edge_passes(self) -> None:
|
||||
snapshot = Project.open(FIXTURES / "alpha").load()
|
||||
nodes = tuple(
|
||||
|
|
@ -177,7 +195,7 @@ class DocForgeCoreTests(unittest.TestCase):
|
|||
node_id=f"linear.node-{index:05d}",
|
||||
source_path=f"docs/node-{index:05d}.md",
|
||||
)
|
||||
for index in range(2_000)
|
||||
for index in range(10_000)
|
||||
)
|
||||
edges = CountingTuple(
|
||||
tuple(
|
||||
|
|
@ -208,6 +226,58 @@ class DocForgeCoreTests(unittest.TestCase):
|
|||
self.assertEqual(3, validated["node_count"])
|
||||
self.assertEqual(2, validated["edge_count"])
|
||||
|
||||
def test_persisted_generic_generation_avoids_warm_source_loading(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
ProjectIndex(Project.open(root)).build()
|
||||
fresh_project = Project.open(root)
|
||||
fresh_index = ProjectIndex(fresh_project)
|
||||
|
||||
with mock.patch.object(
|
||||
fresh_project,
|
||||
"load",
|
||||
side_effect=AssertionError("warm reads must not load canonical sources"),
|
||||
):
|
||||
self.assertEqual(
|
||||
"Editing workflow",
|
||||
fresh_index.get_node("guide.workflow")["node"]["title"],
|
||||
)
|
||||
self.assertEqual(1, fresh_index.search("canonical nodes")["count"])
|
||||
self.assertEqual(1, fresh_index.filter_nodes(family="proof")["count"])
|
||||
self.assertEqual(1, len(fresh_index.backlinks("guide.workflow")["edges"]))
|
||||
self.assertEqual(1, fresh_index.dependencies("guide.workflow")["count"])
|
||||
self.assertEqual(2, fresh_index.impact("guide.foundation")["count"])
|
||||
self.assertEqual("active", compile_context(fresh_index, "active")["profile"])
|
||||
self.assertEqual("current", fresh_index.synchronize()["synchronization"]["action"])
|
||||
|
||||
workflow = root / "docs" / "content" / "workflow.md"
|
||||
workflow.write_text(
|
||||
workflow.read_text(encoding="utf-8") + "\nChanged after generation.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
with self.assertRaisesRegex(DocForgeError, "does not match"):
|
||||
fresh_index.get_node("guide.workflow")
|
||||
|
||||
def test_missing_or_corrupt_generation_falls_back_and_repairs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
project = Project.open(root)
|
||||
index = ProjectIndex(project)
|
||||
index.build()
|
||||
generation_path = project.generation_path
|
||||
|
||||
for raw in (None, "{not-json"):
|
||||
with self.subTest(raw=raw):
|
||||
if raw is None:
|
||||
generation_path.unlink(missing_ok=True)
|
||||
else:
|
||||
generation_path.write_text(raw, encoding="utf-8")
|
||||
with mock.patch.object(project, "load", wraps=project.load) as load:
|
||||
checked = index.check(verify_rows=False)
|
||||
self.assertEqual("ok", checked["status"])
|
||||
self.assertGreaterEqual(load.call_count, 1)
|
||||
self.assertIsNotNone(project.incremental_state())
|
||||
|
||||
def test_index_rejects_tampered_rows_and_another_project_cache(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
parent = Path(directory)
|
||||
|
|
@ -280,23 +350,38 @@ class DocForgeCoreTests(unittest.TestCase):
|
|||
def test_query_rechecks_source_identity_before_returning(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
index = ProjectIndex(Project.open(root))
|
||||
checked = index.build()
|
||||
changed = {**checked, "source_hash": "0" * 64}
|
||||
project = Project.open(root)
|
||||
index = ProjectIndex(project)
|
||||
index.build()
|
||||
current = project.incremental_state()
|
||||
self.assertIsNotNone(current)
|
||||
changed = ProjectState(
|
||||
source_hash="0" * 64,
|
||||
revision=current.revision,
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch.object(index, "check", side_effect=[checked, changed]),
|
||||
mock.patch.object(
|
||||
project,
|
||||
"incremental_state",
|
||||
side_effect=[current, changed],
|
||||
),
|
||||
self.assertRaisesRegex(DocForgeError, "changed during the query"),
|
||||
):
|
||||
index.get_node("guide.workflow")
|
||||
|
||||
def test_source_set_change_during_load_fails_closed(self) -> None:
|
||||
project = Project.open(FIXTURES / "alpha")
|
||||
sources = project.canonical_source_paths()
|
||||
sources, directories = project._canonical_inventory()
|
||||
invented = project.descriptor.root / "docs" / "content" / "invented.md"
|
||||
with (
|
||||
mock.patch.object(
|
||||
project, "canonical_source_paths", side_effect=[sources, (*sources, invented)]
|
||||
project,
|
||||
"_canonical_inventory",
|
||||
side_effect=[
|
||||
(sources, directories),
|
||||
((*sources, invented), directories),
|
||||
],
|
||||
),
|
||||
self.assertRaisesRegex(DocForgeError, "source set changed"),
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue