18 KiB
DB 管道编排器 - 详细设计文档
模块路径:
orchestrator_db.py版本: V3 (2026技术大赛) 行数: 1973 核心类:GixsqlOrchestrator
1. 模块概述
1.1 职责
orchestrator_db.py 是 DB 型 COBOL 程序的 6 步端到端测试管道编排器,负责:
- 环境整备 - gixpp 预处理 + cobc 编译(Step 1)
- 输入数据生成 - 测试数据生成 + 平面文件输出 + DB 初始化(Step 2)
- COBOL 执行 - 调用编译后的 COBOL 程序(Step 3)
- 中间数据提取 - SQLite DB → Java 中介 JSON(Step 4)
- Java 执行 - 调用 Java 转换程序(Step 5)
- 结果验证 - Java 输出与 COBOL 期望值比较(Step 6)
1.2 边界
| 在范围内 | 不在范围内 |
|---|---|
| DB 型 COBOL 程序 6 步管道编排 | Flat-file 型 COBOL 程序处理 |
| SQLite 数据库初始化与种子注入 | 分支树构建(由 cobol_testgen/ 处理) |
| 多场景(多轮)执行调度 | 覆盖率报告生成(可选附件,非管道核心) |
| 测试数据合成与 PK 冲突注入 | Java 程序内部逻辑 |
1.3 依赖关系
orchestrator_db.py
+-- config.py <- 项目配置
+-- config/program_schema.py <- YAML 程序定义
+-- cobol_testgen/ <- 核心引擎
+-- data/diff_result.py <- 验证结果数据模型
+-- runners/gixsql_runner.py <- GnuCOBOL 编译-执行运行器
+-- agents/llm.py <- LLM 客户端(可选)
2. 核心数据结构
2.1 GixsqlOrchestrator
class GixsqlOrchestrator:
def __init__(self, config: Config, program_id: str,
cobol_src_dir: str | Path,
copybook_dirs: list[str | Path] | None = None,
work_dir: str | Path | None = None,
skip_jvm: bool = True):
关键属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| config | Config | 项目配置 |
| program_id | str | COBOL 程序标识符 |
| cobol_src_dir | Path | COBOL 源码目录 |
| copybook_dirs | list[Path] | COPYBOOK 搜索路径 |
| work_dir | Path | 构建产物目录(ASCII 路径) |
| runtime_dir | Path | 运行时数据目录 |
| schema | ProgramSchema | YAML 程序定义 |
| runner | GixsqlCobolRunner | GnuCOBOL 运行器 |
| db_path | Path | 默认 SQLite DB 路径 |
| skip_jvm | bool | 是否跳过 Step 5/6 |
管道状态:
| 状态属性 | 类型 | 说明 |
|---|---|---|
| src_path | Path | 预处理后源码路径 |
| pp_path | Path | gixpp 预处理输出路径 |
| exe_path | Path | 编译后可执行文件路径 |
| java_input_path | Path | Java 中介数据路径 |
| java_output_path | Path | Java 输出路径 |
| _current_db_path | Path | 当前场景的 DB 路径 |
| _multi_run_gcov_data | dict | 多轮合并后的 gcov 数据 |
| generated_records | list[dict] | 生成的测试数据记录 |
| generated_structure | dict | 解析后的程序结构 |
2.2 DbPipelineResult
@dataclass
class DbPipelineResult:
program_id: str
step: int | float
success: bool
message: str = ""
data: dict = field(default_factory=dict)
3. 6 步流程设计
3.1 Step 1: 环境整备 (step1_setup_environment)
职责: gixpp 预处理 → cobc 编译
执行流程:
1. _copy_sources_to_workdir()
|-- 复制主源码 {program_id}.cbl → work_dir/src/
|-- 复制 COPYBOOK (*.cpy) → work_dir/src/
|-- 复制子程序 (SUB*.cbl) → work_dir/src/
(搜索: cobol_src_dir, sub/, production/sub/, cobol-tna-system/sub/)
2. runner.preprocess(src, preprocessed/, copybook_dirs)
|-- gixpp 预处理 + CONNECT TO 路径修补
| gixpp 错误转换: 'data/kin.db' → 'sqlite://localhost/kin'
| 修补为: 'sqlite:///{db_path}'
3. runner.compile(pp, exe, copybook_dirs, extra_srcs)
|-- cobc 编译 → work_dir/bin/{program_id}.exe
|-- 编译日志写入 runtime_dir/logs/compile/
4. 返回 DbPipelineResult(step=1, success, data={exe_path, log})
关键逻辑:
- 源码必须复制到 ASCII-only 路径(gixpp 不支持中文路径)
- CONNECT TO 字符串修补: gixpp 输出的
sqlite://localhost/kin需替换为绝对路径 - 子程序从多个候选目录搜索,未找到仅 warning 不阻断
输入: cobol_src_dir, copybook_dirs, schema.subprograms 输出: src_path, pp_path, exe_path
3.2 Step 2: 输入数据生成 (step2_generate_inputs)
职责: COBOL 解析 → 测试数据生成 → DB 初始化 → 平面文件输出
执行流程:
1. COBOL 解析
|-- extract_structure(src_text) → 分支树 + 赋值表
|-- generate_all_data() → 测试数据记录(白盒+机能+策略)
|-- 后处理: R02APPL-ID 链接 R01APPL-ID
2. DB 初始化
|-- 确定 DB 路径(场景分离: {program_id}_{scenario_id}.db)
|-- 清理旧 DB → _init_database(db_path)
| |-- _create_tables(): 按 YAML schema 创建表 + 主键
|-- _populate_database(): 注入种子行
| |-- 解析 COBOL → 分支树 → 路径枚举
| |-- build_db_input(): 生成 DB 输入行
| |-- 覆盖率驱动数据补充(日期、假期等)
| |-- 区间协调(INSURANCE-RATES / EMP-MASTER)
| |-- INSERT OR IGNORE 写入 DB
|-- _inject_extra_seed_rows(): 大结果集注入
|-- _inject_sql_error_rows(): PK 冲突行注入
3. 记录修补
|-- records[0].R01EMP-ID = SPACE(触发空社员路径)
|-- 全零 EMP-ID → SPACE 清洗
|-- R01LINE 与 EMP-ID 一致性修补
|-- 注入重复 EMP-ID(AGG UPDATE 路径)
|-- _deduplicate_r01_pk(): PK 去重
|-- _inject_aggregation_boundaries(): 聚合边界数据
4. 场景驱动修改
|-- collision 场景: INSERT 重复、OVT-MONTHLY 匹配、COMMIT 阈值
|-- abnormal 场景: orphan cancel ABEND
5. 平面文件输出
|-- write_all_files(): 全 FD 平面文件
|-- write_sysin_file(): SYSIN 配置
|-- _seed_matching_monthly_rows(): MONTHLY_ABSENCE 匹配行预填
6. JSON 输出(可选)
|-- 解析 DATA DIVISION → 字段字典
|-- 分支树 + MC/DC 路径枚举
|-- output_json(): 写入 json/{program_id}.json
7. 返回 DbPipelineResult(step=2, data={records, flat_files, db_path})
关键逻辑:
- 多场景时 DB 路径分离:
{program_id}_{scenario_id}.db - PK 冲突行必须与运行时 INSERT 实际值一致(基于输入记录而非合成值)
- 聚合边界注入: overflow(同月累加溢出)+ table-full(>=110 个不同月)
- 日期值统一为 YYYYMMDD 格式
输入: src_path, pp_path, schema, scenario 输出: generated_records, generated_structure, db_path, 平面文件
3.3 Step 3: COBOL 执行 (step3_run_cobol)
职责: 调用编译后的 COBOL 程序并收集 gcov 覆盖率数据
执行流程:
1. 环境准备
|-- 创建 runtime/run_{id}/main/{input,output}/ 目录
|-- 复制生成的平面文件 → input/
|-- 复制 JSON → json/
2. 文件方向映射 (_scan_assign_to)
|-- 正则扫描 SELECT/ASSIGN-TO → {文件名: 方向}
|-- OPEN 语句解析 → INPUT/OUTPUT 方向确定
3. DB 路径准备
|-- 场景 DB → 复制到默认 DB 路径
|-- CWD/data/kin.db(CONNECT TO 路径)
|-- CWD/kin(gixsql regex 路径)
4. 执行
|-- 清理前次 .gcda 文件
|-- runner.run(exe, cwd, db_path, env_overrides, command_args)
|-- 日志写入 runtime_dir/logs/
5. gcov 数据收集
|-- .gcda 从 CWD + exe_dir 复制到 gcov/run_{id}/
|-- .gcno 同步(共享 .gcno,COPY 不 MOVE)
6. 返回 DbPipelineResult(step=3, data={returncode, log, ...})
关键逻辑:
- GIXSQL_DB_PATH 环境变量不生效,需通过 CWD/data/kin.db 传递
- GnuCOBOL 的 .gcda 写入编译时 CWD,多场景需 COPY 到各自 gcov 目录
- subprogram 的 .gcno 必须同步到每个 run 目录
输入: exe_path, schema, scenario 输出: 运行日志、gcov 数据、返回码
3.4 Step 4: 中间数据提取 (step4_extract_intermediate)
职责: 从 SQLite DB 导出 Java 程序所需的 JSON 中介数据
执行流程:
1. 打开 DB(_current_db_path 或 db_path)
2. 遍历 schema.db_tables,对每张表执行 SELECT * FROM [table]
3. 构建 meta = {program_id, tables: {table_name: [rows]}}
4. 写入 work_dir/intermediate/{program_id}_W01.json
5. 返回 DbPipelineResult(step=4, data={tables, w01_path})
关键逻辑:
- 使用 sql_name 或 name 查询表名
- 即使表不存在也不报错(空列表),允许部分执行
- 输出 JSON 包含所有表的全量行数据
输入: _current_db_path, schema.db_tables 输出: java_input_path(W01 JSON)
3.5 Step 5: Java 执行 (step5_run_java)
职责: 调用 Java 转换程序处理 COBOL 输出数据
执行流程:
1. 创建 java_output 目录
2. 构建命令: java -jar {java_jar} -i {java_input_path} -o {java_out}
3. subprocess.run(cmd, capture_output=True, timeout=60)
4. 返回 DbPipelineResult(step=5, data={returncode, log})
关键逻辑:
- 超时限制 60 秒
- 若未指定 java_jar,仅执行 java -version 检测环境
- 依赖 Step 4 的输出作为输入
输入: java_input_path, java_jar 输出: java_output_path, 执行日志
3.6 Step 6: 结果验证 (step6_verify)
职责: 比较 Java 输出与 COBOL 期望值
执行流程:
1. 构建 VerificationRun 结果对象
2. 读取 DB 各表行数(调试信息)
3. 扫描 java_output_path 下的 .txt/.json 文件
4. 设置 exit_code 和 status(PASS/MISMATCH)
5. 返回 VerificationRun
关键逻辑:
- fields_mismatched == 0 时判定为 PASS
- 输出 Java 输出文件列表作为调试信息
- 返回 VerificationRun 而非 DbPipelineResult
输入: java_output_path, _current_db_path, schema.db_tables 输出: VerificationRun(status, exit_code, debug)
4. 接口定义
4.1 主入口: run_all()
def run_all(self, skip_steps: set[int] | None = None,
generate_coverage: bool = True) -> VerificationRun:
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| skip_steps | set[int] | 要跳过的步骤编号集合(如 {5, 6}) |
| generate_coverage | bool | 是否生成覆盖率报告(默认 True) |
返回: VerificationRun(最终验证结果)
行为:
- skip_jvm=True 时自动将 {5, 6} 加入 skip_steps
- 多场景执行: schema.runs 非空时循环执行 Step 2-3
- 每个场景失败即返回 BLOCKED(不继续后续步骤)
- Step 1 只执行一次(编译共享)
- 多场景执行后自动合并 gcov 数据
4.2 单步接口
| 方法 | 签名 | 返回 |
|---|---|---|
| step1_setup_environment | () -> DbPipelineResult | 编译结果 |
| step2_generate_inputs | (scenario: ScenarioDef?) -> DbPipelineResult | 数据生成结果 |
| step3_run_cobol | (scenario: ScenarioDef?) -> DbPipelineResult | 执行结果 |
| step4_extract_intermediate | () -> DbPipelineResult | 提取结果 |
| step5_run_java | (java_cmd, java_jar) -> DbPipelineResult | Java 执行结果 |
| step6_verify | () -> VerificationRun | 验证结果 |
| generate_coverage_report | (output_dir?) -> DbPipelineResult | 覆盖率报告 |
4.3 内部辅助接口
| 方法 | 职责 |
|---|---|
| _copy_sources_to_workdir | 源码 + COPYBOOK + 子程序复制到 ASCII 工作目录 |
| _scan_assign_to | 扫描 SELECT/ASSIGN-TO + OPEN 确定文件方向 |
| _init_database / _create_tables | 按 YAML schema 创建 SQLite 表结构 |
| _populate_database | 从测试记录生成 DB 种子行 |
| _inject_sql_error_rows | 注入 PK 冲突行触发 SQL 错误路径 |
| _inject_extra_seed_rows | 为 SELECT 型程序注入大结果集 |
| _inject_aggregation_boundaries | 注入聚合边界数据(溢出 + 表满) |
| _deduplicate_r01_pk | 确保 R01 记录 PK 唯一性 |
| _seed_matching_monthly_rows | 预填 MONTHLY_ABSENCE 匹配行 |
| _merge_multi_run_gcov | 多轮场景 gcov 数据合并 |
| _merge_schema_columns | YAML schema 列型合并到 declared_columns |
| _insert_pk_map | 构建 SQL 表 → PK 列名映射 |
| _coordinate_db_rule_matching | DB 属性区间对齐(AGE/DEPENDENTS/REGION) |
| _coordinate_seed_numeric_types | DB 种子值数字化(PIC 9 对齐) |
| _make_synthetic_error_rows | 构建合成 PK 冲突行 |
5. 数据流
5.1 管道级数据流
cobol_src_dir/{program_id}.cbl
|
v
[Step 1: 环境整备]
|-- src_path (预处理源码)
|-- pp_path (gixpp 输出)
|-- exe_path (编译产物)
|
v
[Step 2: 输入数据生成]
|-- generated_records (测试数据)
|-- generated_structure (分支树 + 赋值表)
|-- db_path (SQLite DB with seeds)
|-- 平面文件 (input/)
|-- JSON (json/{program_id}.json)
|
v
[Step 3: COBOL 执行]
|-- 运行日志
|-- 输出文件 (output/)
|-- gcov 数据 (gcov/)
|
v
[Step 4: 中间数据提取]
|-- java_input_path (W01 JSON)
|
v
[Step 5: Java 执行]
|-- java_output_path
|
v
[Step 6: 结果验证]
|-- VerificationRun (PASS/MISMATCH)
5.2 每步输入输出明细
| 步骤 | 输入 | 输出 | 依赖 |
|---|---|---|---|
| Step 1 | cobol_src_dir, copybook_dirs, schema | src_path, pp_path, exe_path | 无 |
| Step 2 | src_path, pp_path, schema, scenario | records, structure, db_path, flat files | Step 1 |
| Step 3 | exe_path, records, db_path, scenario | logs, output files, gcov data | Step 1, 2 |
| Step 4 | db_path, schema.db_tables | java_input_path | Step 2, 3 |
| Step 5 | java_input_path, java_jar | java_output_path | Step 4 |
| Step 6 | java_output_path, db_path | VerificationRun | Step 4, 5 |
5.3 多场景数据流
schema.runs = [scenario_A, scenario_B, ...]
|
v
[Step 1] 编译一次(共享 exe_path)
|
v
[Step 2-A] scenario_A → db_A, records_A, flat_A
[Step 3-A] 运行 A → gcov_A
|
v
[Step 2-B] scenario_B → db_B, records_B, flat_B
[Step 3-B] 运行 B → gcov_B
|
v
[_merge_multi_run_gcov] gcov_A + gcov_B → merged_gcov
|
v
[Step 4] 提取最后一个场景的 DB
[Step 5-6] Java 执行 + 验证
6. 错误处理
6.1 步骤级容错
每个 Step 方法内部用 try-except 包裹,返回 DbPipelineResult(success=False) 而非抛出异常:
def step1_setup_environment(self) -> DbPipelineResult:
try:
# ... 编译逻辑 ...
return DbPipelineResult(self.program_id, 1, result.success, ...)
except Exception as e:
return DbPipelineResult(self.program_id, 1, False, str(e))
6.2 管道级中断
run_all() 中每个 Step 后检查 success,失败则立即返回 BLOCKED:
r1 = self.step1_setup_environment()
if not r1.success:
return VerificationRun(status="BLOCKED", step_reached=1)
r2 = self.step2_generate_inputs(scenario)
if not r2.success:
return VerificationRun(status="BLOCKED", step_reached=2)
6.3 异常分类
| 异常场景 | 处理方式 | 影响 |
|---|---|---|
| gixpp 预处理失败 | Step 1 返回 success=False | 管道终止 |
| cobc 编译失败 | Step 1 返回 success=False | 管道终止 |
| COBOL 运行崩溃 | Step 3 返回 success=False | 管道终止 |
| DB 表不存在 | OperationalError 捕获,空列表 | 不阻断 |
| Java 超时 | subprocess.TimeoutExpired | Step 5 返回 False |
| gcov 文件缺失 | PermissionError 捕获,跳过 | 不阻断 |
| COPYBOOK 未找到 | logger.warning | 不阻断 |
| 子程序未找到 | logger.warning | 不阻断 |
| JSON 输出失败 | logger.warning | 不阻断,继续执行 |
6.4 数据一致性保障
- PK 去重:
_deduplicate_r01_pk确保所有 R01 记录的 (EMP_ID, DATE) 唯一 - EMP-ID 清洗: 全零 '00000000' → SPACE,避免 PK 冲突导致 ABEND
- 日期格式统一: 所有日期值统一为 YYYYMMDD 8 位格式
- DB 列型匹配: INSERT 前按 PRAGMA table_info 转换值类型(INTEGER/DECIMAL)
7. 性能设计
7.1 编译复用
Step 1 只执行一次,所有场景共享编译产物(exe_path)。
7.2 多场景顺序执行
Step 2-3 对每个场景顺序执行,避免 DB 并发写入冲突。每个场景有独立的:
- DB 文件:
{program_id}_{scenario_id}.db - 工作目录:
work_dir/run_{scenario_id}/ - 运行目录:
runtime_dir/run_{scenario_id}/
7.3 gcov 数据合并
多场景执行后调用 _merge_multi_run_gcov(),对每行取 max(count) 合并:
merged[line] = max(merged.get(line, 0), cnt)
子程序 gcov 单独存储(_sub_gcov_data),避免行号冲突。
7.4 文件复制策略
- 构建产物放在 TEMP 目录(ASCII 路径),避免 gixpp 中文路径问题
- 运行时数据放在项目 runtime/ 目录
- DB 文件在多场景间通过 shutil.copy2 复制,而非共享
- .gcda/.gcno 使用 COPY 而非 MOVE(GnuCOBOL 累积写入特性)
7.5 已执行步骤跳过
run_all() 支持 skip_steps 参数,允许跳过已执行的步骤:
orch.run_all(skip_steps={1, 2, 3}) # 仅执行 Step 4-6
7.6 覆盖率报告可选
覆盖率报告生成由 generate_coverage 控制,默认开启但非管道核心路径:
if '--coverage' in cv_flags and generate_coverage:
self.generate_coverage_report()
7.7 LLM 可选
Step 2 的 LLM 客户端仅在 config.llm_model 配置时初始化,未配置时回退到规则引擎:
llm = None
if hasattr(self.config, 'llm_model') and self.config.llm_model:
llm = LLMClient(model=self.config.llm_model, timeout=self.config.llm_timeout)
recs = generate_all_data(..., llm_client=llm, ...)
8. 目录结构
runtime/{program_id}/
+-- main/
| +-- input/ <- 平面输入文件
| +-- output/ <- COBOL 输出文件
| +-- json/ <- JSON 输出
+-- logs/
| +-- compile/ <- 编译日志
| +-- {program_id}.log <- 运行日志
+-- gcov/
| +-- run_{scenario}/ <- 各场景 gcov 数据
+-- run_{scenario}/ <- 多场景隔离目录
+-- main/input/
+-- main/output/
work_dir/{program_id}/
+-- src/ <- ASCII 源码副本
+-- preprocessed/ <- gixpp 输出
+-- bin/ <- 编译产物 (.exe, .gcno)
+-- main/
| +-- input/ <- 生成的平面文件
| +-- json/ <- JSON 输出
+-- intermediate/ <- W01 JSON(Java 中介数据)
+-- java_output/ <- Java 输出
+-- run_{scenario}/ <- 多场景隔离目录