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
18 changes: 17 additions & 1 deletion docs/language_status/register/C18.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ The six `db2.format.*` and `db2.ccsid.*` rows above keep their outcome for a wel
honoured; USA, EUR, 31, a comma and YES refused; an EBCDIC CCSID declared or honoured). A value that is not Db2 syntax at
all is not one IBM accepts, so it is refused by name rather than blamed on the format it resembles or declared as a
CCSID. Burned estates: every recorded cell (`ISO`, `15`, `.`, `NO`, a plain CCSID) is well-formed and resolves as before.
The CICS `cics.region.LOCALCCSID` row is judged in the CICS slice. Rendered from `DB2_OPTIONS`.
Rendered from `DB2_OPTIONS`.

| option | syntax accepted | otherwise |
|---|---|---|
Expand All @@ -175,6 +175,22 @@ The CICS `cics.region.LOCALCCSID` row is judged in the CICS slice. Rendered from
| `db2.ccsid.EBCDIC_CCSID` | a CCSID: one to five digits, not zero | a value outside this syntax is refused by name; a well-formed one is judged by the row above |


### CICS region and program-definition options, judged for syntax (#4707)
`cics.region.LOCALCCSID` keeps its outcome for a well-formed value (37 and 1140 honoured, another CCSID declared); the
other rows below are recorded only for a well-formed value. A value that is not CICS syntax is not one IBM accepts, so it
is refused by name rather than declared as a code page. Burned estates: every recorded cell (`037`, `YES`, `4.2`,
`THREADSAFE`) is well-formed and resolves as before. `cics.translator.options` is judged token by token above. Rendered
from `CICS_OPTIONS`.

| option | syntax accepted | otherwise |
|---|---|---|
| `cics.region.LOCALCCSID` | a CCSID: one to five digits, not zero | a value outside this syntax is refused by name; a well-formed one is judged by the row above |
| `cics.region.DB2CONN` | YES or NO | a value outside this syntax is refused by name; a well-formed one is judged by the row above |
| `cics.region.CICS_TS` | a release number: digits and periods, such as 4.2 or 5.6 | a value outside this syntax is refused by name; a well-formed one is judged by the row above |
| `cics.program_definition.CONCURRENCY` | QUASIRENT, THREADSAFE or REQUIRED | a value outside this syntax is refused by name; a well-formed one is judged by the row above |
| `cics.program_definition.EXECKEY` | USER or CICS | a value outside this syntax is refused by name; a well-formed one is judged by the row above |


### Db2 bind options, judged per value (#4707)
The four bind options that carry a value are checked against what IBM accepts for them; a value outside the set is
refused by name (the rows above stay "recorded only" for an accepted value, so a z/OS measurement need not state them).
Expand Down
41 changes: 36 additions & 5 deletions gitgalaxy/core/runtime_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,38 @@ def _db2_hits(path: str, value: Any, source: str | None) -> list[dict[str, Any]]
"note": f"{path}={value}: not Db2 syntax ({row['grammar']}) (#4707)"}] # fmt: skip


# #4707: the CICS region and program-definition options whose value is judged for syntax, the same way: a well-formed
# value keeps its outcome (LOCALCCSID 37 honoured, another CCSID declared), one that is not CICS syntax is refused by name.
CICS_PREFIX = "cics."

CICS_OPTIONS: dict[str, dict[str, Any]] = {
"region.LOCALCCSID": {"normal": _normal_ccsid, "grammar": "a CCSID: one to five digits, not zero"},
"region.DB2CONN": {"normal": _one_of("YES", "NO"), "grammar": "YES or NO"},
"region.CICS_TS": {"normal": lambda s: s if re.fullmatch(r"\d{1,2}(\.\d{1,2}){0,2}", s) else None,
"grammar": "a release number: digits and periods, such as 4.2 or 5.6"},
"program_definition.CONCURRENCY": {"normal": _one_of("QUASIRENT", "THREADSAFE", "REQUIRED"),
"grammar": "QUASIRENT, THREADSAFE or REQUIRED"},
"program_definition.EXECKEY": {"normal": _one_of("USER", "CICS"), "grammar": "USER or CICS"},
} # fmt: skip


def cics_rows() -> list[str]:
"""The CICS_OPTIONS rows as the markdown table lines register C18 holds (tests pin the page to them)."""
return [
f"| `{CICS_PREFIX}{key}` | {r['grammar']} | a value outside this syntax is refused by name; a well-formed one "
"is judged by the row above |"
for key, r in CICS_OPTIONS.items()
]


def _cics_hits(path: str, value: Any, source: str | None) -> list[dict[str, Any]]:
row = CICS_OPTIONS.get(path[len(CICS_PREFIX) :]) if path.startswith(CICS_PREFIX) else None
if row is None or row["normal"](_state(value)) is not None:
return []
return [{"option": path, "declared": _state(value), "policy": "refuse", "applied": None, "source": source,
"note": f"{path}={value}: not CICS syntax ({row['grammar']}) (#4707)"}] # fmt: skip


# #4707: which burned estates exercise each judged option (policy refuse or declare): the corpora whose source holds the
# construct the row says the option changes. An empty list says no burned program depends on the assumed value, so a
# refusal there protects a customer's estate only. The test greps the corpora for each pattern (STORAGE rests on
Expand Down Expand Up @@ -402,9 +434,8 @@ def runtime_unhonoured(
continue
if row["kind"] == "ccsid":
state = state.lstrip("0") # 037 is 37
if bad := _le_hits(path, value, (sources or {}).get(path)) or _db2_hits(
path, value, (sources or {}).get(path)
):
src = (sources or {}).get(path)
if bad := _le_hits(path, value, src) or _db2_hits(path, value, src) or _cics_hits(path, value, src):
out.extend(bad)
continue
if path == TRANSLATOR_OPTION:
Expand All @@ -426,9 +457,9 @@ def runtime_effect(path: str, value: Any, code_page: str | None = None) -> str:
row = RUNTIME_OPTION_TABLE.get(path)
if row is None:
return "unjudged"
if row["policy"] is None:
return "no effect"
hit = runtime_unhonoured({path.split(".", 1)[0]: {path.split(".", 1)[1]: value}}, None, code_page)
if row["policy"] is None:
return "refused" if hit else "no effect" # a recorded-only row still refuses a value that is not its syntax
return "honoured" if not hit else ("declared" if hit[0]["policy"] == "declare" else "refused")


Expand Down
69 changes: 69 additions & 0 deletions tests/cobol_mainframe/test_runtime_option_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from gitgalaxy.core.runtime_options import (
BIND_OPTIONS,
CICS_CONVENTIONS,
CICS_OPTIONS,
DB2_OPTIONS,
LE_OPTIONS,
RUNTIME_OPTION_TABLE,
Expand All @@ -32,6 +33,7 @@
TRANSLATOR_OPTIONS,
bind_rows,
calibration_problems,
cics_rows,
conventions_rows,
db2_rows,
le_rows,
Expand Down Expand Up @@ -721,6 +723,73 @@ def test_a_malformed_db2_value_reaches_the_oracle(put_estate):
common.compile_options(put_estate("db2", "format.DATE", "ISO"), PLAIN)


# --- the CICS region and program-definition options, judged for syntax (#4707) ---

CICS = "cics."


def _cics(key, value, sources=None):
return runtime_unhonoured({"cics": {key: value}}, sources)


def test_the_register_lists_every_cics_syntax_row():
text = (ROOT / "docs" / "language_status" / "register" / "C18.md").read_text(encoding="utf-8")
assert not [row for row in cics_rows() if row not in text]


def test_the_cics_syntax_rows_cover_exactly_the_region_and_program_definition_options_with_a_value_grammar():
assert {CICS + key for key in CICS_OPTIONS} == {
key for key in RUNTIME_OPTION_TABLE if key.startswith(("cics.region.", "cics.program_definition."))
}
assert all(row["grammar"] for row in CICS_OPTIONS.values())


@pytest.mark.parametrize("path", ESTATES, ids=lambda p: p.stem)
def test_every_cics_value_an_estate_records_is_well_formed(path):
data = json.loads(path.read_text(encoding="utf-8"))
hits = eo.effective_options({"program": "P"}, data).unhonoured_runtime()
assert [h for h in hits if "not CICS syntax" in h["note"]] == []


@pytest.mark.parametrize(
("key", "value"),
[("region.LOCALCCSID", "037"), ("region.LOCALCCSID", "1140"), ("region.DB2CONN", "yes"), ("region.DB2CONN", "NO"),
("region.CICS_TS", "4.2"), ("region.CICS_TS", "5"), ("region.CICS_TS", "6.1.0"),
("program_definition.CONCURRENCY", "THREADSAFE"), ("program_definition.CONCURRENCY", "required"),
("program_definition.EXECKEY", "USER"), ("program_definition.EXECKEY", "cics")],
) # fmt: skip
def test_a_well_formed_cics_value_is_not_refused_for_syntax(key, value):
assert not [h for h in _cics(key, value) if "not CICS syntax" in h["note"]]


def test_a_well_formed_other_ccsid_keeps_its_declaration():
(hit,) = _cics("region.LOCALCCSID", "285")
assert (hit["policy"], hit["applied"]) == ("declare", "data in the case's data_encoding")


@pytest.mark.parametrize(
("key", "value"),
[
("region.LOCALCCSID", "abc"), ("region.LOCALCCSID", "123456"), ("region.LOCALCCSID", "0"), ("region.LOCALCCSID", ""),
("region.DB2CONN", "MAYBE"), ("region.DB2CONN", ""), ("region.DB2CONN", "YES,NO"),
("region.CICS_TS", "v4"), ("region.CICS_TS", ""), ("region.CICS_TS", "4..2"), ("region.CICS_TS", "1.2.3.4"),
("program_definition.CONCURRENCY", "FAST"), ("program_definition.CONCURRENCY", ""),
("program_definition.EXECKEY", "SUPER"), ("program_definition.EXECKEY", ""),
],
) # fmt: skip
def test_a_value_that_is_not_cics_syntax_is_refused_by_name_once(key, value):
(hit,) = _cics(key, value, {CICS + key: "f:3"})
assert (hit["policy"], hit["applied"], hit["source"]) == ("refuse", None, "f:3")
assert CICS + key in hit["note"] and "not CICS syntax" in hit["note"] and "#4707" in hit["note"]
assert runtime_effect(CICS + key, value) == "refused"


def test_a_malformed_cics_value_reaches_the_oracle(put_estate):
with pytest.raises(common.UnsupportedOption, match=re.escape("cics.region.DB2CONN")):
common.compile_options(put_estate("cics", "region.DB2CONN", "MAYBE"), PLAIN)
common.compile_options(put_estate("cics", "region.DB2CONN", "YES"), PLAIN)


# --- the calibration hook against every recorded measurement (#4702 part 2) ---

RULES = ROOT / "tests" / "tools" / "cobolrt_rules.json"
Expand Down
Loading