From 296e762c990bd66b102d9d14510709eb1ad3353c Mon Sep 17 00:00:00 2001 From: hangshuo652 Date: Wed, 15 Jul 2026 22:23:02 +0800 Subject: [PATCH] docs: add comprehensive system analysis document (docs/system-analysis.md) --- docs/system-analysis.md | 679 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 679 insertions(+) create mode 100644 docs/system-analysis.md diff --git a/docs/system-analysis.md b/docs/system-analysis.md new file mode 100644 index 0000000..9e46966 --- /dev/null +++ b/docs/system-analysis.md @@ -0,0 +1,679 @@ +# COBOL → Java/Spark 迁移验证平台 v3 — 系统分析 + +--- + +## 一、系统概览 + +### 职责概述 + +COBOL 测试数据生成 + 迁移验证平台:解析 COBOL 源码,生成分支全覆盖的测试数据,并行运行 COBOL 和 Java/Spark 版本,逐字段比对输出,判定迁移正确性并生成验证报告。 + +### 两条平行管道 + +系统包含两条完全独立的管道,根据源码是否包含 `EXEC SQL` 自动路由: + +| 维度 | 非 DB 管道 | DB 管道 | +|------|------------|---------| +| 入口 | `main.py` → `orchestrator.py` | `python -m cobol_testgen` → `orchestrator_db.py` | +| 适用程序 | 纯 flat file I-O 程序 | 含 EXEC SQL 的 DB 程序 | +| 编译器 | `cobc` 标准编译 | `gixpp` (ESQL 预处理) + `cobc -l gixsql` | +| 存储 | 二进制 flat file | SQLite + flat file | +| 验证 | 二进制字节比对 | SQLite 表行比对 + 中间 JSON | +| 覆盖率 | 静态分支覆盖 | gcov 动态覆盖(跨场景合并) | + +### 目录结构总览 + +``` +cobol-java-v3/ +├── cobol_testgen/ ← 核心引擎(18 个 .py 文件) +├── orchestrator.py ← 非 DB 管道编排 +├── orchestrator_db.py ← DB 管道编排(1334 行) +├── main.py ← 非 DB CLI 入口 +├── config/ ← 配置系统(全局 + per-program YAML) +├── runners/ ← 编译运行引擎(6 个 runner) +├── gixsql/ ← vendored ESQL 工具链 +├── test-data/ ← 测试套件(61 个脚本) +├── docs/ ← 文档 +│── README.md / SETUP.md ← 项目首页 + 搭建指南 +``` + +--- + +## 二、cobol_testgen 核心引擎 + +### 职责概述 + +COBOL 源码解析器 + 测试数据生成器。四层架构(INPUT → CORE → CONDITION → DESIGN)+ 两个正交层(OUTPUT, COVERAGE),依赖单向流动。 + +### 内部结构 + +``` +cobol_testgen/ +│ +├── models.py (163 行) +│ 数据模型层,零内部依赖 +│ ├── PicInfo — PIC 解析结果 (type, digits, decimal, length, signed) +│ ├── FieldDef — DATA DIVISION 字段定义 +│ ├── BrSeq/BrIf/BrEval/BrPerform/BrSearch — 分支树节点类型 +│ ├── Assign — 赋值语句 (MOVE/COMPUTE/SQL/INITIALIZE 等) +│ ├── CondLeaf/CondNot/CondAnd/CondOr — 条件表达式树节点 +│ ├── Constraint — 约束元组 (field, op, value, want_true) +│ └── ProcParseResult — 解析结果容器 +│ +├── read.py (675 行) ──── INPUT 层 +│ 预处理 + DATA DIVISION 解析 +│ ├── preprocess() — 主预处理: COPYBOOK 展开, EXEC 剥离, 注释清除 +│ ├── resolve_copybooks() — 递归 COPY 解析 (10 层深度保护) +│ ├── resolve_sql_includes() — EXEC SQL INCLUDE → COPY +│ ├── strip_exec_sql_from_data_div() — 从 DATA DIVISION 移除 SQL 块 +│ ├── parse_data_division() — Lark Earley parser → list[FieldDef] +│ ├── parse_pic() — PIC 字符串 → PicInfo +│ ├── parse_file_control() — FILE-CONTROL → {filename: {assign, org, mode}} +│ ├── parse_file_section() — FILE SECTION → {FDname: [01-names]} +│ └── scan_open_statements() — OPEN 语句 → {filename: INPUT/OUTPUT/I-O} +│ +├── core.py (2169 行) ──── CORE 层 +│ PROCEDURE DIVISION 解析 + 数据流追踪 +│ ├── build_branch_tree() — 主入口: 源码 → 分支树 +│ ├── _BrParser (类) — 行级状态机解析器 +│ │ ├── parse_seq() — 顺序语句序列 +│ │ ├── _parse_if() — IF ... END-IF +│ │ ├── _parse_evaluate() — EVALUATE ... END-EVALUATE +│ │ ├── _parse_perform() — PERFORM (UNTIL/VARYING/TIMES/THRU/行内) +│ │ ├── _parse_call() — CALL ... USING +│ │ ├── _parse_search() — SEARCH / SEARCH ALL +│ │ ├── _parse_sql() — EXEC SQL (SELECT/INSERT/DELETE/UPDATE/DECLARE CURSOR/FETCH) +│ │ ├── _record_assignment() — MOVE/COMPUTE/ADD/SUBTRACT/MULTIPLY/DIVIDE +│ │ ├── _parse_initialize() — INITIALIZE +│ │ ├── _parse_string() — STRING +│ │ └── _parse_unstring() — UNSTRING +│ ├── propagate_assignments() — 赋值传播 (20 轮收敛循环) +│ ├── trace_to_root() — 追踪赋值链到根变量 +│ ├── classify_field_roles() — 字段角色分类 (input/output/inout/unused) +│ └── extract_sql_assignments() — 提取 SQL 赋值 +│ +├── cond.py (413 行) ──── CONDITION 层 +│ 条件表达式解析 + MC/DC 枚举 +│ ├── parse_single_condition() — 单条件 → (field, op, value) +│ ├── parse_compound_condition() — AND/OR/NOT/括号 → 条件树 +│ ├── evaluate_tree() — 条件树求值 (leaf→bool) +│ ├── collect_leaves() — 收集所有叶子节点 +│ ├── mcdc_sets() — MC/DC 约束集生成 +│ └── satisfying_value() — 计算满足条件的值 +│ +├── design.py (1875 行) ──── DESIGN 层 +│ 路径枚举 + 记录生成 +│ ├── enum_paths() — 递归树遍历路径枚举 (~500 行) +│ ├── make_base_record() — 基础记录生成 (初值) +│ ├── apply_constraint() — 单约束应用到记录 (~125 行) +│ ├── generate_records() — 主生成入口: base → propagate → constrain → sync +│ ├── sync_redefined_fields() — REDEFINES 字段同步 +│ ├── apply_occurs_depending() — OCCURS DEPENDING ON 处理 +│ ├── _rebuild_r01line_csv() — CSV 源字段重建 +│ ├── _reconstruct_unstring_sources() — UNSTRING 源重建 +│ └── _set_invalid_value() — 无效值注入 (CALL ABEND 触发) +│ +├── design_mcdc.py (286 行) +│ 线性路径枚举 (O(2N) 替代 O(2^N),无爆炸风险) +│ └── enum_paths() — 每决策点一条 T + 一条 F +│ +├── pipeline_bridge.py (189 行) +│ 新旧解析器桥接: BranchNode (procedure_parser) ↔ BrSeq 规范模型 +│ └── build_branch_tree_fallback() — 新解析器优先 → 3s 超时回退到旧解析器 +│ +├── procedure_parser.py (610 行) +│ 可选的 PROCEDURE DIVISION 解析器 (行级状态机) +│ └── extract_branch_tree() — 源码 → BranchNode 树 +│ +├── output.py (213 行) ──── OUTPUT 层 +│ JSON 输出 + 二进制输入文件生成 +│ ├── output_json() — 按 FD 方向分 input/expected_output/working_storage +│ └── output_input_files() — 写入 flat file +│ +├── coverage.py (1375 行) ──── COVERAGE 层 +│ 决策点收集 + 覆盖标记 + HTML 报告 (中文) +│ ├── collect_decision_points() — 遍历分支树收集决策点 +│ ├── mark_coverage() — 从路径标记覆盖 +│ ├── _mark_if() / _mark_eval() / _mark_search() / _mark_perform() +│ ├── generate_html_report() — 中文 HTML 报告 (svg, 梯度) +│ ├── generate_coverage_index() — 中文覆盖率索引页 +│ └── run_coverage() — 主覆盖入口 +│ +├── gcov.py (194 行) ──── GCOV 层 +│ gcov 解析 + 运行时覆盖 +│ ├── parse_cbl_gcov() — 解析 .cbl.gcov → {line: count} +│ ├── run_gcov() — 执行 gcov 子进程 +│ └── mark_from_gcov() — 从 gcov 数据标记覆盖 +│ +├── to_sql.py (481 行) ──── SQL 层 +│ SQL WHERE 约束解析 + DB 行生成 (仅 DB 管道) +│ ├── sql_extract_constraints() — WHERE 子句 → 约束列表 +│ ├── collect_sql_meta() — 从 Assignments 收集 SQL 元数据 +│ └── build_db_input() — 按分支路径生成 DB 输入行 +│ +├── file_io.py (268 行) ──── 工具层 +│ COBOL 二进制 I/O (DISPLAY/COMP/COMP-3) +│ +├── flatfile.py (327 行) ──── 工具层 +│ FD 布局分析 + flat file 写入 +│ +├── runner.py (587 行) ──── 工具层 +│ cobc 编译 + 运行 + 非 DB 验证 +│ └── run_all() / run_and_compare() +│ +├── data_merger.py (130 行) ──── 工具层 +│ 白盒 + 功能 + 策略数据合并 +│ +├── __init__.py (~1300 行) ──── 门面层 +│ CLI 入口 main() + 公开 API +│ ├── main() — CLI 入口 (argparse, DB 自动路由) +│ ├── extract_structure() — 公有 API: 静态分析 +│ ├── generate_data() — 公有 API: 数据生成 +│ ├── expand_occurs() — OCCURS 递归展开 +│ ├── _chain_prev() — 跨记录 PREV 链同步 +│ ├── _inject_c01_coverage_records() — C01 决策点注入 +│ └── incremental_supplement() — 增量补充 (质量门) +│ +└── __main__.py (4 行) + python -m cobol_testgen → main() +``` + +### 数据流 + +``` +COBOL 源码 + │ + ▼ +read.py:preprocess() ← COPYBOOK 展开, EXEC SQL 剥离, 注释清除 + │ + ▼ +read.py:parse_data_division() ← Lark parser → list[FieldDef] + │ + ▼ +__init__.py:expand_occurs() ← OCCURS → 下标展开 + │ + ▼ +core.py:build_branch_tree() ← PROCEDURE DIVISION → 分支树 + │ + ▼ +design.py:enum_paths() ← 树遍历 → T/F 路径列表 (含约束) + │ + ▼ +design.py:generate_records() ← 路径 → 记录 + ├── make_base_record() ← 初值 + ├── propagate_assignments() ← MOVE/COMPUTE 传播 (20 轮收敛) + ├── apply_constraint() ← 约束应用 + ├── sync_redefined_fields() ← REDEFINES 同步 + └── _rebuild_r01line_csv() ← CSV 重建 + │ + ▼ +output.py:output_json() ← JSON (input/expected/ws) +output.py:output_input_files() ← 二进制 flat file + │ + ▼ +coverage.py:run_coverage() ← HTML 覆盖率报告 (中文) +``` + +### 依赖关系 + +``` +models.py ← 零依赖, 被所有模块依赖 + │ + ├── read.py ← lark (外部 parser) + ├── cond.py ← 纯 stdlib + ├── core.py ← cond.py + ├── procedure_parser.py ← 纯 stdlib + │ + ├── design.py ← models, cond, core + ├── design_mcdc.py ← models, cond, design + ├── pipeline_bridge.py ← models, procedure_parser, core + │ + ├── output.py ← file_io + ├── coverage.py ← models, cond (optionally gcov) + ├── gcov.py ← 纯 stdlib + ├── to_sql.py ← 纯 stdlib + ├── runner.py ← file_io (optionally gcov) + ├── flatfile.py ← read, file_io + ├── data_merger.py ← cobol_testgen (自身) + │ + └── __init__.py ← 所有上层模块 + japanese_data (外部) +``` + +无循环依赖。`runner.py` / `gcov.py` / `to_sql.py` 通过 try/except 门控导入(惰性依赖),允许核心管线在缺少可选依赖时正常工作。 + +### 关键注意事项 + +1. **OCCURS 展开只支持数值下标** — `WS-CELL(WS-IDX)` 这类变量下标在展开阶段不被支持,处理在 `apply_constraint` 中单独完成 +2. **PREV 链硬编码 R01/W01 前缀** — `_chain_prev()` 只处理以 `R01` 和 `W01` 开头的字段,是特定于 KIN/ZAN 程序集的假设 +3. **C01 程序集硬编码** — `SUB04CHK`、`WRK-CSV-*`、`C01CHKRRC` 等字段名在多个位置硬编码(`_inject_c01_coverage_records`、`_rebuild_r01line_csv`、`generate_records` Pass B.13) +4. **`_MAX_PATHS = 50000`** — 复杂程序可能达到上限导致路径多样性丢失;`_cap_paths` 用 T/F 多样性保护策略做公平裁剪,但仍可能丢失分支 +5. **算术约束启发式** — `_apply_arith_constraint()` 使用启发式(大/小值分配)而非精确求解器,复杂算术条件可能无法精确满足 +6. **`propagate_assignments` 20 轮收敛** — 硬编码迭代上限,理论上自引用 COMPUTE 链可能不收敛 +7. **88-level 假设** — 目标程序没有 88-level VALUE,但解析器仍支持(用于测试程序 `ALLCMDS.cbl`) +8. **False 值语义** — `SET X TO FALSE` 对 88-level 值的取反仅支持单字符值(`Y→N`, `N→Y`) +9. **pipeline_bridge 3 秒超时** — 新解析器通过守护线程超时回退,可能在高负载下误触发 +10. **gcov PERFORM 判断** — `mark_from_gcov()` 使用 `count > 1` 作为 PERFORM Enter 的启发式判断,不是精确判断 + +--- + +## 三、管道编排层 + +### 职责概述 + +两条管道的调度中枢。非 DB 管道(`orchestrator.py`)管理 9 阶段 LLM 驱动的验证流程;DB 管道(`orchestrator_db.py`)管理 6 步 ESQL 程序的编译→生成→运行→比对流程。 + +### 3.1 非 DB 管道 — `orchestrator.py` (203 行) + +#### 内部结构 + +``` +run_pipeline(cfg, cpath, cbl, java, map_path) +│ +├── 1. Agent1Parser(llm).parse() — COPYBOOK → FieldTree (LLM) +├── 2. cobol_testgen.extract_structure() — 静态分析 +│ cobol_testgen.generate_data() — 测试数据生成 +├── 3. hina.classify_program() — 程序类型分类 (LLM) +│ hina.supplement() — 策略补充 +├── 4. cobol_testgen.check_coverage() — 覆盖率检查 +│ quality gate loop: — 质量门循环 (最多 4 次) +│ gate_check() → incremental_supplement() +├── 5. Agent2Data(llm).design() — LLM 测试设计(被 cobol_testgen 数据覆盖) +├── 6. DataWriter — 写入 COBOL 二进制 + JSON +├── 7. CobolRunner.compile()/run() — COBOL 编译运行 +├── 8. JavaRunner.compile()/run() — Java/Spark 编译运行 +├── 9. CobolBinaryReader → align_records → compare_field — 比对 +├── 10. Agent3Diagnostic(llm).analyze() — LLM 诊断 +└── 11. ReportGenerator — JSON + HTML + machine.json +``` + +#### 依赖关系 + +`agents/` (Agent1Parser, Agent2Data, Agent3Diagnostic, LLMClient) + `hina/` (classify, gate, supplement) + `cobol_testgen/` + `runners/` (CobolRunner, JavaRunner, DataWriter) + `comparator/` + `report/` + `storage/` + `data/` + `config/` + +#### 关键注意事项 + +1. **Agent2Data 设计被覆盖** — line 110-114: LLM 生成的 TestSuite 被 cobol_testgen 的数据覆盖,实际只用了 LLM 的结构框架 +2. **质量门循环** — 最多 4 次迭代,每次调用 `incremental_supplement()` 补充未覆盖分支;达到 `quality_gate_branch_threshold` 或超限停止 +3. **DB 管道不可达** — `main.py` 不连接 orchestrator_db.py,DB 程序必须通过 `python -m cobol_testgen` 入口运行 + +### 3.2 DB 管道 — `orchestrator_db.py` (1334 行) + +#### 内部结构 + +``` +class GixsqlOrchestrator +│ +├── __init__() — 加载 Config + ProgramSchema + GixsqlCobolRunner +│ +├── run_all() — 主入口: Step 1→6 顺序执行 +│ │ +│ ├── Step 1: step1_setup_environment() +│ │ ├── _copy_sources_to_workdir() — 复制到 ASCII-only 工作目录 +│ │ └── GixsqlRunner.preprocess()+compile() — gixpp + cobc +│ │ +│ ├── [For each scenario in schema.runs]: +│ │ │ +│ │ ├── Step 2: step2_generate_inputs() +│ │ │ ├── cobol_testgen: extract_structure + generate_all_data +│ │ │ ├── _init_database() — CREATE TABLE +│ │ │ ├── _populate_database() — INSERT 覆盖分支数据 +│ │ │ ├── _inject_sql_error_rows() — 重复 PK (EXIT HANDLER 覆盖) +│ │ │ ├── _seed_matching_monthly_rows() — MONTHLY_ABSENCE 预填充 +│ │ │ ├── flatfile.write_all_files() — 物理文件输出 +│ │ │ └── output_json() — JSON 输出 +│ │ │ +│ │ └── Step 3: step3_run_cobol() +│ │ ├── _scan_assign_to() — 扫描 ASSIGN TO 方向 +│ │ ├── GixsqlRunner.run() — 执行 COBOL +│ │ └── .gcda/.gcno 收集 +│ │ +│ ├── Step 4: step4_extract_intermediate() — SQLite → W01 JSON +│ ├── Step 5: step5_run_java() — java -jar (可跳过) +│ ├── Step 6: step6_verify() — COBOL vs Java 比对 (可跳过) +│ │ +│ └── (可选) generate_coverage_report() +│ └── _merge_multi_run_gcov() + run_coverage() + HTML +│ +├── _populate_database() (114 行) — DB 行生成: build_db_input + INSERT +├── _create_tables() — CREATE TABLE IF NOT EXISTS +├── _inject_sql_error_rows() — 重复 PK 注入 +├── _seed_matching_monthly_rows() — 预填充匹配行 (DP#27/#28) +├── _make_synthetic_error_rows() — 兜底空表处理 +└── _merge_multi_run_gcov() — 多场景 gcov 合并 +``` + +#### 依赖关系 + +`cobol_testgen` (几乎所有子模块: read, core, design, design_mcdc, coverage, gcov, to_sql, flatfile, output, data_merger) + `runners.gixsql_runner` + `config` (Config + ProgramSchema) + `data.diff_result` + +#### 关键注意事项 + +1. **单文件 1334 行** — 是项目中最大的文件,承担了生成、编排、DB 生命周期管理、gcov 合并等多种职责。函数间共享 `self.*` 状态,内聚但高耦合 +2. **gixpp 中文路径 bug** — `_copy_sources_to_workdir()` 将源码复制到无中文路径的工作目录(`C:\Users\...` 含中文字符可能导致 gixpp 崩溃) +3. **多场景 gcov 合并** — gcov 数据跨 `normal/collision/abnormal` 运行合并,但只累加不隔离,可能导致分支标记冲突 +4. **`skip_jvm=True` 常态** — step5/step6 通常被跳过(Java 侧可能未实现) +5. **`_scan_assign_to()` 正则多行感知** — 跨越多行的 ASSIGN TO 语句通过多行正则会处理,但 `OPEN` 方向扫描仅检查单行 +6. **`INSERT OR IGNORE` 使用** — 重复 PK 注入时使用 `INSERT OR IGNORE` 静默忽略失败,可能导致预期行未写入但无报错 +7. **硬编码的 COBOL 字段名** — `WRK-R01-REC`, `R01INNFIL`, `WRK-EMP-ID`, `WRK-C01-*` 等在多个位置直接引用 +8. **EMP-ID 打补丁** — R01LINE 记录的 EMP-ID 在生成后会被 `orchestrator_db.py` 额外处理(line 340+),与 cobol_testgen 生成的初始值可能不一致 + +--- + +## 四、编译运行引擎 — runners/ + +### 职责概述 + +4 类 runner(COBOL 非 DB / COBOL DB / Java Native / Spark)+ 1 个 DataWriter。仅两个 Java runner 继承 `Runner` ABC,两个 COBOL runner 是独立类,没有多态派发。 + +### 内部结构 + +``` +runners/ +│ +├── runner.py (40 行) ← ABC + 共享数据类型 +│ ├── class Runner (ABC) +│ │ ├── compile(source_dir) → BuildResult +│ │ ├── run(artifact, input, output) → RunResult +│ │ └── get_coverage(artifact) → CoverageReport +│ ├── class BuildResult — success, artifact_path, log +│ ├── class RunResult — success, records, log +│ └── class CoverageReport — branch_rate, verdict, detail +│ +├── cobol_runner.py (115 行) ← 非 DB COBOL runner (独立类) +│ ├── compile(src, dialect, gcov) — 旧 API: 单文件编译 +│ ├── run(binary, input_path, output_path) — 旧 API: 管道 I/O +│ ├── compile_with_links(src, work_dir, ...) — 新 API: 多文件+子程序 +│ └── run_file_based(binary, run_dir, input_files) — 新 API: 文件 I/O +│ +├── gixsql_runner.py (423 行) ← DB COBOL runner (独立类) +│ ├── class GixsqlCobolRunner +│ │ ├── preprocess() — gixpp ESQL 预处理 + COPY 展开 + 源码规范化 +│ │ ├── compile() — cobc -x -K GIXSQL* -l gixsql +│ │ ├── run() — 部署 DLL + 执行 + SQLite +│ │ └── read_db_tables() — 运行后回读 SQLite 表 +│ ├── class GixsqlBuildResult +│ ├── class GixsqlRunResult +│ └── class GixsqlTableData +│ +├── native_java_runner.py (30 行) ← Java 本地 runner (继承 Runner) +│ └── compile() → mvn -B package +│ run() → java -jar +│ get_coverage() → JaCoCo (若有) +│ +├── spark_java_runner.py (36 行) ← Spark runner (继承 Runner) +│ └── compile() → mvn package +│ run() → spark-submit +│ get_coverage() → 硬编码 0.80 +│ +├── data_writer.py (33 行) ← 数据序列化 (独立类) +│ ├── write_cobol_binary() — struct.pack 二进制 +│ ├── write_spark_json() — part-00000.json +│ └── write_native_json() — JSON lines +│ +└── __init__.py (31 行) — 门面导出 +``` + +### 依赖关系 + +| Runner | 外部依赖 | +|--------|----------| +| `cobol_runner` | `cobc` 命令行 (GnuCOBOL) | +| `gixsql_runner` | `gixpp.exe` + `libgixsql.dll` + 额外 DLL 链 (`gixsql/lib/`) + `cobc` | +| `native_java_runner` | `mvn` + `java` | +| `spark_java_runner` | `mvn` + `spark-submit` | +| `data_writer` | `data.test_case.TestCase` / `SparkConfig` | + +### 关键注意事项 + +1. **Runner ABC 未覆盖 COBOL runner** — `CobolRunner` 和 `GixsqlCobolRunner` 的 API 与 `Runner` ABC 不兼容,调用方必须知道具体类型 +2. **gixsql_runner 源码规范化** — 预处理阶段做大量源码修改(CONNECT TO 转换、变量注入、FROM/INTO 重排、缺失数据变量补丁),这些修改是 per-program 级别的,耦合了特定程序的知识 +3. **DLL 部署策略** — `GixsqlCobolRunner.run()` 搜索 DLL 的路径顺序:TEMP → lib_path → gixpp_dir → x86/gcc → 系统 PATH;`libfmt.dll` 有独立的搜索逻辑 +4. **`SparkJavaRunner.get_coverage()` 硬编码** — 返回固定的 `branch_rate=0.80`,无论实际测试结果 +5. **`CobolRunner` 新旧两套 API** — 旧 API(stdin/stdout 管道)和新 API(文件 I/O + 子程序链接)并存,由不同调用方使用 +6. **退出码 0 或 1 都算成功** — `gixsql_runner.run()` 将退出码 1 也视为成功(DB 程序可能在非错误路径返回 1) + +--- + +## 五、gixsql ESQL 工具链 + +### 职责概述 + +vendored 在项目 `gixsql/` 目录下的 MinGW 编译版 GixSQL 工具链,提供 ESQL 预处理(`gixpp.exe`)+ 运行时库(`libgixsql.dll`),用于将含 `EXEC SQL` 的 COBOL 程序转换为标准 COBOL + CALL 语句。 + +### 内部结构 + +``` +gixsql/ +├── bin/ +│ ├── gixpp.exe — ESQL 预处理器 (主入口) +│ ├── libgcc_s_dw2-1.dll — GCC 运行时 +│ ├── libstdc++-6.dll — C++ 标准库 +│ └── libwinpthread-1.dll — POSIX 线程 +│ +└── lib/ + ├── libgixsql.dll — 主运行时 (.dll) + ├── libgixsql.a — 静态库 + ├── libgixsql.dll.a — MinGW import lib + │ + ├── libgixsql-sqlite.dll — SQLite 后端插件 (本项目使用) + ├── libgixsql-mysql.dll — MySQL 后端 + ├── libgixsql-pgsql.dll — PostgreSQL 后端 + ├── libgixsql-oracle.dll — Oracle 后端 + ├── libgixsql-odbc.dll — ODBC 后端 + │ + ├── libgcc_s_dw2-1.dll — GCC 运行时 (冗余) + ├── libstdc++-6.dll — C++ 标准库 (冗余) + ├── libwinpthread-1.dll — POSIX 线程 (冗余) + ├── libiconv-2.dll — 字符编码转换 + ├── libintl-8.dll — gettext 国际化 + ├── zlib1.dll — 压缩 + ├── libfmt.dll — C++ fmt 库 + ├── libxml2-2.dll — XML 解析 + ├── libssl-3.dll — OpenSSL TLS + ├── libcrypto-3.dll — OpenSSL 加密原语 + ├── liblzma-5.dll — LZMA 压缩 + ├── libpq.dll — PostgreSQL 客户端 + └── libmariadb.dll — MariaDB 客户端 +``` + +### 数据流 + +``` +含 EXEC SQL 的 COBOL 源码 (.cbl) + │ + ▼ +gixsql_runner.preprocess(): + 1. EXEC SQL INCLUDE SQLCA → COPY SQLCA + 2. CONNECT TO 'literal' → CONNECT TO :WS-GIX-CONN USER :WS-GIX-USR + 3. 注入 WS-GIX-* 变量定义 + 4. Python 端展开所有 COPY 语句 (替代 cobc -E) + 5. 清除注释,规范行首到第 8 列 + 6. 调整 SQL FROM/INTO 顺序 → _norm.cbl + 7. gixpp -i _norm.cbl -o _pp.cbl ← ESQL → 纯 COBOL (CALL gixsql*) + │ + ▼ +gixsql_runner.compile(): + cobc -x -L gixsql/lib/ -K GIXSQL* -l gixsql _pp.cbl → .exe + │ + ▼ +gixsql_runner.run(): + 环境变量 GIXSQL_DB_PATH → .exe 运行时通过 libgixsql-sqlite + 操作 SQLite 数据库 +``` + +### 依赖关系 + +- `gixpp.exe` — 无外部 PATH 依赖(但规避中文路径) +- `libgixsql.dll` + `libgixsql-sqlite.dll` — 运行时需要通过 COB_LIBRARY_PATH 或部署到 exe 目录加载 +- GnuCOBOL 3.2.0 (GC32-BDB-SP1) — 需要含 SQLite 支持的版本 + +### 关键注意事项 + +1. **中文路径 Bug** — `gixpp.exe` 在含中文的路径下崩溃,所有源码必须复制到纯 ASCII 路径 +2. **DLL 部署** — 运行时需要 11+ 个 DLL 文件,`gixsql_runner.run()` 会尝试从 5 个位置搜索并复制到 exe 目录;缺少任一 DLL 都会导致启动失败 +3. **`-K` 符号表** — 编译时链接 11 个 `GIXSQL*` 符号,这些符号名必须与 `libgixsql.dll` 导出的完全一致 +4. **CONNECT TO 转化为宿主变量** — 源码硬编码的 `CONNECT TO 'mydb'` 被替换为 `CONNECT TO :WS-GIX-CONN`,运行时通过环境变量注入实际路径,这是运行时配置与源码强耦合的典型例子 +5. **SQL 语法兼容性** — gixpp 支持标准 ESQL 语法,但不支持所有 DB2 COBOL 的 esoteric 特性(如某些嵌套 SQL 结构) +6. **多后端共存** — 虽然本项目仅使用 SQLite,但 gixsql 包包含了 MySQL/PostgreSQL/Oracle/ODBC 后端,DLL 部署时会复制所有发现的 DLL + +--- + +## 六、配置系统 — config/ + +### 职责概述 + +两级配置:全局级(`Config` dataclass,从 `aurak.toml` 加载)和 per-program 级(`ProgramSchema`,从 `config/programs/*.yaml` 加载)。 + +### 内部结构 + +``` +config/ +│ +├── __init__.py (73 行) ← 全局配置 +│ └── class Config (dataclass, 21 字段) +│ ├── 项目: project_name, copybook_paths, dialect +│ ├── LLM: llm_model, llm_timeout, llm_cache_dir, max_llm_cost +│ ├── 覆盖率: coverage_default, branch_pass +│ ├── 比对: rounding_mode, tolerance +│ ├── 运行: runner_mode, spark_master, spark_input_format +│ ├── 质量门: quality_gate_mode, quality_gate_*_threshold, max_quality_retries +│ ├── GCOV: gcov_enabled, gcov_work_dir, gcov_threshold +│ └── gixsql: gixsql_path, gixsql_lib_path, gixsql_db_path, gixsql_compile_flags +│ └── from_toml() — 从 aurak.toml 加载 +│ +├── mapping.py (39 行) ← 字段映射 +│ ├── class FieldMapping — cobol_field, java_field, type, precision, trim, format +│ └── class MappingConfig — program, dialect, field_mappings, redefines_strategy +│ └── from_yaml() / get_java_field() +│ +└── program_schema.py (93 行) ← per-program DB schema + ├── class ColumnDef — name, type, primary_key, nullable, default, cobol_field + ├── class TableDef — name, columns[], create_if_missing, sql_name + ├── class SysinDef — period, include_invalid_period, modes[] + ├── class ScenarioDef — id, sysin, inject_duplicate_pk + ├── class ProgramSchema — program_id, db_tables[], subprograms[], db_type, runs[] + │ └── from_yaml() + └── load_schema() — 从 config/programs/ 按 ID 加载 +``` + +### Per-Program YAML 配置 (6 个 DB 程序) + +| ID | DB 文件 | 表 | 子程序 | 场景数 | +|----|---------|-----|--------|--------| +| KIN02UPD | kin.db | LEAVE_RECORDS | SUB02MSG, SUB03END | 0 | +| KIN03EXP | kin.db | LEAVE_RECORDS, HOLIDAY_CALENDAR | SUB01DAT, SUB02MSG, SUB03END | 0 | +| KIN06CLD | kin.db | HOLIDAY_CALENDAR, EMP_MASTER | SUB02MSG, SUB03END | 0 | +| KIN08DBU | kin.db | DAILY_RECORDS, MONTHLY_ABSENCE | SUB02MSG, SUB03END | 3 | +| KIN09CSV | kin.db | DAILY_RECORDS, MONTHLY_ABSENCE | SUB02MSG, SUB03END | 0 | +| ZAN06UPD | OVERTIME.DB | ZANTBL01, ZANTBL02 | SUB01DAT, SUB02MSG, SUB03END, SUB04CHK, SUB05TIM | 3 | + +### 关键注意事项 + +1. **配置源不统一** — `Config` 从 `aurak.toml` 加载,`ProgramSchema` 从 YAML 加载,映射配置从另一个 YAML 加载。三个配置系统用不同格式 +2. **4 个程序无运行场景** — KIN02UPD/KIN03EXP/KIN06CLD/KIN09CSV 的 `runs:` 为空,`orchestrator_db.run_all()` 跳过多场景循环但 stpe2/step3 仍会运行 +3. **gixsql 配置翻倍** — `gixsql_path` 和 `gixsql_lib_path` 有默认值(相对于项目根),但 `gixsql_compile_flags` 在 `Config` 和 `gixsql_runner` 中都有定义,可能不一致 + +--- + +## 七、测试套件 — test-data/ + +### 职责概述 + +61 个测试脚本,覆盖从分支覆盖单元测试到端到端 DB 管道的全栈验证。 + +### 关键测试脚本 + +| 脚本 | 职责 | 验证内容 | +|------|------|----------| +| `s15_coverage_verification.py` | 8 个合成 COBOL 片段的管线覆盖测试 | IF/AND/NESTED/EVAL/PERFORM/ELSE-IF/VARYING/NOT | +| `s25_per_program_report.py` | 全量 43 程序覆盖率报告 | 3178/3178 = 100% | +| `s26_regression_check.py` | 回归快速检查 | ALL 43/43 AT 100% | +| `s30_db_e2e.py` | ZAN06UPD 6 步 DB 管线的端到端验证 | 编译→生成→运行→提取→Java→比对 | +| `s19_final_bridge_test.py` | pipeline_bridge 桥接器验证 | 新旧解析器一致性 | +| `s21_cond_fix_verify.py` | 条件解析修复验证 | AND/OR/NOT 处理 | +| `s17_gcov_comparison.py` | gcov 覆盖率比对 | 运行时 vs 静态覆盖率一致性 | +| `s22_tna_e2e.py` | KIN/ZAN 程序端到端 | TNA 系统全程序验证 | + +### 注意事项 + +1. **测试结果预记录** — `test-report.json` 包含预存的结果,可能在某些脚本中用于跳过实际执行(回归风险) +2. **大量一次性脚本** — rXX 系列(`r4_cond_coverage.py` 到 `r16_vuln_review.py`)很可能是开发阶段的探索性脚本,维护状态未知 +3. **benchmark-programs 目录** — 项目外也有独立的 README 文档集,用于 COBOL 语句/模式的基准测试,与核心管线关系较松散 + +--- + +## 八、入口点 + +### 职责概述 + +三个入口点,对应两条管道 + 程序化 API。 + +### 8.1 非 DB CLI — `main.py` (50 行) + +``` +python main.py --copybook COPY.cpy --cobol-src PROG.cbl --java-src PROG.java --mapping map.yaml + │ --runner native|spark + │ --coverage boundary|branch + │ --quality-gate-mode warn|off + │ --gcov + │ --dry-run + │ + └─ orchestrator.run_pipeline(config, copybook, cbl, java, mapping) +``` + +### 8.2 DB CLI — `cobol_testgen/__init__.py:main()` (~1300 行中的 ~500 行 CLI 代码) + +``` +python -m cobol_testgen [--gcov] [--temp-dir DIR] prog1.cbl prog2.cbl ... [outdir] + │ + ├── 自动检测 EXEC SQL → GixsqlOrchestrator (DB 管道) + └── 无 EXEC SQL → 行内管线 (非 DB,类似但独立于 orchestrator.py) +``` + +### 8.3 程序化 API + +```python +from cobol_testgen import extract_structure, generate_data + +st = extract_structure(src, copybook_dirs=[...]) +recs = generate_data(src, st, copybook_dirs=[...]) +# st["coverage"] 包含覆盖率统计 +``` + +### 关键注意事项 + +1. **两个非 DB 管线入口** — `main.py` 走 LLM 代理管线(Agent1→Agent2→Agent3),`python -m cobol_testgen` 的行内管线不走 LLM。同一个"非 DB"语义有两种实现 +2. **DB 自动路由是字符串检查** — 检测 `EXEC SQL` 的方式是简单的字符串包含匹配,可能被注释中的 `EXEC SQL` 误触发 +3. **CLI 代码与门面层混合** — `__init__.py` 同时承担 CLI 解析、公开 API 导出、核心逻辑(OCCURS 展开、PREV 链、C01 注入),职责过重 + +--- + +## 九、跨层关注点 + +### 9.1 硬编码的领域知识 + +系统中散布着对 KIN/ZAN 程序集的假设: +- `R01*` 前缀硬编码(`_chain_prev`, `_rebuild_r01line_csv`) +- `W01*` 前缀硬编码(`_chain_prev`, `_rebuild_r01line_csv`) +- `SUB04CHK`, `WRK-C01*`, `C01CHKRRC`, `WRK-CSV-*` — C01 相关硬编码 +- `EMP-ID`, `APPL-ID`, `YEAR-MONTH` — KIN 领域字段名硬编码 +- `gixsql_runner.py` 中的 KIN06CLD 缺失字段补丁 + +### 9.2 两条管道的隔离度 + +- 共享 `cobol_testgen/` 核心库,但使用方式不同(DB 管道直接导入子模块,非 DB 管道通过 `__init__.py` 门面) +- 没有共享的编排基类 +- 两个 Java runner 通过 `Runner` ABC 有多态,COBOL runner 没有 +- 配置系统有重叠(`Config.gixsql_*` 只在 DB 管道使用,但存在全局 Config 中) + +### 9.3 已知不合理标注 + +| 位置 | 问题 | 类型 | +|------|------|------| +| `orchestrator_db.py` 1334 行 | 单文件承担生成/编排/DB/合并等多种职责 | 职责过重 | +| `__init__.py` 1300 行 | CLI 逻辑与核心 API 混合 | 职责过重 | +| `core.py` 2169 行 | 包含解析、数据流追踪、SQL 提取等多种不相关功能 | 职责过重 | +| `spark_java_runner.py:35` | `get_coverage` 返回硬编码 0.80 | 假覆盖率 | +| `gixsql_runner.py:237-247` | per-program 缺失数据变量补丁(KIN06CLD 特有) | 领域泄漏 | +| `Runner` ABC 未被 COBOL runner 实现 | 无多态派发,调用方必须 `isinstance` | 接口不完整 | +| `main.py` 和 `__init__.py:main()` 两个非 DB 入口 | 同样的"非 DB 测试"有两种不同实现 | 功能重复 |