1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add persistent source generations

This commit is contained in:
Andraxion 2026-07-29 04:00:23 -04:00
parent 3bee200234
commit ad4f52b239
7 changed files with 770 additions and 231 deletions

View file

@ -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"),
):