123 lines
6.2 KiB
Python
123 lines
6.2 KiB
Python
import contextlib
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import read_implementation_source as reader
|
|
|
|
|
|
class SourceBudgetTests(unittest.TestCase):
|
|
def setUp(self):
|
|
directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.root = Path(directory.name)
|
|
current = self.root / "docs/current"
|
|
current.mkdir(parents=True)
|
|
self.policy = current / "execution-policy.json"
|
|
self.packet = current / "packet.json"
|
|
self.packet.write_text(json.dumps({"batchId": "repair", "supportFiles": [{"path": "source.cpp"}]}))
|
|
(self.root / "source.cpp").write_text("".join(f"line {i}\n" for i in range(100)))
|
|
self.marker = self.root / "turn"
|
|
self.enterContext(patch.object(reader, "ROOT", self.root))
|
|
self.enterContext(patch.object(reader, "PACKET", self.packet))
|
|
self.enterContext(patch.dict(os.environ, FREECAD_SOURCE_BUDGET_MARKER=str(self.marker)))
|
|
|
|
def read(self, start, path="source.cpp"):
|
|
output = io.StringIO()
|
|
with patch("sys.argv", ["reader", path, "--start", str(start), "--lines", "2", "--reason", "Resolve the native call anchor"]), \
|
|
contextlib.redirect_stdout(output), contextlib.redirect_stderr(io.StringIO()):
|
|
code = reader.main()
|
|
return code, json.loads(output.getvalue()) if output.getvalue() else None
|
|
|
|
def test_continuous_repair_can_read_fifth_distinct_anchor_without_reloading_duplicates(self):
|
|
self.policy.write_text(json.dumps({"continuousDevelopment": True}))
|
|
for start in (1, 3, 5, 7, 9):
|
|
self.assertEqual(self.read(start)[0], 0)
|
|
self.assertEqual(self.read(9)[1]["status"], "already_in_context")
|
|
state = json.loads(Path(str(self.marker) + ".windows.json").read_text())
|
|
self.assertEqual(len(state["windows"]), 5)
|
|
self.assertEqual(self.read(11)[1]["remainingWindows"], 10)
|
|
|
|
def test_legacy_profile_keeps_its_four_window_limit(self):
|
|
for start in (1, 3, 5, 7):
|
|
self.assertEqual(self.read(start)[0], 0)
|
|
self.assertEqual(self.read(9)[0], 64)
|
|
|
|
def test_continuous_profile_still_enforces_total_byte_budget(self):
|
|
self.policy.write_text(json.dumps({"continuousDevelopment": True}))
|
|
with patch.object(reader, "MAX_TOTAL_WINDOW_BYTES", 20):
|
|
self.assertEqual(self.read(1)[0], 0)
|
|
self.assertEqual(self.read(3)[0], 64)
|
|
|
|
def test_multi_requirement_turn_can_reach_selected_native_anchors_after_sixteen_windows(self):
|
|
self.policy.write_text(json.dumps({"continuousDevelopment": True}))
|
|
packet = json.loads(self.packet.read_text())
|
|
packet["records"] = [{"apiId": f"Web:desktop:{i}"} for i in range(4)]
|
|
self.packet.write_text(json.dumps(packet))
|
|
for start in range(1, 96, 2):
|
|
self.assertEqual(self.read(start)[0], 0)
|
|
self.assertEqual(self.read(95)[1]["status"], "already_in_context")
|
|
self.assertEqual(self.read(97)[0], 64)
|
|
self.assertEqual(reader.source_limits(packet), (48, 144000))
|
|
|
|
def test_contract_delegates_are_readable_but_unlisted_paths_are_not(self):
|
|
directory = self.root / "docs/business-acceptance"
|
|
directory.mkdir()
|
|
(directory / "Web.json").write_text(json.dumps({"command": ["node", "entry.mjs"],
|
|
"dependencies": ["delegate.mjs"], "caseDependencies": ["contract.ts"]}))
|
|
packet = json.loads(self.packet.read_text())
|
|
packet["acceptanceContract"] = "docs/business-acceptance/Web.json"
|
|
self.packet.write_text(json.dumps(packet))
|
|
for name in ["entry.mjs", "delegate.mjs", "contract.ts", "unlisted.ts"]:
|
|
(self.root / name).write_text("export const value = 1;\n")
|
|
for name in ["entry.mjs", "delegate.mjs", "contract.ts"]:
|
|
self.assertEqual(self.read(1, name)[0], 0)
|
|
self.assertEqual(self.read(1, "unlisted.ts")[0], 64)
|
|
summary = json.loads(reader.packet_summary(packet))
|
|
self.assertIn("delegate.mjs", summary["supportPaths"])
|
|
|
|
def test_contract_dependencies_cannot_escape_the_project(self):
|
|
outside = self.root.parent / (self.root.name + "-outside.ts")
|
|
outside.write_text("secret\n")
|
|
self.addCleanup(outside.unlink)
|
|
directory = self.root / "docs/business-acceptance"
|
|
directory.mkdir()
|
|
(directory / "Web.json").write_text(json.dumps({"dependencies": [str(outside)]}))
|
|
self.packet.write_text(json.dumps({"batchId": "repair", "acceptanceContract": "docs/business-acceptance/Web.json"}))
|
|
self.assertEqual(self.read(1, str(outside))[0], 64)
|
|
self.assertNotIn(str(outside), reader.packet_summary(json.loads(self.packet.read_text())))
|
|
|
|
def test_current_module_new_cases_and_native_tasks_do_not_require_coverage_declaration(self):
|
|
self.policy.write_text(json.dumps({"continuousDevelopment": True}))
|
|
self.packet.write_text(json.dumps({"batchId": "repair", "module": "Part"}))
|
|
paths = ["tools/part-new-document-business-cases.mjs", "freecad-source/src/Mod/Part/Gui/TaskOffset.cpp",
|
|
"web/src/part-new-document-task.tsx", "tools/partdesign-new-document-business-cases.mjs",
|
|
"freecad-source/src/Mod/PartDesign/Gui/TaskOffset.cpp", "tools/part-private.json"]
|
|
for name in paths:
|
|
target = self.root / name
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_text("first line\nsecond line\n")
|
|
for name in paths[:3]:
|
|
self.assertEqual(self.read(1, name)[0], 0)
|
|
for name in paths[3:]:
|
|
self.assertEqual(self.read(1, name)[0], 64)
|
|
self.assertEqual(self.read(1, paths[0])[1]["status"], "already_in_context")
|
|
self.policy.write_text("{}")
|
|
self.assertEqual(self.read(1, paths[0])[0], 64)
|
|
|
|
def test_missing_filename_is_not_reported_as_a_scope_rejection(self):
|
|
error = io.StringIO()
|
|
with patch("sys.argv", ["reader", "missing.cpp", "--start", "1", "--reason", "Locate source"]), \
|
|
contextlib.redirect_stderr(error):
|
|
self.assertEqual(reader.main(), 64)
|
|
self.assertIn("does not exist", error.getvalue())
|
|
self.assertNotIn("packet dependency", error.getvalue())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|