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,177 @@
"""INSERT 表主键碰撞预置行生成测试(T2)。
gixsql+SQLite 下,`IF SQLCODE = -803` 分支要求运行时 INSERT 失败。
让 INSERT 失败的唯一途径是 DB 中已存在同主键行。因此 build_db_input
必须为"要求 SQLCODE≠0 的路径"生成主键碰撞预置行(主键值取自该记录)。
"""
import sys, os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from cobol_testgen.to_sql import build_db_input
def _sql_meta_insert(table="EMP-MASTER", columns=("EMP-ID", "EMP-NAME"),
host_vars=("DBV-EMP-ID", "DBV-EMP-NAME")):
return [{
"type": "exec_sql_insert",
"table": table,
"columns": list(columns),
"host_vars": list(host_vars),
"sql_text": f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({', '.join(':'+h for h in host_vars)})",
}]
def test_insert_sql_ok_path_skips_collision_row():
"""要求 SQLCODE=0INSERT 成功)的路径 → 不生成碰撞预置行"""
branch_paths = [
([('SQLCODE', '=', '0', True)], {}), # SQL ok → skip
]
records = [{'DBV-EMP-ID': 'A0000001', 'DBV-EMP-NAME': 'NAME'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_insert(),
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
records=records,
insert_pk={'EMP-MASTER': ['EMP-ID']},
)
assert 'EMP-MASTER' not in db_input or db_input['EMP-MASTER'] == []
def test_insert_error_path_generates_pk_collision_row():
"""要求 SQLCODE<>0INSERT 失败)的路径 → 生成主键碰撞预置行"""
branch_paths = [
([('SQLCODE', '<>', '0', True)], {}), # SQL fails → collision row
]
records = [{'DBV-EMP-ID': 'A0000001', 'DBV-EMP-NAME': 'NAME'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_insert(),
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8},
{'name': 'EMP-NAME', 'db_type': 'CHAR', 'size': 40}]},
records=records,
insert_pk={'EMP-MASTER': ['EMP-ID']},
)
rows = db_input.get('EMP-MASTER', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'] == 'A0000001'
def test_insert_error_path_uses_record_host_var_value():
"""碰撞行主键取自记录中对应 host_var 的值"""
branch_paths = [
([('SQLCODE', '=', '-803', True)], {}),
]
records = [{'DBV-EMP-ID': 'Z9999999', 'DBV-EMP-NAME': 'DUPE'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_insert(),
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
records=records,
insert_pk={'EMP-MASTER': ['EMP-ID']},
)
rows = db_input.get('EMP-MASTER', [])
assert rows and rows[0]['EMP-ID'] == 'Z9999999'
def test_insert_collision_uses_input_record_pk_not_hostvar():
"""碰撞行主键用输入记录主键(R02EMP-ID),而非 expected host_var 值。
generate_records 的 rec 中 DBV-EMP-ID 可能是 DB 预置值(W前缀)或
expected 值,而运行时实际 INSERT 的是输入记录主键(R02EMP-ID)。
碰撞行必须与输入主键一致,运行时 INSERT 才会失败。
"""
branch_paths = [
([('SQLCODE', '=', '-803', True)], {}),
]
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'W0000001'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_insert(),
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
records=records,
insert_pk={'EMP-MASTER': ['EMP-ID']},
)
rows = db_input.get('EMP-MASTER', [])
assert rows and rows[0]['EMP-ID'] == 'A0000001'
def _sql_meta_insert_salary_results():
"""KYU06UPD 的 INSERT 语句形态(复合主键 EMP-ID + YEAR-MONTH)。"""
return [{
"type": "exec_sql_insert",
"table": "SALARY-RESULTS",
"columns": ["EMP-ID", "YEAR-MONTH", "GROSS-PAYMENT"],
"host_vars": ["DBV-EMP-ID", "DBV-YEAR-MONTH", "DBV-GROSS-PAYMENT"],
"sql_text": ("INSERT INTO SALARY-RESULTS (EMP-ID, YEAR-MONTH, GROSS-PAYMENT) "
"VALUES (:DBV-EMP-ID, :DBV-YEAR-MONTH, :DBV-GROSS-PAYMENT)"),
}]
def _declared_salary_results():
return {
"SALARY-RESULTS": [
{"name": "EMP-ID", "db_type": "CHAR", "size": 8},
{"name": "YEAR-MONTH", "db_type": "CHAR", "size": 6},
{"name": "GROSS-PAYMENT", "db_type": "DECIMAL", "size": 9},
],
}
def _kyu06_assignments():
"""KYU06UPD 的主键 MOVE 链:
DBV-EMP-ID ← R02EMP-ID(输入键)
DBV-YEAR-MONTH ← CNS-YEAR-MONTH-PARM ← WRK-YEAR-MONTHACCEPT +
IF = SPACES 默认 '202605'"""
return {
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
'DBV-YEAR-MONTH': [{'type': 'move', 'source_vars': ['CNS-YEAR-MONTH-PARM']}],
'CNS-YEAR-MONTH-PARM': [{'type': 'move', 'source_vars': ['WRK-YEAR-MONTH']}],
'WRK-YEAR-MONTH': [{'type': 'accept', 'from': 'USER'},
{'type': 'move_literal', 'literal': '202605'}],
}
def test_insert_collision_composite_pk_matches_runtime_keys():
"""复合主键碰撞行必须等于运行时 INSERT 键:
EMP-ID 用输入记录键(R02EMP-ID);YEAR-MONTH 用 ACCEPT 后
IF = SPACES 的默认字面量('202605'),而非记录中的合成值('L00007')。
运行时 INSERT 键是 (A0000007, '202605'),碰撞行若用合成 YEAR-MONTH
则永不碰撞,-803 分支永远不可达。
"""
branch_paths = [
([('SQLCODE', '=', '-803', True)], {}),
]
records = [{
'R02EMP-ID': 'A0000007',
'DBV-EMP-ID': 'M0000007',
'DBV-YEAR-MONTH': 'N00007',
'WRK-YEAR-MONTH': 'L00007',
}]
db_input = build_db_input(
branch_paths, [], _kyu06_assignments(), _sql_meta_insert_salary_results(),
_declared_salary_results(),
records=records,
insert_pk={'SALARY-RESULTS': ['EMP_ID', 'YEAR_MONTH'],
'SALARY_RESULTS': ['EMP_ID', 'YEAR_MONTH']},
)
rows = db_input.get('SALARY-RESULTS', [])
assert len(rows) == 1
assert rows[0]['EMP-ID'] == 'A0000007'
assert rows[0]['YEAR-MONTH'] == '202605'
def test_insert_collision_year_month_not_falling_back_to_emp_id():
"""无 MOVE 链/ACCEPT 默认时,YEAR-MONTH 碰撞列不应回退到 EMP-ID 输入字段
(旧 bug 会取 R02EMP-ID 截断成 'A00000'),而应使用宿主变量自身记录值。"""
branch_paths = [
([('SQLCODE', '=', '-803', True)], {}),
]
records = [{'R02EMP-ID': 'A0000007', 'DBV-EMP-ID': 'M0000007',
'DBV-YEAR-MONTH': 'N00007'}]
db_input = build_db_input(
branch_paths, [], {}, _sql_meta_insert_salary_results(),
_declared_salary_results(),
records=records,
insert_pk={'SALARY-RESULTS': ['EMP_ID', 'YEAR_MONTH'],
'SALARY_RESULTS': ['EMP_ID', 'YEAR_MONTH']},
)
rows = db_input.get('SALARY-RESULTS', [])
assert len(rows) == 1
assert rows[0]['YEAR-MONTH'] == 'N00007'