Files
cobol-java-v3/test-data/r4_deep_coverage.py
NB-076 7a562c27a4 R4-R7: 全モジュール深層カバレッジ補完(727テスト/0FAIL)
R4: core.py(289IF) + __init__.py(91IF) 内部関数全網羅
R4-design: design.py(161IF) enum_paths/constraint/redefines/occurs
R4-cond: cond.py(51IF) 全演算子×T/F×MC/DC
R4-coverage: coverage.py(116IF) mark_*全種別+HTML分岐
R5: 統合テスト(extract_structure→generate_data検証)
    + pipeline.py(34IF)+hina_agent.py(12IF)+read.py(54IF)
    + output.py(19IF)+orchestrator.py+classifier.py追加
R6: 複合ネストIF/PERFORM/EVAL/SEARCH+PIC解析全部
R7: FD方向解析+混乱グループ+contradiction+LLM応答

残環境依存: web/api(6IF), web/worker(6IF), runners/(6IF), gcov(6IF)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 00:02:18 +08:00

1140 lines
50 KiB
Python

"""R4: 深層カバレッジ — cobol_testgen/core.py 全関数の分岐網羅
ターゲット: core.py (289IF) + __init__.py (91IF) の内部関数
R3 では外部APIのみカバーしていたものを、内部関数の全分岐まで掘り下げる。
"""
import sys, os, tempfile, shutil, json, re
from pathlib import Path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
P=0;F=0
def ck(v,m=""): global P,F; (P:=P+1) if v else (F:=F+1,print(f" FAIL {m}"))
def sec(n): print(f"\n--- {n} ---")
# ══════════════════════════════════════════════════════════════════
# 1. core.py — _BrParser 内部関数
# ══════════════════════════════════════════════════════════════════
sec("core._BrParser — parse_seq constructs")
from cobol_testgen.core import _BrParser, _basename, _init_child_names, _resolve_subscript, _apply_before_after
from cobol_testgen.core import trace_to_root, invert_through_chain, propagate_assignments, classify_field_roles
from cobol_testgen.core import scan_paragraphs, build_branch_tree
from cobol_testgen.models import BrIf, BrEval, BrSeq, BrPerform, BrSearch, Assign, CallNode, ExitNode, GoTo
# --- scan_paragraphs ---
p1 = scan_paragraphs(["MAIN.", "DISPLAY 'OK'.", "STOP RUN."])
ck("MAIN" in p1, "para basic")
p2 = scan_paragraphs(["PROCEDURE DIVISION."]); ck(len(p2)==0,"para no match")
p3 = scan_paragraphs(["IF .", "STOP RUN."]); ck(len(p3)==0,"para IF dot")
p4 = scan_paragraphs(["END-IF.", "STOP RUN."]); ck(len(p4)==0,"para scope ender")
p5 = scan_paragraphs(["S0 SECTION.","MAIN.","D 'OK'.","SUB.","D X."])
ck("S0" in p5 and "MAIN" in p5 and "SUB" in p5, "para section+multi")
# --- build_branch_tree ---
t1,a1 = build_branch_tree("PROCEDURE DIVISION.\nMAIN.\nSTOP RUN.\n",[])
ck(t1 is not None,"tree basic")
t2,a2 = build_branch_tree("MAIN.\nSTOP RUN.\n",[])
ck(t2 is not None,"tree no div")
t3,a3 = build_branch_tree("STOP RUN.",[])
ck(t3 is not None,"tree single line")
# --- IF ---
bp=_BrParser(["IF X>Y D 'A' ELSE D 'B'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrIf),"IF simple")
# IF compound condition
bp=_BrParser(["IF X>1 AND Y<5 D 'A' ELSE D 'B'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"IF compound")
# ELSE IF multi-line
bp=_BrParser(["IF X=1","D 'A'","ELSE","IF X=2","D 'B'","ELSE","D 'C'","END-IF","END-IF.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"IF ELSE IF")
if len(s.children)>0:
ck(s.children[0].false_seq is not None and len(s.children[0].false_seq.children)>0,"IF ELSE IF false")
# EVALUATE
bp=_BrParser(["EVALUATE X WHEN 1 D 'A' WHEN 2 D 'B' WHEN OTHER D 'C' END-EVALUATE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrEval),"EVAL basic")
# EVALUATE ALSO
bp=_BrParser(["EVALUATE X ALSO Y WHEN 1 ALSO 2 D 'A' WHEN OTHER D 'B' END-EVALUATE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrEval),"EVAL ALSO")
if len(s.children)>0:
ck(s.children[0].subjects is not None and len(s.children[0].subjects)>=2,"EVAL ALSO subjects")
# PERFORM UNTIL
bp=_BrParser(["PERFORM UNTIL WS-EOF='Y' D 'A' END-PERFORM.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrPerform),"PERF UNTIL")
# PERFORM TIMES
bp=_BrParser(["PERFORM 5 TIMES.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrPerform),"PERF TIMES")
# PERFORM THRU
bp=_BrParser(["PERFORM A THRU B.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrPerform),"PERF THRU")
# PERFORM para
bp=_BrParser(["PERFORM SUB.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrPerform),"PERF para")
# PERFORM VARYING (single line)
bp=_BrParser(["PERFORM VARYING I FROM 1 BY 1 UNTIL I>10 D I END-PERFORM.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrPerform),"PERF VARYING single")
# PERFORM VARYING (multi-line UNTIL)
bp=_BrParser(["PERFORM VARYING I FROM 1 BY 1","UNTIL I>10","D I","END-PERFORM.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"PERF VARYING multi")
# PERFORM VARYING para
bp=_BrParser(["PERFORM SUB VARYING I FROM 1 BY 1 UNTIL I>10.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"PERF VARYING para")
# PERFORM para UNTIL
bp=_BrParser(["PERFORM SUB UNTIL WS-EOF='Y'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"PERF para UNTIL")
# PERFORM VARYING with FROM/BY on second line
bp=_BrParser(["PERFORM VARYING I","FROM 1 BY 1","UNTIL I>10","D I","END-PERFORM.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"PERF VARYING splitted")
# CALL
bp=_BrParser(["CALL 'SUB' USING BY REFERENCE WS-A BY CONTENT WS-B BY VALUE 100.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],CallNode),"CALL")
if len(s.children)>0:
ck(len(s.children[0].using_params)>=3,"CALL params")
# SEARCH ALL
bp=_BrParser(["SEARCH ALL TBL WHEN KEY=100 D 'FOUND' END-SEARCH.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrSearch),"SEARCH ALL")
# SEARCH with AT END + VARYING
bp=_BrParser(["SEARCH TBL VARYING IDX AT END D 'NF' WHEN KEY=100 D 'F' END-SEARCH.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],BrSearch),"SEARCH VARYING")
# INITIALIZE
bp=_BrParser(["INITIALIZE WS-A WS-B REPLACING NUMERIC DATA BY 0 ALPHANUMERIC DATA BY SPACE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"INITIALIZE")
# STRING (_parse_string returns BrSeq wrapping Assign)
bp=_BrParser(["STRING WS-A DELIMITED BY SIZE WS-B DELIMITED BY SPACE INTO WS-C","END-STRING","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"STRING")
# UNSTRING
bp=_BrParser(["UNSTRING WS-SRC INTO WS-A WS-B","END-UNSTRING","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"UNSTRING")
# INSPECT TALLYING
bp=_BrParser(["INSPECT WS-TXT TALLYING WS-CNT FOR LEADING 'A' BEFORE INITIAL 'B'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"INSPECT tally")
# INSPECT REPLACING
bp=_BrParser(["INSPECT WS-TXT REPLACING ALL 'X' BY 'Y'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"INSPECT replace")
# INSPECT CONVERTING
bp=_BrParser(["INSPECT WS-TXT CONVERTING 'ABC' TO 'XYZ'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"INSPECT convert")
# READ INTO
bp=_BrParser(["READ F1 INTO WS-REC.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],Assign),"READ INTO")
# WRITE FROM
bp=_BrParser(["WRITE REC FROM WS-DATA.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"WRITE FROM")
# WRITE bare
bp=_BrParser(["WRITE REC.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"WRITE bare")
# REWRITE bare
bp=_BrParser(["REWRITE REC.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"REWRITE bare")
# SET TO TRUE
bp=_BrParser(["SET WS-FLG TO TRUE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SET TRUE")
# SET TO FALSE
bp=_BrParser(["SET WS-FLG TO FALSE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SET FALSE")
# GO TO
bp=_BrParser(["GO TO EXIT-PARA.","STOP RUN."], paragraphs={"EXIT-PARA":(0,1)}, raw_lines=["EXIT-PARA.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],GoTo),"GOTO")
# EXIT PARAGRAPH
bp=_BrParser(["EXIT PARAGRAPH.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1 and isinstance(s.children[0],ExitNode),"EXIT PARA")
# MOVE (variable to variable)
bp=_BrParser(["MOVE WS-SRC TO WS-TGT.","STOP RUN."], fields=[{"name":"WS-SRC"},{"name":"WS-TGT"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MOVE var")
# MOVE (literal)
bp=_BrParser(["MOVE 'HELLO' TO WS-TXT.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MOVE lit")
# COMPUTE (var op const)
bp=_BrParser(["COMPUTE X=Y+1.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE +")
# COMPUTE (const op var)
bp=_BrParser(["COMPUTE X=2*Y.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE *")
# COMPUTE (var op var)
bp=_BrParser(["COMPUTE X=A-B.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE - var")
# COMPUTE (complex)
bp=_BrParser(["COMPUTE X=(A+B)*C.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE complex")
# COMPUTE ROUNDED
bp=_BrParser(["COMPUTE X ROUNDED=Y/3.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE rounded")
# ADD (x TO y) literal
bp=_BrParser(["ADD 1 TO WS-CNT.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD TO")
# ADD (variable TO y)
bp=_BrParser(["ADD WS-INC TO WS-CNT.","STOP RUN."], fields=[{"name":"WS-INC"},{"name":"WS-CNT"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD VAR TO")
# ADD (x TO y GIVING z) literal
bp=_BrParser(["ADD 1 TO WS-CNT GIVING WS-RES.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD TO GIVING")
# ADD variable TO y GIVING z
bp=_BrParser(["ADD WS-A TO WS-B GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD VAR TO GIVING")
# ADD (GIVING multi) literal
bp=_BrParser(["ADD 1 2 3 GIVING WS-TOTAL.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD GIVING multi lit")
# ADD (GIVING multi) mixed
bp=_BrParser(["ADD WS-A WS-B 1 GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD GIVING mixed")
# ADD (GIVING multi) all fields
bp=_BrParser(["ADD WS-A WS-B GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"},{"name":"WS-C"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD GIVING fields")
# SUBTRACT (x FROM y) literal
bp=_BrParser(["SUBTRACT 1 FROM WS-CNT.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SUBTRACT FROM")
# SUBTRACT (x FROM y GIVING z)
bp=_BrParser(["SUBTRACT 1 FROM WS-CNT GIVING WS-RES.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SUB FROM GIVING")
# SUBTRACT variable FROM y GIVING z
bp=_BrParser(["SUBTRACT WS-A FROM WS-B GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SUB VAR FROM GIVING")
# MULTIPLY (x BY y)
bp=_BrParser(["MULTIPLY 2 BY WS-CNT.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MULTIPLY BY")
# MULTIPLY (a BY b GIVING z) literal
bp=_BrParser(["MULTIPLY 3 BY WS-CNT GIVING WS-RES.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MULT BY GIVING lit")
# MULTIPLY var BY var GIVING z
bp=_BrParser(["MULTIPLY WS-A BY WS-B GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MULT VAR BY GIVING")
# DIVIDE (x INTO y)
bp=_BrParser(["DIVIDE 2 INTO WS-NUM.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE INTO")
# DIVIDE (a INTO b GIVING z) literal
bp=_BrParser(["DIVIDE 10 INTO WS-NUM GIVING WS-RES.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE INTO GIVING")
# DIVIDE (a INTO b GIVING z REMAINDER r) literal → returns BrSeq as 1 child
bp=_BrParser(["DIVIDE 10 INTO WS-NUM GIVING WS-Q REMAINDER WS-R.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE INTO GIVING REM")
# DIVIDE var INTO var GIVING z
bp=_BrParser(["DIVIDE WS-A INTO WS-B GIVING WS-C.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE VAR INTO GIVING")
# DIVIDE var INTO var GIVING z REMAINDER r → BrSeq as 1 child
bp=_BrParser(["DIVIDE WS-A INTO WS-B GIVING WS-Q REMAINDER WS-R.","STOP RUN."], fields=[{"name":"WS-A"},{"name":"WS-B"}])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE VAR INTO GIVING REM")
# DIVIDE a BY b GIVING z
bp=_BrParser(["DIVIDE WS-A BY WS-B GIVING WS-C.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE BY GIVING")
# DIVIDE a BY b GIVING z REMAINDER r → BrSeq as 1 child
bp=_BrParser(["DIVIDE WS-A BY WS-B GIVING WS-Q REMAINDER WS-R.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE BY GIVING REM")
# ACCEPT (DATE/TIME/DAY)
bp=_BrParser(["ACCEPT WS-D FROM DATE.","ACCEPT WS-T FROM TIME.","ACCEPT WS-Y FROM DAY.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=3,"ACCEPT DATE/TIME/DAY")
# ACCEPT DAY-OF-WEEK / YEAR / HHMMSS / YYYYMMDD
bp=_BrParser(["ACCEPT WS-D FROM DAY-OF-WEEK.","ACCEPT WS-Y FROM YEAR.","ACCEPT WS-H FROM HHMMSS.","ACCEPT WS-YMD FROM YYYYMMDD.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=4,"ACCEPT DAY-OF-WEEK/YEAR/HHMMSS/YYYYMMDD")
# ACCEPT bare
bp=_BrParser(["ACCEPT WS-X.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ACCEPT bare")
# IF with THEN next line
bp=_BrParser(["IF X>1","THEN","D 'A'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"IF THEN next")
# IF with multi-line condition
bp=_BrParser(["IF X>1 AND","Y<5","D 'A'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"IF multi-line cond")
# ══════════════════════════════════════════════════════════════════
# 2. propagate_assignments — 全パス (1〜8) + 境界値
# ══════════════════════════════════════════════════════════════════
sec("propagate_assignments")
_f = [
{"name":"WS-A","pic_info":{"type":"numeric","digits":5,"decimal":0,"length":5,"signed":False}},
{"name":"WS-B","pic_info":{"type":"numeric","digits":5,"decimal":0,"length":5,"signed":False}},
{"name":"WS-C","pic_info":{"type":"numeric","digits":5,"decimal":0,"length":5,"signed":False}},
{"name":"WS-X","pic_info":{"type":"alphanumeric","length":10,"digits":0,"decimal":0,"signed":False}},
{"name":"WS-Y","pic_info":{"type":"alphabetic","length":5,"digits":0,"decimal":0,"signed":False}},
{"name":"WS-D","pic_info":{"type":"numeric","digits":8,"decimal":2,"length":10,"signed":True}},
{"name":"WS-FLG","pic_info":{"type":"alphanumeric","length":1,"digits":0,"decimal":0,"signed":False}},
]
# Pass 1: variable-to-variable MOVE
r={"WS-SRC":"100","WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"move","source_vars":["WS-SRC"]}]},_f)
ck(r.get("WS-A")==r.get("WS-SRC"),"p1 var move")
# Pass 2: literal MOVE numeric → zero-padded to 5 digits
r={"WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"move_literal","literal":"123"}]},_f)
ck(r.get("WS-A")=="00123","p2 lit num")
# Pass 2: literal MOVE alphanumeric → padded to 10
r={"WS-X":""}; propagate_assignments(r,{"WS-X":[{"type":"move_literal","literal":"HELLO"}]},_f)
ck(r.get("WS-X")=="HELLO ","p2 lit alpha")
# Pass 3: INITIALIZE (numeric → 00000)
r={"WS-A":"999"}; propagate_assignments(r,{"WS-A":[{"type":"initialize"}]},_f)
ck(r.get("WS-A")=="00000","p3 init num")
# Pass 3: INITIALIZE (alphanumeric → spaces)
r={"WS-X":"OLD"}; propagate_assignments(r,{"WS-X":[{"type":"initialize"}]},_f)
ck(" " in str(r.get("WS-X","")),"p3 init alpha")
# Pass 3: INITIALIZE with REPLACING matched
r={"WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"initialize","replacing":{"NUMERIC":"500"}}]},_f)
ck(r.get("WS-A")=="00500","p3 init repl num")
# Pass 3: INITIALIZE with REPLACING unmatched type (alpha but repl says NUMERIC)
r={"WS-X":""}; propagate_assignments(r,{"WS-X":[{"type":"initialize","replacing":{"NUMERIC":"100"}}]},_f)
ck(" " in str(r.get("WS-X","")),"p3 init repl mismatch")
# Pass 3.5: READ INTO
r={"FD-REC":"ABC","WS-REC":""}; propagate_assignments(r,{"WS-REC":[{"type":"read_into","file":"F1"}]},_f,file_sec={"F1":["FD-REC"]})
ck(r.get("WS-REC") is not None,"p3.5 read into")
# Pass 4: COMPUTE +
r={"WS-A":"00010"}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"+","const":5}]},_f)
ck(r.get("WS-A")=="00015","p4 compute +")
# COMPUTE -
r={"WS-A":"00020"}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"-","const":5}]},_f)
ck(r.get("WS-A")=="00015","p4 compute -")
# COMPUTE *
r={"WS-A":"00003"}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"*","const":4}]},_f)
ck(r.get("WS-A")=="00012","p4 compute *")
# COMPUTE /
r={"WS-A":"00100"}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"/","const":3}]},_f)
ck(r.get("WS-A")=="00033","p4 compute /")
# COMPUTE rem
r={"WS-A":"00010"}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"rem","const":3}]},_f)
ck(r.get("WS-A")=="00001","p4 compute rem")
# COMPUTE 2 vars +
r={"WS-A":"00010","WS-B":"00005"}; propagate_assignments(r,{"WS-D":[{"type":"compute","source_vars":["WS-A","WS-B"],"op":"+"}]},_f)
ck(r.get("WS-D") is not None,"p4 compute 2var +")
# COMPUTE 2 vars -
r={"WS-A":"00010","WS-B":"00003"}; propagate_assignments(r,{"WS-D":[{"type":"compute","source_vars":["WS-A","WS-B"],"op":"-"}]},_f)
ck(r.get("WS-D") is not None,"p4 compute 2var -")
# COMPUTE 2 vars /
r={"WS-A":"00006","WS-B":"00003"}; propagate_assignments(r,{"WS-D":[{"type":"compute","source_vars":["WS-A","WS-B"],"op":"/"}]},_f)
ck(r.get("WS-D") is not None,"p4 compute 2var /")
# COMPUTE 3+ vars +
r={"WS-A":"001","WS-B":"002","WS-C":"003"}; propagate_assignments(r,{"WS-D":[{"type":"compute","source_vars":["WS-A","WS-B","WS-C"],"op":"+"}]},_f)
ck(r.get("WS-D") is not None,"p4 compute 3var +")
# INSPECT TALLYING LEADING
r={"WS-X":"AAABBB","WS-CNT":""}; propagate_assignments(r,{"WS-CNT":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("tally",{"count_var":"WS-CNT","kind":"LEADING","char":"A","before_after":"","delimiter":""})]}]},_f)
ck(r.get("WS-CNT") is not None,"p4.5 tally LEADING")
# TALLYING TRAILING
r={"WS-X":"BBBAAA","WS-CNT":""}; propagate_assignments(r,{"WS-CNT":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("tally",{"count_var":"WS-CNT","kind":"TRAILING","char":"A","before_after":"","delimiter":""})]}]},_f)
ck(r.get("WS-CNT") is not None,"p4.5 tally TRAILING")
# TALLYING CHARACTERS
r={"WS-X":"ABCDEF","WS-CNT":""}; propagate_assignments(r,{"WS-CNT":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("tally",{"count_var":"WS-CNT","kind":"CHARACTERS","char":"","before_after":"","delimiter":""})]}]},_f)
ck(r.get("WS-CNT") is not None,"p4.5 tally CHARACTERS")
# REPLACING ALL
r={"WS-X":"HELLO WORLD"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("replace",{"kind":"ALL","src":"L","dst":"X","before_after":"","delimiter":""})]}]},_f)
ck("X" in r.get("WS-X",""),"p4.5 replace ALL")
# REPLACING LEADING
r={"WS-X":"AAABBB"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("replace",{"kind":"LEADING","src":"A","dst":"X","before_after":"","delimiter":""})]}]},_f)
ck("X" in r.get("WS-X",""),"p4.5 replace LEADING")
# REPLACING FIRST
r={"WS-X":"ABABAB"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("replace",{"kind":"FIRST","src":"A","dst":"X","before_after":"","delimiter":""})]}]},_f)
ck("X" in r.get("WS-X",""),"p4.5 replace FIRST")
# REPLACING CHARACTERS (else)
r={"WS-X":"TEST"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("replace",{"kind":"CHARACTERS","src":"A","dst":"X","before_after":"","delimiter":""})]}]},_f)
ck(r.get("WS-X","") is not None,"p4.5 replace CHARACTERS")
# CONVERTING
r={"WS-X":"ABC"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("convert",{"from_chars":"ABC","to_chars":"XYZ","before_after":"","delimiter":""})]}]},_f)
ck(r.get("WS-X")=="XYZ","p4.5 convert")
# INSPECT tally with BEFORE
r={"WS-X":"XXXYYY","WS-CNT":""}; propagate_assignments(r,{"WS-CNT":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("tally",{"count_var":"WS-CNT","kind":"LEADING","char":"X","before_after":"BEFORE","delimiter":"Y"})]}]},_f)
ck(r.get("WS-CNT") is not None,"p4.5 tally BEFORE")
# INSPECT replace with AFTER
r={"WS-X":"PRE--DATA--POST"}; propagate_assignments(r,{"WS-X":[{"type":"inspect","tgt":"WS-X","source_vars":["WS-X"],
"sub_ops":[("replace",{"kind":"ALL","src":"-","dst":"_","before_after":"AFTER","delimiter":"--"})]}]},_f)
ck(r.get("WS-X") is not None,"p4.5 replace AFTER")
# Pass 5: STRING concat
r={"WS-A":"HELLO","WS-B":"WORLD","WS-X":""}; propagate_assignments(r,{"WS-X":[{"type":"string_concat","source_vars":["WS-A","WS-B"]}]},_f)
ck(r.get("WS-X")=="HELLOWORLD","p5 string")
# Pass 5: UNSTRING (index 0)
r={"WS-X":"DATA","WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"unstring_split","source_vars":["WS-X"],"index":0}]},_f)
ck(r.get("WS-A")=="DATA","p5 unstring idx0")
# Pass 5: UNSTRING (index > 0)
r={"WS-X":"DATA","WS-B":""}; propagate_assignments(r,{"WS-B":[{"type":"unstring_split","source_vars":["WS-X"],"index":1}]},_f)
ck(r.get("WS-B") is not None,"p5 unstring idx1")
# Pass 6: WRITE FROM (with proper levels)
_f_fd=[{"name":"REC","level":5,"pic_info":{"type":"group","length":10}},
{"name":"REC-A","level":10,"pic_info":{"type":"alphanumeric","length":5}},
{"name":"REC-B","level":10,"pic_info":{"type":"alphanumeric","length":5}}]
r={"WS-BUF":"AAAAABBBBB"}; propagate_assignments(r,{"WS-BUF":[{"type":"write_from","file":"REC","source_vars":["WS-BUF"]}]},_f_fd)
ck("REC-A" in r or "REC-B" in r,"p6 write from")
# Pass 6: READ INTO (second pass lines)
r={"FD-REC":"XYZ","WS-REC":""}; propagate_assignments(r,{"WS-REC":[{"type":"read_into","file":"F1"}]},_f,file_sec={"F1":["FD-REC"]})
ck(r.get("WS-REC") is not None,"p6 read into 2")
# Pass 7: ACCEPT FROM DATE (alphanumeric)
r={"WS-D":""}; propagate_assignments(r,{"WS-D":[{"type":"accept","from":"DATE"}]},_f)
ck(len(str(r.get("WS-D","")))>0,"p7 accept DATE")
# Pass 7: ACCEPT FROM TIME
r={"WS-D":""}; propagate_assignments(r,{"WS-D":[{"type":"accept","from":"TIME"}]},_f)
ck(len(str(r.get("WS-D","")))>0,"p7 accept TIME")
# Pass 7: ACCEPT FROM DAY
r={"WS-D":""}; propagate_assignments(r,{"WS-D":[{"type":"accept","from":"DAY"}]},_f)
ck(len(str(r.get("WS-D","")))>0,"p7 accept DAY")
# Pass 7: ACCEPT DAY-OF-WEEK (numeric → zfill total=10)
r={"WS-D":""}; propagate_assignments(r,{"WS-D":[{"type":"accept","from":"DAY-OF-WEEK"}]},_f)
ck(r.get("WS-D")=="0000000003","p7 accept DAY-OF-WEEK")
# Pass 7: ACCEPT YEAR (numeric → zfill)
r={"WS-D":""}; propagate_assignments(r,{"WS-D":[{"type":"accept","from":"YEAR"}]},_f)
ck(r.get("WS-D")=="0000002026","p7 accept YEAR")
# Pass 7: ACCEPT numeric DATE
r={"WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"accept","from":"DATE"}]},_f)
ck(len(str(r.get("WS-A","")))>0,"p7 accept DATE numeric")
# Pass 8: SET TRUE
r={"WS-FLG":""}; propagate_assignments(r,{"WS-FLG":[{"type":"set_true","88_name":"FLG-88","value":"Y"}]},_f)
ck(r.get("WS-FLG") is not None,"p8 set true")
# SET TRUE alpha
r={"WS-X":""}; propagate_assignments(r,{"WS-X":[{"type":"set_true","88_name":"X-88","value":"Y"}]},_f)
ck(r.get("WS-X") is not None,"p8 set true alpha")
# Figurative constants
r={"WS-A":"","WS-X":""}; propagate_assignments(r,{"WS-A":[{"type":"move_literal","literal":"ZERO"}],"WS-X":[{"type":"move_literal","literal":"SPACE"}]},_f)
ck(r.get("WS-A") is not None and r.get("WS-X") is not None,"fig ZERO+SPACE")
# HIGH-VALUE numeric
r={"WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"move_literal","literal":"HIGH-VALUE"}]},_f)
ck(r.get("WS-A") is not None,"fig HIGH-VALUE")
# LOW-VALUE alpha
r={"WS-X":""}; propagate_assignments(r,{"WS-X":[{"type":"move_literal","literal":"LOW-VALUE"}]},_f)
ck(r.get("WS-X") is not None,"fig LOW-VALUE")
# Unknown type INITIALIZE
_unk=[{"name":"WS-Z","pic_info":{"type":"unknown","length":0}}]
r={"WS-Z":"X"}; propagate_assignments(r,{"WS-Z":[{"type":"initialize"}]},_unk)
ck(r.get("WS-Z") is not None,"init unknown")
# Dict-style assignment
r={"WS-A":""}; propagate_assignments(r,{"WS-A":{"type":"move_literal","literal":"999"}},_f)
ck(r.get("WS-A")=="00999","dict assign")
# Self-ref unanchored compute (should converge after iter 0)
r={"WS-A":""}; propagate_assignments(r,{"WS-A":[{"type":"compute","source_vars":["WS-A"],"op":"+","const":1}]},_f)
ck(r.get("WS-A") is not None,"self-ref unanchored")
# Anchored compute (not skipped)
r={"WS-A":"10"}; propagate_assignments(r,{"WS-A":[{"type":"move_literal","literal":"10"},{"type":"compute","source_vars":["WS-A"],"op":"+","const":5}]},_f)
ck(int(str(r.get("WS-A","0")))>=10,"anchored compute")
# ══════════════════════════════════════════════════════════════════
# 3. classify_field_roles
# ══════════════════════════════════════════════════════════════════
sec("classify_field_roles")
# Basic branch-tree roles
t=BrSeq(); t.add(BrIf("WS-A>0"))
t.children[0].cond_tree=type('obj',(object,),{"field":"WS-A","op":">","value":"0","is_true":True})()
t.children[0].true_seq=BrSeq(); t.children[0].true_seq.add(Assign("WS-B",{"type":"move_literal","literal":"100","source_vars":[]}))
rf=[{"name":"WS-A"},{"name":"WS-B"},{"name":"WS-C"}]
r=classify_field_roles(t,{},rf)
ck("WS-A" in r and "WS-B" in r,"basic roles")
# LINKAGE defaults to input
r=classify_field_roles(BrSeq(),{},[{"name":"P","section":"LINKAGE"},{"name":"W","section":"WORKING-STORAGE"}])
ck("P" in r or "W" in r,"LINKAGE default")
# CALL reference (read+write)
t=BrSeq(); t.add(CallNode("SUB",using_params=[{"name":"P","mechanism":"reference"}]))
r=classify_field_roles(t,{},[{"name":"P","section":"LINKAGE"}])
ck("P" in r,"CALL ref")
# CALL content (read only)
t=BrSeq(); t.add(CallNode("SUB",using_params=[{"name":"P","mechanism":"content"}]))
r=classify_field_roles(t,{},[{"name":"P","section":"LINKAGE"}])
ck("P" in r,"CALL content")
# ACCEPT/DISPLAY
r=classify_field_roles(BrSeq(),{},[{"name":"WS-INP"},{"name":"WS-OUT"}],proc_text="ACCEPT WS-INP. DISPLAY WS-OUT.")
ck("WS-INP" in r and "WS-OUT" in r,"ACCEPT/DISPLAY")
# EVALUATE subject
t=BrSeq(); en=BrEval("WS-A"); en.when_list=[("1",BrSeq())]; en.cond_trees=[None]; en.other_seq=BrSeq(); t.add(en)
r=classify_field_roles(t,{},[{"name":"WS-A"}])
ck("WS-A" in r,"EVAL subject")
# read_into
t=BrSeq(); t.add(Assign("WS-R",{"type":"read_into","source_vars":[],"file":"F1"}))
r=classify_field_roles(t,{},[{"name":"WS-R"}])
ck("WS-R" in r,"read_into")
# PERFORM condition+varying
t=BrSeq(); pn=BrPerform("until",condition="WS-A>0"); pn.varying_var="WS-I"; pn.body_seq=BrSeq(); t.add(pn)
r=classify_field_roles(t,{},[{"name":"WS-A"},{"name":"WS-I"}])
ck("WS-A" in r and "WS-I" in r,"PERF var")
# Initialize (child names)
t=BrSeq(); t.add(Assign("GRP",{"type":"initialize","source_vars":[]}))
r=classify_field_roles(t,{},[{"name":"GRP"},{"name":"GRP-A"}])
ck("GRP" in r or "GRP-A" in r,"init grp")
# ══════════════════════════════════════════════════════════════════
# 4. trace_to_root
# ══════════════════════════════════════════════════════════════════
sec("trace_to_root")
v,c=trace_to_root("X",{"X":[{"type":"move_literal","literal":"100","source_vars":[]}]},[])
ck(v is not None and len(c)>=1,"trace simple")
v,c=trace_to_root("X",{"X":[{"type":"move","source_vars":["Y"]}],"Y":[{"type":"move","source_vars":["Z"]}],"Z":[{"type":"move_literal","literal":"100","source_vars":[]}]},[])
ck(len(c)>=2,"trace multi-hop")
v,c=trace_to_root("X",{"X":[{"type":"move","source_vars":["X"]}]},[])
ck(v is not None,"trace self-ref")
v,c=trace_to_root("X",{"X":[{"type":"compute","source_vars":["Y"],"op":"+","const":1}],"Y":[{"type":"move_literal","literal":"100","source_vars":[]}]},[])
ck(len(c)>=1,"trace adder")
v,c=trace_to_root("X",{"X":[{"type":"compute","source_vars":["Y","Z"],"op":"+"}],"Y":[{"type":"move_literal","literal":"100","source_vars":[]}]},[])
ck(len(c)>=1,"trace multi-source")
v,c=trace_to_root("X",{"X":[{"type":"move_literal","literal":"100"}]},[],path_assign={"X":[{"type":"move_literal","literal":"200"}]})
ck(len(c)>=1,"trace path_assign")
v,c=trace_to_root("X",{"X":[{"type":"move","source_vars":["X"]},{"type":"move_literal","literal":"100","source_vars":[]}]},[])
ck(len(c)>=1,"trace skip selfref")
v,c=trace_to_root("X",{"X":[{"type":"move","source_vars":["Y"]}]},[])
ck(len(c)==1,"trace missing src")
v,c=trace_to_root("X",{},[]); ck(c==[],"trace empty")
# ══════════════════════════════════════════════════════════════════
# 5. invert_through_chain
# ══════════════════════════════════════════════════════════════════
sec("invert_through_chain")
v,_,_=invert_through_chain("X",[("X",{"type":"move","source_vars":["Y"]})],">","100"); ck(v is not None,"inv move")
v,o,_=invert_through_chain("X",[("X",{"type":"compute","op":"+","const":5,"source_vars":["Y"]})],">","100"); ck(o is not None,"inv +")
v,_,_=invert_through_chain("X",[("X",{"type":"compute","op":"-","const":5,"source_vars":["Y"]})],">","100"); ck(v is not None,"inv -")
v,_,_=invert_through_chain("X",[("X",{"type":"compute","op":"*","const":2,"source_vars":["Y"]})],">","100"); ck(v is not None,"inv *")
v,_,_=invert_through_chain("X",[("X",{"type":"compute","op":"/","const":2,"source_vars":["Y"]})],">","100"); ck(v is not None,"inv /")
v,_,_=invert_through_chain("X",[("X",{"type":"compute","op":"/","const":0,"source_vars":["Y"]})],">","100"); ck(v is not None,"inv div0")
v,_,_=invert_through_chain("X",[("X",{"type":"compute","op":"+","const":None,"source_vars":["Y","Z"]})],">","100")
ck(v is not None,"inv multi")
v,_,_=invert_through_chain("X",[("X",{"type":"move","source_vars":["Y"]})],">","ABC"); ck(v is not None,"inv non-num")
v,o,_=invert_through_chain("X",[("X",{"type":"compute","op":"/","const":3,"source_vars":["Y"]})],">","10"); ck(v is not None,"inv float")
# ══════════════════════════════════════════════════════════════════
# 6. 補助関数
# ══════════════════════════════════════════════════════════════════
sec("Helpers")
ck(_basename("WS-TABLE(1)")=="WS-TABLE","base subscript")
ck(_basename("WS-X")=="WS-X","base no sub")
ck(_basename("")=="","base empty")
# _init_child_names
fg=[{"name":"GRP","level":5,"pic_info":{"type":"group"}},
{"name":"SUB","level":10,"pic_info":{"type":"unknown"}},
{"name":"A","level":15,"pic_info":{"type":"numeric","digits":3}},
{"name":"B","level":15,"pic_info":{"type":"alphanumeric","length":5}},
{"name":"B-88","level":15,"is_88":True},
{"name":"C","level":15,"redefines":"B"},
{"name":"D","level":77}]
c=_init_child_names("GRP",fg); ck(len(c)>=1,"init children")
ck("A" in c or "B" in c,"init recursive")
# _resolve_subscript
ck(_resolve_subscript("X(IDX)",{"IDX":3})=="X(3)","resolve num")
ck(eval("_resolve_subscript('X(IDX)',{'IDX':'VAL'})")=="X(IDX)","resolve non-num")
ck(_resolve_subscript("X",{})=="X","resolve no paren")
ck(_resolve_subscript("WS-TBL(WS-IDX)",{"WS-IDX":5})=="WS-TBL(5)","resolve real")
# _apply_before_after
ck(_apply_before_after("ABCDEF","BEFORE","CD")=="AB","before")
ck(_apply_before_after("ABCDEF","AFTER","CD")=="EF","after")
ck(_apply_before_after("ABCDEF","BEFORE","NONE")=="ABCDEF","before no match")
ck(_apply_before_after("ABCDEF","","")=="ABCDEF","empty")
ck(_apply_before_after("ABCDEF","UNKNOWN","X")=="ABCDEF","unknown")
# _expand_figurative
ck(_BrParser._expand_figurative("ZERO")=="0","fig ZERO")
ck(_BrParser._expand_figurative("SPACE")==" ","fig SPACE")
ck(_BrParser._expand_figurative("OTHER")=="OTHER","fig OTHER")
# _parse_inspect_phrase via instance
bp_ip=_BrParser([])
p0=bp_ip._parse_inspect_phrase("TALLYING CNT FOR LEADING 'A' BEFORE INITIAL 'B'")
ck(p0 is not None and p0[0]=="tally","phrase tally")
p1=bp_ip._parse_inspect_phrase("REPLACING ALL 'X' BY 'Y' AFTER INITIAL 'Z'")
ck(p1 is not None and p1[0]=="replace","phrase replace")
p2=bp_ip._parse_inspect_phrase("CONVERTING 'ABC' TO 'XYZ'")
ck(p2 is not None and p2[0]=="convert","phrase convert")
p3=bp_ip._parse_inspect_phrase("UNKNOWN")
ck(p3 is None,"phrase unknown")
# ══════════════════════════════════════════════════════════════════
# 7. _BrParser._parse_if 詳細
# ══════════════════════════════════════════════════════════════════
sec("_parse_if edge cases")
bp=_BrParser(["IF X=1 D 'A' ELSE IF X=2 D 'B' ELSE D 'C'.","STOP RUN."])
if1=bp._parse_if()
ck(if1 is not None and if1.true_seq is not None,"parse_if ELSE IF")
# IF with THEN next line
bp=_BrParser(["IF X>1","THEN","D 'A'.","END-IF.","STOP RUN."])
if2=bp._parse_if(); ck(if2 is not None,"parse_if THEN line")
# IF multi-line cond
bp=_BrParser(["IF X>1","AND Y<5","D 'A'.","STOP RUN."])
if3=bp._parse_if(); ck(if3 is not None,"parse_if multi cond")
# ══════════════════════════════════════════════════════════════════
# 8. expand_occurs 詳細
# ══════════════════════════════════════════════════════════════════
sec("expand_occurs")
from cobol_testgen import expand_occurs, _add_subscript
ck(_add_subscript("WS-CELL",1)=="WS-CELL(1)","add_sub 1")
ck(_add_subscript("WS-CELL(1)",2)=="WS-CELL(1,2)","add_sub multi")
# with children
eo=expand_occurs([{"name":"T","level":5,"occurs":3,"is_88":False,"occurs_depending":None},
{"name":"E","level":10,"pic":"X","occurs":0,"is_88":False}])
ck(len(eo)>=3,"occurs children")
# without children
eo=expand_occurs([{"name":"T","level":5,"occurs":2,"is_88":False,"occurs_depending":None}])
ck(len(eo)>=2,"occurs no child")
# with 88-level
eo=expand_occurs([{"name":"T","level":5,"occurs":2,"is_88":False,"occurs_depending":None},
{"name":"V","level":10,"pic":"X","occurs":0,"is_88":True}])
ck(len(eo)>=2,"occurs 88")
# nested occurs (child also has occurs)
eo=expand_occurs([{"name":"T","level":5,"occurs":2,"is_88":False,"occurs_depending":None},
{"name":"S","level":10,"occurs":3,"is_88":False,"occurs_depending":None}])
ck(len(eo)>=2,"occurs nested")
# 77-level break
eo=expand_occurs([{"name":"T","level":5,"occurs":2,"is_88":False,"occurs_depending":None},
{"name":"X","level":77,"occurs":0,"is_88":False}])
ck(len(eo)>=2,"occurs 77-break")
# recursive
eo=expand_occurs([{"name":"T","level":5,"occurs":2,"is_88":False,"occurs_depending":None},
{"name":"S","level":10,"occurs":0,"is_88":False,"pic":"X"}])
ck(len(eo)>=3,"occurs recursive")
# ══════════════════════════════════════════════════════════════════
# 9. extract_structure — 内部関数群
# ══════════════════════════════════════════════════════════════════
sec("extract_structure internals")
from cobol_testgen import extract_structure
es=extract_structure(" IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. 01 A PIC 9. PROCEDURE DIVISION. IF A>1 D 'Y' ELSE D 'N'. STOP RUN.")
ck(es.get("total_branches") is not None,"es basic")
_ML = "\n".join # shorthand for multi-line COBOL source
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC 9.",
" PROCEDURE DIVISION.",
" EVALUATE X",
" WHEN 1 DISPLAY 'A'",
" WHEN 2 DISPLAY 'B'",
" WHEN OTHER DISPLAY 'C'",
" END-EVALUATE.",
" STOP RUN."]))
ck(es.get("has_evaluate")==True,"es eval")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 A PIC 9.",
" PROCEDURE DIVISION.",
" CALL 'SUB' USING A.",
" STOP RUN."]))
ck(es.get("has_call")==True,"es call")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC 9.",
" PROCEDURE DIVISION.",
" DIVIDE 100 INTO X.",
" STOP RUN."]))
ck(100.0 in es.get("divide_constants",[]),"es divide")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC X(10).",
" PROCEDURE DIVISION.",
" INSPECT X TALLYING CNT FOR CHARACTERS.",
" STOP RUN."]))
ck(es.get("has_inspect") is not None,"es inspect")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC X(10).",
" 01 Y PIC X(10).",
" PROCEDURE DIVISION.",
" STRING X INTO Y END-STRING.",
" STOP RUN."]))
ck(es.get("has_string") is not None,"es string")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 WS-KEY PIC 9.",
" 01 WS-PREV-KEY PIC 9.",
" PROCEDURE DIVISION.",
" IF WS-KEY = WS-PREV-KEY DISPLAY 'SAME'.",
" STOP RUN."]))
ck(es.get("total_branches")>=1,"es key")
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC 9.",
" PROCEDURE DIVISION.",
" PERFORM 5 TIMES",
" DISPLAY 'A'",
" END-PERFORM.",
" STOP RUN."]))
ck(len(es.get("perform_patterns",[]))>=1,"es perf")
es=extract_structure(" IDENTIFICATION DIVISION.\n PROGRAM-ID. T.")
ck(es.get("total_branches")==0,"es no proc")
es=extract_structure("")
ck(es.get("file_count") is not None,"es empty")
# Compound IF
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 A PIC 9.",
" 01 B PIC 9.",
" PROCEDURE DIVISION.",
" IF A > 1 AND B < 5 DISPLAY 'Y' ELSE DISPLAY 'N'.",
" STOP RUN."]))
ck(es.get("if_types",{}).get("compound",0)>=1,"es compound")
# Equality IF
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 A PIC 9.",
" PROCEDURE DIVISION.",
" IF A = 1 DISPLAY 'Y'.",
" STOP RUN."]))
ck(es.get("if_types",{}).get("equality",0)>=1,"es equality")
# Comparison IF
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 A PIC 9.",
" PROCEDURE DIVISION.",
" IF A > 5 DISPLAY 'Y'.",
" STOP RUN."]))
ck(es.get("if_types",{}).get("comparison",0)>=1,"es comparison")
# Nested IF
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 A PIC 9.",
" 01 B PIC 9.",
" PROCEDURE DIVISION.",
" IF A > 0",
" IF B > 0 DISPLAY 'Y'",
" ELSE DISPLAY 'N'.",
" STOP RUN."]))
ck(es.get("if_types",{}).get("nested_depth",0)>=1,"es nested")
# Variable patterns
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 WS-PREV-KEY PIC 9.",
" 01 WS-CNT PIC 9.",
" 01 WS-ERR PIC X.",
" 01 WS-SW PIC X.",
" 01 WS-IDX PIC 9.",
" 01 WS-SAVE-KEY PIC X.",
" 01 WS-WK PIC X."]))
ck(len(es.get("variable_patterns",{}))>0,"es var patterns")
# Main loop with PERFORM + READ (needs proper COBOL structure, FILE-CONTROL before DATA DIVISION)
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 WS-EOF PIC X.",
" 01 WS-KEY PIC X.",
" PROCEDURE DIVISION.",
" PERFORM UNTIL WS-EOF = 'Y'",
" READ FILE1 INTO WS-KEY",
" END-PERFORM.",
" STOP RUN."]))
ck(es.get("main_loop") is not None or es.get("perform_patterns") is not None,"es main loop")
# OPEN/CLOSE pattern (proper multi-line)
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC 9.",
" PROCEDURE DIVISION.",
" OPEN INPUT F1.",
" CLOSE F1."]))
ck(es.get("open_pattern") in ("sequential","open-close-open"),"es open pattern")
# FILLER
es=extract_structure(_ML([
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. T.",
" DATA DIVISION.",
" WORKING-STORAGE SECTION.",
" 01 X PIC 9.",
" 01 FILLER PIC X(10)."]))
ck(es.get("file_count")>=0,"es filler")
# ══════════════════════════════════════════════════════════════════
# 10. incremental_supplement
# ══════════════════════════════════════════════════════════════════
sec("incremental_supplement")
from cobol_testgen import incremental_supplement
t=BrSeq(); t.add(BrIf("X>0"))
s=incremental_supplement(t,[1]); ck(len(s)>=1,"incr basic")
s=incremental_supplement(t,[]); ck(len(s)==0,"incr empty")
s=incremental_supplement(t,[999]); ck(len(s)==0,"incr gap miss")
t2=BrSeq()
en=BrEval("X"); en.when_list=[("1",BrSeq())]; en.cond_trees=[None]; en.other_seq=BrSeq(); t2.add(en)
s=incremental_supplement(t2,[1]); ck(len(s)>=1,"incr eval")
pn=BrPerform("until",condition="X>0"); pn.body_seq=BrSeq()
t2.add(pn)
s=incremental_supplement(t2,[1]); ck(s is not None,"incr perform")
# ══════════════════════════════════════════════════════════════════
# 11. generate_data
# ══════════════════════════════════════════════════════════════════
sec("generate_data")
from cobol_testgen import generate_data
gd=generate_data(" IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. 01 X PIC 9."); ck(len(gd)==0,"gd no proc")
gd=generate_data(" IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. 01 A PIC 99. PROCEDURE DIVISION. IF A>50 D 'Y' ELSE D 'N'. STOP RUN.")
ck(len(gd)>=1,"gd simple")
gd=generate_data(" IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. 01 X PIC 9. PROCEDURE DIVISION. STOP RUN.",structure={"branch_tree_obj":BrSeq()})
ck(len(gd)>=0,"gd struct")
# ══════════════════════════════════════════════════════════════════
# 12. _parse_compute_expr 全パターン
# ══════════════════════════════════════════════════════════════════
sec("_parse_compute_expr patterns")
px=_BrParser._parse_compute_expr
ck(px(None,"X","2*Y") is not None,"pexpr const*var")
ck(px(None,"X","Y+1") is not None,"pexpr var+const")
ck(px(None,"X","A-B") is not None,"pexpr var-var")
ck(px(None,"X","(A+B)*C-D") is not None,"pexpr complex")
ck(px(None,"X","") is not None,"pexpr empty")
# ══════════════════════════════════════════════════════════════════
# 13. 境界値ケース: _BrParser エッジ
# ══════════════════════════════════════════════════════════════════
sec("boundary cases")
# Empty parse_seq
bp=_BrParser([]); s=bp.parse_seq(); ck(len(s.children)==0,"empty parse")
# Unrecognized line (just advances)
bp=_BrParser(["UNKNOWN STMT.","STOP RUN."]); s=bp.parse_seq(terminators={"STOP RUN"})
ck(bp.pos==2,"unknown line")
# compute with missing expr (peek next line)
bp=_BrParser(["COMPUTE X =","Y+1","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"compute multi-line")
# ADD GIVING mixed with field and literal
bp=_BrParser(["ADD 1 2 3 GIVING WS-TOTAL.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"ADD GIVING all lit")
# DIVIDE BY GIVING (not INTO)
bp=_BrParser(["DIVIDE A BY B GIVING C.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE BY GIVING")
# DIVIDE BY GIVING REMAINDER → BrSeq as 1 child
bp=_BrParser(["DIVIDE A BY B GIVING C REMAINDER D.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"DIVIDE BY GIVING REM")
# MOVE with subscript
bp=_BrParser(["MOVE 100 TO WS-TBL(WS-IDX).","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MOVE subscript")
# MOVE with subscript source
bp=_BrParser(["MOVE WS-SRC TO WS-TGT(1).","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"MOVE subscript tgt")
# ADD variable TO y with unknown var → falls through
bp=_BrParser(["ADD UNKNOWN TO WS-X.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=0,"ADD unknown")
# COMPUTE with continuation on next line
bp=_BrParser(["COMPUTE X ROUNDED =", "Y + 1", "STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"COMPUTE multi expr")
# GO TO with body
bp=_BrParser(["GO TO SUB.","STOP RUN."], paragraphs={"SUB":(0,1)}, raw_lines=["SUB.","D 'OK'."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"GOTO with body")
# _is_end with end_check
bp=_BrParser(["WHEN X>0 D 'A'.","STOP RUN."])
s=bp.parse_seq(end_check=lambda l: l.startswith("WHEN"))
ck(len(s.children)==0,"is_end custom")
# EVALUATE with AND/OR continuation
bp=_BrParser(["EVALUATE X","WHEN 1","AND 2","D 'A'","END-EVALUATE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"EVAL AND cont")
# CALL with empty params
bp=_BrParser(["CALL 'SUB'.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=0,"CALL no params")
# CALL with malformed line
bp=_BrParser(["CALL","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=0,"CALL malformed")
# SET with unknown 88-level
bp=_BrParser(["SET WS-UNKNOWN TO TRUE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SET unknown 88 true")
bp=_BrParser(["SET WS-UNKNOWN TO FALSE.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=1,"SET unknown 88 false")
# MULTIPLY with unknown var (no field match) → fall through
bp=_BrParser(["MULTIPLY UNKNOWN BY WS-X.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=0,"MULT unknown")
# ADD string literal (not numeric)
bp=_BrParser(["ADD 'ABC' TO WS-X.","STOP RUN."])
s=bp.parse_seq(terminators={"STOP RUN"})
ck(len(s.children)>=0,"ADD string")
print(f"\n{'='*55}\nR4: {P} PASS / {F} FAIL\n{'='*55}")
if F > 0: sys.exit(1)