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,188 @@
"""SELECT BETWEEN WHERE 约束的 DB 预置行生成测试(Phase 2)。
根因:KYU05DED 的 `WHERE :WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO`
在修复解析后成为 between 约束,但 build_db_input 不处理 between 类型,
TAX_FROM/TAX_TO 仍落入默认序号值 → 运行时 SELECT 查不到行 → SQLCODE<>0。
修复:build_db_input 对 between 约束,用记录中的 subject 值播种 lo/hi 列,
使运行时 `:hv BETWEEN TAX-FROM AND TAX-TO` 能命中。
"""
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_select_between():
return [{
"type": "exec_sql_select",
"table": "TAX-TABLE",
"select_list": "TAX-RATE, DEDUCTION",
"into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"],
"where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO",
"where_constraints": [
{"type": "between", "neg": False, "op": "BETWEEN",
"subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"},
"lo": {"kind": "column", "name": "TAX-FROM"},
"hi": {"kind": "column", "name": "TAX-TO"}},
],
}]
def _declared_tax_table():
return {
"TAX-TABLE": [
{"name": "TAX_FROM", "db_type": "DECIMAL"},
{"name": "TAX_TO", "db_type": "DECIMAL"},
{"name": "TAX_RATE", "db_type": "DECIMAL"},
{"name": "DEDUCTION", "db_type": "DECIMAL"},
],
}
def test_select_between_hostvar_subject_seeds_lo_hi_from_record():
"""`:hv BETWEEN TAX-FROM AND TAX-TO`TAX_FROM/TAX_TO 播成记录中
WRK-TAXABLE-INCOME 的值,保证运行时 BETWEEN 命中。"""
records = [{"WRK-TAXABLE-INCOME": "000000000"}]
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], [], {},
_sql_meta_select_between(), _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert len(rows) == 1
assert int(rows[0]['TAX_FROM']) == 0
assert int(rows[0]['TAX_TO']) == 0
def test_select_between_hostvar_subject_nonzero():
"""subject 非零时,lo/hi 取记录值。"""
records = [{"WRK-TAXABLE-INCOME": "000102400"}]
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], [], {},
_sql_meta_select_between(), _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert len(rows) == 1
assert int(rows[0]['TAX_FROM']) == 102400
assert int(rows[0]['TAX_TO']) == 102400
def test_select_between_hostvar_subject_moved_from_input():
"""subject 主机变量经 MOVE 链到输入键时,优先用输入键值。"""
sql_meta = [{
"type": "exec_sql_select",
"table": "TAX-TABLE",
"select_list": "TAX-RATE, DEDUCTION",
"into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"],
"where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO",
"where_constraints": [
{"type": "between", "neg": False, "op": "BETWEEN",
"subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"},
"lo": {"kind": "column", "name": "TAX-FROM"},
"hi": {"kind": "column", "name": "TAX-TO"}},
],
}]
records = [{"R01GROSS-PAYMENT": "000102400",
"WRK-TAXABLE-INCOME": "000000000"}]
assignments = {
'WRK-TAXABLE-INCOME': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
}
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], [], assignments,
sql_meta, _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert len(rows) == 1
assert int(rows[0]['TAX_FROM']) == 102400
def test_select_between_column_subject_seeds_subject_col():
"""`SALARY BETWEEN :LO AND :HI`subject 是列时,把该列播成 lo 值
(满足 lo <= col <= hi)。"""
sql_meta = [{
"type": "exec_sql_select",
"table": "EMP",
"select_list": "EMP_NAME",
"into_vars": ["DBV-EMPLOYEE-NAME"],
"where": "SALARY BETWEEN :LO AND :HI",
"where_constraints": [
{"type": "between", "neg": False, "op": "BETWEEN",
"subject": {"kind": "column", "name": "SALARY"},
"lo": {"kind": "host_var", "name": "LO"},
"hi": {"kind": "host_var", "name": "HI"}},
],
}]
declared = {
"EMP": [
{"name": "SALARY", "db_type": "DECIMAL"},
{"name": "EMP_NAME", "db_type": "CHAR", "size": 40},
],
}
records = [{"LO": "000005000", "HI": "000009000"}]
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], [], {},
sql_meta, declared, records=records)
rows = db_input.get('EMP', [])
assert len(rows) == 1
sal = int(rows[0]['SALARY'])
assert 5000 <= sal <= 9000
def test_select_between_sql_error_path_skips_row():
"""SQL 失败路径(SQLCODE<>0)仍不生成预置行。"""
records = [{"WRK-TAXABLE-INCOME": "000102400"}]
db_input = build_db_input(
[([('SQLCODE', '<>', '0', True)], {})], [], {},
_sql_meta_select_between(), _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert not rows
def _fdict_with_ws_constants():
"""KYU05DED 简化的字段定义:CNS-TAX-BASIC-DEDUCTION 常量 + MOVE/COMPUTE 链。"""
return [
{'name': 'R01GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'INPUT', 'value': None},
{'name': 'WRK-GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None},
{'name': 'WRK-TAXABLE-INCOME', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None},
{'name': 'CNS-TAX-BASIC-DEDUCTION', 'pic_info': {'type': 'numeric', 'digits': 6, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': '50000'},
]
def test_select_between_derives_runtime_value_from_input_key():
"""记录中 WRK-TAXABLE-INCOME 为合成值时,按运行时推导值播种带。
输入 gross=000100000 → 运行时 taxable = gross - 50000 = 50000
预置行 TAX_FROM/TAX_TO 应为 50000,而非记录的合成值。"""
assignments = {
'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
'WRK-TAXABLE-INCOME': [
{'type': 'move_literal', 'literal': 'ZERO'},
{'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'},
],
}
records = [{"R01GROSS-PAYMENT": "000100000",
"WRK-TAXABLE-INCOME": "000003701"}]
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments,
_sql_meta_select_between(), _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert len(rows) == 1
assert int(rows[0]['TAX_FROM']) == 50000
assert int(rows[0]['TAX_TO']) == 50000
def test_select_between_derives_zero_for_low_gross():
"""gross < 基本控除(50000)时,运行时 taxable = 0 → 带为 [0,0]。"""
assignments = {
'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
'WRK-TAXABLE-INCOME': [
{'type': 'move_literal', 'literal': 'ZERO'},
{'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'},
],
}
records = [{"R01GROSS-PAYMENT": "000000501",
"WRK-TAXABLE-INCOME": "000003701"}]
db_input = build_db_input(
[([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments,
_sql_meta_select_between(), _declared_tax_table(), records=records)
rows = db_input.get('TAX-TABLE', [])
assert len(rows) == 1
assert int(rows[0]['TAX_FROM']) == 0
assert int(rows[0]['TAX_TO']) == 0