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

413 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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 秒
- 使用 `-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_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` -> `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 运行时需要多个 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(运行时加载) |