Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/language_status/oracle_assumptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ verdict, but it limits what the proof says about inputs outside the scenarios.
| X21 | CICS | EIBTASKN: the task's number is a stated fact of the run (`$GGCICS_TASKN` / `CicsTask.withTaskNumber`, a case's or scenario's `"taskn"`, default 0), not the number CICS assigns; a value outside 0 to 9,999,999 refused | DIFFERS (the value) / MATCHED (both sides) | yes (every CICS task; read by CBSA's Db2 programs, GenApp LGICDB01) |
| X22 | CICS | READ GTEQ / GENERIC on a KSDS: the first record whose key (or its first KEYLENGTH bytes) equals RIDFLD's or, with GTEQ, is greater, in the browse's key order (D1); NOTFND RESP2 80; READ UPDATE holds the record found; RIDFLD not updated; a GENERIC KEYLENGTH not shorter than the key or not above zero, a non-constant KEYLENGTH and a RIDFLD shorter than the key searched refused | ASSUMED (REFUSED where IBM is silent or the layout decides) | yes (GenApp LGICVS01 genapp-lgicvs01) |
| X23 | CICS | A COMMAREA of a stated length (a scenario's `commarea_length`, EIBCALEN shorter than the record): the program is given exactly those bytes; a reference past EIBCALEN refused on both sides, the task judged up to it | ASSUMED (REFUSED past EIBCALEN) | yes (GenApp LGACDB01, LGACDB02, LGDPDB01, LGIPDB01) |
| X24 | CICS | A task started with a channel (a scenario's `channel`: what a RUN TRANSID CHANNEL parent or a LINK CHANNEL caller passed), made its first program's current channel; the containers left on that channel compared at the task's end (dropped on an abend), byte for byte; RUN TRANSID children not run | ASSUMED | yes (async credit-card CRDTCHK, CSSTATS2, CSSTATUS, GETADDR, GETNAME) |
| X25 | COBOL layout | SYNCHRONIZED slack bytes in the layout model (GalaxyIR `record_layout`, `_storage_spans`): IBM Enterprise COBOL boundaries (halfword up to 4 digits, fullword above, the 8-byte binary S9(10)-S9(18) included; COMP-1 / INDEX / pointers fullword; COMP-2 doubleword) counted from the record, the table slack of IBM's rule; GnuCOBOL / Micro Focus may align an 8-byte binary on a doubleword | ASSUMED (IBM's fullword; z/OS is the target) | no (no committed case reaches an 8-byte SYNC binary) |
| L1 | LE | CEEDAYS: documented pictures only | MATCHED / REFUSED | yes |
| L2 | LE | CEE3ABD abend codes | MATCHED | yes |
Expand Down Expand Up @@ -925,6 +926,34 @@ The length READQ TS returns on ITEMERR or QIDERR is not documented, so it is not
CA-ERROR-MSG and is judged up to X6. Pinned by
`tests/cobol_mainframe/test_equivalence_cics.py` (the guard page, the driver, the length's checks) and
`tests/cobol_mainframe/test_det_translate.py` (the det port's cut storage).
### X24. A task started with a channel; the containers it leaves compared — ASSUMED (#4270)
- **What IBM documents** (CICS TS 6.x, "Scope of a channel"; RUN TRANSID CHANNEL: "the name of the channel that is
to be passed to the child task"; FETCH CHILD CHANNEL: "the channel returned by the child task"; LINK CHANNEL: the
called program's current channel, "what it puts there, the caller sees"). A service started by RUN TRANSID CHANNEL,
or LINKed with CHANNEL, finds its input in containers on its current channel and leaves its result there for the
parent to FETCH, or for the caller.
- **The harness's model.** A scenario states `"channel": {"name", "containers": {NAME: {"text" | "hex",
"datatype"}}}`: the channel the caller passed (text in the case's data page; BIT unless CHAR, as PUT CONTAINER's
default). Both sides build it before the program runs and make it the first program's current channel, the only
one in its scope: the stub's `GGCCHIN` (channel.cfg), and the Java side's `CicsTask.withChannel` (with the new
`Channel.with`). Unstated, the task has no current channel, as before (GET CONTAINER INVREQ RESP2 4).
- **What is compared.** At the task's end, the containers on that channel -- every one, by name, byte for byte, and
the channel's name -- as a last CONTAINERS event (the stub's `containers.out`, written by GGCEND; the Java test reads
`task.currentChannel()`), what the parent or caller sees. Dropped when the task abended (as a LINKed program's
COMMAREA is) and for a task judged up to a refusal (X6, X23). The data type is not compared: no conversion is
modelled (X17), so a parent sees the same bytes either way. Other channels the task made die with it and are not
compared.
- **RUN TRANSID children.** The equivalence harness runs one task: a RUN TRANSID CHILD is compared as the command
(TRANSID, RESP; `"transactions"` states the region's transaction definitions, else every one is defined), and the
child is not run. Sound for a parent that never FETCHes it (CSSTATS2): nothing the child does can reach the parent.
A FETCH CHILD / FETCH ANY is refused by the translator and the stub (X18).
- **A channel program's facade.** The generator gives a channel program `handleLink(XChannelIn)`, which no task
facade carries yet (#4343): the java-facade side runs such a program through runTask, and its `entries` say so.
- **Reached.** The async credit-card services CRDTCHK, GETADDR, GETNAME, CSSTATUS (which LINKs GETPOL and GETSPND)
and CSSTATS2 (RUN TRANSID GETP / SPND): their '0001' and other-account branches, and the containers they PUT.
Pinned by `tests/cobol_mainframe/test_equivalence_cics.py` (the stub's channel in and containers out, the
comparison, the drop on abend).

### X25. SYNCHRONIZED slack bytes -- 8-byte binary alignment ASSUMED to be IBM's fullword (#4266)

- **The rule the layout model follows.** IBM Enterprise COBOL aligns a SYNCHRONIZED binary item of up to 4 digits on a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,13 @@ def commarea_alternative_todos(commarea: dict) -> list[str]:
return name;
}

/** #4270 (X24): a container this channel holds before any program runs -- what the parent of a RUN TRANSID
* CHANNEL, or a LINK CHANNEL caller outside the region, put there (BIT or CHAR, as its PUT gave it). */
public Channel with(String container, byte[] data, boolean bit) {
containers.put(channelName(container, "container"), new Container(data.clone(), bit));
return this;
}

/** The containers, by name (a copy). */
public Map<String, byte[]> containers() {
Map<String, byte[]> out = new LinkedHashMap<>();
Expand Down
123 changes: 123 additions & 0 deletions tests/cobol_mainframe/test_equivalence_cics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,3 +1070,126 @@ def run(*args: str) -> tuple[int, str]:
(d / "commarea.exact").write_text("60\n", encoding="ascii")
assert run("60", "59") == (0, "moved=1 first=x\nwrote\n")
assert run("60", "60") == (98, "moved=1 first=x\nCOMMAREA past EIBCALEN: not modelled\n")


# ---- #4270: a task started with a channel; the containers it leaves compared (X24); a case's transactions ----------
def _channel_case():
return {"name": "x", "program": "CRDTCHK", "transid": "ICCK", "scenarios": [], "datasets": {}}


def test_a_scenario_states_the_channel_its_task_starts_with():
"""#4270 (X24): `"channel"` -- a name and containers (text in the case's data page, or hex; BIT unless CHAR, as
PUT CONTAINER's default) -- is what a RUN TRANSID CHANNEL parent passed. Names are CICS's: 1-16, no blank."""
sc = {"name": "s", "channel": {"name": "MYCHANNEL", "containers": {
"INPUTCONTAINER": {"text": "0001"}, "RAW": {"hex": "00ff", "datatype": "CHAR"}}}} # fmt: skip
got = ec.scenario_channel(_channel_case(), sc)
assert got == {"name": "MYCHANNEL", "containers": [{"name": "INPUTCONTAINER", "data": b"0001", "bit": True},
{"name": "RAW", "data": b"\x00\xff", "bit": False}]} # fmt: skip
assert ec.scenario_channel(_channel_case(), {"name": "s"}) is None
assert ec._channel_json(_channel_case(), sc)["containers"][1] == {"name": "RAW", "hex": "00FF", "bit": False}
for bad in (
{"name": "MY CHANNEL"},
{"name": "X" * 17},
{"name": "C", "containers": {"K": {"text": "a", "hex": "61"}}},
{"name": "C", "containers": {"K": {}}},
{"name": "C", "containers": {"K": {"hex": "61", "datatype": "X"}}},
):
with pytest.raises(ec.Unsupported):
ec.scenario_channel(_channel_case(), {"name": "s", "channel": bad})


def test_the_containers_a_task_leaves_are_compared_at_its_end_unless_it_abends(tmp_path):
"""#4270 (X24): the stub's containers.out -> a last CONTAINERS event (before a LINKed program's COMMAREA, as
CicsTask's test writes them); RUN TRANSID CHILD is compared as CicsTask records it; an abend drops the
containers, as it drops the COMMAREA; every container's bytes, both ways, are compared."""
out = tmp_path / "out"
out.mkdir()
(out / "events.txt").write_text("001 RUN pgm=CSSTATS2 transid=GETP resp=0 resp2=0\n"
"002 RUN pgm=CSSTATS2 transid=NONE resp=28 resp2=1\n"
"003 RETURN pgm=CSSTATS2 level=1 transid= len=0\n", encoding="ascii") # fmt: skip
(out / "containers.out").write_text("CHANNEL MYCHANNEL\nINPUTCONTAINER 30303031\nGETVIPSTATUS 56455259\n",
encoding="ascii") # fmt: skip
got = ec.cobol_events(ec.outputs(out, {"name": "x"}, tmp_path, []))
assert got == [{"event": "RUN", "transid": "GETP", "resp": "NORMAL"},
{"event": "RUN", "transid": "NONE", "resp": "TRANSIDERR"},
{"event": "RETURN", "transid": None, "commarea": None},
{"event": "CONTAINERS", "channel": "MYCHANNEL",
"containers": {"INPUTCONTAINER": "30303031", "GETVIPSTATUS": "56455259"}}] # fmt: skip
java = [*got[:3], {"event": "CONTAINERS", "channel": "MYCHANNEL",
"containers": {"GETVIPSTATUS": "56455259", "INPUTCONTAINER": "30303031"}}] # fmt: skip
assert ec.compare_events(got, java)["equal"] == 4 # order of containers does not matter
java[3] = {**java[3], "containers": {"INPUTCONTAINER": "30303031", "GETVIPSTATUS": "52454755"}}
assert [f["field"] for f in ec.compare_events(got, java)["diffs"][0]["fields"]] == ["containers.GETVIPSTATUS"]
abended = [{"event": "ABEND", "abcode": "X"}, got[3]]
assert ec.linked_result({}, abended) == abended[:1] and ec.linked_result({}, got) == got
_, _, _ = ec.x6_judged({"name": "s"}, tmp_path, got, got) # (no refusal: unchanged)
cev, _, _ = ec.x6_judged({"name": "s", "prefix_x6": "WRITEQ TD LENGTH > FROM: not modelled"}, tmp_path, got, got)
assert cev == got[:3] # a task judged up to a refusal: its end state (containers too) is not compared


def test_the_driver_gives_the_task_its_channel_and_a_case_states_its_transactions(tmp_path):
assert "CALL 'GGCCHIN' USING GG-CICS" in ec.cics_driver("PROG", False)
assert ec.case_transactions({"transactions": ["SPND", "GETP"]}) == ["GETP", "SPND"]
with pytest.raises(ec.Unsupported, match="transaction ids"):
ec.case_transactions({"transactions": ["TOOLONG"]})
import equivalence_java as ej

(tmp_path / "service").mkdir()
(tmp_path / "service" / f"{ej._service_class('PROG')}.java").write_text("public void handleLink() {}")
case = {**_facade_case(), "clock": "2026/10/01 10:30:15.00", "screens": {}, "commarea": None,
"transactions": ["GETP", "SPND"]} # fmt: skip
java = ec.cics_equivalence_test(case, tmp_path, [])
assert (
'public boolean transaction(String transid) { return java.util.Set.of("GETP", "SPND").contains(transid); }'
in java
)
assert "task.withChannel(channel);" in java and 'left.put("event", "CONTAINERS");' in java


_CHANNEL_MAIN = r"""
#include "GGCICS"
int main(void) {
gg_cics c;
char into[8] = "....";
memset(&c, 0, sizeof c);
GGCCHIN(&c);
memset(c.qname, ' ', 16);
memcpy(c.qname, "INPUTCONTAINER", 14);
memset(c.flags, ' ', 40);
c.len = 4;
GGCGETC(&c, into);
printf("get resp=%d len=%d into=%.4s\n", c.resp, c.len, into);
memset(c.qname, ' ', 16);
memcpy(c.qname, "OUT", 3);
c.len = 3;
GGCPUTC(&c, "998");
printf("put resp=%d\n", c.resp);
GGCEND(&c);
return 0;
}
"""


@pytest.mark.skipif(not __import__("shutil").which("gcc"), reason="needs gcc")
def test_the_stub_starts_the_task_with_its_channel_and_writes_what_it_leaves(tmp_path):
"""#4270 (X24): GGCCHIN makes channel.cfg's channel the first program's current channel (GET CONTAINER finds the
stated data, PUT CONTAINER adds to it); GGCEND writes its containers to containers.out. No channel.cfg: no
current channel (GET CONTAINER INVREQ RESP2 4) and no containers.out, as before."""
import subprocess

(tmp_path / "main.c").write_text(_CHANNEL_MAIN.replace("GGCICS", str(ec.STUB / "ggcics.c")), encoding="ascii")
exe = tmp_path / "chan"
built = subprocess.run(["gcc", "-o", str(exe), str(tmp_path / "main.c")], capture_output=True, text=True, # noqa: S603,S607
check=False) # fmt: skip
assert built.returncode == 0, built.stderr
d, o = tmp_path / "d", tmp_path / "o"
d.mkdir()
o.mkdir()
env = {"GGCICS_DIR": str(d), "GGCICS_OUT": str(o)}
run = subprocess.run([str(exe)], capture_output=True, text=True, env=env, check=False) # noqa: S603
assert run.stdout.splitlines() == ["get resp=16 len=4 into=....", "put resp=16"] # INVREQ: no current channel
assert not (o / "containers.out").exists()
(d / "channel.cfg").write_text("MYCHANNEL\nINPUTCONTAINER BIT 30303031\n", encoding="ascii")
run = subprocess.run([str(exe)], capture_output=True, text=True, env=env, check=False) # noqa: S603
assert run.stdout.splitlines()[:2] == ["get resp=0 len=4 into=0001", "put resp=0"]
assert (o / "containers.out").read_text() == "CHANNEL MYCHANNEL\nINPUTCONTAINER 30303031\nOUT 393938\n"
Loading
Loading