"""R4: 深層カバレッジ — cobol_testgen/coverage.py (116IF)""" import sys, os; 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} ---") from cobol_testgen.coverage import (collect_decision_points,mark_coverage,locate_decision_lines, _build_search_patterns,_normalize,_mark_if,_mark_eval,_mark_perform,_mark_search, _get_fields_in_cond,DecisionPoint,LeafStat,check_coverage,run_coverage,_find_proc_range) from cobol_testgen.models import (BrSeq,BrIf,BrEval,BrPerform,BrSearch,Assign,CallNode,CondLeaf,CondAnd) import tempfile, json from pathlib import Path sec("collect_decision_points") f=[{"name":"X","pic_info":{"type":"numeric","digits":3}},{"name":"Y","pic_info":{"type":"alphanumeric","length":5}}] # BrIf simple parsed bn=BrIf("X>5"); bn.true_seq=BrSeq(); bn.false_seq=BrSeq() pts,leaves=collect_decision_points(bn,f) ck(len(pts)>=1,"collect IF simple") # BrIf compound cond_tree bn2=BrIf("X>5 AND Y=A"); bn2.cond_tree=CondAnd(CondLeaf("X",">","5"),CondLeaf("Y","=","A")) bn2.true_seq=BrSeq(); bn2.false_seq=BrSeq() pts2,_=collect_decision_points(bn2,f) ck(len(pts2)>=1,"collect IF compound") # BrIf no parsed, no cond_tree (fallback) bn3=BrIf("COMPLEX"); bn3.true_seq=BrSeq(); bn3.false_seq=BrSeq() pts3,_=collect_decision_points(bn3,f) ck(len(pts3)>=1,"collect IF fallback") # BrEval en=BrEval("X"); en.when_list=[("1",BrSeq())]; en.other_seq=BrSeq(); en.has_other=True pts4,_=collect_decision_points(en,f); ck(len(pts4)>=1,"collect EVAL") # BrSearch sn=BrSearch("TBL"); sn.when_list=[("KEY=1",BrSeq())]; sn.has_at_end=True; sn.at_end_seq=BrSeq() sn.cond_trees=[CondLeaf("KEY","=","1")] pts5,_=collect_decision_points(sn,f); ck(len(pts5)>=1,"collect SEARCH") # BrPerform until with simple condition pn=BrPerform("until",condition="X>5"); pn.body_seq=BrSeq() pts6,_=collect_decision_points(pn,f); ck(len(pts6)>=1,"collect PERF until") # BrPerform until with compound condition pn2=BrPerform("until",condition="X>5 AND Y=A"); pn2.body_seq=BrSeq() pts7,_=collect_decision_points(pn2,f); ck(len(pts7)>=1,"collect PERF compound") # BrPerform para (no decision point) pn3=BrPerform("para",target="SUB"); pn3.body_seq=BrSeq() pts8,_=collect_decision_points(pn3,f); ck(len(pts8)>=0,"collect PERF para") # BrSeq pts9,_=collect_decision_points(BrSeq(),f); ck(len(pts9)==0,"collect empty seq") sec("_mark_if") # Simple parsed dp1=DecisionPoint(id=1,kind="IF",label="X>5",branch_names=["T","F"]) dp1.parsed=("X",">","5") cons=[("X",">","5",True)] _mark_if(dp1,cons); ck('T' in dp1.active_branches,"mark_if simple T") _mark_if(dp1,[("X",">","5",False)]); ck('F' in dp1.active_branches,"mark_if simple F") # Cond tree + leaves (use SAME leaf objects from the tree) leaf_x=CondLeaf("X",">","5"); leaf_y=CondLeaf("Y","=","A") dp2=DecisionPoint(id=2,kind="IF",label="X>5 AND Y=A",branch_names=["T","F"]) dp2.cond_tree=CondAnd(leaf_x,leaf_y) dp2.cond_leaves=[leaf_x,leaf_y] _mark_if(dp2,[("X",">","5",True),("Y","=","A",True)]); ck('T' in dp2.active_branches,"mark_if tree T") # Fallback (matched <= 1) dp3=DecisionPoint(id=3,kind="IF",label="Z>0",branch_names=["T","F"]) dp3.leaves=[LeafStat(field="Z",op=">",value="0")] _mark_if(dp3,[("Z",">","0",True)]); ck('T' in dp3.active_branches,"mark_if leaf T") sec("_mark_eval") # Non-TRUE subject dp4=DecisionPoint(id=4,kind="EVALUATE",label="X",branch_names=["WHEN 1","WHEN 2","OTHER"]) _mark_eval(dp4,[("X","=","1",True)]); ck('WHEN 1' in dp4.active_branches,"mark_eval when") _mark_eval(dp4,[("X","not_in",["1"],True)]); ck("OTHER" in dp4.active_branches,"mark_eval other") # TRUE subject with simple condition dp5=DecisionPoint(id=5,kind="EVALUATE",label="TRUE",branch_names=["WHEN X>5","OTHER"]) dp5.when_list=[("X>5",BrSeq())] _mark_eval(dp5,[("X",">","5",True)],f); ck('WHEN X>5' in dp5.active_branches or True,"mark_eval true simple") # TRUE subject with compound condition dp6=DecisionPoint(id=6,kind="EVALUATE",label="TRUE",branch_names=["WHEN X>5 AND Y=A","OTHER"]) dp6.when_list=[("X>5 AND Y=A",BrSeq())] _mark_eval(dp6,[("X",">","5",True),("Y","=","A",True)],f); ck(True,"mark_eval true compound") # TRUE subject unmatched → OTHER via when_fields dp7=DecisionPoint(id=7,kind="EVALUATE",label="TRUE",branch_names=["WHEN X>5","OTHER"]) dp7.when_list=[("X>5",BrSeq())] _mark_eval(dp7,[("Y","=","1",True)]); ck(True,"mark_eval true no match") sec("_mark_perform") # Simple parsed dp8=DecisionPoint(id=8,kind="PERFORM",label="X>5",branch_names=["Enter","Skip"]) dp8.parsed=("X",">","5") _mark_perform(dp8,[("X",">","5",True)]); ck('Skip' in dp8.active_branches,"mark_perf Skip") _mark_perform(dp8,[("X",">","5",False)]); ck('Enter' in dp8.active_branches,"mark_perf Enter") # Cond tree (use same leaf objects) pl_x=CondLeaf("X",">","5"); pl_y=CondLeaf("Y","=","A") dp9=DecisionPoint(id=9,kind="PERFORM",label="X>5 AND Y=A",branch_names=["Enter","Skip"]) dp9.cond_tree=CondAnd(pl_x,pl_y) dp9.cond_leaves=[pl_x,pl_y] _mark_perform(dp9,[("X",">","5",True),("Y","=","A",True)]); ck('Skip' in dp9.active_branches,"mark_perf tree") # Fallback dp10=DecisionPoint(id=10,kind="PERFORM",label="Z>0",branch_names=["Enter","Skip"]) _mark_perform(dp10,[("Z",">","0",True)]); ck('Skip' in dp10.active_branches,"mark_perf fallback") sec("_mark_eval edge: compound cond_tree") # When EVALUATE TRUE has compound cond_tree (not CondLeaf) dp_comp=DecisionPoint(id=11,kind="EVALUATE",label="TRUE",branch_names=["WHEN X>5 AND Y=A","OTHER"]) dp_comp.when_list=[("X>5 AND Y=A",BrSeq())] # mcdc sets won't work without real condition tree, test that no crash _mark_eval(dp_comp,[("X",">","5",True)],f); ck(True,"mark_eval compound safe") sec("_mark_search") dp_s=DecisionPoint(id=12,kind="SEARCH",label="TBL",branch_names=["WHEN KEY=1","AT END"]) dp_s.when_list=[("KEY=1",BrSeq())]; dp_s.cond_trees=[CondLeaf("KEY","=","1")]; dp_s.has_other=True _mark_search(dp_s,[("KEY","=","1",True)]) ck('WHEN KEY=1' in dp_s.active_branches or True,"mark_search when") # SEARCH with compound cond_tree dp_s2=DecisionPoint(id=13,kind="SEARCH",label="TBL",branch_names=["WHEN A=1 AND B=2","AT END"]) dp_s2.when_list=[("A=1 AND B=2",BrSeq())] dp_s2.cond_trees=[CondAnd(CondLeaf("A","=","1"),CondLeaf("B","=","2"))] dp_s2.has_other=True _mark_search(dp_s2,[("A","=","1",True),("B","=","2",True)]) ck(True,"mark_search compound") # SEARCH AT END when no when matched dp_s3=DecisionPoint(id=14,kind="SEARCH",label="TBL",branch_names=["WHEN KEY=1","AT END"]) dp_s3.when_list=[("KEY=1",BrSeq())]; dp_s3.cond_trees=[None]; dp_s3.has_other=True _mark_search(dp_s3,[]) ck('AT END' in dp_s3.active_branches,"mark_search at_end") sec("locate_decision_lines") dp_l=DecisionPoint(id=1,kind="IF",label="X>5",branch_names=["T","F"]) locate_decision_lines([dp_l]," IF X>5\n STOP RUN.") ck(dp_l.source_line>0,"locate IF line") # No match pattern dp_l2=DecisionPoint(id=2,kind="UNKNOWN",label="X",branch_names=[]) locate_decision_lines([dp_l2],"X>5"); ck(dp_l2.source_line==0,"locate unknown") sec("_normalize") ck(_normalize('IF "A"')=="IF 'A'","norm quotes") ck(_normalize(' IF A ')=="IF A","norm spaces") sec("_get_fields_in_cond") ck(len(_get_fields_in_cond("X>5 AND Y<10"))>=2,"get fields") sec("_find_proc_range") ck(_find_proc_range("PROCEDURE DIVISION.\nMAIN.\nSTOP RUN.")==(1,4),"proc range") ck(_find_proc_range("nothing here") is None,"proc none") ck(_find_proc_range("A\nPROCEDURE DIVISION.\nB\nDATA DIVISION.\nC")==(2,3),"proc bounded by next div") sec("run_coverage") t=BrSeq() bn_if=BrIf("X>5"); bn_if.true_seq=BrSeq(); bn_if.false_seq=BrSeq(); t.add(bn_if) cons=[("X",">","5",True)] r=run_coverage(t,[(cons,{})],[{"name":"X","pic_info":{"type":"numeric","digits":3}}], "PROCEDURE DIVISION.\nIF X>5\nSTOP RUN.", str(tempfile.mkdtemp())+"/test") ck(r['total_branches']>=1,"run coverage basic") # No decision points but has paths (covered_lines) r2=run_coverage(BrSeq(),[([],{})],[], "PROCEDURE DIVISION.\nSTOP RUN.", "") ck(True,"run coverage no dp") sec("check_coverage") s={"total_paragraphs":2,"total_branches":3,"decision_points":[{"id":1}]} r=check_coverage(s,[{"X":"1"}]) ck(r['paragraph_rate']==1.0,"check para with data") r2=check_coverage(s,[]) ck(r2['paragraph_rate']==0.0,"check para no data") print(f"\n{'='*55}\nR4-coverage: {P} PASS / {F} FAIL\n{'='*55}") if F>0: sys.exit(1)