diff --git a/docs/language_status/blockers_history.jsonl b/docs/language_status/blockers_history.jsonl index 210b3d075..fc1e90b13 100644 --- a/docs/language_status/blockers_history.jsonl +++ b/docs/language_status/blockers_history.jsonl @@ -14,3 +14,4 @@ {"pr": 4608, "main_sha": "f54d746d56dd1223eb2db7d365e3e2f90a5c3127", "date": "2026-10-07T19:57:59Z", "whole": 76, "total": 125, "whole_burned": 71, "total_burned": 104, "whole_non_burned": 5, "total_non_burned": 21, "refused_whole": 12, "top_gaps": [{"gap": "refused: LayoutError: source defect: source text past column N (…) cuts a literal open: fixed-form COBOL reads columns N", "only": 7, "only_burned": 0, "only_non_burned": 7, "refuses_program": true}, {"gap": "missing copybook CMQGMOV", "only": 2, "only_burned": 2, "only_non_burned": 0, "refuses_program": true}, {"gap": "EXEC CICS FORMATTIME RESP", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": false}, {"gap": "missing copybook BAQRINFO", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": true}, {"gap": "EXEC CICS (no reason given)", "only": 1, "only_burned": 1, "only_non_burned": 0, "refuses_program": false}], "scope": "burned + local + census corpora"} {"pr": 4627, "main_sha": "f8408bf6e4b3490fee2b0b9bda3de72ec07fb4fb", "date": "2026-10-07T19:58:16Z", "whole": 76, "total": 125, "whole_burned": 71, "total_burned": 104, "whole_non_burned": 5, "total_non_burned": 21, "refused_whole": 12, "top_gaps": [{"gap": "refused: LayoutError: source defect: source text past column N (…) cuts a literal open: fixed-form COBOL reads columns N", "only": 7, "only_burned": 0, "only_non_burned": 7, "refuses_program": true}, {"gap": "missing copybook CMQGMOV", "only": 2, "only_burned": 2, "only_non_burned": 0, "refuses_program": true}, {"gap": "EXEC CICS FORMATTIME RESP", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": false}, {"gap": "missing copybook BAQRINFO", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": true}, {"gap": "EXEC CICS (no reason given)", "only": 1, "only_burned": 1, "only_non_burned": 0, "refuses_program": false}], "scope": "burned + local + census corpora"} {"pr": 4636, "main_sha": "b72819c4099b8aafac4242f82a19385a7f43225b", "date": "2026-10-07T20:08:37Z", "whole": 78, "total": 125, "whole_burned": 73, "total_burned": 104, "whole_non_burned": 5, "total_non_burned": 21, "refused_whole": 12, "top_gaps": [{"gap": "refused: LayoutError: source defect: source text past column N (…) cuts a literal open: fixed-form COBOL reads columns N", "only": 7, "only_burned": 0, "only_non_burned": 7, "refuses_program": true}, {"gap": "missing copybook CMQGMOV", "only": 2, "only_burned": 2, "only_non_burned": 0, "refuses_program": true}, {"gap": "EXEC CICS FORMATTIME RESP", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": false}, {"gap": "missing copybook BAQRINFO", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": true}, {"gap": "EXEC CICS (no reason given)", "only": 1, "only_burned": 1, "only_non_burned": 0, "refuses_program": false}], "scope": "burned + local + census corpora"} +{"pr": 7901, "main_sha": "4174b5ef9d28735ccf226e84f36345337ff5f70f", "date": "2026-11-06T17:34:09Z", "whole": 113, "total": 127, "whole_burned": 105, "total_burned": 106, "whole_non_burned": 8, "total_non_burned": 21, "refused_whole": 8, "top_gaps": [{"gap": "refused: LayoutError: source defect: source text past column N (…) cuts a literal open: fixed-form COBOL reads columns N", "only": 7, "only_burned": 0, "only_non_burned": 7, "refuses_program": true}, {"gap": "EXEC CICS WEB CONVERSE POST", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": false}, {"gap": "missing copybook BAQRINFO", "only": 1, "only_burned": 0, "only_non_burned": 1, "refuses_program": true}, {"gap": "MOVE an alphanumeric literal holds a character beyond the single-byte code page (…): its bytes and length depend on how the source reached the compiler", "only": 0, "only_burned": 0, "only_non_burned": 0, "refuses_program": false}, {"gap": "STRING sender , PIC (…): a numeric item with a P or a decimal place is a compile error in Enterprise COBOL", "only": 0, "only_burned": 0, "only_non_burned": 0, "refuses_program": false}], "scope": "burned + local + census corpora", "copy_members": 1, "note": "T9 re-survey; census basis 40 -> 39 (copy member listed, not counted; owner ruling 2026-11-06, #7709)"} diff --git a/tests/tools/cics_census.py b/tests/tools/cics_census.py index 801ab3a1e..6e6e71026 100644 --- a/tests/tools/cics_census.py +++ b/tests/tools/cics_census.py @@ -623,6 +623,12 @@ def blockers(rows: dict[tuple[str, str], dict[str, Any]], only: set[tuple[str, s } +def note_copy_members(res: dict[str, Any], n: int) -> None: + """#7901: name the copy members the inventory listed and did not count (added only when there are some).""" + if n: + res["copy_members"] = n + + def non_burned_text(res: dict[str, Any]) -> str: """#6064: a --no-census run that counted no non-burned program (no local ones either) says `non-burned: not surveyed`, never `non-burned 0 / 0`.""" @@ -634,6 +640,9 @@ def non_burned_text(res: dict[str, Any]) -> str: def format_blockers(res: dict[str, Any], where: str, top: int = 0) -> str: out = [f"# blockers in {where}: {res['scope']}", f"translated whole: {res['whole']} / {res['programs']} ({non_burned_text(res)}); refused whole (no statements translated): {res['refused']}"] # fmt: skip + if res.get("copy_members"): + out.append(f"basis: {res['programs']} programs; {res['copy_members']} copy member(s) listed, not counted " + f"({COPY_MEMBER_REASON})") # fmt: skip if res.get("cics_programs_not_surveyed"): out.append( f"CICS programs with no survey row: {res['cics_programs_not_surveyed']} (a corpus left out of the run?)" @@ -681,6 +690,7 @@ def cmd_blockers(args: argparse.Namespace) -> int: res["census_surveyed"] = not args.no_census if only is not None: res["cics_programs_not_surveyed"] = len(only - set(rows)) + note_copy_members(res, len(load_copy_members(args.dir, args.label))) where = f"{args.dir}/{args.label}-*" if args.json: print(json.dumps(res, indent=1)) @@ -879,6 +889,7 @@ def baseline_blockers(d: Path, all_programs: bool = False, census: bool | None = res["census_surveyed"] = census if only is not None: res["cics_programs_not_surveyed"] = len(only - set(rows)) + note_copy_members(res, len(load_copy_members(d, "before"))) return res @@ -1062,6 +1073,8 @@ def history_entry(res: dict[str, Any], sha: str, date: str, pr: int, note: str | "refused_whole": res["refused"], "top_gaps": top, "scope": scope} # fmt: skip if res.get("census_surveyed") is False and not nb: # #6064: not 0 / 0 -- the census was not surveyed entry |= {"whole_non_burned": None, "total_non_burned": None, "census_surveyed": False} + if res.get("copy_members"): + entry["copy_members"] = res["copy_members"] if note: entry["note"] = note return entry diff --git a/tests/tools/test_cics_census_copy_member_7901.py b/tests/tools/test_cics_census_copy_member_7901.py index 69189a365..e3e5fb894 100644 --- a/tests/tools/test_cics_census_copy_member_7901.py +++ b/tests/tools/test_cics_census_copy_member_7901.py @@ -83,3 +83,30 @@ def test_program_without_program_id_keeps_a_program_row(): rows = {"c": [{"program": "B.cbl", "error": "UnitRefused: no PROGRAM-ID"}]} assert cc.copy_member_reason(NO_PROGRAM_ID) is None assert "| c | 1 | 0 | 0 | 1 |" in ds.markdown(rows) + + +def _copy_survey(tmp_path, with_copy=True): + d = tmp_path / "before-b" + d.mkdir() + rows = [{"program": "A.cbl", "statements": 1}] + if with_copy: + rows.append({"program": "REC.cbl", "copy_member": cc.COPY_MEMBER_REASON}) + (d / "survey.json").write_text(json.dumps({"c": rows}), encoding="utf-8") + return cc.blockers(cc.load_surveys(tmp_path, "before")) + + +def test_blockers_name_the_copy_member_basis(tmp_path): + res = _copy_survey(tmp_path) + cc.note_copy_members(res, len(cc.load_copy_members(tmp_path, "before"))) + res["scope"] = "every surveyed program" + assert res["programs"] == 1 and res["copy_members"] == 1 + assert "basis: 1 programs; 1 copy member(s) listed, not counted" in cc.format_blockers(res, "x") + assert cc.history_entry(res, "abc", "2026-11-06", 1, None, "s")["copy_members"] == 1 + + +def test_blockers_have_no_copy_member_key_when_none(tmp_path): + res = _copy_survey(tmp_path, with_copy=False) + cc.note_copy_members(res, len(cc.load_copy_members(tmp_path, "before"))) + res["scope"] = "every surveyed program" + assert "copy_members" not in res and "basis:" not in cc.format_blockers(res, "x") + assert "copy_members" not in cc.history_entry(res, "abc", "2026-11-06", 1, None, "s")