From f16c8ce64d90d44e6818ccd6be2db377a22c5972 Mon Sep 17 00:00:00 2001 From: squid-protocol Date: Fri, 6 Nov 2026 13:39:53 -0500 Subject: [PATCH] det: run START/RETRIEVE and TS/TD queue areas over a COMP-5 group in Java under zos_comp5 (Part of #7845, #7709) Co-Authored-By: Claude Sonnet 5.5 --- docs/language_status/register/C7.md | 10 ++ .../test_det_zos_comp5_queue_run_7845.py | 117 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/cobol_mainframe/test_det_zos_comp5_queue_run_7845.py diff --git a/docs/language_status/register/C7.md b/docs/language_status/register/C7.md index 9e7280c1a1..ab1908989a 100644 --- a/docs/language_status/register/C7.md +++ b/docs/language_status/register/C7.md @@ -116,6 +116,16 @@ reached: "read as numbers only (a VALUE beyond the PICTURE: fixed, #4501)" item is PUT, held in the channel container as IBM's big-endian image, and GET into a second area reads the same values; with `swapZosComp5(false)` the container holds the reversed (oracle) image. Census T6 (#7846): the bytes-overlap row is gone (11 of 40 whole). +- **START FROM / RETRIEVE INTO and the TS / TD queues (#7845, run in Java by `test_det_zos_comp5_queue_run_7845.py`).** Unlike a + container, these move the area's bytes between the program's page (`CS`) and the region's (`REGION`, cp037 by default) + through `DetCics.toRegion` / `fromRegion` (TS, START, RETRIEVE) or a `String` in `CS` (TD). A text + COMP-5 group written + and read back returns the same values in every case. The stored image is IBM's big-endian bytes only when the two pages + are the same, or for TD; with a different region page the TS item and the START data hold the bytes transcoded one by + one (a COMP-5 byte is rewritten as if it were a character, where z/OS stores it raw). It is not refused: the pages are + a run-time property (`CobolRecords.charset()`, the `gitgalaxy.cics.charset` property), and the values survive. Not + covered: the comparator reverses no queue or START bytes (`comp5_byte_order` needs a layout for those, as + `container_layouts` has for containers), so a case declaring the difference must not hold a COMP-5 item in such an area. + No burned program does (SEQPNT, ASYNCPNT, PAUDBLOD, PAUDBUNL, DBUNLDGS issue no START / RETRIEVE / WRITEQ / READQ over one). - **Slice 5 follow-through (#7709).** Landed with the flip: the default. Done after it: no golden moved (the default acts only on a program that holds COMP-5, and `det_port.py check` changed five ports, all re-proved); PAUDBLOD, PAUDBUNL and DBUNLDGS are unchanged (L4); ASYNCPNT and SEQPNT stay L5 with every survivor accounted (#7756; the evidence report of diff --git a/tests/cobol_mainframe/test_det_zos_comp5_queue_run_7845.py b/tests/cobol_mainframe/test_det_zos_comp5_queue_run_7845.py new file mode 100644 index 0000000000..93a75ee79b --- /dev/null +++ b/tests/cobol_mainframe/test_det_zos_comp5_queue_run_7845.py @@ -0,0 +1,117 @@ +"""#7845 (#7709, register C7), siblings of the container: a COMP-5 group through the byte paths of TS and TD queues and +START FROM / RETRIEVE INTO, run in Java on the runtime those commands generate (DetCics.toRegion / fromRegion around +CicsTask's queue). Those paths move the bytes between the program's page (CS) and the region's (REGION, cp037), unlike a +container, which copies them. Pinned: with the same page the queue holds IBM's big-endian image; with a different one it +holds that image transcoded byte by byte (not IBM's raw bytes -- a boundary, C7.md), and in both the reader gets the same +values back. Synthetic group only.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + +PKG = "t" +ITEMS = [(4, "-2", "FFFE"), (9, "305419896", "12345678"), (18, "72623859790382856", "0102030405060708")] +PAGES = {"same": "ISO_8859_1", "cp037": "IBM037"} # the region's page; the program's is ISO-8859-1 + + +def _jdk() -> Path | None: + home = os.environ.get("JDK_17") or os.environ.get("JAVA_HOME") + if home and (Path(home) / "bin/javac").is_file(): + return Path(home) / "bin" + found = shutil.which("javac") + return Path(found).parent if found else None + + +def _main() -> str: + rows = ",".join(f'{{"{d}", "{v}"}}' for d, v, _ in ITEMS) + pages = ",".join(f'"{p}"' for p in PAGES.values()) + return f"""package {PKG}.cics; +import {PKG}.cobolrt.*; +import {PKG}.cobolrt.cics.DetCics; +import java.math.BigDecimal; +import java.nio.charset.Charset; +public class Main {{ + static String hex(byte[] b) {{ StringBuilder s = new StringBuilder(); for (byte x : b) s.append(String.format("%02X", x & 0xFF)); return s.toString(); }} + static String read(byte[] got, int len, int d, Charset cs) {{ + Storage cst = new Storage(len); // the reader's own group + DetCics.put(Field.group(cst, 0, len), got); + return Cobol.num(Field.binary(cst, 2, d, 0, true, true), cs).toPlainString(); + }} + public static void main(String[] a) {{ + Charset cs = java.nio.charset.StandardCharsets.ISO_8859_1; + String[][] rows = {{{rows}}}; + for (String pg : new String[] {{{pages}}}) {{ + Charset region = Charset.forName(pg); + for (String[] r : rows) {{ + int d = Integer.parseInt(r[0]); int n = d <= 4 ? 2 : d <= 9 ? 4 : 8; int len = n + 2; + boolean before = Cobol.swapZosComp5(true); + Storage ps = new Storage(len); + System.arraycopy("AB".getBytes(cs), 0, ps.bytes, 0, 2); + Cobol.store(Field.binary(ps, 2, d, 0, true, true), new BigDecimal(r[1]), false, cs); + Field group = Field.group(ps, 0, len); + CicsTask task = new CicsTask("ZC01", "ENTER", null, null); + // WRITEQ TS FROM / READQ TS INTO, as cics_queues.ts_queue generates them + byte[] stored = DetCics.toRegion(DetCics.bytes(group, group.length()), cs, region); + task.writeqTs("Q", stored); + CicsTask.TsResult ts = task.readqTsNext("Q", len); + System.out.println("TS " + pg + " " + d + " " + hex(ts.data()) + " " + read(DetCics.fromRegion(ts.data(), region, cs), len, d, cs)); + // WRITEQ TD / READQ TD: the record is the program's own text (a String in CS) + task.writeqTd("QTD1", new String(DetCics.bytes(group, group.length()), cs)); + CicsTask.TdResult td = task.readqTd("QTD1", len); + System.out.println("TD " + pg + " " + d + " " + hex(td.data().getBytes(cs)) + " " + read(td.data().getBytes(cs), len, d, cs)); + // START FROM / RETRIEVE INTO: the data the started task is handed, through the same pages + byte[] sent = DetCics.toRegion(DetCics.startData(group, len), cs, region); + System.out.println("ST " + pg + " " + d + " " + hex(sent) + " " + read(DetCics.fromRegion(sent, region, cs), len, d, cs)); + Cobol.swapZosComp5(before); + }} + }} + }} +}} +""" + + +@pytest.mark.skipif(_jdk() is None, reason="needs a JDK (javac)") +@pytest.mark.slow( + reason="compiles the generated CicsTask and det runtime with javac (cold ~10-20 s on a loaded runner)" +) +def test_a_comp5_group_through_queues_and_start_keeps_its_values_and_its_page_image(tmp_path): + from gitgalaxy.tools.cobol_to_java.cobol_to_java_transaction_forge import CICS_SPEC_JAVA, CICS_TASK_JAVA + from gitgalaxy.tools.cobol_to_java.det import program as P + + src = tmp_path / "src" + for rel, text in P.runtime_files(PKG, batch=False).items(): + (src / PKG / rel).parent.mkdir(parents=True, exist_ok=True) + (src / PKG / rel).write_text(text, encoding="utf-8") + cics = src / PKG / "cics" + cics.mkdir(parents=True, exist_ok=True) + (cics / "CicsTask.java").write_text( + CICS_TASK_JAVA.replace("__PACKAGE__", PKG).replace("__ZONE__", "UTC"), encoding="utf-8" + ) + (cics / "CicsSpec.java").write_text(CICS_SPEC_JAVA.replace("__PACKAGE__", PKG), encoding="utf-8") + (cics / "Main.java").write_text(_main(), encoding="utf-8") + rec = src / PKG / "entity/vsam/CobolRecords.java" + rec.parent.mkdir(parents=True, exist_ok=True) + rec.write_text(f"package {PKG}.entity.vsam;\npublic final class CobolRecords {{\n public static java.nio.charset." + "Charset charset() {\n return java.nio.charset.StandardCharsets.ISO_8859_1;\n }\n}\n") # fmt: skip + jdk = _jdk() + classes = tmp_path / "classes" + built = subprocess.run([str(jdk / "javac"), "-encoding", "UTF-8", "-nowarn", "-d", str(classes), # noqa: S603 + *map(str, src.rglob("*.java"))], capture_output=True, text=True, encoding="utf-8", check=False) # fmt: skip + assert built.returncode == 0, built.stderr + run = subprocess.run([str(jdk / "java"), "-Dfile.encoding=UTF-8", "-Dstdout.encoding=UTF-8", "-cp", str(classes), # noqa: S603 + f"{PKG}.cics.Main"], capture_output=True, text=True, encoding="utf-8", check=True) # fmt: skip + want = [] + for page in PAGES: + for kind in ("TS", "TD", "ST"): + for d, v, h in ITEMS: + raw = bytes.fromhex("4142" + h) # IBM's image: the text pair, then the big-endian item + image = raw if page == "same" or kind == "TD" else raw.decode("latin-1").encode("cp037") + want.append(f"{kind} {PAGES[page]} {d} {image.hex().upper()} {v}") + got = sorted(run.stdout.splitlines()) + assert got == sorted(want), run.stdout + assert len(got) == 18