149 lines
5.9 KiB
Python
149 lines
5.9 KiB
Python
"""JIN02SKL 型"明细键─主表键"后缀配对测试。
|
||
|
||
复现根因:跨文件键协调 _find_key_pairs 只做同名精确匹配 / WORK- 变体 /
|
||
EMP 族回退。当明细键 base='SKILL-CODE' 与主表键 base='MST-SKILL-CODE'
|
||
(多 'MST-' 限定词)时匹配不到,导致 SEARCH ALL 永未命中、
|
||
TBL-EMP-COUNT 恒 0,#7/#8(> ZERO)TRUE 分支不可达。
|
||
|
||
修复:增加"后缀匹配"层级——一方 base 是另一方真后缀且 key 类别一致
|
||
(同为 -CODE/-ID/-NO/DATE/EMP-ID)时配对。通用实现、无程序名硬编码。
|
||
"""
|
||
|
||
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,
|
||
_find_key_pairs_inner,
|
||
)
|
||
|
||
|
||
def _group(name):
|
||
return {'name': name, 'level': 1, 'pic': None,
|
||
'pic_info': {'type': 'group', 'digits': 0, 'decimal': 0, 'length': 200, '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 _skill_fields():
|
||
# 字段顺序与源文件一致:01 记录紧跟其子字段(COPY REPLACING R01/R02)
|
||
return [
|
||
_group('R01INNREC'),
|
||
_field('R01SKILL-CODE', 3, 'X(004)', 'alphanumeric', 4),
|
||
_field('R01EMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||
_field('R01EMP-NAME', 3, 'X(040)', 'alphanumeric', 40),
|
||
_field('R01EVAL-DATE', 3, 'X(008)', 'alphanumeric', 8),
|
||
_field('R01SCORE', 3, '9(003)', 'numeric', 3),
|
||
_group('R02INNREC'),
|
||
_field('R02MST-SKILL-CODE', 3, 'X(004)', 'alphanumeric', 4),
|
||
_field('R02MST-LEVEL', 3, '9(002)', 'numeric', 2),
|
||
_field('R02MST-SCORE-FROM', 3, '9(003)', 'numeric', 3),
|
||
_field('R02MST-SCORE-TO', 3, '9(003)', 'numeric', 3),
|
||
_field('R02MST-LEVEL-NAME', 3, 'X(020)', 'alphanumeric', 20),
|
||
]
|
||
|
||
|
||
def _skill_records(n=6):
|
||
recs = []
|
||
for i in range(n):
|
||
recs.append({
|
||
'R01SKILL-CODE': f'A{i + 1:03d}',
|
||
'R01EMP-ID': f'B{i + 1:07d}',
|
||
'R01EMP-NAME': 'N' * 40,
|
||
'R01EVAL-DATE': f'D{i + 1:08d}',
|
||
'R01SCORE': f'{100 + i:03d}',
|
||
'R02MST-SKILL-CODE': f'F{i + 1:03d}',
|
||
'R02MST-LEVEL': f'{20 + i:02d}',
|
||
'R02MST-SCORE-FROM': f'{300 + i:03d}',
|
||
'R02MST-SCORE-TO': f'{400 + i:03d}',
|
||
'R02MST-LEVEL-NAME': 'G' * 20,
|
||
})
|
||
return recs
|
||
|
||
|
||
FILE_SEC = {
|
||
'R01INNFIL': ['R01INNREC'],
|
||
'R02INNFIL': ['R02INNREC'],
|
||
'W01OUTFIL': ['W01OUTREC'],
|
||
'W02OUTFIL': ['W02OUTREC'],
|
||
}
|
||
OPEN_DIR = {
|
||
'R01INNFIL': 'INPUT',
|
||
'R02INNFIL': 'INPUT',
|
||
'W01OUTFIL': 'OUTPUT',
|
||
'W02OUTFIL': 'OUTPUT',
|
||
}
|
||
|
||
|
||
def test_find_key_pairs_suffix_skill_code():
|
||
"""明细键 SKILL-CODE 应与主表键 MST-SKILL-CODE 后缀配对。"""
|
||
fields = _skill_fields()
|
||
pairs = _find_key_pairs_inner('R01', 'R02', fields)
|
||
assert ('R01SKILL-CODE', 'R02MST-SKILL-CODE') in pairs, f'SKILL-CODE↔MST-SKILL-CODE 应配对, 实际 {pairs}'
|
||
|
||
|
||
def test_find_key_pairs_suffix_no_cross_class():
|
||
"""类别不一致的后缀不应误配:DATE 类键不与 CODE 类键配对。"""
|
||
fields = _skill_fields()
|
||
pairs = _find_key_pairs_inner('R01', 'R02', fields)
|
||
date_keys = [fa for fa, _ in pairs if 'DATE' in fa.upper()]
|
||
code_keys = [fb for _, fb in pairs if '-CODE' in fb.upper()]
|
||
# R01EVAL-DATE 不应与 R02MST-SKILL-CODE 配对
|
||
assert not any('R01EVAL-DATE' == fa and fb == 'R02MST-SKILL-CODE' for fa, fb in pairs), f'DATE 不应与 CODE 后缀配对, 实际 {pairs}'
|
||
|
||
|
||
def test_coordinate_multi_file_keys_skill_match():
|
||
"""协调后应使部分记录 R01SKILL-CODE == R02MST-SKILL-CODE,运行时 SEARCH 可达。"""
|
||
fields = _skill_fields()
|
||
records = _skill_records(6)
|
||
_coordinate_multi_file_keys(
|
||
records, [], fields, {}, FILE_SEC,
|
||
term_types=['normal'] * len(records), open_dir=OPEN_DIR,
|
||
)
|
||
matched = [r for r in records
|
||
if str(r['R01SKILL-CODE']).strip() == str(r['R02MST-SKILL-CODE']).strip()]
|
||
assert matched, '至少一条记录明细键应与主表键一致'
|
||
|
||
|
||
def test_coordinate_exact_match_still_pairs():
|
||
"""既有同名精确匹配(EMP-ID↔EMP-ID)不受后缀层级影响。"""
|
||
fields = [
|
||
_group('R01INNREC'),
|
||
_field('R01EMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||
_group('R02INNREC'),
|
||
_field('R02EMP-ID', 3, 'X(008)', 'alphanumeric', 8),
|
||
]
|
||
pairs = _find_key_pairs_inner('R01', 'R02', fields)
|
||
assert ('R01EMP-ID', 'R02EMP-ID') in pairs, f'同名 EMP-ID 应仍精确配对, 实际 {pairs}'
|
||
|
||
|
||
def test_coordinate_last_record_matches_table_key():
|
||
"""末条 normal 记录明细键应对齐表内键(首条 R02 键)。
|
||
|
||
社员工键中断集约(JIN09RPT 型):最终社员工组在 EOF 後の终了处理で出力される
|
||
(`IF WRK-EMP-EVAL-CNT > ZERO PERFORM 3000OUTPUSOR`)。協調が先頭のみ整列だと
|
||
最終组に照合データがなく最终组输出分支不可達。末条记录も表内键に揃えることで
|
||
該分支が到達可能になる。
|
||
"""
|
||
fields = _skill_fields()
|
||
records = _skill_records(12)
|
||
_coordinate_multi_file_keys(
|
||
records, [], fields, {}, FILE_SEC,
|
||
term_types=['normal'] * len(records), open_dir=OPEN_DIR,
|
||
)
|
||
table_key = str(records[0]['R02MST-SKILL-CODE']).strip()
|
||
last = records[-1]
|
||
assert str(last['R01SKILL-CODE']).strip() == table_key, \
|
||
f'末条记录明细键应对齐表内键 {table_key}, 实际 {last["R01SKILL-CODE"]!r}'
|