From 24b84f44f882793f10bff5b5514f503c0d8e4331 Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 21:14:45 +0800 Subject: [PATCH] refactor(tests): stop guarding historical names The project keeps no compatibility with earlier names, so checks that only rejected retired identifiers no longer protect anything. - check_architecture_docs.sh keeps the core-concept, diagram and version checks and drops the denylists of old biz names, registration macros, removed interfaces and moved paths; its self-test follows. - LayerGuard drops the rules for the removed traceable_item.h path and the removed LLM engine interface; the aggregate-target rule stays with current wording. check_governance.sh drops its obsolete-term list. - Tests that pinned removed fields, commands, flags or registry names (model_paths, execution_mode, engine_type, output_allocations, out_mem type, fix-deps, --biz, retired scaffold options, old Demo biz names) are deleted where generic unknown-field coverage exists, or now use a neutral unknown field to keep coverage at that entry point. The root biz_name rejection stays: it is a current boundary rule. - Meta-tests drop the verbatim ci.yml assertions and the CTest label checker's self-tests; delivery-script and gate-behavior contracts stay. Co-Authored-By: Claude Opus 5.5 --- doc/CHANGELOG.md | 5 + scripts/check_architecture_docs.sh | 111 +------- scripts/check_governance.sh | 8 - scripts/check_layer_isolation.sh | 38 +-- src/custom_nodes/README.md | 7 +- .../test_architecture_docs_drift_gate.sh | 75 ------ .../test_quality_gate_contract.py | 24 -- .../architecture/test_test_labels_contract.py | 255 +----------------- .../catalog/test_catalog_contract_ssot.cpp | 13 - tests/integration/demo/test_demo_runner.cpp | 12 +- .../operator/test_operator_api.cpp | 42 +-- .../pipeline/test_model_backend_pipeline.cpp | 31 --- .../test_pipeline_catalog_validator.cpp | 21 -- tests/tooling/test_dev_recipe.py | 8 - tests/tooling/test_pipeline_studio.py | 108 ++------ tests/tooling/test_scaffold_custom_node.py | 14 +- tests/unit/adapter/test_adapter_purity.cpp | 6 +- .../unit/adapter/test_io_binding_registry.cpp | 66 +---- tests/unit/core/test_pipeline_config.cpp | 109 +------- .../engine/test_onnx_and_reranker_model.cpp | 4 - 20 files changed, 77 insertions(+), 880 deletions(-) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index db11d125..b01d76a3 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +门禁与测试不再甄别历史名称:文档漂移检查只保留核心概念、架构图和版本一致性检查,删除旧业务名、 +旧注册宏、已移除接口与路径的黑名单;LayerGuard 和治理检查删除针对已不存在头文件、接口和旧术语的 +规则;固定已删除字段、命令或参数名的测试改为通用的未知字段检查或删除。元测试删除对 `ci.yml` +文本的逐字断言和 CTest 标签检查器自身的自测。 + 精简未使用的扩展点:批次上限只在 IoBinding 上声明,删除 Converter 的 `max_batch_size`(及 `EffectiveMaxBatchSize`),Catalog 的 Converter 不再导出该字段;字段 Control 只保留整体替换的 `ReplaceFields`,删除 `PatchFields` 及其策略枚举;端口存活期只接受 `request` 与 `session`,删除 diff --git a/scripts/check_architecture_docs.sh b/scripts/check_architecture_docs.sh index 5ec1bc16..4d0efb7e 100755 --- a/scripts/check_architecture_docs.sh +++ b/scripts/check_architecture_docs.sh @@ -11,19 +11,6 @@ echo "================================================================" FAILED=0 -report_matches() { - local matches="$1" - local failure_message="$2" - local success_message="$3" - if [[ -z "${matches}" ]]; then - echo "✅ ${success_message}" - return - fi - echo "❌ ${failure_message}" - echo "${matches}" - FAILED=1 -} - require_concepts() { local document="$1" local concept @@ -36,90 +23,8 @@ require_concepts() { done } -find_deprecated_registration_macros() { - grep -rnE "\bREGISTER_NODE\([A-Za-z0-9_]+\)" \ - "${ACTIVE_DOCS[@]}" 2>/dev/null | grep -v "#define REGISTER_NODE" || true - grep -rnE "\bREGISTER_ENGINE\([A-Za-z0-9_]+,[[:space:]]*[A-Za-z0-9_]+\)" \ - "${ACTIVE_DOCS[@]}" 2>/dev/null | grep -v "#define REGISTER_ENGINE" || true - grep -rnE '\bREGISTER_ENGINE_WITH_DEFINITION[[:space:]]*\([[:space:]]*"' \ - "${ACTIVE_DOCS[@]}" 2>/dev/null || true -} - -ACTIVE_DOCS=( - "${DOC_ROOT}/architecture.md" - "${DOC_ROOT}/developer_guide.md" - "${DOC_ROOT}/dev_guide" - "${DOC_ROOT}/README.md" - "${DOC_ROOT}/architecture_classes.puml" - "${DOC_ROOT}/architecture_flow.puml" - "${DOC_ROOT}/assets/architecture_class_diagram.svg" - "${DOC_ROOT}/assets/architecture_flow.svg" - "${DOC_ROOT}/assets/framework_overview.svg" - "${ROOT_DIR}/README.md" - "${ROOT_DIR}/CONTRIBUTING.md" - "${ROOT_DIR}/AGENTS.md" - "${ROOT_DIR}/.github/copilot-instructions.md" - "${ROOT_DIR}/.agents/skills" - "${ROOT_DIR}/src" - "${ROOT_DIR}/include" - "${ROOT_DIR}/demo" - "${ROOT_DIR}/configs" -) - -# 1. 检查权威文档与资产中是否存在旧业务名 (包含 SVG 资产) -echo "[Check 1/8] Checking for legacy business names and identifiers..." -LEGACY_BIZ=$(grep -rnE "(doc_qa_embedding_v1|doc_qa_rerank_v1|(keyword_match|entity_extract|translate|smart_doc_qa|dialogue_compliance_audit)_v1|dense_cross_rerank_scoring|multimodal_ocr_invoice_qa|speech_audio_asr_intent_slot|compliance_audit|ocr_doc_qa|audio_asr_(cpu|mock|demo))" \ - "${ACTIVE_DOCS[@]}" 2>/dev/null || true) -report_matches "${LEGACY_BIZ}" \ - "Found deprecated business names in active docs, assets or codebase:" \ - "No legacy business names found." - -# 2. 检查旧注册宏 (REGISTER_NODE( / REGISTER_ENGINE( / REGISTER_ENGINE_WITH_DEFINITION("str", ...)) -echo "[Check 2/8] Checking for deprecated registration macros..." -LEGACY_MACROS="$(find_deprecated_registration_macros)" -report_matches "${LEGACY_MACROS}" \ - "Found deprecated registration macro invocations:" \ - "No deprecated registration macro invocations found in active docs/source." - -# 3. 检查虚构生产节点 (PassthroughNode, ComplianceReportPostNode) -echo "[Check 3/8] Checking for fictitious production nodes..." -FICTITIOUS_NODES=$(grep -rnE "\b(PassthroughNode|ComplianceReportPostNode)\b" "${ACTIVE_DOCS[@]}" 2>/dev/null || true) -report_matches "${FICTITIOUS_NODES}" \ - "Found fictitious production nodes in active docs or codebase:" \ - "No fictitious production nodes found." - -# 4. 检查当前治理入口是否引用已移除的 Engine / Biz Node 架构或已迁移的仓库路径。 -echo "[Check 4/8] Checking active governance for removed architecture identifiers..." -REMOVED_ARCH=$(grep -rnE \ - '(IModelEngine|include/engine/engine_interface\.h|REGISTER_ENGINE_WITH_DEFINITION|src/business/|src/biz/|26 production nodes)' \ - "${ACTIVE_DOCS[@]}" 2>/dev/null || true) -report_matches "${REMOVED_ARCH}" \ - "Found removed architecture identifiers in active governance/docs:" \ - "Active governance matches the Model/Backend and Common Node architecture." -MOVED_PATHS=$(grep -rnE \ - '(src/tools/|scripts/(dev_recipe|scaffold_custom_node)\.py|(^|[^_[:alnum:]])architecture(_v2)?\.puml|\./show([[:space:]]|$))' \ - "${ACTIVE_DOCS[@]}" 2>/dev/null || true) -report_matches "${MOVED_PATHS}" \ - "Found moved repository paths in active governance/docs:" \ - "Active governance uses current tool and diagram paths." - -OVERVIEW_DOCS=( - "${DOC_ROOT}/architecture.md" - "${DOC_ROOT}/architecture_classes.puml" - "${DOC_ROOT}/architecture_flow.puml" - "${DOC_ROOT}/assets/architecture_class_diagram.svg" - "${DOC_ROOT}/assets/architecture_flow.svg" - "${DOC_ROOT}/assets/framework_overview.svg" -) -REMOVED_C_ABI=$(grep -nE \ - '\bAlg_(Init|Create|Process|Control|Destroy|DeInit)\b|C ABI[[:space:]]*(/|或)[[:space:]]*Operator' \ - "${OVERVIEW_DOCS[@]}" 2>/dev/null || true) -report_matches "${REMOVED_C_ABI}" \ - "Found removed C ABI entrypoints or alternate C ABI access in active architecture overviews:" \ - "Architecture overviews use the current Operator entrypoint." - -# 5. 检查架构文档核心概念完备性 (ValidatedPipelinePlan, BlackboardKey, NodeBase, FixedBatchExecutor) -echo "[Check 5/8] Verifying core architectural concepts in architecture documents..." +# 1. 检查架构文档核心概念完备性 (ValidatedPipelinePlan, BlackboardKey, NodeBase, FixedBatchExecutor) +echo "[Check 1/4] Verifying core architectural concepts in architecture documents..." require_concepts "${DOC_ROOT}/architecture.md" \ "ValidatedPipelinePlan" "BlackboardKey" "NodeBase" "FixedBatchExecutor" require_concepts "${DOC_ROOT}/developer_guide.md" \ @@ -130,14 +35,14 @@ if [ ${FAILED} -eq 0 ]; then echo "✅ All core architectural concepts verified in architecture docs." fi -# 6. 检查当前部署解析、计划与 Node 注册流程 -echo "[Check 6/8] Checking current deployment and runtime planning concepts..." +# 2. 检查当前部署解析、计划与 Node 注册流程 +echo "[Check 2/4] Checking current deployment and runtime planning concepts..." require_concepts "${DOC_ROOT}/architecture_flow.puml" \ "PrepareDeploymentDocument" "ValidatedIoPlan" "ValidatedPipelinePlan" \ "REGISTER_FUNCTION_NODE" -# 7. 检查 PlantUML 与 SVG 资产存在性与非空 -echo "[Check 7/8] Verifying architecture diagrams exist and are non-empty..." +# 3. 检查 PlantUML 与 SVG 资产存在性与非空 +echo "[Check 3/4] Verifying architecture diagrams exist and are non-empty..." for diagram in \ "${DOC_ROOT}/architecture_classes.puml" \ "${DOC_ROOT}/architecture_flow.puml" \ @@ -150,8 +55,8 @@ for diagram in \ fi done -# 8. 检查 CMake、生成版本头和活跃文档是否共享同一产品/ABI 版本。 -echo "[Check 8/8] Verifying product and ABI version single source of truth..." +# 4. 检查 CMake、生成版本头和活跃文档是否共享同一产品/ABI 版本。 +echo "[Check 4/4] Verifying product and ABI version single source of truth..." PRODUCT_VERSION="$({ sed -nE 's/^project\(LLMEdgeFlow VERSION ([0-9]+\.[0-9]+\.[0-9]+) LANGUAGES C CXX\)$/\1/p' \ "${ROOT_DIR}/CMakeLists.txt" diff --git a/scripts/check_governance.sh b/scripts/check_governance.sh index 18b3fbf5..55ea253e 100755 --- a/scripts/check_governance.sh +++ b/scripts/check_governance.sh @@ -71,12 +71,4 @@ if [[ "${QUALITY_GATE_CALLS}" -ne 1 ]]; then fail "GitHub delivery must invoke the canonical quality gate exactly once" fi -if grep -rnE '(six-stage|6-stage|六阶段|7 CTest|src/business/|src/biz/|IModelEngine|REGISTER_ENGINE_WITH_DEFINITION)' \ - "${ROOT_DIR}/AGENTS.md" \ - "${ROOT_DIR}/CONTRIBUTING.md" \ - "${ROOT_DIR}/.github/copilot-instructions.md" \ - "${ROOT_DIR}/.agents/skills"; then - fail "active governance contains obsolete architecture or test-count guidance" -fi - echo "Governance sources, routing, and delivery safety invariants are consistent." diff --git a/scripts/check_layer_isolation.sh b/scripts/check_layer_isolation.sh index 2eef6586..30fcb63e 100755 --- a/scripts/check_layer_isolation.sh +++ b/scripts/check_layer_isolation.sh @@ -289,20 +289,7 @@ if [ -n "$WHISPER_VENDOR_OUTSIDE_BACKEND" ]; then fi echo "✅ [LayerGuard PASS] The whisper.h vendor header stays inside its concrete Backend." -# 规则 5:中性的 TraceableItem 契约只有一个规范 include 路径。 -LEGACY_TRACEABLE_HEADER="$REPO_ROOT/include/core/traceable_item.h" -LEGACY_TRACEABLE_INCLUDES=$(grep -rnE \ - '#include\s*["<]core/traceable_item\.h[">]' \ - "$REPO_ROOT/include" "$REPO_ROOT/src" "$REPO_ROOT/demo" \ - "$REPO_ROOT/dev_support" "$REPO_ROOT/tests" 2>/dev/null || true) -if [ -e "$LEGACY_TRACEABLE_HEADER" ] || [ -n "$LEGACY_TRACEABLE_INCLUDES" ]; then - echo "❌ [LayerGuard ERROR] Legacy core/traceable_item.h compatibility path remains:" - echo "$LEGACY_TRACEABLE_INCLUDES" - exit 1 -fi -echo "✅ [LayerGuard PASS] TraceableItem uses the neutral contracts include path." - -# 规则 6:Node 支持代码只消费抽取出的已校验 Node 计划, +# 规则 5:Node 支持代码只消费抽取出的已校验 Node 计划, # 而不是完整的编排层 Validator 实现契约。 NODE_SUPPORT_HEADER="$REPO_ROOT/include/nodes/node_base.h" if [ ! -f "$NODE_SUPPORT_HEADER" ] || \ @@ -313,7 +300,7 @@ if [ ! -f "$NODE_SUPPORT_HEADER" ] || \ fi echo "✅ [LayerGuard PASS] Node support is decoupled from PipelineValidator." -# 规则 7:CMake 中的源码归属必须保持四个编译期分层和显式的组合根。 +# 规则 6:CMake 中的源码归属必须保持四个编译期分层和显式的组合根。 for OWNERSHIP in \ "src/engine/CMakeLists.txt:edgeflow_model_execution_objects" \ "src/common_nodes/CMakeLists.txt:edgeflow_capability_nodes_objects" \ @@ -328,11 +315,11 @@ for OWNERSHIP in \ exit 1 fi done -LEGACY_SOURCE_OWNERSHIP=$(grep -rn 'target_sources(edgeflow_runtime_objects' \ +AGGREGATE_SOURCE_OWNERSHIP=$(grep -rn 'target_sources(edgeflow_runtime_objects' \ "$REPO_ROOT/src" 2>/dev/null || true) -if [ -n "$LEGACY_SOURCE_OWNERSHIP" ]; then - echo "❌ [LayerGuard ERROR] Layer sources still use the legacy aggregate target:" - echo "$LEGACY_SOURCE_OWNERSHIP" +if [ -n "$AGGREGATE_SOURCE_OWNERSHIP" ]; then + echo "❌ [LayerGuard ERROR] Layer sources must not be attached to the aggregate runtime target:" + echo "$AGGREGATE_SOURCE_OWNERSHIP" exit 1 fi if ! grep -q 'target_sources(edgeflow_composition_objects' \ @@ -346,7 +333,7 @@ if ! grep -q 'target_sources(edgeflow_composition_objects' \ fi echo "✅ [LayerGuard PASS] CMake source ownership preserves all four layers and the composition root." -# 规则 8:用标准 C 编译器检查纯 C11 语法与 ABI 合规性 +# 规则 7:用标准 C 编译器检查纯 C11 语法与 ABI 合规性 GENERATED_VERSION_INCLUDE="$(mktemp -d "${TMPDIR:-/tmp}/edgeflow-version-header.XXXXXX")" cleanup_generated_version() { rm -rf "${GENERATED_VERSION_INCLUDE}" @@ -397,7 +384,7 @@ else echo "⚠️ [LayerGuard WARN] Neither gcc nor clang found for C11 syntax-only check." fi -# 规则 9:Demo 层 (demo/) 绝不能直接 include SDK 内部头文件 +# 规则 8:Demo 层 (demo/) 绝不能直接 include SDK 内部头文件 # (adapter/、core/、biz/、business/、engine/、src/) VIOLATIONS_DEMO_INTERNAL=$(grep -rnE '#include\s*["<](adapter/|core/|biz/|business/|engine/|src/)' "$REPO_ROOT/demo" || true) @@ -409,7 +396,7 @@ if [ -n "$VIOLATIONS_DEMO_INTERNAL" ]; then fi echo "✅ [LayerGuard PASS] Zero Demo -> Internal SDK header violations." -# 规则 10:LLM 厂商运行时与模型语义边界。 +# 规则 9:LLM 厂商运行时与模型语义边界。 LLAMA_VENDOR_OUTSIDE_BACKEND=$(grep -rnE '#include\s*["<]llama\.h[">]' \ "$REPO_ROOT/include" "$REPO_ROOT/src" \ --exclude-dir=backends 2>/dev/null || true) @@ -451,13 +438,6 @@ if ! grep -rq 'ITextGenerationSession' \ exit 1 fi -LLM_NODE_LEGACY=$(grep -nE 'ILlmEngine|engine_interface' \ - "$REPO_ROOT/src/common_nodes/llm_generate_node.cpp" 2>/dev/null || true) -if [ -n "$LLM_NODE_LEGACY" ]; then - echo "❌ [LayerGuard ERROR] LlmGenerateNode still depends on the legacy engine interface:" - echo "$LLM_NODE_LEGACY" - exit 1 -fi echo "✅ [LayerGuard PASS] Backend vendor resources and Qwen generation semantics are isolated." python3 "$(dirname "${BASH_SOURCE[0]}")/check_layer_dependencies.py" --root "${REPO_ROOT}" diff --git a/src/custom_nodes/README.md b/src/custom_nodes/README.md index e0b028e2..392bdae9 100644 --- a/src/custom_nodes/README.md +++ b/src/custom_nodes/README.md @@ -141,16 +141,15 @@ cmake --build build --target edgeflow_test_nodes_runner -j 4 prompt_prefix 中的花括号保留原文,不再作为模板解析。未知占位符、无效生成参数 和非法 stop_words 在原生校验与初始化时拒绝。 -`prompt_prefix` 是普通输入文本前缀,非空时在模板前追加一行;旧的节点字段 -`system_prompt` 已改名并拒绝使用。模型的 `model_config.system_prompt` 仍表示真正的 -system 消息,不能用节点前缀替代该角色。 +`prompt_prefix` 是普通输入文本前缀,非空时在模板前追加一行。真正的 system 消息由模型的 +`model_config.system_prompt` 设置,不能用节点前缀替代该角色。 TextTemplate 的 `missing_variable_policy` 也适用于内置变量。`fail` 会拒绝未连接的 引用或缺失的主输入样本;需要保留占位符或填空时显式使用 `preserve/empty`。 聚合输入批次存在而某请求没有结果时,仍表示合法的空上下文。 模型失败、输出数量不符或 `(req_id, sub_id)` 不符时,节点返回错误且不发布结果。 -`fallback_text` 已删除并明确拒绝;业务降级应携带可辨识的状态,不能伪装成功。 +业务降级应携带可辨识的状态,不能伪装成功。 ## 复用与边界 diff --git a/tests/contract/architecture/test_architecture_docs_drift_gate.sh b/tests/contract/architecture/test_architecture_docs_drift_gate.sh index ba7f4b16..916f557b 100755 --- a/tests/contract/architecture/test_architecture_docs_drift_gate.sh +++ b/tests/contract/architecture/test_architecture_docs_drift_gate.sh @@ -23,42 +23,6 @@ run_fixture_gate() { ./scripts/check_architecture_docs.sh >/dev/null 2>&1 -echo "" >> \ - "${FIXTURE_DOC_ROOT}/assets/architecture_flow.svg" -if run_fixture_gate; then - echo "❌ Docs drift gate missed a legacy business name in SVG" - exit 1 -fi -cp doc/assets/architecture_flow.svg \ - "${FIXTURE_DOC_ROOT}/assets/architecture_flow.svg" - -for legacy_name in \ - keyword_match_v1 smart_doc_qa_v1 dense_cross_rerank_scoring \ - compliance_audit.operator.v1 ocr_doc_qa.operator.v1 audio_asr_cpu; do - echo "${legacy_name}" >> "${FIXTURE_DOC_ROOT}/developer_guide.md" - if run_fixture_gate; then - echo "❌ Docs drift gate missed legacy business identifier '${legacy_name}'" - exit 1 - fi - cp doc/developer_guide.md "${FIXTURE_DOC_ROOT}/developer_guide.md" -done - -echo "REGISTER_NODE(OldNode);" >> "${FIXTURE_DOC_ROOT}/developer_guide.md" -if run_fixture_gate; then - echo "❌ Docs drift gate missed a deprecated registration macro" - exit 1 -fi -cp doc/developer_guide.md "${FIXTURE_DOC_ROOT}/developer_guide.md" - -echo "REGISTER_NODE(OldTutorialNode);" >> \ - "${FIXTURE_DOC_ROOT}/dev_guide/first_custom_node.md" -if run_fixture_gate; then - echo "❌ Docs drift gate missed a deprecated registration macro in a tutorial" - exit 1 -fi -cp doc/dev_guide/first_custom_node.md \ - "${FIXTURE_DOC_ROOT}/dev_guide/first_custom_node.md" - rm "${FIXTURE_DOC_ROOT}/assets/framework_overview.svg" if run_fixture_gate; then echo "❌ Docs drift gate missed the missing README overview SVG" @@ -67,19 +31,6 @@ fi cp doc/assets/framework_overview.svg \ "${FIXTURE_DOC_ROOT}/assets/framework_overview.svg" -for removed_api in \ - Alg_Init Alg_Create Alg_Process Alg_Control Alg_Destroy Alg_DeInit \ - "C ABI / Operator" "C ABI 或 Operator"; do - echo "" >> \ - "${FIXTURE_DOC_ROOT}/assets/framework_overview.svg" - if run_fixture_gate; then - echo "❌ Docs drift gate missed removed C ABI access '${removed_api}' in the README SVG" - exit 1 - fi - cp doc/assets/framework_overview.svg \ - "${FIXTURE_DOC_ROOT}/assets/framework_overview.svg" -done - sed -i.bak 's/ValidatedIoPlan/ObsoleteIoPlan/g' \ "${FIXTURE_DOC_ROOT}/architecture_flow.puml" rm -f "${FIXTURE_DOC_ROOT}/architecture_flow.puml.bak" @@ -89,32 +40,6 @@ if run_fixture_gate; then fi cp doc/architecture_flow.puml "${FIXTURE_DOC_ROOT}/architecture_flow.puml" -for moved_path in \ - "src/tools/pipeline_authoring.h" "./scripts/scaffold_custom_node.py" \ - "scripts/dev_recipe.py" "architecture_v2.puml" "(architecture.puml)" "./show --web"; do - echo "${moved_path}" >> "${FIXTURE_DOC_ROOT}/dev_guide/first_custom_node.md" - if run_fixture_gate; then - echo "❌ Docs drift gate missed moved path '${moved_path}'" - exit 1 - fi - cp doc/dev_guide/first_custom_node.md \ - "${FIXTURE_DOC_ROOT}/dev_guide/first_custom_node.md" -done - -echo "PassthroughNode" >> "${FIXTURE_DOC_ROOT}/developer_guide.md" -if run_fixture_gate; then - echo "❌ Docs drift gate missed a fictitious production node" - exit 1 -fi -cp doc/developer_guide.md "${FIXTURE_DOC_ROOT}/developer_guide.md" - -echo "IModelEngine" >> "${FIXTURE_DOC_ROOT}/developer_guide.md" -if run_fixture_gate; then - echo "❌ Docs drift gate missed a removed architecture identifier" - exit 1 -fi -cp doc/developer_guide.md "${FIXTURE_DOC_ROOT}/developer_guide.md" - CURRENT_PRODUCT_VERSION="$(sed -nE 's/^project\(LLMEdgeFlow VERSION ([0-9]+\.[0-9]+\.[0-9]+) LANGUAGES C CXX\)$/\1/p' CMakeLists.txt)" if [[ -z "${CURRENT_PRODUCT_VERSION}" ]]; then echo "❌ Failed to parse project VERSION from CMakeLists.txt" diff --git a/tests/contract/architecture/test_quality_gate_contract.py b/tests/contract/architecture/test_quality_gate_contract.py index cb2d5431..ef6d5d50 100644 --- a/tests/contract/architecture/test_quality_gate_contract.py +++ b/tests/contract/architecture/test_quality_gate_contract.py @@ -429,9 +429,6 @@ def check_sanitizer_contract(root, env, log): check_ctest_records(records, root / "build/sanitizers/thread-fast") assert sum(row["command"][0] == "setarch" for row in records) == 1, records assert all(row["arch_wrapped"] == "1" for row in records if row["command"][0] == "ctest") - workflow = (ROOT / ".github/workflows/ci.yml").read_text() - assert "CCACHE_DIR: ${{ github.workspace }}/build/.ccache-sanitizers" in workflow - assert "key: sanitizer-ccache-v2-" in workflow return configurations @@ -536,27 +533,6 @@ def main(): configurations += check_sanitizer_contract(root, env, log) configurations += check_real_model_contract(root, env, log) check_cache_reset(root, configurations) - workflow = (ROOT / ".github/workflows/ci.yml").read_text() - assert "WHISPER_GATE_RESULT: ${{ needs.whisper-asr.result }}" in workflow - assert "KITELLM_GATE_RESULT: ${{ needs.kite-llm.result }}" in workflow - assert "run: ./scripts/fetch_real_test_models.sh --gguf-only" not in workflow - assert "run: ./scripts/fetch_real_test_models.sh --whisper" not in workflow - # 每个 ccache 作业先回退到自身的 main 快照,再回退到共享前缀, - # 且每个恢复的 ccache 都会通过清理 action 保存。 - for prefix in ("ccache-real-", "ccache-whisper-"): - assert f" {prefix}${{{{ runner.os }}}}-\n" in workflow, prefix - assert "uses: actions/cache@v4\n with:\n path: ${{ env.CCACHE_DIR }}" \ - not in workflow - assert (workflow.count("uses: ./.github/actions/ccache-restore") - == workflow.count("uses: ./.github/actions/ccache-save") == 5) - manifest = json.loads((ROOT / "models/asset_manifest.json").read_text()) - artifact_groups = { - group: {name for name, artifact in manifest["artifacts"].items() - if group in artifact.get("download_groups", [])} - for group in ("whisper", "whisper-e2e") - } - assert artifact_groups["whisper-e2e"] == {"ggml-base.bin"} - assert artifact_groups["whisper"] == {"ggml-base.bin", "ggml-tiny-q5_1.bin"} evidence = root / "evidence.json" for state in ("success", "failure", "skipped", "cancelled"): evidence_env = {**os.environ, "WHISPER_GATE_RESULT": state, diff --git a/tests/contract/architecture/test_test_labels_contract.py b/tests/contract/architecture/test_test_labels_contract.py index 7287e2e5..8f6c57f4 100644 --- a/tests/contract/architecture/test_test_labels_contract.py +++ b/tests/contract/architecture/test_test_labels_contract.py @@ -25,11 +25,6 @@ def parse_args(): default=Path("build"), help="Path to build directory containing CTest configuration", ) - parser.add_argument( - "--self-test", - action="store_true", - help="Run self-tests verifying parser, filter semantics, and error handling", - ) return parser.parse_args() @@ -176,7 +171,7 @@ def resolve_command_executable(cmd: list, build_dir: Path) -> Path | None: def verify_compiled_gtest_coverage( - build_dir: Path, ctest_data: dict, errors: list, runner_fn=None, is_gtest_fn=None + build_dir: Path, ctest_data: dict, errors: list ) -> int: exec_filters = {} resolved_build_dir = build_dir.resolve() @@ -200,7 +195,7 @@ def verify_compiled_gtest_coverage( f"could not resolve executable from command: {cmd}" ) continue - if runner_fn is None and not (exe.is_file() and os.access(exe, os.X_OK)): + if not (exe.is_file() and os.access(exe, os.X_OK)): errors.append( f"Failed to enumerate GoogleTest cases from '{test_name}': " f"executable '{exe}' not found or not executable." @@ -209,30 +204,18 @@ def verify_compiled_gtest_coverage( exec_filters.setdefault(exe, []).append(filt) elif exe: # 检查该可执行文件是否为 GoogleTest 二进制 - is_gtest = ( - is_gtest_fn(exe) - if is_gtest_fn is not None - else ( - is_gtest_binary(exe) - if (runner_fn is None and exe.is_file()) - else False - ) - ) - if is_gtest: + if exe.is_file() and is_gtest_binary(exe): exec_filters.setdefault(exe, []).append("*") covered_test_names = set() for exe, filters in exec_filters.items(): try: - if runner_fn is not None: - proc = runner_fn([str(exe), "--gtest_list_tests"]) - else: - proc = subprocess.run( - [str(exe), "--gtest_list_tests"], - capture_output=True, - text=True, - timeout=30, - ) + proc = subprocess.run( + [str(exe), "--gtest_list_tests"], + capture_output=True, + text=True, + timeout=30, + ) except Exception as ex: errors.append( f"Failed to execute '{exe.name} --gtest_list_tests': {ex}" @@ -271,228 +254,8 @@ def verify_compiled_gtest_coverage( return len(covered_test_names) -def run_self_tests(): - # 1. 问题 1:解析带 "# TypeParam = ..." 的类型化测试并去除注释 - sample_typed = """ -Running main() from /path/to/gtest_main.cc -Note: Random seed = 12345. -NormalSuite. - NormalTest -TypedSuite/0. # TypeParam = int - TypedTestA - TypedTestB -[Init] Setup completed. - failed to open some file -TypedSuite/1. # TypeParam = float - TypedTestA - TypedTestB -ValueParamSuite/Inst. - ValueTest # GetParam() = 42 -""" - parsed = parse_gtest_list_tests_output(sample_typed) - expected = [ - "NormalSuite.NormalTest", - "TypedSuite/0.TypedTestA", - "TypedSuite/0.TypedTestB", - "TypedSuite/1.TypedTestA", - "TypedSuite/1.TypedTestB", - "ValueParamSuite/Inst.ValueTest", - ] - assert parsed == expected, f"Parsed tests mismatch: {parsed} != {expected}" - - # 类型化测试不能被前一个套件的过滤器误判为已覆盖 - assert not match_gtest_filter("TypedSuite/0.TypedTestA", "NormalSuite.*") - assert match_gtest_filter("TypedSuite/0.TypedTestA", "TypedSuite/*") - assert match_gtest_filter("TypedSuite/0.TypedTestA", "TypedSuite/0.*") - assert not match_gtest_filter("TypedSuite/0.TypedTestA", "TypedSuite/1.*") - - # 2. 问题 2:空过滤器 "--gtest_filter=" 不选中任何测试 - assert not match_gtest_filter("AnySuite.AnyTest", "") - assert not match_gtest_filter("AnySuite.AnyTest", " ") - assert not match_gtest_filter("AnySuite.AnyTest", '""') - assert not match_gtest_filter("AnySuite.AnyTest", "''") - assert not is_test_covered("AnySuite.AnyTest", [""]) - assert is_test_covered("AnySuite.AnyTest", ["*"]) - assert is_test_covered("AnySuite.AnyTest", ['"*"']) - assert is_test_covered("AnySuite.AnyTest", ["", "AnySuite.*"]) - assert not is_test_covered("OtherSuite.AnyTest", ["", "AnySuite.*"]) - - # 过滤器区分大小写 - assert not match_gtest_filter("AnySuite.AnyTest", "anysuite.*") - assert match_gtest_filter("AnySuite.AnyTest", "AnySuite.*") - - # 反向模式处理 - assert match_gtest_filter("Suite.Good", "-Suite.Bad") - assert not match_gtest_filter("Suite.Bad", "-Suite.Bad") - assert match_gtest_filter("Suite.Good", "Suite.*:-Suite.Bad") - assert not match_gtest_filter("Suite.Bad", "Suite.*:-Suite.Bad") - assert not match_gtest_filter("Suite.Good", "-*") - - # 3. 问题 3:显式 GoogleTest 目标枚举失败时必须报告错误 - class FakeProc: - def __init__(self, returncode, stdout="", stderr=""): - self.returncode = returncode - self.stdout = stdout - self.stderr = stderr - - fake_build = Path("/fake/build") - fake_exe = fake_build / "fake_test_runner" - - # 3a. 带显式 --gtest_filter 的目标枚举失败 (退出码) - mock_ctest_explicit = { - "tests": [ - { - "name": "MockGtest", - "command": [str(fake_exe), "--gtest_filter=MockSuite.*"], - } - ] - } - errs = [] - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_explicit, - errs, - runner_fn=lambda cmd: FakeProc(2, "", "Unknown flag or crash"), - ) - assert any( - "Failed to enumerate GoogleTest cases from 'fake_test_runner'" in e - for e in errs - ), errs - assert any("exit code 2" in e for e in errs), errs - - # 3b. 目标返回 0 但没有产出测试用例 - errs = [] - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_explicit, - errs, - runner_fn=lambda cmd: FakeProc(0, "No tests here\n", ""), - ) - assert any("no test cases discovered" in e for e in errs), errs - - # 3c. 执行目标时抛异常 - errs = [] - def crashing_runner(cmd): - raise RuntimeError("Subprocess timeout or spawn failure") - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_explicit, - errs, - runner_fn=crashing_runner, - ) - assert any("Failed to execute 'fake_test_runner --gtest_list_tests'" in e for e in errs), errs - - # 3d. 通过 runner_fn 解析被包装的命令 (如 cmake -E env) - called_cmds = [] - def recording_runner(cmd): - called_cmds.append(cmd) - return FakeProc(0, "MockSuite.\n Test1\n", "") - - mock_ctest_wrapped = { - "tests": [ - { - "name": "MockWrappedGtest", - "command": [ - "/usr/bin/cmake", - "-E", - "env", - "EDGEFLOW_VAR=1", - str(fake_exe), - "--gtest_filter=MockSuite.*", - ], - } - ] - } - errs = [] - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_wrapped, - errs, - runner_fn=recording_runner, - ) - assert not errs, errs - assert called_cmds == [[str(fake_exe), "--gtest_list_tests"]], called_cmds - - # 3e. 显式 GoogleTest 目标的可执行文件缺失或无法解析时报告错误 - errs = [] - mock_ctest_missing_exe = { - "tests": [ - { - "name": "MockMissingGtest", - "command": [str(fake_build / "nonexistent_bin"), "--gtest_filter=MockSuite.*"], - } - ] - } - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_missing_exe, - errs, - ) - assert any("not found or not executable" in e for e in errs), errs - - errs = [] - mock_ctest_no_exe = { - "tests": [ - { - "name": "MockNoExeGtest", - "command": ["--gtest_filter=MockSuite.*"], - } - ] - } - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_no_exe, - errs, - ) - assert any("could not resolve executable" in e for e in errs), errs - - # 3f. 存在测试时,显式的 --gtest_filter= (空) 必须使覆盖检查失败 - errs = [] - mock_ctest_empty_filter = { - "tests": [ - { - "name": "MockEmptyFilter", - "command": [str(fake_exe), "--gtest_filter="], - } - ] - } - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_empty_filter, - errs, - runner_fn=lambda cmd: FakeProc(0, "Suite.\n Test1\n", ""), - ) - assert any("is not covered by any CTest filter" in e for e in errs), errs - assert any("tests/RuntimeTests.cmake" in e and "edgeflow_add_runner_test" in e - and "CustomNodeCatalogTest" in e for e in errs), errs - - # 3g. 复现类型化测试被遗漏的问题 - mock_ctest_typed = { - "tests": [ - { - "name": "MockTypedOnlyNormal", - "command": [str(fake_exe), "--gtest_filter=NormalSuite.*"], - } - ] - } - errs = [] - verify_compiled_gtest_coverage( - fake_build, - mock_ctest_typed, - errs, - runner_fn=lambda cmd: FakeProc(0, sample_typed, ""), - ) - assert any("TypedSuite/0.TypedTestA" in e for e in errs), errs - assert any("TypedSuite/0.TypedTestB" in e for e in errs), errs - assert not any("NormalSuite.NormalTest" in e for e in errs), errs - - def main(): args = parse_args() - run_self_tests() - if args.self_test: - print("✓ All test_test_labels_contract self-tests passed.") - return build_dir = args.build_dir if not build_dir.is_dir(): diff --git a/tests/contract/catalog/test_catalog_contract_ssot.cpp b/tests/contract/catalog/test_catalog_contract_ssot.cpp index 74ef76ec..e933cf2c 100644 --- a/tests/contract/catalog/test_catalog_contract_ssot.cpp +++ b/tests/contract/catalog/test_catalog_contract_ssot.cpp @@ -7,7 +7,6 @@ #include #include -#include "adapter/io_binding_registry.h" #include "adapter/io_catalog.h" #include "adapter/io_converter_registry.h" #include "core/node_interface.h" @@ -75,18 +74,6 @@ TEST_F(CatalogContractSsotTest, AllProductionNodesHaveValidDefinitions) { EXPECT_TRUE(seen_types.count("TextCorpusSourceNode")); } -TEST_F(CatalogContractSsotTest, BizContractsDoNotDependOnDeploymentVariants) { - for (const char* biz : {"entity_extract", "doc_qa"}) { - EXPECT_TRUE(PipelineCatalog::FindBiz(biz).has_value()); - } - for (const char* name : - {"entity_extract_0.6b_v1", "entity_extract_llamacpp_0.6b_v1", - "smart_doc_qa_onnx_llamacpp_v1", "smart_doc_qa_rerank_llm_v1"}) { - EXPECT_FALSE(PipelineCatalog::FindBiz(name)); - EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(name), nullptr); - } -} - TEST_F(CatalogContractSsotTest, ProductionModelBackendCatalogHasNoFixtures) { std::set model_types; for (const auto& model : PipelineCatalog::Models()) { diff --git a/tests/integration/demo/test_demo_runner.cpp b/tests/integration/demo/test_demo_runner.cpp index e8df7922..363c2811 100644 --- a/tests/integration/demo/test_demo_runner.cpp +++ b/tests/integration/demo/test_demo_runner.cpp @@ -579,14 +579,6 @@ TEST(DemoRunnerTest, RegistryLookupAndConflictDetection) { EXPECT_EQ(descriptor->biz_name, biz); EXPECT_NE(descriptor->run, nullptr); } - // 已退役的标识符不保留为别名。 - for (const char* retired : - {"entity_extract_v1", "keyword_match_v1", "smart_doc_qa_v1", - "dialogue_compliance_audit_v1", "multimodal_ocr_invoice_qa", - "speech_audio_asr_intent_slot", "dense_cross_rerank_scoring", - "translate_v1", "ocr_doc_qa", "audio_asr"}) { - EXPECT_EQ(reg.Find(retired), nullptr) << retired; - } EXPECT_FALSE(reg.Register( {"entity_extract", "Duplicate", [](const DemoOptions&) { return 0; }})); @@ -992,7 +984,7 @@ TEST(DemoRunnerTest, ExampleControlIsExplicitAndFileControlTakesPrecedence) { EXPECT_EQ(ops.DeInit(), 0); } -TEST(DemoRunnerTest, ExampleControlCliAndRejectsRemovedFlag) { +TEST(DemoRunnerTest, ExampleControlCliAndRejectsUnknownFlag) { std::string error; { DemoOptions options; @@ -1012,7 +1004,7 @@ TEST(DemoRunnerTest, ExampleControlCliAndRejectsRemovedFlag) { } { DemoOptions options; - const char* args[] = {"alg_demo", "--no-default-control"}; + const char* args[] = {"alg_demo", "--unknown-option"}; EXPECT_EQ(ParseCommandLine(2, const_cast(args), &options, &error), 2); EXPECT_NE(error.find("Unknown CLI option"), std::string::npos); diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index f52bdae3..09f078fb 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -1326,14 +1326,14 @@ TEST_F(OperatorApiTest, OutputsConfigValidationFailClosed) { EXPECT_EQ(ops_.Destroy(handle), 0); handle = nullptr; - // 2. 已移除的输出分配 type 字段即使匹配也会被拒绝。 + // 2. 输出槽配置中的未知字段被拒绝。 { std::ifstream json_in(std::filesystem::path(GetConfDir()) / "configs/pipeline_keyword_match_rules.json"); nlohmann::json pipe_json; json_in >> pipe_json; - pipe_json["deployment"]["io"]["out_mem"]["keyword_out"]["type"] = - "keyword_out"; + pipe_json["deployment"]["io"]["out_mem"]["keyword_out"]["unknown_field"] = + 1; std::ofstream p_ofs(root / "configs/pipeline_keyword_match_rules.json"); p_ofs << pipe_json.dump(2); p_ofs.close(); @@ -1369,24 +1369,6 @@ TEST_F(OperatorApiTest, OutputsConfigValidationFailClosed) { } EXPECT_EQ(ops_.Create(&handle, ¶m), -2); - // 5. 旧的根级 outputs 结构在 .conf 中被拒绝 -> -2 - { - std::ofstream ofs(conf_path); - ofs << R"({ - "pipe_path": "pipeline_keyword_match_rules.json", - "outputs": { - "keyword_out": { - "type": "keyword_out", - "meta_num": 0, - "metadata_type_id": 0 - } - } - })"; - } - EXPECT_EQ(ops_.Create(&handle, ¶m), -2); - EXPECT_NE(std::string(GetOperatorLastError()).find("Unknown field"), - std::string::npos); - // 6. deployment.model_path 单值字段被拒绝(路径只存在 models 条目中)-> -2 { std::ifstream json_in(std::filesystem::path(GetConfDir()) / @@ -1406,24 +1388,6 @@ TEST_F(OperatorApiTest, OutputsConfigValidationFailClosed) { .find("Unknown field at /deployment/model_path"), std::string::npos); - // 6b. 已移除的 model_paths 即使为空或冗余也会被拒绝。 - for (const auto& legacy_value : - nlohmann::json::array({nlohmann::json::object(), - {{"unused_model", "models/unused.bin"}}})) { - std::ifstream json_in(std::filesystem::path(GetConfDir()) / - "configs/pipeline_keyword_match_rules.json"); - nlohmann::json pipe_json; - json_in >> pipe_json; - pipe_json["deployment"]["model_paths"] = legacy_value; - std::ofstream(root / "configs/pipeline_keyword_match_rules.json") - << pipe_json.dump(2); - EXPECT_EQ(ops_.Create(&handle, ¶m), -2); - EXPECT_EQ(handle, nullptr); - EXPECT_NE(std::string(GetOperatorLastError()) - .find("Unknown field at /deployment/model_paths"), - std::string::npos); - } - // 7. .conf 根对象仅允许 pipe_path -> -2 { std::ofstream ofs(conf_path); diff --git a/tests/integration/pipeline/test_model_backend_pipeline.cpp b/tests/integration/pipeline/test_model_backend_pipeline.cpp index 6c21e034..6cd480dd 100644 --- a/tests/integration/pipeline/test_model_backend_pipeline.cpp +++ b/tests/integration/pipeline/test_model_backend_pipeline.cpp @@ -481,37 +481,6 @@ TEST_F(ModelBackendPipelineTest, ValidatorRejectsProtocolMismatch) { EXPECT_EQ(g_backend_create_count.load(), 0); } -TEST_F(ModelBackendPipelineTest, ValidatorRejectsRemovedCapabilityField) { - nlohmann::json cfg = { - {"biz_name", "cap_mismatch_test"}, - {"models", - nlohmann::json::array({{ - {"model_id", "emb_model"}, - {"capability", "rerank"}, // 已移除的字段无论取值都会被拒绝。 - {"model_type", "mock_bge_embedding"}, // Definition 为 embedding - {"backend", "mock_test_backend"}, - {"model_path", "./model.onnx"}, - }})}, - {"pipeline", nlohmann::json::array({{ - {"id", "node1"}, - {"node_type", "MockEmbeddingConsumerNode"}, - {"depends_on", nlohmann::json::array()}, - {"config", {{"bind_model", "emb_model"}}}, - }})}, - }; - - auto report = PipelineValidator::Validate(cfg); - EXPECT_FALSE(report.ok); - - bool found_removed_field = false; - for (const auto& diag : report.diagnostics) { - if (diag.code == DiagnosticCode::kUnknownField) { - found_removed_field = true; - } - } - EXPECT_TRUE(found_removed_field); -} - // 3. Pipeline 构建与原子化实例化测试 TEST_F(ModelBackendPipelineTest, PipelineBuildMaterializesAndRegistersModel) { nlohmann::json cfg = { diff --git a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp index 0e43f3c0..9729c89b 100644 --- a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp +++ b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp @@ -444,27 +444,6 @@ TEST(PipelineValidatorTest, AllRepositoryPipelinesValidate) { EXPECT_EQ(validated + skipped_optional, candidates); } -TEST(PipelineValidatorTest, RejectsRemovedRuleCategoriesField) { - std::ifstream stream("configs/pipeline_keyword_match_rules.json"); - ASSERT_TRUE(stream.is_open()); - nlohmann::json pipeline; - stream >> pipeline; - pipeline = PrepareExternalFixtureForCore(pipeline); - ASSERT_FALSE(pipeline["pipeline"].empty()); - auto& config = pipeline["pipeline"][0]["config"]; - config["default_categories"] = config["categories"]; - config.erase("categories"); - - const auto report = ValidateWithRemediation(pipeline); - ASSERT_FALSE(report.ok); - EXPECT_TRUE(std::any_of( - report.diagnostics.begin(), report.diagnostics.end(), - [](const ValidationDiagnostic& diagnostic) { - return diagnostic.code == DiagnosticCode::kUnknownConfigField && - diagnostic.path == "/pipeline/0/config/default_categories"; - })); -} - TEST(PipelineValidatorTest, ModelPathsUseLexicalChecksWithoutDeploymentRoots) { std::ifstream stream("demo/fixtures/mock/pipeline_doc_qa.json"); ASSERT_TRUE(stream.is_open()); diff --git a/tests/tooling/test_dev_recipe.py b/tests/tooling/test_dev_recipe.py index 8bdcc063..6f2619b8 100644 --- a/tests/tooling/test_dev_recipe.py +++ b/tests/tooling/test_dev_recipe.py @@ -176,7 +176,6 @@ def test_external_destination_preserves_deployment_and_dataset_paths(self): for model in pipeline.get("models", []): self.assertEqual((bundle / model["model_path"]).resolve(), self.root / "demo/fixtures/mock/artifacts/neutral-llm.fixture") - self.assertNotIn("model_paths", pipeline["deployment"]) verified = self.verify(pipeline_path=target, effects_path=target.with_name(target.stem + "_effects.json")) self.assertTrue(verified["ok"], verified) @@ -220,13 +219,6 @@ def test_missing_outputs_deployment_uses_native_required_slot_defaults(self): self.assertEqual(generated["deployment"]["io"], {"io_binding": "keyword_match"}) self.assertTrue(self.verify()["ok"]) - def test_legacy_mem_que_deployment_rejected_as_missing_outputs(self): - pipe_path = self.root / "configs/pipeline_keyword_match_rules.json" - pipe = json.loads(pipe_path.read_text()) - pipe.setdefault("deployment", {}).setdefault("io", {})["mem_que"] = {"type": "keyword_out"} - pipe_path.write_text(json.dumps(pipe)) - self.assert_prepare_rejected_without_writes() - def test_unlabelled_or_duplicate_effects_rejected_before_generation(self): source = self.root / "tests/fixtures/effects/keyword_exact.json" labelled = json.loads(source.read_text()) diff --git a/tests/tooling/test_pipeline_studio.py b/tests/tooling/test_pipeline_studio.py index 05e5f928..a2f34505 100644 --- a/tests/tooling/test_pipeline_studio.py +++ b/tests/tooling/test_pipeline_studio.py @@ -385,7 +385,6 @@ def test_conf_preserves_single_model_path_without_adding_model_root(self): pipeline["models"][0]["model_path"] = "models/replacement.gguf" saved = self.service.save_solution("pipeline_replaced.json", pipeline, "entity_extract_mock", "models") self.assertEqual(saved["pipeline"]["models"][0]["model_path"], "models/replacement.gguf") - self.assertNotIn("model_paths", saved["pipeline"]["deployment"]) self.assertEqual(json.loads((self.configs / "pipeline_replaced.json").read_text()), pipeline) def test_ordinary_save_updates_managed_model_paths_and_node_parameters(self): @@ -400,7 +399,6 @@ def test_ordinary_save_updates_managed_model_paths_and_node_parameters(self): ) self.assertEqual(updated["command"], saved["command"]) self.assertEqual(updated["pipeline"]["models"][0]["model_path"], "models/replacement.gguf") - self.assertNotIn("model_paths", updated["pipeline"]["deployment"]) self.assertEqual(json.loads((self.configs / saved["filename"]).read_text()), pipeline) profile, _ = self.service.profile_inputs(pipeline, "entity_extract_mock") effective = self.service.resolve_run_conf(self.configs / saved["conf_filename"], profile) @@ -450,7 +448,6 @@ def test_restarted_service_saves_model_path_without_shadowed_values(self): pipeline["models"][0]["model_path"] = "models/replacement.gguf" updated = restarted.save_pipeline(saved["filename"], pipeline, saved["revision"]) self.assertEqual(updated["pipeline"]["models"][0]["model_path"], "models/replacement.gguf") - self.assertNotIn("model_paths", updated["pipeline"]["deployment"]) self.assertEqual(json.loads((self.configs / saved["filename"]).read_text()), pipeline) self.assertFalse(restarted.generated_solutions) @@ -476,7 +473,6 @@ def inspect_launch(args, **kwargs): time.sleep(0.05) self.assertEqual(job["status"], "completed", job) self.assertEqual(observed["pipeline"]["models"], pipeline["models"]) - self.assertNotIn("model_paths", observed["pipeline"]["deployment"]) self.assertEqual(observed["conf"]["pipe_path"], "pipeline.json") self.assertFalse(observed["directory"].exists()) @@ -639,19 +635,6 @@ def test_edit_explains_unknown_registrations(self): self.assertEqual(payload["validation"]["ok"], operation["kind"] == "rename_node") self.assertNotIn("提示:", process.stderr) - def test_removed_dependency_repair_command_is_rejected_without_writing(self): - original = (ROOT / "configs/pipeline_keyword_match_rules.json").read_bytes() - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "pipeline.json" - path.write_bytes(original) - result = subprocess.run( - [str(PIPELINE_TOOL), "fix-deps", str(path), "--in-place"], - text=True, capture_output=True, cwd=ROOT, check=False, - ) - self.assertEqual(result.returncode, 2) - self.assertEqual(path.read_bytes(), original) - self.assertNotIn("fix-deps", result.stderr) - def test_explicit_binding_uses_native_required_output_defaults(self): original = json.loads((ROOT / "configs/pipeline_keyword_match_rules.json").read_text()) original.pop("deployment", None) @@ -671,12 +654,11 @@ def test_explicit_binding_uses_native_required_output_defaults(self): code, resolved = self.command("validate-io", str(path / "pipeline.conf")) self.assertEqual(code, 0, resolved) self.assertEqual(resolved["binding"]["biz_name"], "keyword_match") - self.assertNotIn("binding_id", resolved["binding"]) self.assertEqual(set(resolved["output_pools"]), {"keyword_out"}) self.assertEqual(resolved["output_pools"]["keyword_out"]["capacities"], {"match_result_json": 2047}) self.assertEqual((path / "pipeline.json").read_text(), before) - def test_external_selector_is_required_and_legacy_selectors_are_rejected(self): + def test_external_selector_is_required_and_root_biz_name_is_rejected(self): original = json.loads((ROOT / "configs/pipeline_keyword_match_rules.json").read_text()) variants = [(None, "/deployment"), ({}, "/deployment/io"), ({"io": {}}, "/deployment/io/io_binding"), @@ -691,33 +673,22 @@ def test_external_selector_is_required_and_legacy_selectors_are_rejected(self): code, result = self.command(*entrypoint, "--stdin", input_pipeline=pipeline) self.assertEqual(code, 1, result) self.assertEqual(result["diagnostics"][0]["path"], path) - for field, path in (("biz_name", "/biz_name"), - ("output_allocations", "/deployment/io/output_allocations")): - pipeline = copy.deepcopy(original) - if field == "biz_name": - pipeline[field] = "keyword_match" - else: - io_overrides(pipeline)[field] = {} - code, result = self.command("validate", "--stdin", input_pipeline=pipeline) - self.assertEqual(code, 1, result) - self.assertEqual(result["diagnostics"][0]["path"], path) + pipeline = copy.deepcopy(original) + pipeline["biz_name"] = "keyword_match" + code, result = self.command("validate", "--stdin", input_pipeline=pipeline) + self.assertEqual(code, 1, result) + self.assertEqual(result["diagnostics"][0]["path"], "/biz_name") code, result = self.command("catalog", "--io-binding", "unknown_biz") self.assertEqual(code, 1, result) self.assertEqual(result["diagnostics"][0]["code"], "UNKNOWN_IO_BINDING") - for command in ("catalog", "init"): - process = subprocess.run([str(PIPELINE_TOOL), command, "--biz", "keyword_match"], - cwd=ROOT, capture_output=True, text=True) - self.assertEqual(process.returncode, 2, process.stdout) - def test_all_commands_return_versioned_json(self): + def test_all_commands_return_json(self): first_code, first = self.command("catalog", "--io-binding", "keyword_match") second_code, second = self.command("catalog", "--io-binding", "keyword_match") self.assertEqual((first_code, first), (second_code, second)) self.assertTrue(first["nodes"]) self.assertTrue(first["profiles"]) self.assertTrue(all(profile["io_binding"] == "keyword_match" for profile in first["profiles"])) - self.assertTrue(all("biz" not in profile and "pipeline_biz" not in profile for profile in first["profiles"])) - self.assertTrue(all("demo_biz" not in biz for biz in first["bizs"])) code, described = self.command("describe-node", "TextRuleMatchNode") self.assertEqual(code, 0) self.assertEqual(described["node_type"], "TextRuleMatchNode") @@ -727,24 +698,6 @@ def test_all_commands_return_versioned_json(self): self.assertEqual(code, 0) self.assertEqual(initialized["pipeline"]["pipeline"], []) self.assertEqual(initialized["pipeline"]["deployment"]["io"]["io_binding"], "keyword_match") - self.assertNotIn("business_name", initialized["pipeline"]) - rejected = subprocess.run( - [str(PIPELINE_TOOL), "catalog", "--business", "keyword_match"], - text=True, - capture_output=True, - cwd=ROOT, - check=False, - ) - self.assertEqual(rejected.returncode, 2) - removed_normalizer = subprocess.run( - [str(PIPELINE_TOOL), "normalize", "--explicit-dag", "--stdin"], - input="{}", - text=True, - capture_output=True, - cwd=ROOT, - check=False, - ) - self.assertEqual(removed_normalizer.returncode, 2) pipeline = json.loads( (ROOT / "configs" / "pipeline_keyword_match_rules.json").read_text() ) @@ -931,7 +884,6 @@ def test_resolve_conf_exposes_model_sources_defaults_and_native_pool_errors(self changed = Path(directory) / "pipeline.conf" pipe_file = conf_path.with_name(conf["pipe_path"]) pipe_doc = json.loads(pipe_file.read_text()) - self.assertNotIn("model_paths", pipe_doc["deployment"]) (Path(directory) / conf["pipe_path"]).write_text(json.dumps(pipe_doc)) changed.write_text(json.dumps(conf)) code, direct = self.command("resolve-conf", str(changed.relative_to(ROOT)), "--root", str(ROOT)) @@ -992,9 +944,9 @@ def test_cli_plan_envelopes_across_entrypoints(self): pipeline = json.loads( (ROOT / "demo/fixtures/mock/pipeline_entity_extract.json").read_text() ) - # 1. 已移除的部署字段被拒绝 + # 1. 部署段的未知字段被拒绝 invalid_doc = copy.deepcopy(pipeline) - invalid_doc["deployment"]["model_paths"] = {"unknown_model_id": "models/foo.bin"} + invalid_doc["deployment"]["unknown_field"] = 1 for ep in self.CLI_PARITY_ENTRYPOINTS: with self.subTest(case="deployment_failure", entrypoint=ep): code, res = self.command(*ep, "--stdin", input_pipeline=invalid_doc) @@ -1002,7 +954,7 @@ def test_cli_plan_envelopes_across_entrypoints(self): self.assertFalse(res["ok"]) self.assertIn("diagnostics", res) self.assertEqual(res["diagnostics"][0]["code"], "DEPLOYMENT_ERROR") - self.assertEqual(res["diagnostics"][0]["path"], "/deployment/model_paths") + self.assertEqual(res["diagnostics"][0]["path"], "/deployment/unknown_field") if ep[0] == "plan": self.assertEqual(res["plan"], {"layers": [], "topological_order": []}) @@ -1062,20 +1014,6 @@ def test_cli_validate_io_exact_diagnostic_pointer(self): "/deployment/io/out_mem/entity_out", ) - def test_cli_rejects_removed_model_paths_field_for_every_value(self): - pipeline = json.loads((ROOT / "demo/fixtures/mock/pipeline_entity_extract.json").read_text()) - for value in ({}, {"entity_llm": "models/ignored.bin"}, {"bad~id/extra": "ignored"}, None, 42, ""): - document = copy.deepcopy(pipeline) - document["deployment"]["model_paths"] = value - for endpoint in self.CLI_PARITY_ENTRYPOINTS: - with self.subTest(value=value, endpoint=endpoint): - code, report = self.command(*endpoint, "--stdin", input_pipeline=document) - self.assertEqual(code, 1) - self.assertEqual(report["diagnostics"][0]["code"], "DEPLOYMENT_ERROR") - self.assertEqual(report["diagnostics"][0]["path"], "/deployment/model_paths") - if endpoint[0] == "plan": - self.assertEqual(report["plan"], {"layers": [], "topological_order": []}) - def test_cli_unknown_binding_unknown_root_and_malformed_binding(self): # 外部选择器的诊断在各原生入口之间完全一致。 pipeline = json.loads( @@ -1085,7 +1023,7 @@ def test_cli_unknown_binding_unknown_root_and_malformed_binding(self): doc1 = copy.deepcopy(pipeline) io_overrides(doc1)["io_binding"] = "nonexistent_biz" - # 2. 即使存在 binding,已移除的根选择器也会被拒绝。 + # 2. 即使存在 binding,外部文档也不得声明根级 biz_name。 doc2 = copy.deepcopy(pipeline) doc2["biz_name"] = "unmatched_biz_name" io_overrides(doc2)["io_binding"] = "entity_extract" @@ -1096,7 +1034,7 @@ def test_cli_unknown_binding_unknown_root_and_malformed_binding(self): cases = [ ("unknown_binding", doc1, "UNKNOWN_IO_BINDING", "/deployment/io/io_binding"), - ("removed_biz", doc2, "DEPLOYMENT_ERROR", "/biz_name"), + ("root_biz_name", doc2, "DEPLOYMENT_ERROR", "/biz_name"), ("binding_non_string", doc3, "DEPLOYMENT_ERROR", "/deployment/io/io_binding"), ] @@ -1112,13 +1050,13 @@ def test_cli_unknown_binding_unknown_root_and_malformed_binding(self): self.assertEqual(res["plan"], {"layers": [], "topological_order": []}) def test_cli_output_slot_allocation_errors(self): - # 拒绝已移除的 type 字段、未知槽位、非法的分配结构和容量。 + # 拒绝槽位配置中的未知字段、未知槽位、非法的分配结构和容量。 pipeline = json.loads( (ROOT / "demo/fixtures/mock/pipeline_entity_extract.json").read_text() ) - # 1. 已移除的 type 声明属于未知字段。 + # 1. 槽位配置中的未知字段 doc1 = copy.deepcopy(pipeline) - output_override(doc1, "entity_out")["type"] = "entity_out" + output_override(doc1, "entity_out")["unknown_field"] = 1 # 2. 未知输出槽位 doc2 = copy.deepcopy(pipeline) @@ -1133,7 +1071,7 @@ def test_cli_output_slot_allocation_errors(self): output_override(doc4, "entity_out")["capacities"] = {"entities_json": 0} cases = [ - ("removed_type", doc1, "INVALID_OUTPUT_ALLOCATION", "/deployment/io/out_mem/entity_out"), + ("unknown_field", doc1, "INVALID_OUTPUT_ALLOCATION", "/deployment/io/out_mem/entity_out"), ("unknown_slot", doc2, "UNKNOWN_OUTPUT_SLOT", "/deployment/io/out_mem/bogus_slot"), ("invalid_alloc", doc3, "INVALID_OUTPUT_ALLOCATION", "/deployment/io/out_mem/entity_out"), ("invalid_capacity", doc4, "INVALID_OUTPUT_ALLOCATION", "/deployment/io/out_mem/entity_out"), @@ -1147,8 +1085,8 @@ def test_cli_output_slot_allocation_errors(self): self.assertFalse(res["ok"]) self.assertEqual(res["diagnostics"][0]["code"], exp_code) self.assertEqual(res["diagnostics"][0]["path"], exp_path) - if case_name == "removed_type": - self.assertIn("Unknown output allocation field: type", res["diagnostics"][0]["message"]) + if case_name == "unknown_field": + self.assertIn("Unknown output allocation field: unknown_field", res["diagnostics"][0]["message"]) if ep[0] == "plan": self.assertEqual(res["plan"], {"layers": [], "topological_order": []}) @@ -1492,17 +1430,17 @@ def test_web_modules_apply_catalog_semantics_and_topological_layout(self): const models = [ {{ model_id: "embed", model_type: "embed_type" }}, {{ model_id: "llm", model_type: "llm_type" }}, - {{ model_id: "legacy_embed", model_type: "legacy_embed_type" }}, + {{ model_id: "other_embed", model_type: "other_embed_type" }}, ]; const modelDefinitions = [ {{ model_type: "embed_type", capability: "embedding" }}, {{ model_type: "llm_type", capability: "llm" }}, - {{ model_type: "legacy_embed_type", capability: "embedding" }}, + {{ model_type: "other_embed_type", capability: "embedding" }}, ]; const nodeDefinition = {{ model_dependencies: [{{ name: "encoder", capability: "embedding", config_field: "model_slot" }}] }}; assert.deepEqual( workbench.compatibleModels(models, modelDefinitions, nodeDefinition).map(model => model.model_id), - ["embed", "legacy_embed"] + ["embed", "other_embed"] ); assert.deepEqual( [...workbench.modelBoundNodeIds([ @@ -2631,7 +2569,6 @@ def capture_resolve(conf_file, profile): args = thread.call_args.kwargs["args"] run_profile, run_conf = args[2], args[3] self.assertEqual(args[1]["models"], pipeline["models"]) - self.assertNotIn("model_paths", args[1]["deployment"]) self.assertNotIn("biz", run_profile) self.service.save_pipeline( path.name, pipeline, SHOW.revision_for(path.read_bytes()), @@ -2640,7 +2577,6 @@ def capture_resolve(conf_file, profile): self.assertEqual(saved_conf, expected_conf) saved_pipe = json.loads(path.read_text()) self.assertEqual(saved_pipe["models"][0]["model_path"], "models/selected_A.onnx") - self.assertNotIn("model_paths", saved_pipe["deployment"]) self.assertEqual(run_conf, expected_conf) self.assertEqual(resolved_candidates[0], {"pipe_path": "pipeline.json"}) @@ -2649,7 +2585,6 @@ def test_associated_raw_model_edit_is_the_effective_path(self): pipeline["models"][0]["model_path"] = "models/raw_edit.onnx" _, candidate = self.service.deployment_candidate(pipeline, filename=path.name) self.assertEqual(pipeline["models"][0]["model_path"], "models/raw_edit.onnx") - self.assertNotIn("model_paths", pipeline["deployment"]) self.assertEqual(candidate, {"pipe_path": path.name}) self.assertEqual(pipeline["deployment"]["io"]["out_mem"]["doc_out"]["capacities"]["answer_text"], 2047) @@ -2659,7 +2594,6 @@ def test_associated_new_model_does_not_invent_deployment_override(self): new_model.update(model_id="new_model", model_path="new.onnx") pipeline["models"].append(new_model) _, candidate = self.service.deployment_candidate(pipeline, filename=path.name) - self.assertNotIn("model_paths", pipeline["deployment"]) self.assertEqual(pipeline["models"][-1]["model_path"], "new.onnx") def test_associated_external_file_changes_block_candidate_and_save(self): diff --git a/tests/tooling/test_scaffold_custom_node.py b/tests/tooling/test_scaffold_custom_node.py index f86bb9c5..35a09388 100755 --- a/tests/tooling/test_scaffold_custom_node.py +++ b/tests/tooling/test_scaffold_custom_node.py @@ -45,7 +45,6 @@ def test_port_cardinality_is_not_split_into_provenance(self): def test_cli_rejects_unsupported_signatures_before_writing(self): with tempfile.TemporaryDirectory() as temp: for args in [ - ["--kind", "unary_inference", "-m", "ocr"], ["--kind", "model", "-m", "asr", "--in-port", "i:TextBatch"], ["--kind", "model", "--out-port", "o:TextBatch:1:N:generate_sub_id"], ["-m", "llm"], @@ -375,7 +374,7 @@ def test_embedding_and_control_use_existing_function_contracts(self): self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("ControlChangesOutputAndPreservesOnFailure", result.stdout) - def test_generates_function_nodes_and_rejects_retired_options(self): + def test_generates_function_nodes(self): result = self.run_cli("BasicMapNode", "--dry-run") self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("MakeMapSpec", result.stdout) @@ -389,17 +388,6 @@ def test_generates_function_nodes_and_rejects_retired_options(self): self.assertIn("BuildPrompt", result.stdout) self.assertIn("FormatAnswer", result.stdout) - with tempfile.TemporaryDirectory() as temp: - for bad_args in [ - ["--add-to-cmake"], ["--authoring", "basic"], ["--authoring", "advanced"], - ["--authoring", "auto"], ["--kind", "unary_inference", "-m", "llm"], - ]: - res = self.run_cli("RejectedBasicNode", "--output-dir", temp, *bad_args) - self.assertNotEqual(res.returncode, 0, f"Expected failure for {bad_args}") - if bad_args == ["--add-to-cmake"]: - self.assertIn("unrecognized arguments: --add-to-cmake", res.stderr) - self.assertEqual(list(Path(temp).iterdir()), []) - with tempfile.TemporaryDirectory() as temp: env = self._setup_mock_repo(temp) res = self.run_cli("HarnessMapNode", "--write-test", env=env) diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index e7cb1846..4ccc335c 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -1283,10 +1283,10 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { EXPECT_NE(error.find("Unknown configured output slot: unknown_slot"), std::string::npos); - // 5. 已移除的部署字段 model_paths 作为未知字段被拒绝 + // 5. 部署段的未知字段被拒绝 nlohmann::json unknown_mid_json = { {"deployment", - {{"model_paths", {{"non_existent_model", "dummy_path"}}}, + {{"unknown_field", 1}, {"io", {{"io_binding", "keyword_match"}, {"out_mem", @@ -1299,7 +1299,7 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { ret = IoBindingResolver::ResolveFromPipelineJson(unknown_mid_json, "./models", &plan, &error); EXPECT_EQ(ret, -2); - EXPECT_NE(error.find("Unknown field at /deployment/model_paths"), + EXPECT_NE(error.find("Unknown field at /deployment/unknown_field"), std::string::npos) << "actual error was: " << error; } diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index e2304d64..fddb67c0 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -880,48 +880,6 @@ TEST_F(IoBindingRegistryTest, ModelPathNonexistentOnDiskIsAllowed_T05) { fs::remove_all(temp_dir); } -TEST_F(IoBindingRegistryTest, RemovedModelPathsIsAlwaysUnknownField_T06) { - RegisterTestBizBinding(); - const nlohmann::json base_doc = { - {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, - {"models", - {{{"model_id", "mid_1"}, - {"model_type", "test_biz_embedding"}, - {"backend", "test_tensor_backend"}, - {"model_config", {{"embedding_dim", 128}, {"max_batch_size", 4}}}, - {"backend_config", nlohmann::json::object()}, - {"model_path", "models/current.bin"}}}}, - {"pipeline", DefaultPipelineNodes()}}; - DeploymentPrepareOptions options; - options.path_mode = DeploymentPathMode::kLexicalOnly; - for (const auto& legacy_value : - nlohmann::json::array({nlohmann::json::object(), - {{"mid_1", "models/current.bin"}}, - {{"mid_1", "models/conflicting.bin"}}, - {{"non/exist~id", "models/foo.bin"}}, - {{"mid_1", 999}}, - {{"mid_1", ""}}, - nullptr, - "", - 999})) { - SCOPED_TRACE(legacy_value.dump()); - auto doc = base_doc; - doc["deployment"]["model_paths"] = legacy_value; - const auto original = doc; - PreparedDeployment prepared; - DeploymentDiagnostic diag; - ASSERT_TRUE(PrepareDeploymentDocument(base_doc, options, &prepared, &diag)); - EXPECT_FALSE(PrepareDeploymentDocument(doc, options, &prepared, &diag)); - EXPECT_EQ(diag.code, "DEPLOYMENT_ERROR"); - EXPECT_EQ(diag.path, "/deployment/model_paths"); - EXPECT_NE(diag.message.find("Unknown field at /deployment/model_paths"), - std::string::npos); - EXPECT_TRUE(prepared.neutral_pipeline_json.is_null()); - EXPECT_TRUE(prepared.binding.biz_name.empty()); - EXPECT_EQ(doc, original); - } -} - TEST_F(IoBindingRegistryTest, OutputMemoryOverridesRemainOptional) { RegisterTestBizBinding(); for (int variant = 0; variant < 3; ++variant) { @@ -1086,19 +1044,19 @@ TEST_F(IoBindingRegistryTest, EffectiveBatchLimitIsBindingLimit) { } } -TEST_F(IoBindingRegistryTest, RemovedAllocationFieldIsRejected) { +TEST_F(IoBindingRegistryTest, UnknownDeploymentIoFieldIsRejected) { RegisterTestBizBinding(); const nlohmann::json document = { {"deployment", {{"io", {{"io_binding", "test_biz"}, - {"output_allocations", nlohmann::json::object()}}}}}, + {"unknown_field", nlohmann::json::object()}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; EXPECT_FALSE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)); EXPECT_EQ(diagnostic.code, "DEPLOYMENT_ERROR"); - EXPECT_EQ(diagnostic.path, "/deployment/io/output_allocations"); + EXPECT_EQ(diagnostic.path, "/deployment/io/unknown_field"); } TEST_F(IoBindingRegistryTest, @@ -1172,7 +1130,7 @@ TEST_F(IoBindingRegistryTest, DeploymentIoUnknownBindingOrMismatch_T07) { EXPECT_EQ(diag.path, "/deployment/io/io_binding"); } - // 情形 2:即使 binding 存在,已移除的根业务标识也会被拒绝。 + // 情形 2:业务身份只来自 io_binding,外部文档不得声明根级 biz_name。 { nlohmann::json doc = base_doc; doc["biz_name"] = "other_biz"; @@ -1222,10 +1180,10 @@ TEST_F(IoBindingRegistryTest, DeploymentIoSlotValidation_T08) { EXPECT_EQ(diag.path, "/deployment/io/out_mem/unexpected_extra_slot"); } - // 情形 3:已移除的 type 字段即使与槽位一致也会被拒绝。 + // 情形 3:槽位配置中的未知字段被拒绝。 { nlohmann::json doc = base_doc; - doc["deployment"]["io"]["out_mem"]["entity_out"]["type"] = "entity_out"; + doc["deployment"]["io"]["out_mem"]["entity_out"]["unknown_field"] = 1; EXPECT_FALSE(PrepareDeploymentDocument(doc, options, &prepared, &diag)); EXPECT_EQ(diag.code, "INVALID_OUTPUT_ALLOCATION"); EXPECT_EQ(diag.path, "/deployment/io/out_mem/entity_out"); @@ -1466,16 +1424,16 @@ TEST_F(IoBindingRegistryTest, EXPECT_EQ(diag.code, "MISSING_FIELD"); EXPECT_EQ(diag.path, "/models/0/model_path"); - // T06:已移除的部署字段 model_paths + // T06:部署段的未知字段 nlohmann::json t06_doc = t03_doc; t06_doc["models"][0]["model_path"] = "models/original.bin"; - t06_doc["deployment"]["model_paths"] = {{"unknown_mid", "models/foo.bin"}}; + t06_doc["deployment"]["unknown_field"] = 1; rc = IoBindingResolver::ResolveFromPipelineJson(t06_doc, "./models", &plan, &err, &diag); EXPECT_EQ(rc, -2); EXPECT_EQ(plan, nullptr); EXPECT_EQ(diag.code, "DEPLOYMENT_ERROR"); - EXPECT_EQ(diag.path, "/deployment/model_paths"); + EXPECT_EQ(diag.path, "/deployment/unknown_field"); // T07:经 IoBindingResolver 的未知 binding nlohmann::json t07_doc = t03_doc; @@ -1579,10 +1537,10 @@ TEST_F(IoBindingRegistryTest, EXPECT_EQ(diag.path, "/models/0/model_path"); } - // 3. 经文件的 T06:已移除的部署字段 model_paths + // 3. 经文件的 T06:部署段的未知字段 { nlohmann::json t06_pipe = base_pipeline; - t06_pipe["deployment"]["model_paths"] = {{"unknown_mid", "models/foo.bin"}}; + t06_pipe["deployment"]["unknown_field"] = 1; write_file(pipe_path, t06_pipe); rc = IoBindingResolver::ResolveFromFile(conf_path.string(), "", &plan, &err, @@ -1590,7 +1548,7 @@ TEST_F(IoBindingRegistryTest, EXPECT_EQ(rc, -2); EXPECT_EQ(plan, nullptr); EXPECT_EQ(diag.code, "DEPLOYMENT_ERROR"); - EXPECT_EQ(diag.path, "/deployment/model_paths"); + EXPECT_EQ(diag.path, "/deployment/unknown_field"); } // 4. 经文件的 T07:未知 io_binding diff --git a/tests/unit/core/test_pipeline_config.cpp b/tests/unit/core/test_pipeline_config.cpp index 68723578..a4a2f795 100644 --- a/tests/unit/core/test_pipeline_config.cpp +++ b/tests/unit/core/test_pipeline_config.cpp @@ -442,34 +442,6 @@ TEST_F(PipelineConfigTest, RejectsPipelineWithoutId) { EXPECT_EQ(diag.path, "/pipeline/0/id"); } -TEST_F(PipelineConfigTest, RejectsRemovedNodePortsWrapper) { - const nlohmann::json root = { - {"biz_name", "test"}, - {"pipeline", - {{{"id", "node"}, - {"node_type", "CountingNode"}, - {"ports", {{"inputs", nlohmann::json::object()}}}}}}}; - ParsedPipelineConfig config; - PipelineDiagnostic diagnostic; - EXPECT_FALSE(ParsePipelineConfig(root, &config, &diagnostic)); - EXPECT_EQ(diagnostic.code, DiagnosticCode::kUnknownField); - EXPECT_EQ(diagnostic.path, "/pipeline/0/ports"); -} - -TEST_F(PipelineConfigTest, RejectsRemovedModelCapabilityField) { - nlohmann::json model = CountingModelEntry("model"); - model["capability"] = "test"; - const nlohmann::json root = { - {"biz_name", "test"}, - {"models", {model}}, - {"pipeline", {{{"id", "node"}, {"node_type", "CountingNode"}}}}}; - ParsedPipelineConfig config; - PipelineDiagnostic diagnostic; - EXPECT_FALSE(ParsePipelineConfig(root, &config, &diagnostic)); - EXPECT_EQ(diagnostic.code, DiagnosticCode::kUnknownField); - EXPECT_EQ(diagnostic.path, "/models/0/capability"); -} - TEST_F(PipelineConfigTest, MissingDependenciesAndWorkerBudgetUseSimpleDefaults) { const nlohmann::json root = { @@ -488,21 +460,6 @@ TEST_F(PipelineConfigTest, EXPECT_EQ(pipeline.GetExecutionMode(), Pipeline::ExecutionMode::kSequential); } -TEST_F(PipelineConfigTest, RejectsLegacyBusinessNameField) { - nlohmann::json root = { - {"business_name", "legacy_compat_test"}, - {"pipeline", - nlohmann::json::array({{{"id", "node_0"}, - {"node_type", "CountingNode"}, - {"depends_on", nlohmann::json::array()}}})}}; - - ParsedPipelineConfig parsed_cfg; - PipelineDiagnostic diag; - EXPECT_FALSE(ParsePipelineConfig(root, &parsed_cfg, &diag)); - EXPECT_EQ(diag.code, DiagnosticCode::kUnknownField); - EXPECT_EQ(diag.path, "/business_name"); -} - // 3. 表驱动负例测试:结构、类型、字段、组合、DAG 负例与零副作用断言 // (R1-ACC-003, R1-ACC-006) struct NegativeTestCase { @@ -546,32 +503,7 @@ TEST_F(PipelineConfigTest, TableDrivenNegativeValidationAndZeroSideEffects) { nlohmann::json{{"biz_name", 12345}, {"pipeline", valid_pipe}}, DiagnosticCode::kFieldType, "/biz_name"}); - // --- Execution Mode & Workers 组合校验 (R1-ACC-003) --- - cases.push_back( - NegativeTestCase{"SequentialModeWithMaxParallelWorkers", - nlohmann::json{{"biz_name", "test"}, - {"execution_mode", "sequential"}, - {"max_parallel_workers", 4}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/execution_mode"}); - cases.push_back(NegativeTestCase{"ExecutionModeAsyncRejected", - nlohmann::json{{"biz_name", "test"}, - {"execution_mode", "async"}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, - "/execution_mode"}); - cases.push_back( - NegativeTestCase{"ExecutionModeUnknownString", - nlohmann::json{{"biz_name", "test"}, - {"execution_mode", "coroutine_mode"}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/execution_mode"}); - cases.push_back(NegativeTestCase{"ExecutionModeNonString", - nlohmann::json{{"biz_name", "test"}, - {"execution_mode", true}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, - "/execution_mode"}); + // --- 并行 Worker 数校验 (R1-ACC-003) --- cases.push_back(NegativeTestCase{ "WorkersZeroInParallel", nlohmann::json{ @@ -670,15 +602,6 @@ TEST_F(PipelineConfigTest, TableDrivenNegativeValidationAndZeroSideEffects) { {"model_config", "invalid"}}})}, {"pipeline", valid_pipe}}, DiagnosticCode::kFieldType, "/models/0/model_config"}); - cases.push_back(NegativeTestCase{ - "LegacyEngineTypeRejected", - nlohmann::json{ - {"biz_name", "test"}, - {"models", nlohmann::json::array( - {{{"model_id", "m1"}, - {"engine_type", "unregistered_mock_engine_xyz"}}})}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/models/0/engine_type"}); // --- Model/Backend 方言及混用校验 --- cases.push_back(NegativeTestCase{ @@ -801,36 +724,6 @@ TEST_F(PipelineConfigTest, TableDrivenNegativeValidationAndZeroSideEffects) { {"unsupported_opt", true}}})}, {"pipeline", valid_pipe}}, DiagnosticCode::kUnknownField, "/models/0/unsupported_opt"}); - cases.push_back(NegativeTestCase{ - "LegacyEngineTypeIsUnknown", - nlohmann::json{{"biz_name", "test"}, - {"models", nlohmann::json::array( - {{{"model_id", "m1"}, - {"engine_type", "counting_engine"}}})}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/models/0/engine_type"}); - cases.push_back(NegativeTestCase{ - "LegacyEngineTypeWithBackendIsUnknown", - nlohmann::json{ - {"biz_name", "test"}, - {"models", nlohmann::json::array({{{"model_id", "m1"}, - {"engine_type", "counting_engine"}, - {"backend", "onnxruntime"}}})}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/models/0/engine_type"}); - cases.push_back(NegativeTestCase{ - "LegacyConfigFieldIsUnknown", - nlohmann::json{ - {"biz_name", "test"}, - {"models", nlohmann::json::array( - {{{"model_id", "m1"}, - {"model_type", "bge_embedding"}, - {"backend", "onnxruntime"}, - {"model_path", "./model.onnx"}, - {"config", nlohmann::json::object()}, - {"model_config", nlohmann::json::object()}}})}, - {"pipeline", valid_pipe}}, - DiagnosticCode::kUnknownField, "/models/0/config"}); // --- Pipeline Nodes 校验 --- cases.push_back(NegativeTestCase{"MissingPipeline", diff --git a/tests/unit/engine/test_onnx_and_reranker_model.cpp b/tests/unit/engine/test_onnx_and_reranker_model.cpp index 249ee532..7cde79fd 100644 --- a/tests/unit/engine/test_onnx_and_reranker_model.cpp +++ b/tests/unit/engine/test_onnx_and_reranker_model.cpp @@ -17,7 +17,6 @@ #include "core/common_contracts.h" #include "core/node_registry.h" #include "core/pipeline.h" -#include "core/pipeline_catalog.h" #include "core/pipeline_validator.h" #include "core/session_context.h" #include "dev_support/inference/bge_model_test_support.h" @@ -845,9 +844,6 @@ TEST_F(OnnxAndRerankerModelTest, CatalogRegistrations) { EXPECT_EQ(mdef_opt->capability, "rerank"); EXPECT_EQ(mdef_opt->required_protocol, ExecutionProtocol::kTensorGraph); EXPECT_EQ(mdef_opt->concurrency, InferenceConcurrency::kConcurrent); - - // 确认旧组合型名称未被伪装成 Model 注册。 - EXPECT_FALSE(PipelineCatalog::FindModel("onnx_rerank").has_value()); } // =============================================================================