03 - runners 编译运行引擎
1. 模块概述
runners 模块负责 COBOL 程序与 Java 程序的编译、运行、覆盖率采集全流程。模块采用策略模式,通过抽象基类 Runner 统一不同语言运行时的接口,对外暴露一致的 compile -> run -> get_coverage 三阶段管线。
核心职责:
| 职责 |
说明 |
| COBOL 编译 |
调用 cobc(COBOL 编译器)将 .cbl 源码编译为可执行文件 |
| Java 编译 |
调用 mvn package 将 Maven 项目打包为 .jar |
| 运行执行 |
通过 subprocess 启动编译产物,捕获 stdout/stderr 和返回码 |
| 测试数据写入 |
将 TestCase 列表序列化为 COBOL 二进制 / JSON 格式 |
| 覆盖率报告 |
采集分支覆盖数据(gcov / JaCoCo)并返回量化报告 |
2. 文件清单
3. 接口定义
3.1 数据类(runner.py)
3.2 抽象基类(runner.py)
3.3 CobolRunner(cobol_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 GixsqlCobolRunner(gixsql_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 NativeJavaRunner(native_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 SparkJavaRunner(spark_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 DataWriter(data_writer.py)
| 方法 |
签名 |
说明 |
write_cobol_binary |
(cases, out) |
将 TestCase 列表写为 COBOL 二进制格式(大端序 int64 / float64 / ASCII) |
write_spark_json |
(cases, cfg, d) |
写 Spark 输入 JSON(part-00000.json),key 字段加序号后缀 |
write_native_json |
(cases, out) |
写 Native JSON(每行一个 JSON 对象) |
4. 编译流程
4.1 COBOL 编译(cobol_runner.py)
旧式编译流程
- 默认超时 30 秒
gcov=True 时追加 --coverage 参数生成 .gcno 文件
新式编译流程(带链接)
- 默认超时 120 秒
- 工作目录切换至
work_dir(确保 .gcno 产出位置正确)
- 不使用
-std=ibm-strict,依赖默认方言
4.2 DB COBOL 编译(gixsql_runner.py)
DB 程序编译采用 gixpp + cobc 两阶段管线:
4.3 Java 编译(native_java_runner.py / spark_java_runner.py)
两个 Java Runner 共享相同的 Maven 编译流程:
- 默认超时 120 秒
- 使用
-B(batch mode)避免交互式提示
- 产物固定为
target/program.jar
5. 运行流程
5.1 COBOL 执行
旧式执行(stdin 到 stdout)
新式执行(基于文件)
- 超时 60 秒(可配置)
- 工作目录为
run_dir,程序通过 ASSIGN 名读取文件
- 日志截断至 2000 字符
5.2 DB COBOL 执行(gixsql_runner.py)
- 超时 30 秒(可配置)
- 环境变量
GIXSQL_DB_PATH 指向 SQLite 数据库
- DLL 部署策略:优先 lib_path,回退到 gixpp bin 目录
- returncode 0 或 1 均视为成功(COBOL STOP RUN 返回码差异)
5.3 Java 执行
NativeJavaRunner
SparkJavaRunner
- 超时 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 程序超时 |
捕获 TimeoutExpired(300s) |
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 -> TABLE(SQLite 无 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 的预处理是模块中最复杂的部分:
- Python 侧 COPY 展开:替代
cobc -E,解决 gixpp ESQL 解析器对 COPY REPLACING 伪文本的兼容问题
- SQL 归一化:处理 DB2 与 SQLite 的语法差异(标识符、时间函数、schema 限定符)
- SQLCODE 映射:注入代码将 SQLite 特定错误码转换为 DB2 标准码,确保
IF SQLCODE = -803 分支可达
7.3 DLL 部署策略
gixsql 运行时需要多个 DLL(libgixsql.dll, libgixsql-sqlite.dll 等)。部署策略:
- 优先从
lib_path 复制
- 若不存在,回退到
gixpp bin 目录
- 检查文件大小避免重复复制
7.4 DataWriter 格式
| 格式 |
字节序 |
数值类型 |
字符串处理 |
| COBOL 二进制 |
大端序(Network Byte Order) |
int64 (>q) / float64 (>d) |
ASCII,右补空格至 10 字节 |
| Spark JSON |
N/A |
JSON 数字 |
UTF-8,key 字段加序号后缀 |
| Native JSON |
N/A |
JSON 数字 |
UTF-8,每行一个 JSON 对象 |
8. 依赖关系
外部工具依赖:
| 工具 |
用途 |
使用者 |
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(运行时加载) |