diff --git a/docs/language_status/register/Q3.md b/docs/language_status/register/Q3.md index 139c03a764..51f4669cc9 100644 --- a/docs/language_status/register/Q3.md +++ b/docs/language_status/register/Q3.md @@ -16,8 +16,10 @@ reached: "CICS: yes (CBSA XFRFUN); batch: yes (synthetic UOWDEMO, `tests/equival at the SYNCPOINT ROLLBACK and goes on in the same transaction, so the SQL that follows is committed with the task, as the COBOL side's Db2 commits it. Reached: CBSA INQACCCU's cursor failing inside CREACC's task (its SYNCPOINT ROLLBACK, then CREACC's CONTROL updates and INSERTs: cbsa-creacc, cbsa-bnk1cac). A recoverable FILE changed after a - SYNCPOINT ROLLBACK is still undone with the task on the Java side (the transaction is marked rollback-only): no case - reaches one, and it would show as a difference. + SYNCPOINT ROLLBACK is still undone with the task on the Java side (the transaction is marked rollback-only), and it + shows as a difference: CBSA DELCUS deleting the CUSTOMER record after INQACCCU's rollback (cbsa-delcus: customer 3's + NULL statement date, and the derived SQL faults at INQACCCU lines 246 and 466) -- listed in det_sweep_baseline.json, + every other output of those scenarios equal. - **CICS, EXEC SQL COMMIT / ROLLBACK.** IBM: both are invalid under CICS -- Db2 for z/OS answers -925 / -926 (SQLSTATE 2D521; Db2 for z/OS Codes) and the task's unit of work is CICS's (SYNCPOINT). The det translator REFUSES a CICS program's ROLLBACK by name. A CICS program's COMMIT stays a reset of the SQLCA on the Java side and a real commit on diff --git a/gitgalaxy/tools/cobol_to_java/det/cics.py b/gitgalaxy/tools/cobol_to_java/det/cics.py index 0b670792bd..0820c7a297 100644 --- a/gitgalaxy/tools/cobol_to_java/det/cics.py +++ b/gitgalaxy/tools/cobol_to_java/det/cics.py @@ -439,7 +439,14 @@ def entity_key(self, entity: str, prop: str | None) -> tuple[int, int]: def screen_fields(self, cls: str) -> list[str]: text = self._file("dto/screen", cls).read_text(encoding="utf-8") - return re.findall(r'values\.put\("([A-Z0-9-]+)",', text) + return re.findall(r'values\.put\("([A-Z0-9-]+)"(?:,| \+ )', text) # #5076: a repeated field: "NAME" + "." + i + + def screen_occurs(self, cls: str) -> dict[str, int]: + """#5076: the BMS fields a map repeats (DFHMDF OCCURS=n), by name -> n, read off the screen class's LAYOUT + (`new ScreenField(name, ..., occurs, justify)`); a field without OCCURS is absent.""" + text = self._file("dto/screen", cls).read_text(encoding="utf-8") + rows = re.findall(r'new ScreenField\("([A-Z0-9-]+)",.*, (\d+), (?:null|"[A-Z,]+")\)\)?[,;]?\s*$', text, re.M) + return {name: int(n) for name, n in rows if int(n) > 1} def screen_justify(self, cls: str) -> dict[str, str]: """#5082: a named field's DFHMDF JUSTIFY ("RIGHT", "RIGHT,ZERO", "LEFT,ZERO" ...), read off the screen class's @@ -2485,17 +2492,18 @@ def ts_queue(self, verb: str, opts: dict, ind: str) -> list[str]: ) return out + self.outcome(opts, f"DetCics.resp({r}.resp())", "0", ind) - def symbolic(self, map_name: str, suffix: str, name: str, record: str | None) -> str | None: - """The symbolic map item under `record` (COSGN0AI / COSGN0AO), or None.""" + def symbolic(self, map_name: str, suffix: str, name: str, record: str | None, k: int | None = None) -> str | None: + """The symbolic map item under `record` (COSGN0AI / COSGN0AO), or None. #5076 `k`: occurrence k + of a repeated field (ACCOUNTO(3)).""" for rec in [*([record] if record else []), map_name + "I", map_name + "O"]: - found = self._field_or_none(name + suffix, rec) + found = self._field_or_none(name + suffix, rec, k) if found is not None: return found return None - def _field_or_none(self, name: str, rec: str) -> str | None: + def _field_or_none(self, name: str, rec: str, subscript: int | None = None) -> str | None: try: - return self.g.field_expr(E.Ref(name, [rec])) + return self.g.field_expr(E.Ref(name, [rec], [] if subscript is None else [E.Lit(E.NumLit(str(subscript)))])) except Exception: return None @@ -2559,6 +2567,14 @@ def map_not_found(self, m: str, ms: str, ind: str) -> list[str] | None: f"{ind}if ({lbl} == null) throw abended();", f"{ind}if (true) {self.g.jump(f'paragraph({lbl})')}"] # fmt: skip + def screen_slots(self, cls: str) -> list[tuple[str, int | None]]: + """#5076: a screen class's fields as (name, occurrence): a repeated field once per occurrence (ACCOUNT, 3), any + other once (COMPANY, None).""" + repeated = self.gp.screen_occurs(cls) + return [ + (n, k) for n in self.gp.screen_fields(cls) for k in (range(1, repeated[n] + 1) if n in repeated else [None]) + ] + def no_screen(self, m: str) -> CicsError: """Why map `m` has no generated screen, by name (a map of no BMS source we hold).""" return CicsError(f"no generated screen for map {m}") @@ -2576,15 +2592,16 @@ def send_map(self, opts: dict, ind: str) -> list[str]: v, sub, scr = self.g.tmpname("values"), self.g.tmpname("sub"), self.g.tmpname("screen") out = [*guard, f"{ind}java.util.Map {v} = new java.util.LinkedHashMap<>();", f"{ind}CicsTask.MapSubfields {sub} = new CicsTask.MapSubfields();"] # fmt: skip - for name in self.gp.screen_fields(cls): - o = self.symbolic(m, "O", name, frm) + for name, k in self.screen_slots(cls): # #5076: a repeated field's every occurrence, keyed NAME.k + key = name if k is None else f"{name}.{k}" + o = self.symbolic(m, "O", name, frm, k) if o is None: - raise CicsError(f"map {m}: no {name}O") - out.append(f"{ind}{v}.put({G_jstr(name)}, Cobol.text({o}, CS));") - parts = [self.symbolic(m, s, name, None) for s in ("L", "A", "C", "H")] + raise CicsError(f"map {m}: no {name}O" + ("" if k is None else f"({k})")) + out.append(f"{ind}{v}.put({G_jstr(key)}, Cobol.text({o}, CS));") + parts = [self.symbolic(m, s, name, None, k) for s in ("L", "A", "C", "H")] if parts[1] is None: - parts[1] = self.symbolic(m, "F", name, None) - out.append(f"{ind}DetCics.subfields({sub}, {G_jstr(name)}, " + parts[1] = self.symbolic(m, "F", name, None, k) + out.append(f"{ind}DetCics.subfields({sub}, {G_jstr(key)}, " f"{', '.join(p or 'null' for p in parts)}, CS);") # fmt: skip if opts.get("CURSOR"): out.append(f"{ind}{sub}.cursorAt({self.int_(opts['CURSOR'])});") @@ -2615,16 +2632,17 @@ def receive_map(self, opts: dict, ind: str) -> list[str]: "(byte) 0);", f"{ind} java.util.Map {vals} = {r}.get().screenValues();"] # fmt: skip justify = self.gp.screen_justify(cls) - for name in self.gp.screen_fields(cls): - i = self.symbolic(m, "I", name, into.name) - ln = self.symbolic(m, "L", name, into.name) + for name, k in self.screen_slots(cls): # #5076: every occurrence of a repeated field, keyed NAME.k + key = name if k is None else f"{name}.{k}" + i = self.symbolic(m, "I", name, into.name, k) + ln = self.symbolic(m, "L", name, into.name, k) if i is None: continue - out.append(f"{ind} if ({vals}.get({G_jstr(name)}) != null) {{") + out.append(f"{ind} if ({vals}.get({G_jstr(key)}) != null) {{") # #5082: IBM DFHMDF JUSTIFY -- the input right-justified / zero-filled as the field says; none: as typed tail = f", {G_jstr(justify[name])}" if name in justify else "" call = f"{self.package}.cobolrt.cics.DetJustify.place" if name in justify else "DetCics.typed" - out.append(f"{ind} {call}({i}, {ln or 'null'}, {vals}.get({G_jstr(name)}), CS{tail});") + out.append(f"{ind} {call}({i}, {ln or 'null'}, {vals}.get({G_jstr(key)}), CS{tail});") out.append(f"{ind} }}") out.append(f"{ind}}}") return out + self.input_outcome(opts, resp, ind) diff --git a/tests/cobol_mainframe/test_call_compare_4778.py b/tests/cobol_mainframe/test_call_compare_4778.py index e0cb4b59eb..60dada4207 100644 --- a/tests/cobol_mainframe/test_call_compare_4778.py +++ b/tests/cobol_mainframe/test_call_compare_4778.py @@ -11,7 +11,9 @@ - a det DTO whose record ends in a FILLER no property names (CBSA's CUSTCTRL DFHCOMMAREA, GenApp LGICVS01's) spans its declared record (Dto.size), as a table's (#4765) does; a COMMAREA's FILLER is compared as bytes when both sides are bytes, and declared not compared when the Java side is a DTO. -- a symbolic map with OCCURS is refused by name (screen_layout): screens are compared at a single-occurrence layout. +- a symbolic map with OCCURS is compared at every occurrence by subscript (screen_layout, screen_key: ACCOUNT.3, the + key the generated screen's screenValues() writes), as a COMMAREA is (#5076); one with OCCURS DEPENDING ON, and a + RECEIVE typed into one occurrence, are refused by name. """ from __future__ import annotations @@ -173,30 +175,106 @@ def test_a_det_call_entry_takes_the_items_bytes(tmp_path): assert "byte[][] areas = callAreas;" in body and body.count("System.arraycopy(") == 2 -# ---- screens: a symbolic map with OCCURS is refused by name ------------------------------------------------------- +# ---- screens: a symbolic map with OCCURS is compared at every occurrence ----------------------------------------- MAP = """\ 01 MAPI. 02 FILLER PIC X(12). 02 ROWL COMP PIC S9(4). 02 ROWF PIC X. 02 ROWI PIC X(5). + 01 MAPO REDEFINES MAPI. + 02 FILLER PIC X(12). + 02 FILLER PIC X(3). + 02 ROWC PIC X. + 02 ROWO PIC X(5). """ MAP_OCCURS = """\ 01 MAPI. 02 FILLER PIC X(12). - 02 LINES OCCURS 3. + 02 LINESD OCCURS 3. 03 ROWL COMP PIC S9(4). 03 ROWF PIC X. 03 ROWI PIC X(5). + 01 MAPO REDEFINES MAPI. + 02 FILLER PIC X(12). + 02 LINESD OCCURS 3. + 03 FILLER PIC X(3). + 03 ROWC PIC X. + 03 ROWO PIC X(5). """ +MAP_ODO = """\ + 01 MAPI. + 02 N PIC 9. + 02 ROWI PIC X(5) OCCURS 1 TO 3 DEPENDING ON N. +""" + + +def _names(layout): + return [f["name"] for f in layout] + + +def test_a_symbolic_map_with_occurs_lists_every_occurrence(tmp_path): + (tmp_path / "map.cpy").write_text(MAP, encoding="utf-8") + (tmp_path / "occ.cpy").write_text(MAP_OCCURS, encoding="utf-8") + assert _names(ec.screen_layout(tmp_path, "map.cpy", "MAPI")) == ["FILLER", "ROWL", "ROWF", "ROWI"] + out = ec.screen_layout(tmp_path, "occ.cpy", "MAPO") + assert [n for n in _names(out) if n.startswith("ROWO")] == ["ROWO(1)", "ROWO(2)", "ROWO(3)"] + assert [f["offset"] for f in out if f["name"].startswith("ROWO")] == [16, 25, 34] # one 9-byte row each -def test_a_symbolic_map_with_occurs_is_refused(tmp_path): +def test_screen_key_names_an_occurrence_as_the_generated_screen_does(tmp_path): + (tmp_path / "occ.cpy").write_text(MAP_OCCURS, encoding="utf-8") (tmp_path / "map.cpy").write_text(MAP, encoding="utf-8") + keys = [ec.screen_key(f) for f in ec.screen_layout(tmp_path, "occ.cpy", "MAPO")] + assert ("ROW.2", "O") in keys and ("ROW.3", "C") in keys + assert ec.screen_key(ec.screen_layout(tmp_path, "map.cpy", "MAPO")[-1]) == ("ROW", "O") + + +def test_a_second_occurrence_that_differs_is_a_difference(tmp_path): + """The old single-occurrence layout read ROW.1 only: a second line one side left out compared equal.""" + (tmp_path / "occ.cpy").write_text(MAP_OCCURS, encoding="utf-8") + layout = ec.screen_layout(tmp_path, "occ.cpy", "MAPO") + keys = {f["name"]: ec.screen_key(f) for f in layout} + + def screen(rows): + data = bytearray(b" " * 40) + for i, text in enumerate(rows): + at = 16 + 9 * i + data[at : at + 5] = text.encode() + vals = ec.decode_record(bytes(data), layout, "latin-1") + return {keys[k][0]: v for k, v in vals.items() if keys[k][1] == "O"} + + a, b = screen(["AAAAA", "BBBBB", "CCCCC"]), screen(["AAAAA", "BBBBB", "CCCCX"]) + assert sorted(a) == ["ROW.1", "ROW.2", "ROW.3"] + cob = [{"event": "SEND-MAP", "map": "M", "screen": a, "subfields": {}, "options": [], "cursor": None}] + jav = [{"event": "SEND-MAP", "map": "M", "screen": b, "subfields": {}, "options": [], "cursor": None}] + diffs = ec.compare_events(cob, jav)["diffs"] + assert [(f["field"], f["cobol"], f["java"]) for d in diffs for f in d["fields"]] == [ + ("screen.ROW.3", "CCCCC", "CCCCX") + ] + + +def test_an_occurrences_attribute_is_a_subfield_of_its_own(tmp_path): + (tmp_path / "occ.cpy").write_text(MAP_OCCURS, encoding="utf-8") + case = {"screens": {"M": {"copybook": "occ.cpy", "input": "MAPI", "output": "MAPO"}}} + data = bytearray(b"\x00" * 40) + data[15 + 9] = 0xF1 # ROWC(2): the colour byte of the second line + sub = ec.map_subfields(tmp_path, case, "M", bytes(data), "latin-1") + assert sub == {"ROW.2": {"color": 0xF1}} + + +def test_a_symbolic_map_with_occurs_depending_on_is_refused(tmp_path): + (tmp_path / "odo.cpy").write_text(MAP_ODO, encoding="utf-8") + with pytest.raises(ec.Unsupported, match="OCCURS DEPENDING ON"): + ec.screen_layout(tmp_path, "odo.cpy", "MAPI") + + +def test_a_receive_typed_into_one_occurrence_is_refused(tmp_path): (tmp_path / "occ.cpy").write_text(MAP_OCCURS, encoding="utf-8") - assert [f["name"] for f in ec.screen_layout(tmp_path, "map.cpy", "MAPI")] == ["FILLER", "ROWL", "ROWF", "ROWI"] - with pytest.raises(ec.Unsupported, match="symbolic map with OCCURS"): - ec.screen_layout(tmp_path, "occ.cpy", "MAPI") + layout = ec.screen_layout(tmp_path, "occ.cpy", "MAPI") + assert len(ec.map_input(layout, {}, "latin-1")) == 12 + 3 * 8 + with pytest.raises(ec.Unsupported, match="one occurrence"): + ec.map_input(layout, {"ROWI(2)": "TYPED"}, "latin-1") def _corpora() -> Path | None: @@ -206,15 +284,15 @@ def _corpora() -> Path | None: return p if p.is_dir() else None -def test_no_case_map_has_occurs_today(): - """The screens' single-occurrence compare holds for every case's map; the first map with OCCURS fails here (and - is refused by its proof) until the screen compare names every occurrence.""" +def test_every_case_map_resolves_and_bnk1cca_has_occurs(): + """Every case's map lays out (a map with OCCURS DEPENDING ON would be refused here), and CBSA's BNK1ACC is read at + each of its ten ACCOUNT occurrences (#5076).""" root = _corpora() if root is None: pytest.skip("no mainframe corpora") import equivalence as eq - seen = 0 + seen = occurs = 0 for path in sorted(eq.CASES.glob("*/case.json")): case = json.loads(path.read_text(encoding="utf-8")) corpus = root / case.get("corpus", "") @@ -222,7 +300,16 @@ def test_no_case_map_has_occurs_today(): continue for name in case["screens"]: for side in ("input", "output"): - ec.screen_fields(corpus, case, name, side) # raises Unsupported on an OCCURS + layout = ec.screen_fields(corpus, case, name, side) # raises Unsupported on an ODO seen += 1 + if path.parent.name == "cbsa-bnk1cca" and side == "output": + keys = {ec.screen_key(f) for f in layout} + assert {(f"ACCOUNT.{i}", "O") for i in range(1, 11)} <= keys + occurs += 1 if not seen: pytest.skip("no case's corpus is checked out") + assert ( + occurs + or not (eq.CASES / "cbsa-bnk1cca").is_dir() + or not (root / "cics-banking-sample-application-cbsa").is_dir() + ) diff --git a/tests/cobol_mainframe/test_det_screen_occurs_5076.py b/tests/cobol_mainframe/test_det_screen_occurs_5076.py new file mode 100644 index 0000000000..d287444ace --- /dev/null +++ b/tests/cobol_mainframe/test_det_screen_occurs_5076.py @@ -0,0 +1,75 @@ +"""#5076: SEND MAP / RECEIVE MAP over a map whose DFHMDF repeats (OCCURS=n). + +The screen forge writes a repeated field's value as `values.put("NAME" + "." + (i + 1), ...)`, which the translator's +reader of the screen class did not match: SEND MAP left every occurrence out of the port's screen, silently. Whole +operand class: no OCCURS, OCCURS=n, an OCCURS beside a JUSTIFY field, and an OCCURS=1 (not a repeat). +""" + +from __future__ import annotations + +import pytest + +pytest.importorskip("tree_sitter_language_pack") + +from gitgalaxy.tools.cobol_to_java.det import cics as C # noqa: E402 + +_SCREEN = """class LstScreen { + public static final List LAYOUT = List.of( + new ScreenField(null, 1, 1, 6, false, false, false, false, false, "Rows", null, 1, null), + new ScreenField("HEAD", 2, 1, 8, true, true, false, false, true, null, null, 1, "RIGHT,ZERO"), + new ScreenField("ROW", 3, 1, 79, false, false, false, false, false, null, "NEUTRAL", 10, null), + new ScreenField("PAIR", 14, 1, 5, false, false, false, false, false, null, null, 2, "LEFT"), + new ScreenField("ONE", 20, 1, 5, false, false, false, false, false, null, null, 1, null), + new ScreenField("MSG", 23, 1, 79, false, false, true, false, false, null, "YELLOW", 1, null)); + public Map screenValues() { + values.put("HEAD", head); + if (row != null) { + for (int i = 0; i < row.size(); i++) { + values.put("ROW" + "." + (i + 1), row.get(i)); + } + } + if (pair != null) { + for (int i = 0; i < pair.size(); i++) { + values.put("PAIR" + "." + (i + 1), pair.get(i)); + } + } + values.put("ONE", one); + values.put("MSG", msg); + } +} +""" + + +def _gp(tmp_path): + d = tmp_path / "src/main/java/x/dto/screen" + d.mkdir(parents=True) + (d / "LstScreen.java").write_text(_SCREEN, encoding="utf-8") + return C.Generated(tmp_path, "") + + +def test_every_field_is_listed_a_repeated_one_too(tmp_path): + assert _gp(tmp_path).screen_fields("LstScreen") == ["HEAD", "ROW", "PAIR", "ONE", "MSG"] + + +def test_the_repeats_are_read_off_the_layout(tmp_path): + assert _gp(tmp_path).screen_occurs("LstScreen") == {"ROW": 10, "PAIR": 2} # OCCURS=1 and no OCCURS: absent + + +def test_a_screen_without_repeats_has_none(tmp_path): + d = tmp_path / "src/main/java/x/dto/screen" + d.mkdir(parents=True) + (d / "OldScreen.java").write_text( + 'new ScreenField("AMT", 2, 10, 8, true, true, false, false, true, null, null, 1, null));\n values.put("AMT", a);\n', + encoding="utf-8", + ) + gp = C.Generated(tmp_path, "") + assert gp.screen_occurs("OldScreen") == {} and gp.screen_fields("OldScreen") == ["AMT"] + + +def test_a_slot_per_occurrence(tmp_path): + c = C.Cics.__new__(C.Cics) + c.gp = _gp(tmp_path) + slots = c.screen_slots("LstScreen") + assert slots[0] == ("HEAD", None) and slots[1] == ("ROW", 1) and slots[10] == ("ROW", 10) + assert slots[11:13] == [("PAIR", 1), ("PAIR", 2)] and slots[-2:] == [("ONE", None), ("MSG", None)] + assert len(slots) == 1 + 10 + 2 + 2 diff --git a/tests/equivalence/cbsa-bnk1cca/LICENSE b/tests/equivalence/cbsa-bnk1cca/LICENSE new file mode 100644 index 0000000000..e48e096345 --- /dev/null +++ b/tests/equivalence/cbsa-bnk1cca/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/tests/equivalence/cbsa-bnk1cca/NOTICE b/tests/equivalence/cbsa-bnk1cca/NOTICE new file mode 100644 index 0000000000..f2df8d5692 --- /dev/null +++ b/tests/equivalence/cbsa-bnk1cca/NOTICE @@ -0,0 +1,16 @@ +CICS Bank Sample Application (CBSA) +https://github.com/cicsdev/cics-banking-sample-application-cbsa +Copyright IBM Corp. 2023 + +Licensed under the Eclipse Public License - v 2.0 (see LICENSE, the corpus's +own licence file). The programs under test -- src/base/cobol_src/BNK1CCA.cbl +and the programs it LINKs in the task, INQACCCU.cbl, INQCUST.cbl and +ABNDPROC.cbl -- their copybooks, the BMS mapset its symbolic map is +generated from (src/base/bms_src/BNK1ACC.bms) and the tables' DDL +(etc/install/base/db2jcl/CRETB01.jcl, CREI101.jcl) are CBSA's, used unmodified from +the corpus. + +seed.sql and customer.txt in this directory are synthetic test data written +for these cases in the layout of CBSA's ACCOUNT, PROCTRAN and CONTROL tables +and CUSTOMER file; they are not CBSA data. The scenarios in case.json are +written for this case. diff --git a/tests/equivalence/cbsa-bnk1cca/abndfile.bin b/tests/equivalence/cbsa-bnk1cca/abndfile.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/equivalence/cbsa-bnk1cca/case.json b/tests/equivalence/cbsa-bnk1cca/case.json new file mode 100644 index 0000000000..4589449275 --- /dev/null +++ b/tests/equivalence/cbsa-bnk1cca/case.json @@ -0,0 +1,257 @@ +{ + "kind": "cics", + "corpus": "cics-banking-sample-application-cbsa", + "program": "BNK1CCA", + "program_source": "src/base/cobol_src/BNK1CCA.cbl", + "copy_dirs": [ + "src/base/cobol_copy" + ], + "transid": "OCCA", + "termid": "T001", + "clock": "2026/10/01 10:30:15.00", + "region": { + "applid": "CBSAREGN", + "sysid": "CICA" + }, + "taskn": 4242, + "programs": [ + { + "program": "INQACCCU", + "program_source": "src/base/cobol_src/INQACCCU.cbl" + }, + { + "program": "INQCUST", + "program_source": "src/base/cobol_src/INQCUST.cbl" + }, + { + "program": "ABNDPROC", + "program_source": "src/base/cobol_src/ABNDPROC.cbl" + } + ], + "datasets": { + "CBSA.CICSBSA.ABNDFILE": { + "input": "@case/abndfile.bin", + "entity": "WsAbndArea", + "copybook": "src/base/cobol_src/ABNDPROC.cbl", + "record": "WS-ABND-AREA", + "recovery": "NONE", + "recovery_why": "etc/install/base/installjcl/BANK.csd: DEFINE FILE(ABNDFILE) ... RECOVERY(NONE) -- the abend log a backout must not undo" + }, + "CBSA.CICSBSA.CUSTOMER": { + "input": "@case/customer.txt", + "entity": "OutputData", + "copybook": "src/base/cobol_copy/CUSTOMER.cpy", + "record": "CUSTOMER-RECORD" + } + }, + "db2": { + "ddl": [ + "etc/install/base/db2jcl/CRETB01.jcl", + "etc/install/base/db2jcl/CREI101.jcl" + ], + "seed": "@case/seed.sql", + "compare": [ + "IBMUSER.ACCOUNT" + ], + "qualifier": "IBMUSER" + }, + "commarea": { + "segments": [ + { + "copybook": "src/base/cobol_src/BNK1CCA.cbl", + "record": "WS-COMM-AREA" + } + ] + }, + "screens": { + "BNK1ACC": { + "copybook": "@bms/BNK1ACC.cpy", + "input": "BNK1ACCI", + "output": "BNK1ACCO" + } + }, + "scenarios": [ + { + "name": "first-entry", + "aid": "DFHENTER", + "commarea": null, + "why": "EIBCALEN 0: BNK1ACCO set to LOW-VALUES, the cursor on CUSTNO, SEND MAP ERASE; RETURN TRANSID('OCCA') with an initialised COMMAREA" + }, + { + "name": "pa1", + "aid": "DFHPA1", + "commarea": {}, + "why": "PA1: CONTINUE, nothing sent; RETURN TRANSID with the COMMAREA" + }, + { + "name": "pf3", + "aid": "DFHPF3", + "commarea": {}, + "why": "PF3: RETURN TRANSID('OMEN') IMMEDIATE: back to the main menu" + }, + { + "name": "pf12", + "aid": "DFHPF12", + "commarea": {}, + "why": "PF12: SEND-TERMINATION-MSG, RETURN" + }, + { + "name": "clear", + "aid": "DFHCLEAR", + "commarea": {}, + "why": "CLEAR: SEND CONTROL ERASE FREEKB, RETURN" + }, + { + "name": "other-key", + "aid": "DFHPF5", + "commarea": {}, + "why": "PF5: WHEN OTHER -- 'Invalid key pressed.', SEND MAP DATAONLY ALARM" + }, + { + "name": "mapfail", + "aid": "DFHENTER", + "commarea": {}, + "why": "ENTER with no field modified: RM010 -- ABNDPROC LINKed (an ABNDFILE record) and ABEND HBNK" + }, + { + "name": "three-accounts", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000001" + } + }, + "why": "customer 1: INQACCCU returns its three accounts of the program's sort code; '3 accounts found', ACCOUNT(1) to ACCOUNT(3) each a formatted line (sort code, number, type, +/- available and actual balance), ACCOUNT(4) to ACCOUNT(10) spaces -- every occurrence is compared (#5076)" + }, + { + "name": "negative-loan", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000002" + } + }, + "why": "customer 2, one LOAN account: both balance signs '-', '1 accounts found'" + }, + { + "name": "no-accounts", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000003" + } + }, + "why": "customer 3 is on file with no account: 'No accounts found for customer', every ACCOUNT occurrence spaces" + }, + { + "name": "customer-not-on-file", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000005" + } + }, + "why": "customer 5 has an account but no CUSTOMER record: CUSTOMER-FOUND 'N', 'Unable to find customer 0000000005', the ten ACCOUNT occurrences emptied" + }, + { + "name": "customer-zero", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000000" + } + }, + "why": "customer 0000000000 is not looked up by INQACCCU: CUSTOMER-FOUND 'N', 'Unable to find customer'" + }, + { + "name": "customer-9999999999", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "9999999999" + } + }, + "why": "customer 9999999999 is not looked up either: 'Unable to find customer'" + }, + { + "name": "null-statement-date", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000004" + } + }, + "why": "customer 4's account has a NULL last-statement date with no indicator: INQACCCU's FETCH fails (-305), COMM-SUCCESS 'N' and the program shows its own error message" + }, + { + "name": "ten-accounts", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000007" + } + }, + "why": "customer 7 has exactly ten accounts: '10 accounts found', all ten ACCOUNT occurrences filled, the table's last (10) included" + }, + { + "name": "twelve-accounts", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000006" + } + }, + "why": "customer 6 has twelve accounts: INQACCCU returns them all (20 asked for), the program shows the first ten (WS-INDEX > 10 ends the loop) and says '12 accounts found'" + }, + { + "name": "link-inqacccu-pgmiderr", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000001" + } + }, + "faults": [ + { + "cmd": "LINK", + "program": "INQACCCU", + "resp": "PGMIDERR", + "nth": 1, + "why": "the LINK of INQACCCU raises PGMIDERR" + } + ], + "why": "customer 1, but the LINK INQACCCU fails with PGMIDERR: GCD010 fills ABNDINFO from the clock and EIB, LINKs ABNDPROC (an ABNDFILE record) and abends HBNK; no account line is sent" + }, + { + "name": "link-inqacccu-notauth", + "aid": "DFHENTER", + "commarea": {}, + "receive": { + "BNK1ACC": { + "CUSTNOI": "0000000001" + } + }, + "faults": [ + { + "cmd": "LINK", + "program": "INQACCCU", + "resp": "NOTAUTH", + "nth": 1, + "why": "the LINK of INQACCCU raises NOTAUTH" + } + ], + "why": "customer 1, but the LINK INQACCCU fails with NOTAUTH: GCD010 fills ABNDINFO from the clock and EIB, LINKs ABNDPROC (an ABNDFILE record) and abends HBNK; no account line is sent" + } + ], + "notes": "CBSA's customer-account-list screen (BNK1CCA, transaction OCCA, BMS map BNK1ACC of mapset BNK1ACC: the symbolic map is generated from src/base/bms_src/BNK1ACC.bms, @bms/BNK1ACC.cpy). Pseudo-conversational: a first task (EIBCALEN 0) sends the empty map; a returning one acts on the AID -- PA keys nothing, PF3 RETURN TRANSID('OMEN') IMMEDIATE (the task's terminal T001 is stated: oracle_assumptions.md X27), PF12 the termination message, CLEAR, ENTER, any other key 'Invalid key pressed.'. ENTER RECEIVEs the map (MAPFAIL abends HBNK through ABNDPROC), checks the customer number is numeric, LINKs INQACCCU (INQCUST and ABNDPROC run in the task, as in cbsa-inqacccu) and shows one formatted line per account in the map's ACCOUNT field, which BMS repeats ten times (OCCURS=10). The screen is compared at every occurrence by its subscript (ACCOUNT.1 ... ACCOUNT.10, #5076), as a COMMAREA's OCCURS is (#4765): a second or tenth line that differs is a difference, as is a line one side leaves out. No scenario types letters into the NUM field CUSTNO (owner decision on #5116, 2026-10-15), and none types into one occurrence of ACCOUNT (a protected output field; refused by name). The COMMAREA is compared as the program RETURNs it, WS-COMM-AREA (the same 248-byte layout as its DFHCOMMAREA, which it reads back only for EIBCALEN). ACCOUNT, CUSTOMER and ABNDFILE are compared after every scenario. LINK INQACCCU is faulted with PGMIDERR and NOTAUTH. Not reached: the failure branches after SEND MAP / SEND TEXT / SEND CONTROL / RETURN (no fault can be injected on them), and a CUSTNO that is not numeric (a 3270 NUM field cannot key it, owner decision on #5116). Data: seed.sql and customer.txt are SYNTHETIC (cbsa-inqacccu's, with customers 6 and 7 added for the full table). Derived from the CICS Bank Sample Application (CBSA, github.com/cicsdev/cics-banking-sample-application-cbsa; Copyright IBM Corp. 2023), licensed under the Eclipse Public License 2.0: LICENSE here is the corpus's own; NOTICE says what is derived." +} diff --git a/tests/equivalence/cbsa-bnk1cca/customer.txt b/tests/equivalence/cbsa-bnk1cca/customer.txt new file mode 100644 index 0000000000..b82ba88399 --- /dev/null +++ b/tests/equivalence/cbsa-bnk1cca/customer.txt @@ -0,0 +1,8 @@ +CUST1234560000000001Mr Other Branch Another Sortcode Road 0202198230002022027 +CUST9876540000000001Mr Alan B Smith 12 Station Road, Winchester, SO23 8AB 1103195241201102026 +CUST9876540000000002Mrs Carol D Jones 42 High Street, Romsey, SO51 8AA 0506196069915102026 +CUST9876540000000003Dr Edward F Green 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 +CUST9876540000000004Professor Gail H Brown 1 University Road, Southampton, SO17 1BJ 0101190000031122026 +CUST9876549999999999Ms Highest Number Last Street 3112199950001012027 +CUST9876540000000006Ms Fiona G Lee 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 +CUST9876540000000007Mr Hugh J Park 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 diff --git a/tests/equivalence/cbsa-bnk1cca/seed.sql b/tests/equivalence/cbsa-bnk1cca/seed.sql new file mode 100644 index 0000000000..ee50037ab4 --- /dev/null +++ b/tests/equivalence/cbsa-bnk1cca/seed.sql @@ -0,0 +1,30 @@ +INSERT INTO IBMUSER.ACCOUNT VALUES + ('ACCT','0000000001','987654','00000001','CURRENT ', 0.00,'2019-03-15', 500,'2026-08-01','2026-09-01', 1234.56, 1200.00), + ('ACCT','0000000001','987654','00000002','SAVING ', 2.50,'2020-01-31', 0,'2026-08-01','2026-09-01', 98765.43, 98765.43), + ('ACCT','0000000001','987654','00000003','ISA ', 3.10,'2024-02-29', 0,'2026-07-15','2026-10-30', 0.00, 0.00), + ('ACCT','0000000001','123456','00000010','CURRENT ', 0.00,'2017-05-05', 100,'2026-08-01','2026-09-01', 50.00, 50.00), + ('ACCT','0000000002','987654','00000004','LOAN ',17.90,'2018-12-01', 0,'2026-07-15','2026-08-15', -4500.00, -4500.00), + ('ACCT','0000000004','987654','00000005','CURRENT ', 0.00,'2021-06-30', 0, NULL, '2026-10-30', 10.00, 10.00), + ('ACCT','0000000005','987654','00000006','MORTGAGE', 4.25,'2015-02-28', 0,'2026-08-28','2026-09-28', -150000.00, -150000.00), + ('ACCT','0000000006','987654','00000020','CURRENT ', 0.50,'2020-01-15', 0,'2026-08-01','2026-09-01', 1000.25, 900.50), + ('ACCT','0000000006','987654','00000021','SAVING ', 1.50,'2020-02-15', 100,'2026-08-01','2026-09-01', 2000.50, 1801.00), + ('ACCT','0000000006','987654','00000022','ISA ', 2.50,'2020-03-15', 200,'2026-08-01','2026-09-01', 3000.75, 2701.50), + ('ACCT','0000000006','987654','00000023','CURRENT ', 3.50,'2020-04-15', 300,'2026-08-01','2026-09-01', 4001.00, 3602.00), + ('ACCT','0000000006','987654','00000024','SAVING ', 4.50,'2020-05-15', 400,'2026-08-01','2026-09-01', 5001.25, 4502.50), + ('ACCT','0000000006','987654','00000025','ISA ', 5.50,'2020-06-15', 500,'2026-08-01','2026-09-01', 6001.50, 5403.00), + ('ACCT','0000000006','987654','00000026','CURRENT ', 6.50,'2020-07-15', 600,'2026-08-01','2026-09-01', 7001.75, 6303.50), + ('ACCT','0000000006','987654','00000027','SAVING ', 7.50,'2020-08-15', 700,'2026-08-01','2026-09-01', 8002.00, 7204.00), + ('ACCT','0000000006','987654','00000028','ISA ', 8.50,'2020-09-15', 800,'2026-08-01','2026-09-01', 9002.25, 8104.50), + ('ACCT','0000000006','987654','00000029','CURRENT ', 9.50,'2020-01-15', 900,'2026-08-01','2026-09-01', 10002.50, 9005.00), + ('ACCT','0000000006','987654','00000030','SAVING ', 10.50,'2020-02-15', 1000,'2026-08-01','2026-09-01', 11002.75, 9905.50), + ('ACCT','0000000006','987654','00000031','ISA ', 11.50,'2020-03-15', 1100,'2026-08-01','2026-09-01', 12003.00, 10806.00), + ('ACCT','0000000007','987654','00000040','LOAN ', 1.25,'2019-10-20', 0,'2026-07-15','2026-08-15', -250.50, -250.50), + ('ACCT','0000000007','987654','00000041','SAVING ', 2.25,'2019-11-20', 1,'2026-07-15','2026-08-15', 155.40, 155.40), + ('ACCT','0000000007','987654','00000042','LOAN ', 3.25,'2019-12-20', 2,'2026-07-15','2026-08-15', -751.50, -751.50), + ('ACCT','0000000007','987654','00000043','SAVING ', 4.25,'2019-10-20', 3,'2026-07-15','2026-08-15', 310.80, 310.80), + ('ACCT','0000000007','987654','00000044','LOAN ', 5.25,'2019-11-20', 4,'2026-07-15','2026-08-15', -1252.50, -1252.50), + ('ACCT','0000000007','987654','00000045','SAVING ', 6.25,'2019-12-20', 5,'2026-07-15','2026-08-15', 466.20, 466.20), + ('ACCT','0000000007','987654','00000046','LOAN ', 7.25,'2019-10-20', 6,'2026-07-15','2026-08-15', -1753.50, -1753.50), + ('ACCT','0000000007','987654','00000047','SAVING ', 8.25,'2019-11-20', 7,'2026-07-15','2026-08-15', 621.60, 621.60), + ('ACCT','0000000007','987654','00000048','LOAN ', 9.25,'2019-12-20', 8,'2026-07-15','2026-08-15', -2254.50, -2254.50), + ('ACCT','0000000007','987654','00000049','SAVING ', 10.25,'2019-10-20', 9,'2026-07-15','2026-08-15', 777.00, 777.00); diff --git a/tests/equivalence/cbsa-delcus/LICENSE b/tests/equivalence/cbsa-delcus/LICENSE new file mode 100644 index 0000000000..e48e096345 --- /dev/null +++ b/tests/equivalence/cbsa-delcus/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/tests/equivalence/cbsa-delcus/NOTICE b/tests/equivalence/cbsa-delcus/NOTICE new file mode 100644 index 0000000000..360cf83f69 --- /dev/null +++ b/tests/equivalence/cbsa-delcus/NOTICE @@ -0,0 +1,15 @@ +CICS Bank Sample Application (CBSA) +https://github.com/cicsdev/cics-banking-sample-application-cbsa +Copyright IBM Corp. 2023 + +Licensed under the Eclipse Public License - v 2.0 (see LICENSE, the corpus's +own licence file). The programs under test -- src/base/cobol_src/DELCUS.cbl +and the programs it LINKs in the task, INQCUST.cbl, INQACCCU.cbl, DELACC.cbl +and ABNDPROC.cbl -- their copybooks and the tables' DDL +(etc/install/base/db2jcl/CRETB01.jcl, CREI101.jcl, CRETB02.jcl) are CBSA's, +used unmodified from the corpus. + +seed.sql and customer.txt in this directory are synthetic test data written +for these cases in the layout of CBSA's ACCOUNT and PROCTRAN tables and +CUSTOMER file; they are not CBSA data. The scenarios in case.json are +written for this case. diff --git a/tests/equivalence/cbsa-delcus/abndfile.bin b/tests/equivalence/cbsa-delcus/abndfile.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/equivalence/cbsa-delcus/case.json b/tests/equivalence/cbsa-delcus/case.json new file mode 100644 index 0000000000..4175540164 --- /dev/null +++ b/tests/equivalence/cbsa-delcus/case.json @@ -0,0 +1,164 @@ +{ + "kind": "cics", + "corpus": "cics-banking-sample-application-cbsa", + "program": "DELCUS", + "program_source": "src/base/cobol_src/DELCUS.cbl", + "copy_dirs": [ + "src/base/cobol_copy" + ], + "transid": "ODCS", + "clock": "2026/10/01 10:30:15.00", + "region": { + "applid": "CBSAREGN", + "sysid": "CICA" + }, + "taskn": 4242, + "programs": [ + { + "program": "INQCUST", + "program_source": "src/base/cobol_src/INQCUST.cbl" + }, + { + "program": "INQACCCU", + "program_source": "src/base/cobol_src/INQACCCU.cbl" + }, + { + "program": "DELACC", + "program_source": "src/base/cobol_src/DELACC.cbl" + }, + { + "program": "ABNDPROC", + "program_source": "src/base/cobol_src/ABNDPROC.cbl" + } + ], + "datasets": { + "CBSA.CICSBSA.ABNDFILE": { + "input": "@case/abndfile.bin", + "entity": "WsAbndArea", + "copybook": "src/base/cobol_src/ABNDPROC.cbl", + "record": "WS-ABND-AREA", + "recovery": "NONE", + "recovery_why": "etc/install/base/installjcl/BANK.csd: DEFINE FILE(ABNDFILE) ... RECOVERY(NONE) -- the abend log a backout must not undo" + }, + "CBSA.CICSBSA.CUSTOMER": { + "input": "@case/customer.txt", + "entity": "OutputData", + "copybook": "src/base/cobol_copy/CUSTOMER.cpy", + "record": "CUSTOMER-RECORD" + } + }, + "db2": { + "ddl": [ + "etc/install/base/db2jcl/CRETB01.jcl", + "etc/install/base/db2jcl/CREI101.jcl", + "etc/install/base/db2jcl/CRETB02.jcl" + ], + "seed": "@case/seed.sql", + "compare": [ + "IBMUSER.ACCOUNT", + "IBMUSER.PROCTRAN" + ], + "qualifier": "IBMUSER" + }, + "commarea": { + "segments": [ + { + "copybook": "src/base/cobol_src/DELCUS.cbl", + "record": "DFHCOMMAREA" + } + ] + }, + "linked": true, + "screens": {}, + "scenarios": [ + { + "name": "three-accounts", + "commarea": { + "COMM-CUSTNO": "0000000001" + }, + "why": "customer 1 is on file with three accounts of the program's sort code: INQCUST finds it, INQACCCU lists them, DELACC deletes each (ACCOUNT rows gone, a PROCTRAN 'ODA' row each), the CUSTOMER record is read for update and deleted, COMM-DEL-SUCCESS 'Y'" + }, + { + "name": "one-loan", + "commarea": { + "COMM-CUSTNO": "0000000002" + }, + "why": "customer 2, one LOAN account with a negative balance: one DELACC, then the customer is deleted" + }, + { + "name": "no-accounts", + "commarea": { + "COMM-CUSTNO": "0000000007" + }, + "why": "customer 7 is on file with no account: NUMBER-OF-ACCOUNTS 0, DELETE-ACCOUNTS is not performed, the customer is deleted" + }, + { + "name": "null-statement-date", + "commarea": { + "COMM-CUSTNO": "0000000003" + }, + "why": "customer 3's account has a NULL last-statement date with no indicator: INQACCCU's FETCH fails (-305) and returns no account, so DELCUS deletes the customer alone" + }, + { + "name": "max-values", + "commarea": { + "COMM-CUSTNO": "0000000006" + }, + "why": "customer 6's account holds the extreme picture values (9999.99 rate, 99999999 overdraft, +/-9999999999.99): DELACC's PROCTRAN row and the deletion at the top of every range" + }, + { + "name": "last-customer", + "commarea": { + "COMM-CUSTNO": "9999999999" + }, + "why": "customer 9999999999: INQCUST resolves it to the last customer on file; DELCUS reads and deletes the record keyed 9999999999 (the last one) and its account 99999999" + }, + { + "name": "not-on-file", + "commarea": { + "COMM-CUSTNO": "0000000005" + }, + "why": "customer 5 has an account but no CUSTOMER record: INQCUST fails, COMM-DEL-SUCCESS 'N' with INQCUST's fail code, RETURN; nothing deleted" + }, + { + "name": "customer-zero", + "commarea": { + "COMM-CUSTNO": "0000000000" + }, + "why": "customer 0000000000: INQCUST resolves it to the last customer, the READ of key 0000000000 is NOTFND: the program's own failure path" + }, + { + "name": "read-sysiderr-retried", + "commarea": { + "COMM-CUSTNO": "0000000002" + }, + "faults": [ + { + "cmd": "READ", + "file": "CUSTOMER", + "resp": "SYSIDERR", + "nth": 3, + "why": "53 on DELCUS's READ UPDATE (the 3rd READ of CUSTOMER in the task: INQCUST, INQACCCU's INQCUST, DELCUS)" + } + ], + "why": "DELCUS's READ FOR UPDATE gets SYSIDERR once: DELAY 3 seconds (a task takes no time, X4), READ again, then the deletion goes on" + }, + { + "name": "delete-ioerr", + "commarea": { + "COMM-CUSTNO": "0000000002" + }, + "faults": [ + { + "cmd": "DELETE", + "file": "CUSTOMER", + "resp": "IOERR", + "nth": 1, + "why": "17 on the DELETE" + } + ], + "why": "the DELETE of the CUSTOMER record fails with IOERR: the program's failure path" + } + ], + "notes": "CBSA's delete-customer service (DELCUS, LINKed by BNK1DCS with the DELCUS copybook's COMMAREA). It LINKs INQCUST (the customer must exist: 'N' and INQCUST's fail code otherwise), lists the customer's accounts through INQACCCU (SYNCONRETURN), LINKs DELACC for each (ACCOUNT row deleted, PROCTRAN 'ODA' row written, as in cbsa-delacc), then READs the CUSTOMER record FOR UPDATE and DELETEs it, retrying on SYSIDERR (DELAY FOR SECONDS(3), no time passes in a task, oracle_assumptions.md X4). ACCOUNT, PROCTRAN, CUSTOMER and ABNDFILE are compared after every scenario. Data: seed.sql is cbsa-delacc's and customer.txt is cbsa-bnk1cca's (SYNTHETIC). Not reached: failure branches no fault can be injected on (ASKTIME / FORMATTIME / ASSIGN, the Db2 INSERT of the PROCTRAN row, LINK DELACC). Derived from the CICS Bank Sample Application (CBSA, github.com/cicsdev/cics-banking-sample-application-cbsa; Copyright IBM Corp. 2023), licensed under the Eclipse Public License 2.0: LICENSE here is the corpus's own; NOTICE says what is derived." +} diff --git a/tests/equivalence/cbsa-delcus/customer.txt b/tests/equivalence/cbsa-delcus/customer.txt new file mode 100644 index 0000000000..b82ba88399 --- /dev/null +++ b/tests/equivalence/cbsa-delcus/customer.txt @@ -0,0 +1,8 @@ +CUST1234560000000001Mr Other Branch Another Sortcode Road 0202198230002022027 +CUST9876540000000001Mr Alan B Smith 12 Station Road, Winchester, SO23 8AB 1103195241201102026 +CUST9876540000000002Mrs Carol D Jones 42 High Street, Romsey, SO51 8AA 0506196069915102026 +CUST9876540000000003Dr Edward F Green 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 +CUST9876540000000004Professor Gail H Brown 1 University Road, Southampton, SO17 1BJ 0101190000031122026 +CUST9876549999999999Ms Highest Number Last Street 3112199950001012027 +CUST9876540000000006Ms Fiona G Lee 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 +CUST9876540000000007Mr Hugh J Park 7 Church Lane, Hursley, SO21 2JN 2902196499929022028 diff --git a/tests/equivalence/cbsa-delcus/seed.sql b/tests/equivalence/cbsa-delcus/seed.sql new file mode 100644 index 0000000000..8a0964876f --- /dev/null +++ b/tests/equivalence/cbsa-delcus/seed.sql @@ -0,0 +1,13 @@ +INSERT INTO IBMUSER.ACCOUNT VALUES + ('ACCT','0000000001','987654','00000000','CURRENT ', 0.00,'2016-01-01', 0,'2026-08-01','2026-09-01', 0.00, 0.00), + ('ACCT','0000000001','987654','00000001','CURRENT ', 0.00,'2019-03-15', 500,'2026-08-01','2026-09-01', 1234.56, 1200.00), + ('ACCT','0000000001','987654','00000002','SAVING ', 2.50,'2020-01-31', 0,'2026-08-01','2026-09-01', 98765.43, 98765.43), + ('ACCT','0000000002','987654','00000003','LOAN ',17.90,'2018-12-01', 0,'2026-07-15','2026-08-15', -4500.00, -4500.00), + ('ACCT','0000000003','987654','00000004','ISA ', 3.10,'2021-06-30', 0, NULL, '2026-10-30', 0.00, 0.00), + ('ACCT','0000000004','123456','00000010','CURRENT ', 0.00,'2017-05-05', 100,'2026-08-01','2026-09-01', 50.00, 50.00), + ('ACCT','0000000005','987654','00000005','MORTGAGE', 4.25,'2015-02-28', 0,'2026-08-28','2026-09-28', -150000.00, -150000.00), + ('ACCT','0000000006','987654','00000006','CURRENT ', 9999.99,'2024-02-29',99999999,'2024-02-29','2028-02-29', 9999999999.99, -9999999999.99), + ('ACCT','0000000008','987654','00000011','CURRENT ', 0.00,'2023-04-02',123456789,'2026-12-31','2027-01-01', 0.00, 0.01), + ('ACCT','9999999999','987654','99999999','SAVING ', 1.05,'2001-09-09', 50,'2026-09-15','2026-10-15', 321.09, 300.00); +INSERT INTO IBMUSER.PROCTRAN VALUES + ('PRTR','987654','00000001','2026-09-30','091500','000000000042','CRE','COUNTER RECVED ', 1000.00); diff --git a/tests/equivalence/det_sweep_baseline.json b/tests/equivalence/det_sweep_baseline.json index 4e948b9aef..73b727e701 100644 --- a/tests/equivalence/det_sweep_baseline.json +++ b/tests/equivalence/det_sweep_baseline.json @@ -33,6 +33,10 @@ "mortgage-cmort": { "issue": "#4803", "why": "EPSCMORT's ENTER path LINKs EPSCSMRT (PROGRAM from a variable), which CALLs EPSMPMT, which has holes (line 125: (1 + C) ** N floating-point exponentiation, oracle_assumptions.md C6; line 144: FUNCTION ANNUITY, dead code). The case's 16 scenarios run equal on both sides (runTask and java-facade) and the ENTER / WHEN OTHER scenarios stay on EPSMPMT's validation paths, but a program is not proven while a program it LINKs or CALLs has holes, whichever scenarios were written (owner decision 2026-10-11, mainframe-evidence-policy.md 4): the proof passes, the port stays unproven until EPSMPMT's holes are closed (#4271)." + }, + "cbsa-delcus": { + "issue": "#5076", + "why": "DELCUS deletes the CUSTOMER record after INQACCCU has issued a SYNCPOINT ROLLBACK (its cursor FETCH failing: customer 3's NULL last-statement date -305, and the derived SQL faults at INQACCCU lines 246 and 466). IBM: the rollback ends the unit of work and the DELETE that follows is committed with the task; the COBOL side does that. The Java side marks the whole transaction rollback-only at the rollback (the Db2 connection is rolled back there and goes on, #4270, but a recoverable FILE is not), so the DELETE is lost and the CUSTOMER file differs (register Q3: 'A recoverable FILE changed after a SYNCPOINT ROLLBACK is still undone with the task on the Java side'). Every event, COMMAREA, ACCOUNT, PROCTRAN and ABNDFILE of every scenario is equal; only those three scenarios differ, in the CUSTOMER file. Not loosened: the three stay compared. The fix is a harness change (the task's file transaction restarted at a rollback), a different mechanism from this PR's." } }, "model": {} diff --git a/tests/equivalence/det_sweep_coverage/cbsa-bnk1cca.json b/tests/equivalence/det_sweep_coverage/cbsa-bnk1cca.json new file mode 100644 index 0000000000..9c7cfbc619 --- /dev/null +++ b/tests/equivalence/det_sweep_coverage/cbsa-bnk1cca.json @@ -0,0 +1,65 @@ +{ + "format": "det-sweep-coverage/2", + "scenarios": 20, + "paragraphs": { + "covered": 25, + "live": 25 + }, + "branches": { + "covered": 29, + "total": 41 + }, + "linked": { + "ABNDPROC": { + "paragraphs": { + "covered": 4, + "live": 4 + }, + "branches": { + "covered": 1, + "total": 2 + } + }, + "INQACCCU": { + "paragraphs": { + "covered": 16, + "live": 22 + }, + "branches": { + "covered": 19, + "total": 33 + } + }, + "INQCUST": { + "paragraphs": { + "covered": 7, + "live": 25 + }, + "branches": { + "covered": 10, + "total": 46 + } + } + }, + "inputs": { + "case": "dd13d26230ce404cd362af34b6099d91cb137bbe7b659b321e14123dc91aa053", + "corpus": "7a4c86e6d6688f1516ec00cbd81941457457c861bf900e2b3a7f269c6bf9058b", + "harness": "7037f567eb0cbde2bfb1a67e97a6c3c467789be357a8ca6d124f513d53b77fc7", + "oracle": "810664ca8478483a487d19ec662907928ec80dfdbd68f723d27307fd0f55610c", + "components": { + "harness": { + "harness:cics": "09d7d978e0b9176179ed02d78fe3ecf9de54afce92750a6722d8a728fe28ccc9", + "harness:core": "6f13d22bab57c67e9caaf5d2714906e9c7c0149a83dcfd6650e2514e2c478318", + "harness:db2": "e5278abdc64341d0e53d9bd81423cf2362cc2d2a791254e1e67aff17a0565fb4" + }, + "oracle": { + "oracle:cics-stub": "bee3c4c7f1416491ec75aa79b1e64a4d371a8b2223e4e1248762294ee60c772d", + "oracle:core": "eea2a006cc58ebd1141804b10def30c657d4d2d2a3d2a19cf5d137d9dea04359", + "oracle:db2": "ddbfe636cb402ffc4f9cb0ba1e156fcffebc9403e889a4add24a5e6483e5c57e", + "oracle:image-db2": "f138cc0d724e5e2bb4ff947dcd4c1cfed39aa35bfd7dcb27ff18a4e20b2c6198", + "oracle:le": "66f2875621048177f9d8c839aa5b86b7a6c4888fcf8a837c04900fe6e6fe673b" + } + } + }, + "measured_at": "348b704ec543f1551aa9a6b8ca4ad7f263d332f4" +} diff --git a/tests/tools/equivalence_cics.py b/tests/tools/equivalence_cics.py index ec35a17871..e35194dd07 100644 --- a/tests/tools/equivalence_cics.py +++ b/tests/tools/equivalence_cics.py @@ -2808,6 +2808,9 @@ def map_input(fields: list[dict[str, Any]], values: dict[str, str], enc: str = c typed: dict[str, Any] = {} width = {f["name"]: f["bytes"] for f in fields} for field, text in values.items(): + if "(" in field: # #5076: the Java side's typed values are keyed by the BMS name, not by a subscript + raise Unsupported(f"RECEIVE MAP: {field} -- typing into one occurrence of a repeated field is not " + "modelled", ["BMS OCCURS input"]) # fmt: skip placed = ( justified(text, width[field], justify[field]) if justify and field in justify and field in width else text ) @@ -3101,17 +3104,25 @@ def screen_fields(corpus: Path, case: dict[str, Any], map_name: str, side: str) def screen_layout(corpus: Path, copybook: str, record: str) -> list[dict[str, Any]]: - """A symbolic map's layout (one side's record), as a screen is compared: each field once. #4778: a map whose - fields repeat (BMS OCCURS=, an OCCURS in its symbolic map) is refused by name -- a screen's fields are compared - by their single-occurrence layout, which would read only the first of them -- until the screen compare names - every occurrence, as a COMMAREA's (#4765) does. No case's map has one yet; test_call_compare_4778 keeps it so.""" - fields = common.layout_fields(corpus, copybook, record) - if any(f.get("subscripts") for f in common.layout_fields(corpus, copybook, record, occurrences=True)): - raise Unsupported(f"{record} ({copybook}): a symbolic map with OCCURS -- its repeated fields would be compared " - "at their first occurrence only", ["BMS OCCURS"]) # fmt: skip + """A symbolic map's layout (one side's record), as a screen is compared: every occurrence of a repeated field + (BMS OCCURS=, an OCCURS in its symbolic map) is a field of its own, named by its subscript (ACCOUNTO(3)), as a + COMMAREA's (#4765) is -- a compare that read only the first would call a differing second occurrence equal + (#4778, #5076). `screen_key` gives each its screen name. An OCCURS DEPENDING ON in a map is refused by name: a + generated symbolic map has none, and which occurrences a screen holds would be a guess.""" + fields = common.layout_fields(corpus, copybook, record, occurrences=True) + if any(f.get("odo") for f in fields): + raise Unsupported(f"{record} ({copybook}): a symbolic map with OCCURS DEPENDING ON -- which occurrences the " + "screen holds is not stated", ["BMS OCCURS DEPENDING ON"]) # fmt: skip return fields +def screen_key(f: dict[str, Any]) -> tuple[str, str]: + """(screen name, kind letter) of a symbolic-map field: ACCOUNTO(3) -> ("ACCOUNT.3", "O"), COMPANYO -> ("COMPANY", + "O"). The name is the BMS field's, the occurrence after a dot, as the generated screen's screenValues() keys it.""" + base = f.get("base") or f["name"] + return base[:-1] + "".join(f".{k}" for k in f.get("subscripts") or []), base[-1:] + + # #4023 follow-up: the conditions a scenario may inject, by name -> their DFHRESP numbers (the spec's, #4270 spec PR 3) CICS_RESP = {c: DFHRESP[c] for c in ("NORMAL", "FILENOTFOUND", "NOTFND", "DUPREC", "DUPKEY", "INVREQ", "IOERR", "NOSPACE", "NOTOPEN", "ILLOGIC", "LENGERR", "PGMIDERR", "NOTAUTH", "DISABLED", "LOADING", @@ -3933,7 +3944,9 @@ def outputs(out: Path, case: dict[str, Any], corpus: Path, ca_fields: list[dict[ if verb == "SEND-MAP": fields = screen_fields(corpus, case, kv["map"], "output") vals = decode_record(data, fields, enc) - res["screens"].append({"map": kv["map"], "fields": {k[:-1]: v for k, v in vals.items() if k.endswith("O")}, + keys = {f["name"]: screen_key(f) for f in fields} # #5076: an OCCURS field's every occurrence, ACCOUNT.3 + res["screens"].append({"map": kv["map"], + "fields": {keys[k][0]: v for k, v in vals.items() if keys[k][1] == "O"}, "subfields": map_subfields(corpus, case, kv["map"], data, enc), "options": sorted(args.partition("opts=")[2].split()), "cursor": None if kv.get("cursor", "-1") == "-1" else int(kv["cursor"])}) # fmt: skip @@ -4007,10 +4020,10 @@ def map_subfields( scr = case["screens"][map_name] layouts = [screen_layout(corpus, str(common.case_path(corpus, scr["copybook"])), scr[side]) for side in ("input", "output")] # fmt: skip - data_names = {f["name"][:-1] for f in layouts[1] if f["name"].endswith("O")} + data_names = {screen_key(f)[0] for f in layouts[1] if screen_key(f)[1] == "O"} space = " ".encode(enc) for f in (x for layout in layouts for x in layout): - name, kind = f["name"][:-1], f["name"][-1:] + name, kind = screen_key(f) # #5076: ACCOUNT.3 for ACCOUNTA(3) if name not in data_names or f["offset"] + f["bytes"] > len(data): continue raw = data[f["offset"] : f["offset"] + f["bytes"]]