413 lines
14 KiB
Markdown
413 lines
14 KiB
Markdown
# 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. 文件清单
|
||
|
||
```
|
||
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)
|
||
|
||
```python
|
||
@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)
|
||
|
||
```python
|
||
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 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)
|
||
|
||
#### 旧式编译流程
|
||
|
||
```
|
||
.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 ... FROM(gixpp 要求 INTO 在前)
|
||
| 9. CURRENT TIMESTAMP -> CURRENT_TIMESTAMP(SQLite 后端兼容)
|
||
| 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 秒
|
||
- 使用 `-B`(batch 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_path(SQLite 数据库)
|
||
|
|
||
v 部署 DLL 到 exe_dir(libgixsql.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_path(JSON 文件)
|
||
|
|
||
v java -jar artifact(stdin 输入)
|
||
|
|
||
v 解析 stdout JSON 行 -> records 列表
|
||
|
|
||
v RunResult(success, records, log)
|
||
```
|
||
|
||
- 超时 60 秒
|
||
- 每行一个 JSON 对象
|
||
|
||
#### SparkJavaRunner
|
||
|
||
```
|
||
input_path(JSON 文件)
|
||
|
|
||
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 程序超时 | 捕获 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 的预处理是模块中最复杂的部分:
|
||
|
||
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 运行时需要多个 DLL(libgixsql.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-8,key 字段加序号后缀 |
|
||
| 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(运行时加载) |
|