world_sync/scripts/m11b2b_selftest.py
2026-09-20 17:16:46 +08:00

256 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""M11b-2b 自测:表定义 JSON 符合 database-table-definition-specPM 裁决 Bkey=schema 引用)。
覆盖全部 4 个落点world_sync 真源 2 + 应用打包镜像 2任一不符即 RESULT: FAIL。
断言集(判定项,计入 RESULT
A1 JSON 可解析;
A2 根键 == {"key","schema"} 且 key == "schema"(引用形态,不再自带结构体);
A3 引用体内不含 summary/fields/indexes/codes 结构体(列集以 authority_path 为唯一真源);
A4 各段键集合 ⊆ 白名单(引用体键白名单;禁止把元信息藏在标准结构体里);
A5 authority_module/authority_path/table 必填,且 authority_path 指向的真源文件真实存在;
A7 真源与镜像 sha256 两两相同(禁止内容级分叉)。
U 段(上游体检项,不计入本任务 RESULT但逐条打印 DEFECT 供冒泡留证):
A6 权威真源 modules/pbl_runtime_ext/models/*.json 自身对 spec 四段式白名单/抽象类型的符合性。
—— 该侧为 M11b-1 已批准产物,本任务按 PM 裁决「不得反向修改」,故只体检登记不判定。
"""
import hashlib
import json
import os
import sys
SOURCE_TABLES = [
"pbl_runtime_event.json",
"pbl_entity_state.json",
]
# 落点 1world_sync 真源modules 侧,唯一手改入口)
MODULE_SRC_DIR = "modules/world_sync/models"
# 落点 2应用打包镜像只由 sync_models_mirror.py 生成,禁止手工双写)
APP_MIRROR_DIR = os.path.join("apps", "scense", "pkgs", "world_sync", "models")
# 权威真源pbl_runtime_extM11b-1 已批准产物,本任务只读体检)
AUTHORITY_DIR = "modules/pbl_runtime_ext/models"
# 引用体允许出现的键(表级元信息全部登记在此白名单内,不得散落到结构体里)
REFERENCE_KEY_WHITELIST = {
"table", "authority_module", "authority_path", "owner", "milestone",
"decision_ref", "decision_basis", "structure_note", "consumers", "mirror_note",
"comment", "note",
}
# database-table-definition-spec 四段式与各段键白名单
SPEC_ROOT_KEYS = {"summary", "fields", "indexes", "codes"}
SPEC_SUMMARY_KEYS = {"name", "title", "primary", "catelog"}
SPEC_FIELD_KEYS = {"name", "title", "type", "length", "dec", "nullable", "default"}
SPEC_INDEX_KEYS = {"name", "idxtype", "idxfields"}
SPEC_CODE_KEYS = {"field", "table", "valuefield", "textfield", "cond"}
ABSTRACT_TYPES = {
"str", "char", "short", "int", "long", "float", "double", "ddouble",
"decimal", "date", "time", "datetime", "timestamp", "text", "bin",
}
NEED_LENGTH = {"str", "char", "float", "double", "ddouble", "decimal"}
NEED_DEC = {"float", "double", "ddouble", "decimal"}
_failures = [] # 判定项失败
_upstream_defects = [] # 上游体检项缺陷(登记不判定)
_assert_count = 0
_upstream_count = 0
def check(cond, label):
global _assert_count
_assert_count += 1
if not cond:
_failures.append(label)
print(" FAIL %s" % label)
else:
print(" ok %s" % label)
return bool(cond)
def check_upstream(cond, label):
global _upstream_count
_upstream_count += 1
if not cond:
_upstream_defects.append(label)
print(" DEFECT %s" % label)
else:
print(" ok %s" % label)
return bool(cond)
def find_root():
here = os.path.dirname(os.path.abspath(__file__))
cur = here
for _ in range(10):
if os.path.isdir(os.path.join(cur, "modules")) and os.path.isdir(os.path.join(cur, "apps")):
return cur
parent = os.path.dirname(cur)
if parent == cur:
break
cur = parent
return os.path.abspath(os.path.join(here, os.pardir, os.pardir, os.pardir))
def sha256_of(path):
h = hashlib.sha256()
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def load_json(path):
with open(path, "r", encoding="utf-8") as fh:
return json.load(fh)
def validate_reference(path, root):
"""A1~A5world_sync 侧引用文件(真源与镜像执行同一套断言)。"""
print("== reference: %s" % path)
try:
data = load_json(path)
except Exception as exc: # noqa: BLE001
check(False, "A1 json-parseable (%s)" % exc)
return None
check(isinstance(data, dict), "A1 json-is-object")
check(set(data.keys()) == {"key", "schema"}, "A2 root-keys == {key,schema} (got %s)" % sorted(data.keys()))
check(data.get("key") == "schema", 'A2 key == "schema"')
body = data.get("schema")
if not check(isinstance(body, dict), "A2 schema-is-object"):
return None
for seg in ("summary", "fields", "indexes", "codes"):
check(seg not in body, "A3 no self-carried struct: %s" % seg)
extra = set(body.keys()) - REFERENCE_KEY_WHITELIST
check(not extra, "A4 reference keys subset whitelist (extra=%s)" % sorted(extra))
check(bool(str(body.get("table", "")).strip()), "A5 table non-empty")
check(body.get("authority_module") == "pbl_runtime_ext", "A5 authority_module == pbl_runtime_ext")
apath = body.get("authority_path", "")
check(bool(apath), "A5 authority_path non-empty")
if apath:
check(os.path.isfile(os.path.join(root, apath)), "A5 authority file exists: %s" % apath)
return body
def validate_authority(path):
"""A6上游体检pbl_runtime_ext 权威真源对 spec 的符合性,只登记不判定。"""
print("== authority(upstream audit): %s" % path)
if not os.path.isfile(path):
check_upstream(False, "A6 authority file present: %s" % path)
return
try:
data = load_json(path)
except Exception as exc: # noqa: BLE001
check_upstream(False, "A6 json-parseable (%s)" % exc)
return
check_upstream(isinstance(data, dict), "A6 root-is-object")
check_upstream(set(data.keys()) <= SPEC_ROOT_KEYS,
"A6 root-keys subset of four-section (got %s)" % sorted(data.keys()))
summary = data.get("summary") or []
check_upstream(len(summary) == 1, "A6 summary has exactly one record (got %d)" % len(summary))
if summary and isinstance(summary[0], dict):
s0 = summary[0]
check_upstream(set(s0.keys()) <= SPEC_SUMMARY_KEYS,
"A6 summary keys subset whitelist (got %s)" % sorted(s0.keys()))
check_upstream(bool(s0.get("name")), "A6 summary.name non-empty")
check_upstream(isinstance(s0.get("primary"), list) and len(s0.get("primary") or []) > 0,
"A6 summary.primary is non-empty array")
fields = data.get("fields") or []
check_upstream(len(fields) > 0, "A6 fields non-empty")
names = []
for f in fields:
if not isinstance(f, dict):
check_upstream(False, "A6 field-is-object")
continue
fname = f.get("name", "?")
names.append(fname)
check_upstream(set(f.keys()) <= SPEC_FIELD_KEYS,
"A6[%s] field keys subset whitelist (got %s)" % (fname, sorted(f.keys())))
t = f.get("type")
check_upstream(t in ABSTRACT_TYPES, "A6[%s] abstract type (%r)" % (fname, t))
if t in NEED_LENGTH:
check_upstream(isinstance(f.get("length"), int) and f.get("length") > 0,
"A6[%s] positive int length" % fname)
if t in NEED_DEC:
check_upstream(isinstance(f.get("dec"), int) and f.get("dec") > 0, "A6[%s] positive int dec" % fname)
check_upstream(str(f.get("nullable", "no")) in ("yes", "no"), "A6[%s] nullable in yes|no" % fname)
for idx in (data.get("indexes") or []):
if not isinstance(idx, dict):
check_upstream(False, "A6 index-is-object")
continue
iname = idx.get("name", "?")
check_upstream(set(idx.keys()) <= SPEC_INDEX_KEYS,
"A6[%s] index keys subset whitelist (got %s)" % (iname, sorted(idx.keys())))
check_upstream(isinstance(idx.get("idxfields"), list) and len(idx.get("idxfields") or []) > 0,
"A6[%s] idxfields non-empty array" % iname)
check_upstream(idx.get("idxtype") in ("unique", "index"), "A6[%s] idxtype unique|index" % iname)
for ifld in (idx.get("idxfields") or []):
check_upstream(ifld in names, "A6[%s] idxfield exists in fields: %s" % (iname, ifld))
for code in (data.get("codes") or []):
if not isinstance(code, dict):
check_upstream(False, "A6 code-is-object")
continue
check_upstream(set(code.keys()) <= SPEC_CODE_KEYS,
"A6 code keys subset whitelist (got %s)" % sorted(code.keys()))
if code.get("table") == "appcodes_kv":
check_upstream("parentid=" in str(code.get("cond", "")), "A6 appcodes_kv cond uses parentid=")
check_upstream("." not in str(code.get("table", "")), "A6 codes.table has no dot notation")
def main():
root = find_root()
print("workspace root: %s" % root)
paths = []
for name in SOURCE_TABLES:
paths.append(os.path.join(root, MODULE_SRC_DIR, name))
paths.append(os.path.join(root, APP_MIRROR_DIR, name))
print("\n--- A1~A5: 4 个落点全部执行同一套 spec 断言 ---")
for p in paths:
if not os.path.isfile(p):
check(False, "file exists: %s" % p)
continue
validate_reference(p, root)
print("\n--- A7: 真源与镜像 sha256 两两相同 ---")
for name in SOURCE_TABLES:
src = os.path.join(root, MODULE_SRC_DIR, name)
dst = os.path.join(root, APP_MIRROR_DIR, name)
if os.path.isfile(src) and os.path.isfile(dst):
hs, hd = sha256_of(src), sha256_of(dst)
print(" %s\n src=%s\n dst=%s" % (name, hs, hd))
check(hs == hd, "A7 sha256 equal: %s" % name)
else:
check(False, "A7 both files present for sha256: %s" % name)
print("\n--- A6: 权威真源pbl_runtime_ext上游体检只登记不判定 ---")
for name in SOURCE_TABLES:
validate_authority(os.path.join(root, AUTHORITY_DIR, name))
print("\njudged assertions executed: %d" % _assert_count)
print("upstream audit checks executed: %d" % _upstream_count)
if _upstream_defects:
print("UPSTREAM DEFECTS (%d) -> 已登记,需冒泡 agent.pm / pbl_runtime_ext owner" % len(_upstream_defects))
for d in _upstream_defects:
print(" * %s" % d)
if _failures:
print("FAILURES (%d):" % len(_failures))
for f in _failures:
print(" - %s" % f)
print("RESULT: FAIL")
return 1
print("RESULT: PASS")
return 0
if __name__ == "__main__":
sys.exit(main())