test: cover between/hostvar/gcov-merge, class conditions, schema drop-tables, gixsql fixes
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""build_db_input 事务调度感知种子测试(JIN05UPD 根因)。
|
||||
|
||||
复现根因:UPDATE/DELETE 种子对每个 sql_ok 路径都用该路径记录的主键建行,
|
||||
不区分记录运行时是否真的执行 UPDATE。JIN05UPD 的 4 条 INSERT 记录主键因此
|
||||
全部被预置进 EMPLOYEE → 运行时 INSERT 全部 -803,INSERT 成功分支不可达。
|
||||
另外:非 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/B0000009(OTHER 记录)绝不能入表。
|
||||
"""
|
||||
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}'
|
||||
Reference in New Issue
Block a user