test: cover between/hostvar/gcov-merge, class conditions, schema drop-tables, gixsql fixes
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
"""KIN07DAI 型多文件照合程序的前缀检测与前置序列构造测试。
|
||||
|
||||
复现根因:_coordinate_multi_file_keys 从 01 记录名推导前缀(R01INNREC→'R01'),
|
||||
但 COPY REPLACING 把字段前缀改为 SW/SR/SL,导致键对匹配不到、跨文件键协调失效。
|
||||
修复后应生成前置匹配序列,运行时 merge-join 应覆盖全匹配/单明细匹配等组合。
|
||||
"""
|
||||
|
||||
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.__init__ import (
|
||||
_coordinate_multi_file_keys,
|
||||
_coordinate_tertiary_fd,
|
||||
_find_key_pairs_inner,
|
||||
)
|
||||
|
||||
|
||||
def _group(name):
|
||||
return {'name': name, 'level': 1, 'pic': None,
|
||||
'pic_info': {'type': 'group', 'digits': 0, 'decimal': 0, 'length': 80, 'signed': False},
|
||||
'section': 'FILE', 'is_filler': False, 'redefines': None, 'usage': 'DISPLAY',
|
||||
'occurs': 0, 'occurs_depending': None, 'value': None, 'values': None}
|
||||
|
||||
|
||||
def _field(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': 'FILE', 'is_filler': False, 'redefines': None, 'usage': 'DISPLAY',
|
||||
'occurs': 0, 'occurs_depending': None, 'value': None, 'values': None}
|
||||
|
||||
|
||||
def _make_data_fields():
|
||||
# 字段顺序与源文件一致:01 记录紧跟其子字段
|
||||
return [
|
||||
_group('R01INNREC'),
|
||||
_field('SWEMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||||
_field('SWDATE', 3, '9(008)', 'numeric', 8),
|
||||
_group('R02INNREC'),
|
||||
_field('SREMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||||
_field('SRWORK-DATE', 3, '9(008)', 'numeric', 8),
|
||||
_field('SRLEAVE-TYPE', 3, 'X(002)', 'alphanumeric', 2),
|
||||
_group('R03INNREC'),
|
||||
_field('SLEMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||||
_field('SLDATE', 3, '9(008)', 'numeric', 8),
|
||||
_field('SLLEAVE-TYPE', 3, 'X(002)', 'alphanumeric', 2),
|
||||
_field('SLSTART-TIME', 3, '9(004)', 'numeric', 4),
|
||||
_field('SLEND-TIME', 3, '9(004)', 'numeric', 4),
|
||||
]
|
||||
|
||||
|
||||
def _make_records(n=12):
|
||||
recs = []
|
||||
for i in range(n):
|
||||
recs.append({
|
||||
'SWEMP-ID': f'R{i:07d}', 'SWDATE': f'{20000101 + i:08d}',
|
||||
'SREMP-ID': f'C{i:07d}', 'SRWORK-DATE': f'{20000101 + i:08d}',
|
||||
'SRLEAVE-TYPE': '99',
|
||||
'SLEMP-ID': f'I{i:07d}', 'SLDATE': f'{20000101 + i:08d}',
|
||||
'SLLEAVE-TYPE': '99',
|
||||
'SLSTART-TIME': '0822', 'SLEND-TIME': '0922',
|
||||
})
|
||||
return recs
|
||||
|
||||
|
||||
FILE_SEC = {
|
||||
'R01INNFIL': ['R01INNREC'],
|
||||
'R02INNFIL': ['R02INNREC'],
|
||||
'R03INNFIL': ['R03INNREC'],
|
||||
'W01OUTFIL': ['W01OUTREC'],
|
||||
}
|
||||
OPEN_DIR = {
|
||||
'R01INNFIL': 'INPUT',
|
||||
'R02INNFIL': 'INPUT',
|
||||
'R03INNFIL': 'INPUT',
|
||||
'W01OUTFIL': 'OUTPUT',
|
||||
}
|
||||
|
||||
|
||||
def test_find_key_pairs_uses_field_prefix_not_record_prefix():
|
||||
"""字段前缀 SW/SR/SL 应能配对。"""
|
||||
fields = _make_data_fields()
|
||||
assert _find_key_pairs_inner('SW', 'SR', fields), 'SW-SR 应配对'
|
||||
assert _find_key_pairs_inner('SW', 'SL', fields), 'SW-SL 应配对'
|
||||
|
||||
|
||||
def test_coordinate_multi_file_keys_builds_front_sequence():
|
||||
"""KIN07DAI 形态(记录名 R01INNREC + 字段 SW/SR/SL)下协调应生效:
|
||||
记录0 三文件键一致、R03 同键重复块 + 休暇种别注入、键单调递增。"""
|
||||
fields = _make_data_fields()
|
||||
records = _make_records(12)
|
||||
_coordinate_multi_file_keys(
|
||||
records, [], fields, {}, FILE_SEC,
|
||||
term_types=['normal'] * len(records), open_dir=OPEN_DIR,
|
||||
)
|
||||
|
||||
r0 = records[0]
|
||||
assert r0['SWEMP-ID'] == r0['SREMP-ID'] == r0['SLEMP-ID'], '记录0 三文件键应一致'
|
||||
assert r0['SWDATE'] == r0['SRWORK-DATE'] == r0['SLDATE'], '记录0 三文件日期应一致'
|
||||
|
||||
# R03 同键重复块(休暇种别 01-04 + WHEN OTHER '99')
|
||||
r03_keys = [records[i]['SLEMP-ID'] for i in range(5)]
|
||||
assert all(k == r03_keys[0] for k in r03_keys), 'R03 前5条记录键应一致'
|
||||
lts = [records[i]['SLLEAVE-TYPE'] for i in range(5)]
|
||||
assert lts == ['01', '02', '03', '04', '99'], "休暇种别应注入 01-04+'99', 实际 {lts}".format(lts=lts)
|
||||
|
||||
# 键单调递增(避免 merge-join 指针跳过)
|
||||
k0, k1 = records[0]['SWEMP-ID'], records[1]['SWEMP-ID']
|
||||
assert k0 < k1, f'前置序列主键应递增: {k0} < {k1}'
|
||||
|
||||
|
||||
def _simulate_merge(R01, R02, R03):
|
||||
"""模拟 KIN07DAI 的 3 文件 merge-join,返回 (pattern_counts, match_flags)。
|
||||
|
||||
pattern 集合: A=全匹配, B=仅R02, C=仅R03, D=无匹配
|
||||
match_flags: (r02_match, r03_match, leave_calc, lt_seen)
|
||||
"""
|
||||
def key(e, d):
|
||||
return (str(e).strip(), str(d))
|
||||
|
||||
n1, n2, n3 = len(R01), len(R02), len(R03)
|
||||
r1 = r2 = r3 = 0
|
||||
e1 = e2 = e3 = False
|
||||
|
||||
def rdR1():
|
||||
nonlocal r1, e1
|
||||
if r1 < n1:
|
||||
r1 += 1
|
||||
else:
|
||||
e1 = True
|
||||
|
||||
def rdR2():
|
||||
nonlocal r2, e2
|
||||
if r2 < n2:
|
||||
r2 += 1
|
||||
else:
|
||||
e2 = True
|
||||
|
||||
def rdR3():
|
||||
nonlocal r3, e3
|
||||
if r3 < n3:
|
||||
r3 += 1
|
||||
else:
|
||||
e3 = True
|
||||
|
||||
rdR1(); rdR2(); rdR3()
|
||||
pat = {'A': 0, 'B': 0, 'C': 0, 'D': 0}
|
||||
r02m = r03m = calcs = 0
|
||||
lt_seen = set()
|
||||
while not e1:
|
||||
r01k = key(R01[r1 - 1][0], R01[r1 - 1][1])
|
||||
ptn = 4
|
||||
while not e2:
|
||||
if key(R02[r2 - 1][0], R02[r2 - 1][1]) >= r01k:
|
||||
break
|
||||
rdR2()
|
||||
if not e2 and key(R02[r2 - 1][0], R02[r2 - 1][1]) == r01k:
|
||||
ptn = 2
|
||||
r02m += 1
|
||||
rdR2()
|
||||
while not e3:
|
||||
if key(R03[r3 - 1][0], R03[r3 - 1][1]) >= r01k:
|
||||
break
|
||||
rdR3()
|
||||
if not e3 and key(R03[r3 - 1][0], R03[r3 - 1][1]) == r01k:
|
||||
r03m += 1
|
||||
while True:
|
||||
calcs += 1
|
||||
lt_seen.add(R03[r3 - 1][3])
|
||||
rdR3()
|
||||
if e3:
|
||||
break
|
||||
if key(R03[r3 - 1][0], R03[r3 - 1][1]) != r01k:
|
||||
break
|
||||
ptn = 1 if ptn == 2 else 3
|
||||
pat[['A', 'B', 'C', 'D'][ptn - 1]] += 1
|
||||
rdR1()
|
||||
return pat, (r02m, r03m, calcs, lt_seen)
|
||||
|
||||
|
||||
def test_coordination_reaches_all_match_combinations_at_runtime():
|
||||
"""协调后运行时 merge-join 应覆盖全匹配/仅R02/仅R03 三种组合。"""
|
||||
fields = _make_data_fields()
|
||||
records = _make_records(12)
|
||||
_coordinate_multi_file_keys(
|
||||
records, [], fields, {}, FILE_SEC,
|
||||
term_types=['normal'] * len(records), open_dir=OPEN_DIR,
|
||||
)
|
||||
|
||||
R01 = [(r['SWEMP-ID'], r['SWDATE']) for r in records]
|
||||
R02 = [(r['SREMP-ID'], r['SRWORK-DATE']) for r in records]
|
||||
R03 = [(r['SLEMP-ID'], r['SLDATE'], r['SLLEAVE-TYPE'], r['SLLEAVE-TYPE']) for r in records]
|
||||
pat, (r02m, r03m, calcs, lt_seen) = _simulate_merge(R01, R02, R03)
|
||||
|
||||
assert r02m >= 1, 'R02 照合应至少发生 1 次'
|
||||
assert r03m >= 1, 'R03 照合应至少发生 1 次'
|
||||
assert calcs >= 4, '休暇計算应至少 4 次(EVALUATE 全 WHEN)'
|
||||
assert lt_seen >= {'01', '02', '03', '04'}, f'休暇種別应全覆盖, 实际 {lt_seen}'
|
||||
assert pat['A'] >= 1, f'全匹配(PATTERN-A)应覆盖, 实际 {pat}'
|
||||
assert pat['B'] >= 1, f'仅R02匹配(PATTERN-B)应覆盖, 实际 {pat}'
|
||||
assert pat['C'] >= 1, f'仅R03匹配(PATTERN-C)应覆盖, 实际 {pat}'
|
||||
|
||||
|
||||
def test_coordinate_tertiary_fd_long_leave_for_cap():
|
||||
"""PATTERN-C 用判别块应注入长休暇时长(覆盖 8h 上限 T 分支)。"""
|
||||
fields = _make_data_fields()
|
||||
records = _make_records(12)
|
||||
_coordinate_tertiary_fd(records, ['SW', 'SR', 'SL'], fields)
|
||||
|
||||
# 块2(索引 max(3,5)=5..9):长时长 0600-2200
|
||||
for i in range(5, 10):
|
||||
assert records[i]['SLSTART-TIME'] == '0600', f'rec{i} 长休暇开始时间'
|
||||
assert records[i]['SLEND-TIME'] == '2200', f'rec{i} 长休暇结束时间'
|
||||
Reference in New Issue
Block a user