217 lines
7.8 KiB
Python
217 lines
7.8 KiB
Python
"""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} 长休暇结束时间'
|