test: cover between/hostvar/gcov-merge, class conditions, schema drop-tables, gixsql fixes

This commit is contained in:
hangshuo652
2026-08-09 17:43:16 +08:00
parent 273a3f8211
commit ff51bda962
22 changed files with 2559 additions and 0 deletions
@@ -0,0 +1,126 @@
"""build_db_input 事务调度感知种子测试(JIN05UPD 根因)。
复现根因:UPDATE/DELETE 种子对每个 sql_ok 路径都用该路径记录的主键建行,
不区分记录运行时是否真的执行 UPDATE。JIN05UPD 的 4 条 INSERT 记录主键因此
全部被预置进 EMPLOYEE → 运行时 INSERT 全部 -803INSERT 成功分支不可达。
另外:非 sql_ok INSERT 路径的记录可能不是 INSERT 类(运行时走 WHEN OTHER),
碰撞行必须回退到真实 INSERT 记录的主键。
修复:按 88 级事务类型(INSERT/UPDATE/DELETE 语义标记)沿 MOVE 链追溯调度
字段,分类每条记录;UPDATE/DELETE 种子仅对 UPDATE/DELETE 记录;INSERT 碰撞
回退到首条 INSERT 类记录。
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
import pytest
from cobol_testgen.to_sql import build_db_input
def _fields():
def f(name, level, pic, ftype, length):
return {'name': name, 'level': level, 'pic': pic,
'pic_info': {'type': ftype, 'digits': 0, 'decimal': 0,
'length': length, 'signed': False},
'section': 'WS', 'is_filler': False, 'redefines': None,
'usage': 'DISPLAY', 'occurs': 0, 'occurs_depending': None,
'value': None, 'values': None}
base = [
f('WRK-TRAN-TYPE', 3, 'X(001)', 'alphanumeric', 1),
f('R01TRAN-TYPE', 3, 'X(001)', 'alphanumeric', 1),
f('R01TRAN-EMP-ID', 3, 'X(008)', 'alphanumeric', 8),
f('HV-EMP-ID', 3, 'X(008)', 'alphanumeric', 8),
]
for name, parent, vals in [('WRK-TRAN-INSERT', 'WRK-TRAN-TYPE', ['A', 'B', 'C']),
('WRK-TRAN-UPDATE', 'WRK-TRAN-TYPE', ['D', 'E', 'F', 'G']),
('WRK-TRAN-DELETE', 'WRK-TRAN-TYPE', ['Z'])]:
e = f(name, 88, None, 'alphanumeric', 1)
e['is_88'] = True
e['parent'] = parent
e['values'] = vals
e['value'] = vals[0]
base.append(e)
return base
_ASSIGN = {
'WRK-TRAN-TYPE': [{'type': 'move', 'source_vars': ['R01TRAN-TYPE']}],
'HV-EMP-ID': [{'type': 'move', 'source_vars': ['R01TRAN-EMP-ID']}],
}
def _declared():
return {'EMPLOYEE': [{'name': 'EMP_ID', 'db_type': 'CHAR', 'size': 8}]}
def _sql_meta():
return [
{'type': 'exec_sql_insert', 'table': 'EMPLOYEE',
'columns': ['EMP_ID'], 'host_vars': ['HV-EMP-ID'],
'sql_text': 'INSERT INTO EMPLOYEE (EMP_ID) VALUES (:HV-EMP-ID)'},
{'type': 'exec_sql_update', 'table': 'EMPLOYEE',
'set_clause': 'STATUS = :HV-STATUS',
'where': 'EMP_ID = :HV-EMP-ID', 'host_vars': ['HV-EMP-ID', 'HV-STATUS'],
'where_constraints': [{'col': 'EMP_ID', 'type': 'host_var', 'host_var': 'HV-EMP-ID'}],
'sql_text': 'UPDATE EMPLOYEE SET STATUS = :HV-STATUS WHERE EMP_ID = :HV-EMP-ID'},
]
def _records():
return [
{'R01TRAN-TYPE': 'A', 'R01TRAN-EMP-ID': 'B0000001', 'HV-EMP-ID': 'B0000001'}, # INSERT
{'R01TRAN-TYPE': 'D', 'R01TRAN-EMP-ID': 'B0000005', 'HV-EMP-ID': 'B0000005'}, # UPDATE
{'R01TRAN-TYPE': 'H', 'R01TRAN-EMP-ID': 'B0000008', 'HV-EMP-ID': 'B0000008'}, # OTHER
{'R01TRAN-TYPE': 'I', 'R01TRAN-EMP-ID': 'B0000009', 'HV-EMP-ID': 'B0000009'}, # OTHER (非sql_ok INSERT 路径)
]
def _paths():
return [
([], {}), # path0 sql_ok → rec0 (INSERT)
([], {}), # path1 sql_ok → rec1 (UPDATE)
([], {}), # path2 sql_ok → rec2 (OTHER)
([('SQLCODE', '<>', '0', True)], {}), # path3 INSERT 错误 → rec3 (OTHER)
]
_INSERT_PK = {'EMPLOYEE': ['EMP_ID']}
def test_update_seed_only_for_update_records():
"""INSERT/OTHER 记录不应被 UPDATE 种子注入;OTHER 记录主键不得入表。
B0000001 允许作为 INSERT 碰撞行出现一次(供 -803 分支),但不得因
UPDATE 种子而额外注入;B0000008/B0000009OTHER 记录)绝不能入表。
"""
di = build_db_input(_paths(), _fields(), _ASSIGN, _sql_meta(), _declared(),
records=_records(), insert_pk=_INSERT_PK)
keys = [r.get('EMP_ID', '').strip() for r in di.get('EMPLOYEE', [])]
assert keys.count('B0000001') <= 1, 'INSERT 记录主键仅允许作为碰撞行出现一次'
assert 'B0000005' in keys, 'UPDATE 记录主键应被种子注入'
assert 'B0000008' not in keys, 'OTHER 记录主键不应被种子注入'
assert 'B0000009' not in keys, '非 sql_ok OTHER 记录主键不应被 UPDATE 种子注入'
def test_insert_collision_falls_back_to_insert_record():
"""非 sql_ok INSERT 路径的记录若非 INSERT 类,碰撞行应回退到首条 INSERT 类记录主键。"""
di = build_db_input(_paths(), _fields(), _ASSIGN, _sql_meta(), _declared(),
records=_records(), insert_pk=_INSERT_PK)
keys = [r.get('EMP_ID', '').strip() for r in di.get('EMPLOYEE', [])]
assert 'B0000001' in keys, f'碰撞行应回退到首条 INSERT 记录主键, 实际 {keys}'
def test_insert_success_path_skips_seed():
"""sql_ok INSERT 路径不应生成任何种子行(INSERT 成功需主键空闲)。"""
paths = [
([('SQLCODE', '=', '0', True)], {}), # INSERT 成功
]
recs = [{'R01TRAN-TYPE': 'A', 'R01TRAN-EMP-ID': 'B0000001', 'HV-EMP-ID': 'B0000001'}]
di = build_db_input(paths, _fields(), _ASSIGN, _sql_meta(), _declared(),
records=recs, insert_pk=_INSERT_PK)
keys = [r.get('EMP_ID', '').strip() for r in di.get('EMPLOYEE', [])]
assert 'B0000001' not in keys, f'INSERT 成功路径不应生成种子, 实际 {keys}'