migration: prove standalone maintenance and correct visual evidence

This commit is contained in:
leefer
2026-08-01 03:40:18 +08:00
parent 78c4f3586a
commit 1da0807ad8
7 changed files with 93 additions and 22 deletions
+7 -7
View File
@@ -254,7 +254,7 @@
"code_hotspots": [ "code_hotspots": [
{ {
"path": "frontend/styles/styles.css", "path": "frontend/styles/styles.css",
"bytes": 361780, "bytes": 361776,
"lines": 15465 "lines": 15465
}, },
{ {
@@ -279,12 +279,12 @@
}, },
{ {
"path": "frontend/app.js", "path": "frontend/app.js",
"bytes": 91151, "bytes": 89213,
"lines": 1939 "lines": 1939
}, },
{ {
"path": "frontend/pages/heaven/page.js", "path": "frontend/pages/heaven/page.js",
"bytes": 88322, "bytes": 86493,
"lines": 1830 "lines": 1830
}, },
{ {
@@ -299,7 +299,7 @@
}, },
{ {
"path": "backend/features/heaven/service.py", "path": "backend/features/heaven/service.py",
"bytes": 64421, "bytes": 63123,
"lines": 1303 "lines": 1303
}, },
{ {
@@ -309,7 +309,7 @@
}, },
{ {
"path": "frontend/pages/market/runtime.js", "path": "frontend/pages/market/runtime.js",
"bytes": 57053, "bytes": 55720,
"lines": 1333 "lines": 1333
}, },
{ {
@@ -319,7 +319,7 @@
}, },
{ {
"path": "backend/application.py", "path": "backend/application.py",
"bytes": 50934, "bytes": 49784,
"lines": 1165 "lines": 1165
}, },
{ {
@@ -329,7 +329,7 @@
}, },
{ {
"path": "database.py", "path": "database.py",
"bytes": 34013, "bytes": 33284,
"lines": 746 "lines": 746
} }
] ]
+4 -5
View File
@@ -26,6 +26,7 @@ RETIRED_FRONTEND_SOURCE_RANGES = (
(9022, 9027), (9022, 9027),
(9052, 9055), (9052, 9055),
) )
AUDITED_FRONTEND_SOURCE_LINE_COUNT = 9283
def sha256(path: Path) -> str: def sha256(path: Path) -> str:
@@ -51,12 +52,10 @@ def reassembled_frontend_runtime() -> str:
assembled.append(content) assembled.append(content)
next_line = end + 1 next_line = end + 1
original_line_count = len( if next_line != AUDITED_FRONTEND_SOURCE_LINE_COUNT + 1:
(ORIGINAL_STATIC / "app.js").read_text(encoding="utf-8").splitlines()
)
if next_line != original_line_count + 1:
raise AssertionError( raise AssertionError(
f"app.js source coverage ended at {next_line - 1}, expected {original_line_count}" "app.js source coverage ended at "
f"{next_line - 1}, expected {AUDITED_FRONTEND_SOURCE_LINE_COUNT}"
) )
return "".join(assembled) return "".join(assembled)
+9
View File
@@ -4,6 +4,7 @@ import json
import re import re
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import patch
from tests.preservation_helpers import reassembled_frontend_runtime from tests.preservation_helpers import reassembled_frontend_runtime
@@ -46,6 +47,14 @@ class FrontendBoundaryTests(unittest.TestCase):
self.assertIn("const state = window.XiaobaiState.create({", app) self.assertIn("const state = window.XiaobaiState.create({", app)
self.assertNotIn("const state = {", app) self.assertNotIn("const state = {", app)
def test_candidate_runtime_reassembly_does_not_require_original_static(self) -> None:
with patch(
"tests.preservation_helpers.ORIGINAL_STATIC",
ROOT / "missing-original-static",
):
app = reassembled_frontend_runtime()
self.assertIn("function openView(", app)
def test_runtime_page_registry_matches_governance_registry(self) -> None: def test_runtime_page_registry_matches_governance_registry(self) -> None:
expected = json.loads( expected = json.loads(
(ROOT / "config" / "pages.config.json").read_text(encoding="utf-8") (ROOT / "config" / "pages.config.json").read_text(encoding="utf-8")
+22 -1
View File
@@ -3,11 +3,16 @@ from __future__ import annotations
import json import json
import subprocess import subprocess
import sys import sys
import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from tools.build_api_registry import build as build_api_registry from tools.build_api_registry import build as build_api_registry
from tools.build_architecture_inventory import build as build_architecture_inventory from tools.build_architecture_inventory import (
build as build_architecture_inventory,
source_metrics,
)
from tools.verify_baseline import python_test_command
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
@@ -34,6 +39,22 @@ class MaintenanceToolTests(unittest.TestCase):
self.assertIn("stop_e2e_server(server)", source) self.assertIn("stop_e2e_server(server)", source)
self.assertNotIn('"static/app.js"', source) self.assertNotIn('"static/app.js"', source)
def test_standalone_verifier_excludes_only_migration_comparison_modules(self) -> None:
repository_command = python_test_command(preservation_baseline=True)
standalone_command = python_test_command(preservation_baseline=False)
self.assertIn("discover", repository_command)
self.assertTrue(any(item == "tests.test_frontend_contract" for item in standalone_command))
self.assertFalse(any("test_preservation_" in item for item in standalone_command))
def test_architecture_metrics_are_independent_of_checkout_line_endings(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
lf = root / "lf.js"
crlf = root / "crlf.js"
lf.write_bytes(b"const a = 1;\nconst b = 2;\n")
crlf.write_bytes(b"const a = 1;\r\nconst b = 2;\r\n")
self.assertEqual(source_metrics(lf), source_metrics(crlf))
def test_every_tool_has_a_non_mutating_help_path(self) -> None: def test_every_tool_has_a_non_mutating_help_path(self) -> None:
for path in sorted((ROOT / "tools").glob("*.py")): for path in sorted((ROOT / "tools").glob("*.py")):
if path.name.startswith("_"): if path.name.startswith("_"):
+7
View File
@@ -14,6 +14,13 @@ maintenance command cannot be mistaken for a historical migration rewrite.
- `python tools/build_architecture_inventory.py [--check]`: generate or verify - `python tools/build_architecture_inventory.py [--check]`: generate or verify
`config/architecture-inventory.json` from the candidate source tree. `config/architecture-inventory.json` from the candidate source tree.
Inside the canonical `webapp/app/` checkout, `verify_baseline.py` runs the full preservation
suite against the retained original baseline and enforces `git diff --check`. In a standalone
`app/` export where that baseline and Git checkout do not exist, the same command runs all
candidate-owned tests, skips only `test_preservation_*` comparison modules, and reports the Git
check as skipped. Product, registry, JavaScript, database, and optional Playwright checks remain
active in both modes.
## Acceptance and differential checks ## Acceptance and differential checks
- `run_preservation_runtime.py`: start an isolated original or candidate runtime with an - `run_preservation_runtime.py`: start an isolated original or candidate runtime with an
+9 -7
View File
@@ -75,6 +75,14 @@ def css_layers(html: str) -> list[str]:
return re.findall(r'<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"', html) return re.findall(r'<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"', html)
def source_metrics(path: Path) -> dict[str, int]:
text = path.read_text(encoding="utf-8")
return {
"bytes": len(text.encode("utf-8")),
"lines": len(text.splitlines()),
}
def code_hotspots() -> list[dict[str, Any]]: def code_hotspots() -> list[dict[str, Any]]:
candidates = [ candidates = [
"backend/application.py", "backend/application.py",
@@ -99,13 +107,7 @@ def code_hotspots() -> list[dict[str, Any]]:
path = ROOT / name path = ROOT / name
if not path.is_file(): if not path.is_file():
continue continue
rows.append( rows.append({"path": name, **source_metrics(path)})
{
"path": name,
"bytes": path.stat().st_size,
"lines": len(path.read_text(encoding="utf-8").splitlines()),
}
)
return sorted(rows, key=lambda item: item["bytes"], reverse=True) return sorted(rows, key=lambda item: item["bytes"], reverse=True)
+35 -2
View File
@@ -21,6 +21,39 @@ def run(label: str, command: list[str]) -> None:
subprocess.run(command, cwd=ROOT, check=True) subprocess.run(command, cwd=ROOT, check=True)
def python_test_command(preservation_baseline: bool | None = None) -> list[str]:
if preservation_baseline is None:
preservation_baseline = (ROOT.parent / "static" / "app.js").is_file()
if preservation_baseline:
return [sys.executable, "-m", "unittest", "discover", "-s", "tests"]
modules = [
f"tests.{path.stem}"
for path in sorted((ROOT / "tests").glob("test_*.py"))
if not path.stem.startswith("test_preservation_")
]
if not modules:
raise RuntimeError("no standalone candidate tests found")
return [sys.executable, "-m", "unittest", *modules]
def verify_git_diff() -> None:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
cwd=ROOT,
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
print("\n[patch] skipped: standalone export is not a Git checkout")
return
repository_root = Path(result.stdout.strip()).resolve()
if ROOT != repository_root / "app":
print("\n[patch] skipped: standalone export is outside the canonical app path")
return
run("patch", ["git", "diff", "--check"])
def verify_database() -> None: def verify_database() -> None:
database = ROOT / "data" / "review.db" database = ROOT / "data" / "review.db"
if not database.exists(): if not database.exists():
@@ -97,7 +130,7 @@ def main() -> int:
) )
args = parser.parse_args() args = parser.parse_args()
run("python", [sys.executable, "-m", "unittest", "discover", "-s", "tests"]) run("python", python_test_command())
run( run(
"api-registry", "api-registry",
[sys.executable, "tools/build_api_registry.py", "--check"], [sys.executable, "tools/build_api_registry.py", "--check"],
@@ -117,7 +150,7 @@ def main() -> int:
"javascript", "javascript",
[node, "--check", script.relative_to(ROOT).as_posix()], [node, "--check", script.relative_to(ROOT).as_posix()],
) )
run("patch", ["git", "diff", "--check"]) verify_git_diff()
verify_database() verify_database()
if args.e2e: if args.e2e: