"""RI-01~05: opaque FD + READ INTO — WS 子字段值序列化回 FD 记录字节 + 跨记录 key 差异保证""" import sys, os, json, tempfile from pathlib import Path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) from cobol_testgen import _serialize_read_into_records from cobol_testgen.core import build_branch_tree def _mk_fields(): return [ {'name': 'R01INNREC', 'level': 1, 'pic': 'X(080)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 80, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'WRK-ABSENCE-REC', 'level': 1, 'pic': '', 'pic_info': {'type': 'unknown', 'digits': 0, 'decimal': 0, 'length': 0, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'WRK-EMP-ID', 'level': 5, 'pic': 'X(008)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 8, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'WRK-YEAR-MONTH', 'level': 5, 'pic': 'X(006)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 6, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'WRK-ABSENT-HOURS', 'level': 5, 'pic': '9(004)V9(001)', 'pic_info': {'type': 'numeric', 'digits': 4, 'decimal': 1, 'length': 0, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'FILLER', 'level': 5, 'pic': 'X(062)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 62, 'signed': False}, 'is_filler': True, 'redefines': None, 'is_88': False}, {'name': 'WRK-PREV-EMP-ID', 'level': 1, 'pic': 'X(008)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 8, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, ] def _mk_records(): return [ {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000001', 'WRK-YEAR-MONTH': 'H00001', 'WRK-ABSENT-HOURS': '01501'}, {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000007', 'WRK-YEAR-MONTH': 'H00007', 'WRK-ABSENT-HOURS': '01507'}, {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000007', 'WRK-YEAR-MONTH': 'H00008', 'WRK-ABSENT-HOURS': '01508'}, ] def test_serialize_into_fd_bytes(): """RI-01: INTO 组子字段值按 PIC 布局 pack 回 R01INNREC 字节""" records = _mk_records() file_sec = {'R01INNFIL': ['R01INNREC']} assignments = {'WRK-ABSENCE-REC': [{'type': 'read_into', 'file': 'R01INNFIL', 'source_vars': []}]} _serialize_read_into_records(records, assignments, _mk_fields(), file_sec) r0 = records[0]['R01INNREC'] assert r0[0:8] == 'G0000001', r0[0:8] assert r0[8:14] == 'H00001', r0[8:14] assert r0[14:19] == '01501', r0[14:19] assert len(r0) == 80 assert r0[19:].strip(' ') == '', r0[19:] def test_key_variation_both_pairs(): """RI-02: 相邻记录 key 既有相同又有差异(同 key 累加 + 新 key flush 两路径均可达)""" records = _mk_records() file_sec = {'R01INNFIL': ['R01INNREC']} assignments = {'WRK-ABSENCE-REC': [{'type': 'read_into', 'file': 'R01INNFIL', 'source_vars': []}]} _serialize_read_into_records(records, assignments, _mk_fields(), file_sec) keys = [r['R01INNREC'][0:8] for r in records] has_same = any(keys[i] == keys[i + 1] for i in range(len(keys) - 1)) has_diff = any(keys[i] != keys[i + 1] for i in range(len(keys) - 1)) assert has_same, f"no same-key pair: {keys}" assert has_diff, f"no diff-key pair: {keys}" def test_all_same_key_gets_break(): """RI-03: 全记录同 key 时自动注入差异,保证新 key 分支可达""" records = [ {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000001', 'WRK-YEAR-MONTH': 'H00001', 'WRK-ABSENT-HOURS': '01501'}, {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000001', 'WRK-YEAR-MONTH': 'H00002', 'WRK-ABSENT-HOURS': '01502'}, {'R01INNREC': 'A' + '0' * 79, 'WRK-EMP-ID': 'G0000001', 'WRK-YEAR-MONTH': 'H00003', 'WRK-ABSENT-HOURS': '01503'}, ] file_sec = {'R01INNFIL': ['R01INNREC']} assignments = {'WRK-ABSENCE-REC': [{'type': 'read_into', 'file': 'R01INNFIL', 'source_vars': []}]} _serialize_read_into_records(records, assignments, _mk_fields(), file_sec) keys = [r['R01INNREC'][0:8] for r in records] has_diff = any(keys[i] != keys[i + 1] for i in range(len(keys) - 1)) assert has_diff, f"no diff-key pair: {keys}" def test_noop_without_read_into(): """RI-04: 无 READ INTO 或 FD 有子字段时不改动记录""" records = _mk_records() file_sec = {'R01INNFIL': ['R01INNREC']} before = [dict(r) for r in records] _serialize_read_into_records(records, {}, _mk_fields(), file_sec) assert records == before fields_with_children = [ {'name': 'R01INNREC', 'level': 1, 'pic': 'X(080)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 80, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, {'name': 'R01EMP-ID', 'level': 5, 'pic': 'X(008)', 'pic_info': {'type': 'alphanumeric', 'digits': 0, 'decimal': 0, 'length': 8, 'signed': False}, 'is_filler': False, 'redefines': None, 'is_88': False}, ] records2 = _mk_records() before2 = [dict(r) for r in records2] assignments = {'WRK-ABSENCE-REC': [{'type': 'read_into', 'file': 'R01INNFIL', 'source_vars': []}]} _serialize_read_into_records(records2, assignments, fields_with_children, file_sec) assert records2 == before2 def test_multiline_read_into_parsed(): """RI-05: READ / INTO 分行书写仍能解析为 read_into 赋值""" proc = """PROCEDURE DIVISION. MAIN. PERFORM UNTIL WRK-EOF = 'Y' READ R01INNFIL INTO WRK-ABSENCE-REC AT END MOVE 'Y' TO WRK-EOF END-READ IF WRK-EOF = 'Y' EXIT PERFORM END-IF END-PERFORM. GOBACK. """ tree, asgn = build_branch_tree(proc, []) read_keys = [k for k in asgn if any( isinstance(a, dict) and a.get('type') == 'read_into' for a in asgn[k])] assert read_keys == ['WRK-ABSENCE-REC'], read_keys assert asgn['WRK-ABSENCE-REC'][0]['file'] == 'R01INNFIL'