"""S23: Per-program branch coverage + code coverage report""" import sys, os, re, subprocess, time sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) ROOT_BENCH = "D:/cobol-java/cobol-test-programs/" COPYBOOKS_BENCH = os.path.join(ROOT_BENCH, "common", "copybooks") ROOT_TNA = "D:/cobol-java/cobol-tna-system/" COPYBOOKS_TNA = os.path.join(ROOT_TNA, "cpy") from cobol_testgen import extract_structure, generate_data from cobol_testgen.read import preprocess, resolve_copybooks, extract_data_division, extract_procedure_division, parse_data_division from cobol_testgen.design_mcdc import enum_paths from cobol_testgen.pipeline_bridge import build_branch_tree_fallback from cobol_testgen.flatfile import analyze_fd_layout def find_main(d): cbls = [f for f in os.listdir(d) if f.endswith('.cbl')] ws = [f for f in cbls if re.match(r'main-\d{2}-', f, re.IGNORECASE)] if ws: return max(ws, key=lambda f: os.path.getsize(os.path.join(d, f))) return max(cbls, key=lambda f: os.path.getsize(os.path.join(d, f))) if cbls else None def analyze_one(name, fpath, source_dir, copybook_dirs): """Return dict: {branches, dpoints, paths, records, flat_files, lines, code_lines, compile, run, error}""" result = {"name": name, "branches": 0, "dpoints": 0, "paths": 0, "records": 0, "flat_files": 0, "lines": 0, "code_lines": 0, "compile": "-", "run": "-", "error": ""} try: src = open(fpath, encoding="utf-8-sig").read() result["lines"] = len(src.split("\n")) result["code_lines"] = sum(1 for l in src.split("\n") if l.strip() and not l.strip().startswith("*")) t0 = time.time() st = extract_structure(src) result["branches"] = st.get("total_branches", 0) result["dpoints"] = len(st.get("decision_points", [])) pp = resolve_copybooks(src, source_dir, extra_search_paths=copybook_dirs) pp = preprocess(pp) recs = generate_data(pp, st) result["records"] = len(recs) # Coverage data from generate_data (mark_coverage result) cov = st.get('coverage', {}) result["cov_total"] = cov.get('total', 0) result["cov_covered"] = cov.get('covered', 0) result["cov_pct"] = cov.get('pct', 0) # Path count dd = extract_data_division(pp) fields = parse_data_division(dd) if dd else [] fdict = [{'name': f.name, 'pic_info': {'type': f.pic_info.type if f.pic_info else 'unknown'}} for f in fields] proc = extract_procedure_division(pp) tree, ass = build_branch_tree_fallback(proc, fdict) paths = enum_paths(tree, fdict) result["paths"] = len(paths) layouts = analyze_fd_layout(pp) result["flat_files"] = len(layouts) result["time_ms"] = int((time.time()-t0)*1000) except Exception as e: result["error"] = str(e)[:80] return result def analyze_tna(name, fpath): """Analyze TNA program""" return analyze_one(name, fpath, os.path.dirname(fpath), [COPYBOOKS_TNA]) def analyze_bench(name, fpath): """Analyze benchmark program""" return analyze_one(name, fpath, os.path.dirname(fpath), [COPYBOOKS_BENCH]) # ── Run all benchmark programs ── print("=" * 110) print(f"{'Program':<28} {'Br':>4} {'DPs':>4} {'Paths':>5} {'Recs':>4} {'Flats':>4} {'CovBr':>5} {'Cov%':>5} {'Lines':>5} {'CodeL':>5} {'Time':>6}") print("-" * 110) bench_results = [] for d in sorted(os.listdir(ROOT_BENCH)): dp = os.path.join(ROOT_BENCH, d) if not os.path.isdir(dp) or d in ('common','docs','cross-cutting'): continue fn = find_main(dp) if not fn: continue r = analyze_bench(d, os.path.join(dp, fn)) bench_results.append(r) br_pct = r["paths"] / r["branches"] * 100 if r["branches"] > 0 else 0 cov_pct = r.get("cov_pct", 0) codel = r["code_lines"] status = r.get("error", "")[:8] if r.get("error") else "" print(f" {r['name']:<28} {r['branches']:>4} {r['dpoints']:>4} {r['paths']:>5} {r['records']:>4} {r['flat_files']:>4} {r.get('cov_covered',0):>5} {cov_pct:>4.0f}% {r['lines']:>5} {r['code_lines']:>5} {r.get('time_ms',0):>5}ms {status}") # ── Run all TNA programs ── print("-" * 110) for f in ["ZAN01CHK", "ZAN02CHK", "ZAN03CHK", "ZAN04MAT", "ZAN05CAL", "ZAN06UPD"]: fpath = os.path.join(ROOT_TNA, "src", f + ".cbl") if not os.path.exists(fpath): continue r = analyze_tna(f, fpath) bench_results.append(r) cov_pct = r.get("cov_pct", 0) codel = r["code_lines"] status = r.get("error", "")[:8] if r.get("error") else "" print(f" {r['name']:<28} {r['branches']:>4} {r['dpoints']:>4} {r['paths']:>5} {r['records']:>4} {r['flat_files']:>4} {r.get('cov_covered',0):>5} {cov_pct:>4.0f}% {r['lines']:>5} {r['code_lines']:>5} {r.get('time_ms',0):>5}ms {status}") print("=" * 110) # ── Totals ── total_br = sum(r["branches"] for r in bench_results) total_paths = sum(r["paths"] for r in bench_results) total_recs = sum(r["records"] for r in bench_results) total_lines = sum(r["code_lines"] for r in bench_results) total_flats = sum(r["flat_files"] for r in bench_results) total_cov = sum(r.get("cov_covered", 0) for r in bench_results) total_cov_all = sum(r.get("cov_total", 0) for r in bench_results) with_br = sum(1 for r in bench_results if r["branches"] > 0) print(f"\n{'TOTAL':<28} {total_br:>4} {total_paths:>5} {total_recs:>4} {total_flats:>4} {total_cov:>5} {total_cov/max(total_cov_all,1)*100:>4.0f}%") print(f"Programs with branch detection: {with_br}/{len(bench_results)}") print(f"Total code lines (non-comment): {total_lines}") print(f"\n{'='*110}") print("NOTES:") print(" Br = Decision branches detected by static analysis") print(" DPs = Decision points (IF/EVAL/PERFORM)") print(" Paths = Generated test paths (O(N) linear)") print(" Recs = Generated data records") print(" CovBr = Branches actually covered by generated data") print(" Cov% = Real branch coverage via mark_coverage") print(" Time = Parse + generate time in ms")