Files
cobol-java-v3/docs/detailed-design/03-runners.md
T

14 KiB
Raw Blame History

03 - runners 编译运行引擎

1. 模块概述

runners 模块负责 COBOL 程序与 Java 程序的编译、运行、覆盖率采集全流程。模块采用策略模式,通过抽象基类 Runner 统一不同语言运行时的接口,对外暴露一致的 compile -> run -> get_coverage 三阶段管线。

核心职责:

职责 说明
COBOL 编译 调用 cobcCOBOL 编译器)将 .cbl 源码编译为可执行文件
Java 编译 调用 mvn package 将 Maven 项目打包为 .jar
运行执行 通过 subprocess 启动编译产物,捕获 stdout/stderr 和返回码
测试数据写入 TestCase 列表序列化为 COBOL 二进制 / JSON 格式
覆盖率报告 采集分支覆盖数据(gcov / JaCoCo)并返回量化报告

2. 文件清单

runners/
  __init__.py              # 包导出:公开 API 声明
  runner.py                # 抽象基类 Runner + 数据类 BuildResult / RunResult / CoverageReport
  cobol_runner.py          # COBOL 编译·执行器(cobc 管线)
  gixsql_runner.py         # DB COBOL 程序编译·执行器(gixpp + cobc + gixsql 链接)
  native_java_runner.py    # Java 本地运行器(mvn + java -jar
  spark_java_runner.py     # Spark 运行器(spark-submit
  data_writer.py           # 测试数据序列化(COBOL 二进制 / Spark JSON / Native JSON

3. 接口定义

3.1 数据类(runner.py

@dataclass
class BuildResult:
    success: bool            # 编译是否成功
    artifact_path: str = ""  # 编译产物路径(.exe / .jar
    log: str = ""            # 编译日志(stdout + stderr

@dataclass
class RunResult:
    success: bool                # 运行是否成功(returncode == 0
    records: list[dict]          # 运行输出记录(JSON 格式)
    log: str = ""                # 运行日志
    coverage_exec: str = ""      # 覆盖率执行文件路径

@dataclass
class CoverageReport:
    branch_rate: float = 0.0     # 分支覆盖率(0.0 ~ 1.0
    covered_branches: int = 0    # 已覆盖分支数
    total_branches: int = 0      # 总分支数
    verdict: str = "PASS"        # 判定结果(PASS / FAIL

3.2 抽象基类(runner.py

class Runner(ABC):
    @abstractmethod
    def compile(self, source_dir: str) -> BuildResult: ...

    @abstractmethod
    def run(self, artifact: str, input_path: str, output_path: str) -> RunResult: ...

    @abstractmethod
    def get_coverage(self, artifact: str, run_id: str) -> CoverageReport: ...

3.3 CobolRunnercobol_runner.py

方法 签名 说明
compile (src, dialect="ibm", gcov=False) -> BuildResult 旧式编译,-std=ibm-strict,供 orchestrator.py 使用
run (binary, input_path, output_path) -> RunResult 旧式执行,stdin 管道 stdout
compile_with_links (src, work_dir, copybook_dirs, sub_objects, gcov) -> BuildResult 新式编译:主程序 + 链接 SUB.o,支持 COPYBOOK 搜索路径和 gcov
run_file_based (binary, run_dir, input_files, timeout) -> RunResult 新式执行:基于文件的 I/O,将输入文件复制到运行目录后启动程序

3.4 GixsqlCobolRunnergixsql_runner.py

方法 签名 说明
preprocess (src_path, out_dir, copybook_dirs) -> str gixpp 预处理:COPY 展开、SQL 归一化、格式修正
compile (pp_path, exe_path, copybook_dirs, extra_srcs) -> GixsqlBuildResult cobc 编译,链接 gixsql 库
run (exe_path, work_dir, db_path, ...) -> GixsqlRunResult 执行 DB 程序,设置 SQLite 数据库路径
read_db_tables (db_path, table_names) -> list[GixsqlTableData] 读取 SQLite 数据库表内容

3.5 NativeJavaRunnernative_java_runner.py

方法 签名 说明
compile (source_dir) -> BuildResult Maven 打包,输出 target/program.jar
run (artifact, input_path, output_path) -> RunResult java -jar 执行,解析 stdout JSON 行
get_coverage (artifact, run_id) -> CoverageReport 检查 jacoco.exec 是否存在,返回覆盖率

3.6 SparkJavaRunnerspark_java_runner.py

方法 签名 说明
compile (source_dir) -> BuildResult Maven 打包,输出 target/program.jar
run (artifact, input_path, output_path) -> RunResult spark-submit 执行,读取 part-* 输出文件
get_coverage (artifact, run_id) -> CoverageReport 返回固定 0.80 覆盖率(Spark 无原生覆盖率集成)

3.7 DataWriterdata_writer.py

方法 签名 说明
write_cobol_binary (cases, out) 将 TestCase 列表写为 COBOL 二进制格式(大端序 int64 / float64 / ASCII
write_spark_json (cases, cfg, d) 写 Spark 输入 JSONpart-00000.json),key 字段加序号后缀
write_native_json (cases, out) 写 Native JSON(每行一个 JSON 对象)

4. 编译流程

4.1 COBOL 编译(cobol_runner.py

旧式编译流程

.cbl 源文件
    |
    v
cobc -x -std=ibm-strict [-o 输出路径] [-g] [--coverage] 源文件
    |
    v
BuildResult(success, artifact_path, log)
  • 默认超时 30 秒
  • gcov=True 时追加 --coverage 参数生成 .gcno 文件

新式编译流程(带链接)

.cbl 源文件 + SUB*.o 对象文件
    |
    v
cobc -x -g [--coverage] [-I COPYBOOK路径...] -o 输出.exe 源文件 SUB1.o SUB2.o ...
    |
    v
BuildResult(success, exe_path, log)
  • 默认超时 120 秒
  • 工作目录切换至 work_dir(确保 .gcno 产出位置正确)
  • 不使用 -std=ibm-strict,依赖默认方言

4.2 DB COBOL 编译(gixsql_runner.py

DB 程序编译采用 gixpp + cobc 两阶段管线:

原始 .cbl 源文件
    |
    v -- _normalize_source()
    |   1. 剥离注释行(避免日文注释中的 EXEC SQL 被误匹配)
    |   2. EXEC SQL INCLUDE SQLCA -> COPY SQLCA
    |   3. EXEC SQL CONNECT TO 'literal' -> CONNECT TO :WS-GIX-CONN USER :WS-GIX-USR
    |   4. COPY REPLACING 内联展开(Python 侧)
    |   5. ALL COPY 展开(替代 cobc -E
    |   6. 关键字间多空格压缩
    |   7. DIVISION/SECTION 头部列位置修正(Area A,列 8 起)
    |   8. SELECT ... FROM ... INTO -> SELECT ... INTO ... FROMgixpp 要求 INTO 在前)
    |   9. CURRENT TIMESTAMP -> CURRENT_TIMESTAMPSQLite 后端兼容)
    |  10. DB2 schema 限定表名去限定(SCHEMA.TABLE -> TABLE
    v
_norm.cbl(规范源文件)
    |
    v -- preprocess()
    |   gixpp -i _norm.cbl -o _pp.cbl -e [-I COPYBOOK路径...]
    |   将 EXEC SQL 块转换为 GIXSQL 调用序列
    v
_pp.cbl(预处理后源文件)
    |
    v -- compile()
    |   1. _patch_sql_identifiers()DB2 连字符标识符 -> 下划线(SQLite 兼容)
    |   2. _patch_sqlcode_normalize()SQLite 约束错误码映射为 DB2 标准码
    |   3. cobc -x -L gixsql库路径 -K GIXSQL*函数 -l gixsql [-I COPYBOOK...] -o exe pp.cbl
    v
GixsqlBuildResult(success, exe_path, log)

4.3 Java 编译(native_java_runner.py / spark_java_runner.py

两个 Java Runner 共享相同的 Maven 编译流程:

source_dir/pom.xml
    |
    v
mvn -B package -f pom.xml
    |
    v
source_dir/target/program.jar
    |
    v
BuildResult(success, artifact_path, log)
  • 默认超时 120 秒
  • 使用 -Bbatch mode)避免交互式提示
  • 产物固定为 target/program.jar

5. 运行流程

5.1 COBOL 执行

旧式执行(stdin 到 stdout

input_path(二进制数据)
    |
    v subprocess.run([binary], input=data, capture_output=True)
    |
    v stdout 写入 output_path
    |
    v RunResult(success)
  • 超时 30 秒
  • 无输出记录解析(仅写入文件)

新式执行(基于文件)

input_files: {assign_name: source_path}
    |
    v 复制输入文件到 run_dir
    |
    v subprocess.run([binary], cwd=run_dir, capture_output=True)
    |
    v RunResult(success, log)
  • 超时 60 秒(可配置)
  • 工作目录为 run_dir,程序通过 ASSIGN 名读取文件
  • 日志截断至 2000 字符

5.2 DB COBOL 执行(gixsql_runner.py

exe_path + db_pathSQLite 数据库)
    |
    v 部署 DLL 到 exe_dirlibgixsql.dll, libgixsql-sqlite.dll 等)
    |
    v 复制输入文件到 work_dir
    |
    v subprocess.run([exe], cwd=work_dir, env={GIXSQL_DB_PATH: db_path})
    |
    v GixsqlRunResult(success, returncode, db_path, log)
    |
    v read_db_tables(db_path, table_names) -> 读取数据库输出
  • 超时 30 秒(可配置)
  • 环境变量 GIXSQL_DB_PATH 指向 SQLite 数据库
  • DLL 部署策略:优先 lib_path,回退到 gixpp bin 目录
  • returncode 0 或 1 均视为成功(COBOL STOP RUN 返回码差异)

5.3 Java 执行

NativeJavaRunner

input_pathJSON 文件)
    |
    v java -jar artifactstdin 输入)
    |
    v 解析 stdout JSON 行 -> records 列表
    |
    v RunResult(success, records, log)
  • 超时 60 秒
  • 每行一个 JSON 对象

SparkJavaRunner

input_pathJSON 文件)
    |
    v spark-submit --class Main --master local[*]
    |   --conf spark.input.path=...
    |   --conf spark.output.path=...
    |   --conf spark.input.format=json
    |   --conf spark.output.format=json
    |   artifact
    |
    v 读取 output_path/part-* 文件 -> records 列表
    |
    v RunResult(success, records, log)
  • 超时 300 秒(5 分钟)
  • 输入输出格式通过 spark conf 配置
  • 输出文件自动 glob 匹配 part-*

6. 错误处理

6.1 编译失败

场景 处理方式 返回值
cobc 编译错误 捕获 returncode != 0 BuildResult(success=False, log=stdout+stderr)
cobc 超时 捕获 TimeoutExpired BuildResult(success=False, log="Compile timeout")
gixpp 预处理失败 捕获 returncode != 0,抛出 RuntimeError raise RuntimeError("gixpp failed")
Maven 编译错误 捕获 returncode != 0 BuildResult(success=False, log=stdout+stderr)

6.2 运行时异常

场景 处理方式 返回值
COBOL 程序异常终止 捕获 returncode != 0 RunResult(success=False, log=stdout+stderr)
COBOL 程序超时 捕获 TimeoutExpired RunResult(success=False, log="Run timeout")
DB 程序 returncode 1 视为成功(COBOL 语义差异) GixsqlRunResult(success=True)
Java 程序异常 捕获 returncode != 0 RunResult(success=False, log=stdout+stderr)
Spark 程序超时 捕获 TimeoutExpired300s RunResult(success=False, log="Run timeout")
DLL 未找到 运行时加载失败,日志记录 GixsqlRunResult(success=False, log=...)

6.3 Gixsql 专用处理

机制 说明
SQL 标识符归一化 DB2 连字符标识符自动转换为下划线(EMP-MASTER -> EMP_MASTER
SQLCODE 映射 SQLite 约束错误码(-1555/-2067/-19)映射为 DB2 标准码(-803
Schema 限定去限定 SCHEMA.TABLE -> TABLESQLite 无 schema 概念)
CURRENT TIMESTAMP DB2 CURRENT TIMESTAMP -> SQLite CURRENT_TIMESTAMP
DLL 自动部署 运行前将 gixsql DLL 复制到 exe_dir 确保加载器找到

6.4 日志截断

所有 Runner 的日志均截断至固定长度以避免内存溢出:

Runner 日志截断长度
CobolRunner(旧式) 无截断(完整 stdout+stderr
CobolRunner(新式) 2000 字符
GixsqlCobolRunner 500-1000 字符
NativeJavaRunner 无截断(完整 stdout+stderr
SparkJavaRunner 无截断(完整 stdout+stderr

7. 设计特点

7.1 双轨架构

CobolRunner 维护两套接口:

  • 旧式接口compile + run):供 orchestrator.py 使用,兼容现有调用链
  • 新式接口compile_with_links + run_file_based):支持非 DB 程序的文件 I/O 和 SUB.o 链接

两套接口互不干扰,通过不同方法名区分。

7.2 Gixsql 预处理管线

GixsqlCobolRunner 的预处理是模块中最复杂的部分:

  1. Python 侧 COPY 展开:替代 cobc -E,解决 gixpp ESQL 解析器对 COPY REPLACING 伪文本的兼容问题
  2. SQL 归一化:处理 DB2 与 SQLite 的语法差异(标识符、时间函数、schema 限定符)
  3. SQLCODE 映射:注入代码将 SQLite 特定错误码转换为 DB2 标准码,确保 IF SQLCODE = -803 分支可达

7.3 DLL 部署策略

gixsql 运行时需要多个 DLLlibgixsql.dll, libgixsql-sqlite.dll 等)。部署策略:

  1. 优先从 lib_path 复制
  2. 若不存在,回退到 gixpp bin 目录
  3. 检查文件大小避免重复复制

7.4 DataWriter 格式

格式 字节序 数值类型 字符串处理
COBOL 二进制 大端序(Network Byte Order int64 (>q) / float64 (>d) ASCII,右补空格至 10 字节
Spark JSON N/A JSON 数字 UTF-8key 字段加序号后缀
Native JSON N/A JSON 数字 UTF-8,每行一个 JSON 对象

8. 依赖关系

runners/
  runner.py          -> 无外部依赖
  cobol_runner.py    -> runners.runner, subprocess, pathlib
  gixsql_runner.py   -> runners.runner(仅类型引用),subprocess, sqlite3, pathlib, logging
  native_java_runner.py -> runners.runner, subprocess, json, shutil, pathlib
  spark_java_runner.py  -> runners.runner, subprocess, json, shutil, pathlib
  data_writer.py     -> data.test_case.TestCase, struct, json, pathlib

外部工具依赖:

工具 用途 使用者
cobc COBOL 编译器 CobolRunner, GixsqlCobolRunner
gixpp COBOL EXEC SQL 预处理器 GixsqlCobolRunner
mvn Java 构建工具 NativeJavaRunner, SparkJavaRunner
java Java 运行时 NativeJavaRunner
spark-submit Spark 提交工具 SparkJavaRunner
libgixsql.dll gixsql 运行时库 GixsqlCobolRunner(运行时链接)
libgixsql-sqlite.dll SQLite 后端驱动 GixsqlCobolRunner(运行时加载)