Проверить VSIX после разделения и принять размерный эталон SDK

This commit is contained in:
Александр Петров
2026-09-16 15:00:12 +03:00
parent 4ee45f0ace
commit d25f28e1cd
4 changed files with 105 additions and 48 deletions
+68 -21
View File
@@ -4,14 +4,18 @@ size_check.py — размерный регресс-тест: сравнение
docs/size_baseline.tsv.
Использование:
python3 toolchain/size_check.py # сверка с эталоном
python3 toolchain/size_check.py --update # перезаписать эталон
make size-check # собрать обязательные тесты и сверить с эталоном
make size-baseline # принять свежие размеры, сохранив необязательные
Логика: собираем _CODE из .map каждой программы под tests/, сравниваем с
эталоном. Любое отличие печатается; выход 1 — если есть РОСТ (случайное
Логика: собираем _CODE из свежей .map каждой программы под tests/, сравниваем с
эталоном. Карты, оставшиеся от другой цели или более старые, чем входы
сборки, пропускаются. Любое отличие печатается; выход 1 — если есть РОСТ (случайное
разжирение libc), 0 — если только уменьшения/без изменений. Новые
программы (нет в эталоне) — предупреждение, добавить через --update.
Запускать после `make all`.
--update заменяет размеры собранных тестов, сохраняя эталоны необязательных
тестов, которые сейчас не собирались.
Прямой запуск скрипта рассчитан на уже собранные тесты; Make-цели сами
собирают обязательный набор и передают его через --require.
ТОЛЬКО tests/, без отдельного репозитория Examples: регресс ловит разжирение libc, а для этого
хватает мелких тестов — каждый тянет свой кусок библиотеки и пересобирается
@@ -20,18 +24,20 @@ docs/size_baseline.tsv.
`make all` они убраны (см. корневой Makefile).
"""
import argparse
import glob
import os
from pathlib import Path
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASELINE = os.path.join(ROOT, "docs", "size_baseline.tsv")
def collect():
"""Имя программы → размер _CODE (байт) по свежим .map."""
"""Имя программы → размер _CODE (байт) по свежим .map своей цели."""
sizes = {}
skipped = set()
pats = [
os.path.join(ROOT, "tests", "*", ".sprinter-cc-*", "*.map"),
]
@@ -41,13 +47,43 @@ def collect():
# .map должен лежать в своей папке .sprinter-cc-<name>
if os.path.basename(os.path.dirname(map_path)) != ".sprinter-cc-" + name:
continue
project = Path(map_path).parents[1]
makefile = project / "Makefile"
if not makefile.is_file():
continue
declared = re.search(r"^EXAMPLE\s*[:?+]?=\s*(\S+)",
makefile.read_text(), re.M)
if not declared or declared.group(1) != name:
# Например, старый .sprinter-cc-w3bankgfx рядом с w3bgfx.
continue
flags = makefile.read_text()
safe = "--safe" in flags
gfx = "--gfx" in flags
variant = "_safe" if safe else ""
# app.mk сам по себе не является входом линкера: его изменения
# учитываются сборкой через sdbg-config и CC_FLAGS. Проверяем
# только реальные входы, иначе правка run-цели объявит все карты
# устаревшими, хотя размер программы не менялся.
dependencies = [makefile, Path(ROOT) / "bin/sprinter-cc",
Path(ROOT) / "toolchain/mkexe/mkexe",
Path(ROOT) / f"lib/sprinter{variant}.lib"]
if gfx:
dependencies.append(Path(ROOT) / f"lib/bgi256{variant}.lib")
dependencies.extend(project.glob("*.c"))
dependencies.extend(project.glob("*.h"))
dependencies.extend((Path(ROOT) / "runtime").glob("*.s"))
if any(not path.is_file() or
os.path.getmtime(map_path) + 1 < path.stat().st_mtime
for path in dependencies):
skipped.add(name)
continue
with open(map_path, encoding="utf-8", errors="replace") as f:
for line in f:
m = re.match(r"^_CODE\s+([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+)", line)
if m:
sizes[name] = int(m.group(2), 16)
break
return sizes
return sizes, skipped
def load_baseline():
@@ -64,23 +100,33 @@ def load_baseline():
def save_baseline(sizes):
accepted = load_baseline()
accepted.update(sizes)
with open(BASELINE, "w") as f:
f.write("# Эталон размеров _CODE (байт); обновление: "
"python3 toolchain/size_check.py --update\n")
for name in sorted(sizes):
f.write(f"{name}\t{sizes[name]}\n")
"make size-baseline\n")
for name in sorted(accepted):
f.write(f"{name}\t{accepted[name]}\n")
def main():
update = "--update" in sys.argv
sizes = collect()
parser = argparse.ArgumentParser(description="Проверка размера _CODE SDK-тестов")
parser.add_argument("--update", action="store_true")
parser.add_argument("--require", nargs="*", default=[], metavar="TEST")
args = parser.parse_args()
sizes, skipped = collect()
missing = set(args.require) - sizes.keys()
if missing:
print("size_check: нет свежей карты для обязательных тестов: " +
", ".join(sorted(missing)))
return 2
if not sizes:
print("size_check: .map не найдены — сначала `make all`")
print("size_check: свежие .map не найдены — сначала `make all`")
return 2
if update:
if args.update:
save_baseline(sizes)
print(f"size_check: эталон обновлён ({len(sizes)} программ) → "
print(f"size_check: эталон обновлён ({len(sizes)} свежих программ) → "
f"{os.path.relpath(BASELINE, ROOT)}")
return 0
@@ -90,17 +136,18 @@ def main():
return 2
grew = 0
for name in sorted(set(sizes) | set(base)):
for name in sorted(sizes):
new, old = sizes.get(name), base.get(name)
if new is None:
print(f" {name}: исчез из сборки (был {old} Б)")
elif old is None:
if old is None:
print(f" {name}: НОВЫЙ ({new} Б) — добавить в эталон (--update)")
elif new != old:
mark = "РОСТ" if new > old else "ок, меньше"
print(f" {name}: {old} -> {new} ({new - old:+d} Б) [{mark}]")
if new > old:
grew += 1
if skipped:
print(f"size_check: {len(skipped)} устаревших карт пропущено: " +
", ".join(sorted(skipped)))
if grew:
print(f"size_check: {grew} программ(ы) ВЫРОСЛИ — проверить правки libc")
return 1
@@ -109,4 +156,4 @@ def main():
if __name__ == "__main__":
sys.exit(main())
raise SystemExit(main())