arxo-codegen 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""E-0210/E-0242 locality of key-conflict evaluation status (Python target)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import unittest
|
|
11
|
+
|
|
12
|
+
HERE = Path(__file__).resolve().parent
|
|
13
|
+
sys.path.insert(0, str(HERE))
|
|
14
|
+
import deliver # noqa: E402
|
|
15
|
+
import plan # noqa: E402
|
|
16
|
+
from test_support import vector # noqa: E402
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
L0_VECTOR = vector("errata/ERR-E0242-KEY-PER-RELATION")
|
|
20
|
+
L1_VECTOR = vector("errata/ERR-E0210-KEYED-RELATION-STAYS-BLOCKED")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KeyConflictScopeTests(unittest.TestCase):
|
|
24
|
+
def execute(self, ir: dict, case: dict, query: dict) -> dict:
|
|
25
|
+
with tempfile.TemporaryDirectory(prefix="backend-key-conflict-") as tmp:
|
|
26
|
+
root = Path(tmp)
|
|
27
|
+
deliver.emit_project(plan.build_plan(ir), "py", root / "py")
|
|
28
|
+
for name, document in (("case.json", case), ("query.json", query)):
|
|
29
|
+
(root / name).write_text(json.dumps(document), encoding="utf-8")
|
|
30
|
+
result = subprocess.run([sys.executable, str(root / "py/runner.py"), str(root / "case.json"), str(root / "query.json")],
|
|
31
|
+
capture_output=True, text=True, check=True)
|
|
32
|
+
return json.loads(result.stdout)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def truth_query(predicate: str, args: list, name: str) -> dict:
|
|
36
|
+
return {"queryId": f"urn:query:{name}", "kind": "truth",
|
|
37
|
+
"literal": {"kind": "literal", "polarity": "positive", "predicate": predicate,
|
|
38
|
+
"args": copy.deepcopy(args)}}
|
|
39
|
+
|
|
40
|
+
def assert_scope(self, ir: dict, case: dict, *, source: dict, downstream: dict, independent: dict) -> None:
|
|
41
|
+
"""A source, its two-hop reader, and an unrelated positive fact stay distinct."""
|
|
42
|
+
for query, expected in ((source, ("TRUE_ONLY", "NON_EXECUTABLE")),
|
|
43
|
+
(downstream, ("TRUE_ONLY", "NON_EXECUTABLE")),
|
|
44
|
+
(independent, ("TRUE_ONLY", "COMPUTED"))):
|
|
45
|
+
answer = self.execute(copy.deepcopy(ir), copy.deepcopy(case), query)
|
|
46
|
+
self.assertEqual((answer["truthStatus"], answer["evaluationStatus"]), expected)
|
|
47
|
+
self.assertIn("KEY_CONFLICT", answer["issues"])
|
|
48
|
+
|
|
49
|
+
def test_l0_conflict_blocks_source_and_two_hop_reader_only(self):
|
|
50
|
+
ir = json.loads((L0_VECTOR / "ir.json").read_text(encoding="utf-8"))
|
|
51
|
+
case = json.loads((L0_VECTOR / "case.json").read_text(encoding="utf-8"))
|
|
52
|
+
coefficient = ir["nodes"][0]
|
|
53
|
+
priced = ir["nodes"][2]
|
|
54
|
+
audited = copy.deepcopy(priced)
|
|
55
|
+
audited.update(id="urn:law:vectors:err-e0242-key#audited", name="audited")
|
|
56
|
+
ir["nodes"].append(audited)
|
|
57
|
+
audited_rule = copy.deepcopy(ir["nodes"][-2])
|
|
58
|
+
audited_rule.update(id="urn:law:vectors:err-e0242-key#Audited")
|
|
59
|
+
audited_rule["head"]["predicate"] = audited["id"]
|
|
60
|
+
audited_rule["body"]["formula"]["predicate"] = priced["id"]
|
|
61
|
+
audited_rule["body"]["formula"]["args"] = [{"kind": "var", "var": "v0"}]
|
|
62
|
+
ir["nodes"].append(audited_rule)
|
|
63
|
+
independent = copy.deepcopy(priced)
|
|
64
|
+
independent.update(id="urn:law:vectors:err-e0242-key#independent", name="independent")
|
|
65
|
+
ir["nodes"].append(independent)
|
|
66
|
+
case["assertions"].append({"id": "urn:assert:independent", "kind": "assertion", "origin": "case_input",
|
|
67
|
+
"literal": {"kind": "literal", "polarity": "positive", "predicate": independent["id"],
|
|
68
|
+
"args": [{"kind": "entity_ref", "id": "urn:entity:zone-1"}]}})
|
|
69
|
+
source_args = case["assertions"][0]["literal"]["args"]
|
|
70
|
+
self.assert_scope(ir, case,
|
|
71
|
+
source=self.truth_query(coefficient["id"], source_args, "l0-key-source"),
|
|
72
|
+
downstream=self.truth_query(audited["id"], source_args[:1], "l0-key-two-hop"),
|
|
73
|
+
independent=self.truth_query(independent["id"], source_args[:1], "l0-key-independent"))
|
|
74
|
+
|
|
75
|
+
def test_l1_conflict_blocks_source_and_two_hop_reader_only(self):
|
|
76
|
+
ir = json.loads((L1_VECTOR / "ir.json").read_text(encoding="utf-8"))
|
|
77
|
+
case = json.loads((L1_VECTOR / "case.json").read_text(encoding="utf-8"))
|
|
78
|
+
predicates = {node["name"]: node["id"] for node in ir["nodes"] if node.get("kind") == "symbol_decl"}
|
|
79
|
+
coefficient_args = case["assertions"][0]["literal"]["args"]
|
|
80
|
+
zone_args = coefficient_args[:1]
|
|
81
|
+
holder_args = case["assertions"][-1]["literal"]["args"]
|
|
82
|
+
self.assert_scope(ir, case,
|
|
83
|
+
source=self.truth_query(predicates["coefficient"], coefficient_args, "l1-key-source"),
|
|
84
|
+
downstream=self.truth_query(predicates["audited"], zone_args, "l1-key-two-hop"),
|
|
85
|
+
independent=self.truth_query(predicates["eligible"], holder_args, "l1-key-independent"))
|
|
86
|
+
status_reader = self.execute(ir, case, self.truth_query(
|
|
87
|
+
predicates["no_coefficient"], case["assertions"][2]["literal"]["args"], "l1-key-status-reader"))
|
|
88
|
+
self.assertEqual((status_reader["truthStatus"], status_reader["evaluationStatus"]),
|
|
89
|
+
("TRUE_ONLY", "NON_EXECUTABLE"))
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
unittest.main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Secondary emitters must refuse current admission until separately recertified."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
import deliver
|
|
6
|
+
import plan
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KeyConflictTargetTests(unittest.TestCase):
|
|
10
|
+
def test_current_release_refuses_before_writing_any_generated_file(self):
|
|
11
|
+
current = plan.build_plan({"semanticVersion": "0.2.11", "schemaVersion": "law.core.ir/0.7", "nodes": []})
|
|
12
|
+
for target in ('rb', 'php', 'dart', 'scala', 'bsl'):
|
|
13
|
+
with self.subTest(target=target), tempfile.TemporaryDirectory() as tmp:
|
|
14
|
+
output = Path(tmp) / "project"
|
|
15
|
+
with self.assertRaisesRegex(plan.Refused, "admission:current"):
|
|
16
|
+
deliver.emit_project(current, target, output)
|
|
17
|
+
self.assertFalse(output.exists())
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""DECISION-0423 §2.7 (шаг 5): имена API по языку, различимые внутренние имена
|
|
2
|
+
и устанавливаемый пакет Python с исполняемым примером README."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
import tomllib
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
HERE = Path(__file__).resolve().parent
|
|
16
|
+
sys.path.insert(0, str(HERE))
|
|
17
|
+
sys.path.insert(0, str(HERE / "harness/verify/ci/gates/backends"))
|
|
18
|
+
import deliver # noqa: E402
|
|
19
|
+
import l1_fixtures as FX # noqa: E402
|
|
20
|
+
import package # noqa: E402
|
|
21
|
+
import plan # noqa: E402
|
|
22
|
+
from test_support import fixture_root, materialize_core_kit # noqa: E402
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def names(lang: str, entries: list) -> dict:
|
|
26
|
+
return deliver.emitter(lang).api_names([{"name": n} for n in entries])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ApiNamesTests(unittest.TestCase):
|
|
30
|
+
def test_case_follows_the_language_and_collisions_are_resolved_after_it(self):
|
|
31
|
+
entries = ["isEligible", "is_eligible", "vat_rate", "HTTPServer", "vatID"]
|
|
32
|
+
self.assertEqual(names("py", entries), {"isEligible": "is_eligible", "is_eligible": "is_eligible_2",
|
|
33
|
+
"vat_rate": "vat_rate", "HTTPServer": "http_server",
|
|
34
|
+
"vatID": "vat_id"})
|
|
35
|
+
self.assertEqual(names("ts", entries), {"isEligible": "isEligible", "is_eligible": "isEligible2",
|
|
36
|
+
"vat_rate": "vatRate", "HTTPServer": "httpServer",
|
|
37
|
+
"vatID": "vatID"})
|
|
38
|
+
self.assertEqual(names("go", entries), {"isEligible": "IsEligible", "is_eligible": "IsEligible2",
|
|
39
|
+
"vat_rate": "VatRate", "HTTPServer": "HTTPServer",
|
|
40
|
+
"vatID": "VatID"})
|
|
41
|
+
|
|
42
|
+
def test_letters_outside_ascii_are_kept(self):
|
|
43
|
+
name = plan._api_slug("urn:x#безопасность_обеспечена")
|
|
44
|
+
self.assertEqual(name, "безопасность_обеспечена")
|
|
45
|
+
self.assertEqual(names("py", [name])[name], "безопасность_обеспечена")
|
|
46
|
+
self.assertEqual(names("ts", [name])[name], "безопасностьОбеспечена")
|
|
47
|
+
self.assertEqual(names("go", [name])[name], "БезопасностьОбеспечена")
|
|
48
|
+
|
|
49
|
+
def test_go_keeps_only_letters_and_digits_and_ts_never_starts_with_a_digit(self):
|
|
50
|
+
# «ि» и «ा» — знаки гласных (Mc): Python и JavaScript их допускают, Go — нет.
|
|
51
|
+
self.assertEqual(names("go", ["किताब"])["किताब"], "कतब")
|
|
52
|
+
self.assertEqual(names("py", ["किताब"])["किताब"], "किताब")
|
|
53
|
+
self.assertEqual(names("ts", ["2x"])["2x"], "_2x")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InternalNamesTests(unittest.TestCase):
|
|
57
|
+
def test_ascii_names_are_unchanged_and_other_scripts_get_a_digest(self):
|
|
58
|
+
self.assertEqual(plan._slug("urn:x#vehicle_insured"), "vehicle_insured")
|
|
59
|
+
first, second = plan._slug("urn:x#вред_возмещён"), plan._slug("urn:x#долг_погашено")
|
|
60
|
+
self.assertTrue(first.isascii() and second.isascii())
|
|
61
|
+
self.assertNotEqual(first, second)
|
|
62
|
+
|
|
63
|
+
def test_relations_of_one_package_never_share_a_table(self):
|
|
64
|
+
fx = {"name": "cyr", "preds": {"вред_возмещён": 1, "долг_погашено": 1, "итог_получен": 1},
|
|
65
|
+
"rules": [("R1", "strict", "итог_получен(X)", "вред_возмещён(X)"),
|
|
66
|
+
("R2", "strict", "итог_получен(X)", "долг_погашено(X)")]}
|
|
67
|
+
tables = [t["name"] for t in plan.build_plan(FX.program(fx))["tables"]]
|
|
68
|
+
self.assertEqual(len(tables), len(set(tables)), tables)
|
|
69
|
+
|
|
70
|
+
def test_a_third_collision_is_a_named_refusal(self):
|
|
71
|
+
builder = plan.Builder(FX.program({"name": "x", "preds": {"p": 1}}))
|
|
72
|
+
builder.table_name("urn:x#a-b")
|
|
73
|
+
builder.table_name("urn:x#a_b")
|
|
74
|
+
with self.assertRaisesRegex(plan.Refused, "совпадающие имена таблиц"):
|
|
75
|
+
builder.table_name("urn:x#a.b")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def rybka() -> dict:
|
|
79
|
+
return json.loads((fixture_root() / "corpus/clir/codegen-api-defeasible.lawir.json").read_text(encoding="utf-8"))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PythonPackageTests(unittest.TestCase):
|
|
83
|
+
@classmethod
|
|
84
|
+
def setUpClass(cls):
|
|
85
|
+
# Паспорт пакета несёт семантический хэш §209 — нужен проверенный комплект ядра.
|
|
86
|
+
import core_support
|
|
87
|
+
cls._kit = tempfile.TemporaryDirectory(prefix="package-kit-")
|
|
88
|
+
kit = Path(cls._kit.name) / "kit"
|
|
89
|
+
materialize_core_kit(kit, source_commit="5" * 40)
|
|
90
|
+
core_support.configure_core_kit(kit)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def tearDownClass(cls):
|
|
94
|
+
import core_support
|
|
95
|
+
core_support.configure_core_kit(None)
|
|
96
|
+
cls._kit.cleanup()
|
|
97
|
+
|
|
98
|
+
def test_package_imports_from_src_runs_its_readme_example_and_reports_itself(self):
|
|
99
|
+
ir = rybka()
|
|
100
|
+
with tempfile.TemporaryDirectory(prefix="package-py-") as raw:
|
|
101
|
+
out = Path(raw) / "pkg"
|
|
102
|
+
passport = package.package(ir, "py", out, name="example-rybka", version="1.2.3")
|
|
103
|
+
self.assertEqual(passport["semanticHash"], deliver.semantic_hash(ir))
|
|
104
|
+
self.assertEqual(passport["distribution"],
|
|
105
|
+
{"format": package.FORMAT, "name": "example-rybka", "version": "1.2.3"})
|
|
106
|
+
project = tomllib.loads((out / "pyproject.toml").read_text(encoding="utf-8"))["project"]
|
|
107
|
+
self.assertEqual(project["requires-python"], ">=" + deliver.emitter("py").TOOLCHAIN_MIN)
|
|
108
|
+
self.assertEqual(json.loads((out / "law-backend.json").read_text(encoding="utf-8")), passport)
|
|
109
|
+
sample = re.search(r"```python\n(.*?)```", (out / "README.md").read_text(encoding="utf-8"), re.S).group(1)
|
|
110
|
+
env = dict(os.environ, PYTHONPATH=str(out / "src"))
|
|
111
|
+
run = subprocess.run([sys.executable, "-c", sample], cwd=raw, env=env, capture_output=True, text=True,
|
|
112
|
+
check=True)
|
|
113
|
+
self.assertEqual(run.stdout.split(), ["TRUE_ONLY", "COMPUTED"])
|
|
114
|
+
info = subprocess.run([sys.executable, "-c", "import json, example_rybka as m; print(json.dumps(m.info()))"],
|
|
115
|
+
cwd=raw, env=env, capture_output=True, text=True, check=True)
|
|
116
|
+
self.assertEqual(json.loads(info.stdout)["files"], passport["files"])
|
|
117
|
+
mapping = json.loads((out / "src/example_rybka/api-map.json").read_text(encoding="utf-8"))
|
|
118
|
+
ask = next(f for f in mapping["functions"] if f["function"] == "ask_exceeds_grant_limit")
|
|
119
|
+
self.assertTrue(ask["rules"] and all(r["id"].startswith("urn:") for r in ask["rules"]))
|
|
120
|
+
self.assertFalse((out / "src/example_rybka/_internal/runner.py").exists())
|
|
121
|
+
# DECISION-0423 §2.8: провенанс генератора и ABI в паспорте.
|
|
122
|
+
self.assertEqual(passport["generator"]["name"], "arxo-codegen")
|
|
123
|
+
self.assertTrue(passport["coreKit"]["manifestSha256"].startswith("sha256:"))
|
|
124
|
+
self.assertEqual(passport["api"], {"abi": package.API_ABI, "valueFormat": package.VALUE_FORMAT})
|
|
125
|
+
self.assertIn("defeasible", passport["capabilities"])
|
|
126
|
+
probe = ("import example_rybka as m\n"
|
|
127
|
+
"t = m.money('100.50', 'KZT')\n"
|
|
128
|
+
"print(m.to_value(t)); print(m.from_value(m.to_value(t)) == t)\n"
|
|
129
|
+
"print(m.to_value(m.integer(2**62))); print(m.VALUE_FORMAT)\n")
|
|
130
|
+
value = subprocess.run([sys.executable, "-c", probe], cwd=raw, env=env, capture_output=True, text=True,
|
|
131
|
+
check=True)
|
|
132
|
+
self.assertEqual(value.stdout.splitlines(), [
|
|
133
|
+
'{"currency":"KZT","kind":"value","type":{"name":"urn:law:std#Money"},"value":"100.50"}', "True",
|
|
134
|
+
'{"kind":"value","type":{"name":"urn:law:std#Integer"},"value":4611686018427387904}',
|
|
135
|
+
package.VALUE_FORMAT])
|
|
136
|
+
|
|
137
|
+
def test_refusals_are_named(self):
|
|
138
|
+
ir = rybka()
|
|
139
|
+
with tempfile.TemporaryDirectory(prefix="package-refusal-") as raw:
|
|
140
|
+
with self.assertRaisesRegex(package.PackageError, "SemVer"):
|
|
141
|
+
package.package(ir, "py", Path(raw) / "a", name="x", version="1")
|
|
142
|
+
with self.assertRaisesRegex(package.PackageError, "--module"):
|
|
143
|
+
package.package(ir, "go", Path(raw) / "b", name="x", version="1.0.0")
|
|
144
|
+
with self.assertRaisesRegex(package.PackageError, "только для"):
|
|
145
|
+
package.package(ir, "xlsx", Path(raw) / "c", name="x", version="1.0.0")
|
|
146
|
+
with self.assertRaisesRegex(package.PackageError, "уже существует"):
|
|
147
|
+
package.package(ir, "py", Path(raw), name="x", version="1.0.0")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ToolchainTests(unittest.TestCase):
|
|
151
|
+
def test_minimum_versions_are_the_versions_ci_checks(self):
|
|
152
|
+
workflow = HERE / ".github/workflows/candidate.yml"
|
|
153
|
+
if not workflow.is_file():
|
|
154
|
+
self.skipTest("вне checkout arxo-codegen нет workflow CI")
|
|
155
|
+
text = workflow.read_text(encoding="utf-8")
|
|
156
|
+
version = lambda key: re.search(rf"{key}:\s*'([\d.]+)'", text).group(1)
|
|
157
|
+
self.assertEqual(deliver.emitter("py").TOOLCHAIN_MIN, ".".join(version("python-version").split(".")[:2]))
|
|
158
|
+
self.assertEqual(deliver.emitter("ts").TOOLCHAIN_MIN, version("node-version").split(".")[0])
|
|
159
|
+
self.assertEqual(deliver.emitter("go").TOOLCHAIN_MIN, ".".join(version("go-version").split(".")[:2]))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
unittest.main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Stage R: exact current release/schema admission at every planner entry."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import sys
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
7
|
+
import plan
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ir(version="0.2.11", schema="law.core.ir/0.7"):
|
|
11
|
+
return {"semanticVersion": version, "schemaVersion": schema, "nodes": []}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BackendPlanLanguageVersionTests(unittest.TestCase):
|
|
15
|
+
def test_exact_current_release_is_accepted(self):
|
|
16
|
+
self.assertEqual(plan.build_plan(ir())["planVersion"], "law-backend-plan/0.20")
|
|
17
|
+
self.assertEqual(plan.build_plan(ir())["basePlanVersion"], "law-backend-plan/0.7")
|
|
18
|
+
self.assertEqual(plan.survey_plan(ir()), [])
|
|
19
|
+
self.assertEqual(plan.Builder(ir()).build()["planVersion"], "law-backend-plan/0.7")
|
|
20
|
+
|
|
21
|
+
def test_no_family_alias_old_or_future_release_is_admitted(self):
|
|
22
|
+
for entry in (plan.build_plan, plan.survey_plan, plan.Builder):
|
|
23
|
+
for version in (None, "0.1.1", "0.2", "0.2.4", "0.2.10", "0.2.12", "0.4.1", "law.core/0.2.11"):
|
|
24
|
+
with self.subTest(entry=entry.__name__, version=version), self.assertRaisesRegex(
|
|
25
|
+
plan.Refused, "SEMANTIC_RELEASE_UNAVAILABLE"):
|
|
26
|
+
entry(ir(version))
|
|
27
|
+
|
|
28
|
+
def test_schema_is_checked_independently_of_semantics(self):
|
|
29
|
+
for entry in (plan.build_plan, plan.survey_plan, plan.Builder):
|
|
30
|
+
for schema in (None, "law.core.ir/0.2", "law.core.ir/0.6", "law.core.ir/0.8"):
|
|
31
|
+
with self.subTest(entry=entry.__name__, schema=schema), self.assertRaisesRegex(
|
|
32
|
+
plan.Refused, "SEMANTIC_RELEASE_UNAVAILABLE"):
|
|
33
|
+
entry(ir(schema=schema))
|
|
34
|
+
|
|
35
|
+
def test_each_dependency_must_have_the_current_envelope(self):
|
|
36
|
+
for entry in (plan.build_plan, plan.survey_plan, plan.Builder):
|
|
37
|
+
for dependency in (ir("0.2.4"), ir(schema="law.core.ir/0.6")):
|
|
38
|
+
with self.subTest(entry=entry.__name__, dependency=dependency), self.assertRaisesRegex(
|
|
39
|
+
plan.Refused, "SEMANTIC_RELEASE_UNAVAILABLE"):
|
|
40
|
+
entry(ir(), [dependency])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
unittest.main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Owner's stage-R choice: positions cannot run the old norm status ladder."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
10
|
+
import deliver
|
|
11
|
+
import plan
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PositionsRefusalTests(unittest.TestCase):
|
|
15
|
+
def test_primary_targets_do_not_advertise_positions(self):
|
|
16
|
+
for language in ("py", "ts", "go"):
|
|
17
|
+
with self.subTest(language=language):
|
|
18
|
+
self.assertNotIn("positions", deliver.emitter(language).QUERY_KINDS)
|
|
19
|
+
|
|
20
|
+
def test_library_refuses_before_creating_or_running_a_model(self):
|
|
21
|
+
model = plan.build_plan({"schemaVersion": "law.core.ir/0.7", "semanticVersion": "0.2.11", "nodes": []})
|
|
22
|
+
with tempfile.TemporaryDirectory(prefix="positions-api-refusal-") as tmp:
|
|
23
|
+
root = Path(tmp)
|
|
24
|
+
deliver.emit_project(model, "py", root)
|
|
25
|
+
probe = """import json, model, evaluate, runtime
|
|
26
|
+
def must_not_run():
|
|
27
|
+
raise AssertionError('old norm model was executed')
|
|
28
|
+
model.new_model = must_not_run
|
|
29
|
+
try:
|
|
30
|
+
evaluate.evaluate({}, {'kind': 'positions'})
|
|
31
|
+
except runtime.Refused as exc:
|
|
32
|
+
print(json.dumps({'refused': str(exc)}))
|
|
33
|
+
else:
|
|
34
|
+
raise AssertionError('positions unexpectedly answered')
|
|
35
|
+
"""
|
|
36
|
+
result = subprocess.run([sys.executable, "-B", "-c", probe], cwd=root,
|
|
37
|
+
capture_output=True, text=True, check=True)
|
|
38
|
+
self.assertEqual(json.loads(result.stdout), {
|
|
39
|
+
"refused": "POSITIONS_UNAVAILABLE: positions requires current norm ranks (W6)"})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
unittest.main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import hashlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
import check_conformance as C
|
|
8
|
+
import recertify_registry as R
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RegistryRecertificationTests(unittest.TestCase):
|
|
12
|
+
def setUp(self):
|
|
13
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
14
|
+
self.addCleanup(self.tmp.cleanup)
|
|
15
|
+
self.root = Path(self.tmp.name)
|
|
16
|
+
(self.root / "emit.py").write_text("unchanged input")
|
|
17
|
+
self.manifest = {"format": "law.codegen-fixtures/0.1", "files": [
|
|
18
|
+
{"path": "vectors/A/ir.json", "sha256": "sha256:x", "bytes": 1}],
|
|
19
|
+
"vectors": [{"id": "A", "path": "vectors/A"}]}
|
|
20
|
+
self.report = {"scope": {"mode": "full", "targets": ["py", "ts", "go"],
|
|
21
|
+
"activeSemanticVersions": ["0.2.11"]},
|
|
22
|
+
"fixturePayloadSha256": C.fixture_payload_digest(self.manifest),
|
|
23
|
+
"generatorInputs": {"emit.py": "sha256:" + hashlib.sha256(b"unchanged input").hexdigest()},
|
|
24
|
+
"vectors": [{"id": "A", "semanticVersion": "0.2.11", "actual": "match"}]}
|
|
25
|
+
self.previous = {"vectors": {"A": {"outcome": "match"}}}
|
|
26
|
+
|
|
27
|
+
def certify(self, report=None, qualifications=()):
|
|
28
|
+
return R.certify(report or self.report, self.manifest, self.previous, qualifications, self.root)
|
|
29
|
+
|
|
30
|
+
def test_complete_frozen_observation_pins_every_vector(self):
|
|
31
|
+
registry, receipt = self.certify()
|
|
32
|
+
self.assertEqual(registry["vectors"]["A"]["inputsSha256"], C.vector_input_digests(self.manifest)["A"])
|
|
33
|
+
self.assertEqual(receipt["status"], "verified")
|
|
34
|
+
|
|
35
|
+
def test_partial_or_missing_target_cannot_be_certified(self):
|
|
36
|
+
for changes in ({"mode": "partial"}, {"targets": ["py"]}, {"activeSemanticVersions": ["0.2.4"]}):
|
|
37
|
+
x = copy.deepcopy(self.report); x["scope"].update(changes)
|
|
38
|
+
with self.subTest(changes=changes), self.assertRaises(ValueError):
|
|
39
|
+
self.certify(x)
|
|
40
|
+
|
|
41
|
+
def test_changed_sources_or_fixture_bytes_invalidate_observation(self):
|
|
42
|
+
x = copy.deepcopy(self.report); x["fixturePayloadSha256"] = "sha256:stale"
|
|
43
|
+
with self.assertRaisesRegex(ValueError, "payload is stale"):
|
|
44
|
+
self.certify(x)
|
|
45
|
+
(self.root / "emit.py").write_text("new implementation")
|
|
46
|
+
with self.assertRaisesRegex(ValueError, "source is stale"):
|
|
47
|
+
self.certify()
|
|
48
|
+
|
|
49
|
+
def test_crash_divergence_and_execution_errors_cannot_be_pinned(self):
|
|
50
|
+
for outcome in ("crash", "divergence", "not-executed"):
|
|
51
|
+
x = copy.deepcopy(self.report); x["vectors"][0]["actual"] = outcome
|
|
52
|
+
with self.subTest(outcome=outcome), self.assertRaises(ValueError):
|
|
53
|
+
self.certify(x)
|
|
54
|
+
x = copy.deepcopy(self.report); x["commandFailures"] = ["timeout"]
|
|
55
|
+
with self.assertRaises(ValueError):
|
|
56
|
+
self.certify(x)
|
|
57
|
+
|
|
58
|
+
def test_missing_or_duplicated_vector_is_not_full_coverage(self):
|
|
59
|
+
for rows in ([], self.report["vectors"] * 2):
|
|
60
|
+
x = copy.deepcopy(self.report); x["vectors"] = rows
|
|
61
|
+
with self.assertRaisesRegex(ValueError, "inventory exactly"):
|
|
62
|
+
self.certify(x)
|
|
63
|
+
|
|
64
|
+
def test_lost_match_requires_a_qualification_of_this_exact_outcome(self):
|
|
65
|
+
x = copy.deepcopy(self.report); x["vectors"][0]["actual"] = "plan: NORM_RANKS_UNAVAILABLE"
|
|
66
|
+
with self.assertRaisesRegex(ValueError, "explicit current qualification"):
|
|
67
|
+
self.certify(x)
|
|
68
|
+
q = {"id": "A", "after": "plan: NORM_RANKS_UNAVAILABLE", "category": "semantic_change",
|
|
69
|
+
"reason": "E-0325: owner defers ranks to W6"}
|
|
70
|
+
self.assertEqual(self.certify(x, [q])[1]["qualifiedLostMatches"], ["A"])
|
|
71
|
+
q["after"] = "match"
|
|
72
|
+
with self.assertRaises(ValueError):
|
|
73
|
+
self.certify(x, [q])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
unittest.main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Availability contract for the Ruby target; no Ruby compilation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.util
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import unittest
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PATH = Path(__file__).resolve().parent / "targets" / "rb" / "emit.py"
|
|
11
|
+
SPEC = importlib.util.spec_from_file_location("ruby_target_for_test", PATH)
|
|
12
|
+
ruby = importlib.util.module_from_spec(SPEC)
|
|
13
|
+
SPEC.loader.exec_module(ruby)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RubyTargetAvailabilityTests(unittest.TestCase):
|
|
17
|
+
def test_rejects_pre_three_ruby_before_build(self):
|
|
18
|
+
with mock.patch.object(ruby.shutil, "which", return_value="/usr/bin/ruby"), \
|
|
19
|
+
mock.patch.object(ruby.subprocess, "check_output", return_value="ruby 2.6.10p210"):
|
|
20
|
+
available, reason = ruby.available()
|
|
21
|
+
self.assertFalse(available)
|
|
22
|
+
self.assertIn("Ruby >= 3.0", reason)
|
|
23
|
+
self.assertIn("ruby 2.6", reason)
|
|
24
|
+
|
|
25
|
+
def test_accepts_supported_ruby(self):
|
|
26
|
+
with mock.patch.object(ruby.shutil, "which", return_value="/opt/ruby/bin/ruby"), \
|
|
27
|
+
mock.patch.object(ruby.subprocess, "check_output", return_value="ruby 3.3.1p55"):
|
|
28
|
+
available, reason = ruby.available()
|
|
29
|
+
self.assertTrue(available)
|
|
30
|
+
self.assertIn("ruby 3.3", reason)
|
|
31
|
+
|
|
32
|
+
def test_rejects_unreadable_version(self):
|
|
33
|
+
with mock.patch.object(ruby.shutil, "which", return_value="/usr/bin/ruby"), \
|
|
34
|
+
mock.patch.object(ruby.subprocess, "check_output", return_value="unexpected"):
|
|
35
|
+
available, reason = ruby.available()
|
|
36
|
+
self.assertFalse(available)
|
|
37
|
+
self.assertIn("не прочитана версия", reason)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
unittest.main()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Focused tests for runtime capability contracts; no target compilation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
12
|
+
import runtime_cert as cert
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
FIXTURE = Path(__file__).resolve().parent / "runtime-cert.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RuntimeCertTests(unittest.TestCase):
|
|
19
|
+
def fixture(self) -> dict:
|
|
20
|
+
return cert.load_fixture(FIXTURE)
|
|
21
|
+
|
|
22
|
+
def test_every_declared_target_keeps_the_mandatory_common_subset(self):
|
|
23
|
+
fixture = self.fixture()
|
|
24
|
+
for target in cert.TARGET_OPERATIONS:
|
|
25
|
+
selected = cert.profile(target, fixture)
|
|
26
|
+
self.assertTrue(cert.BASELINE_OPERATIONS <= set(selected["checkedOperations"]), target)
|
|
27
|
+
|
|
28
|
+
def test_lightweight_profile_is_bounded_and_reviewable(self):
|
|
29
|
+
selected = cert.profile("lua", self.fixture())
|
|
30
|
+
self.assertIn("dec-plain", selected["checkedFixtureIds"])
|
|
31
|
+
self.assertIn("rat-canonical", selected["unsupportedFixtureIds"])
|
|
32
|
+
self.assertIn("abs-rational", selected["unsupportedOperandFixtureIds"])
|
|
33
|
+
self.assertEqual(selected["unsupportedOperandForms"]["abs-rational"], ["rat"])
|
|
34
|
+
self.assertNotIn("rat", selected["checkedOperations"])
|
|
35
|
+
self.assertIn("rat", selected["unsupportedOperations"])
|
|
36
|
+
self.assertEqual(len(selected["checkedFixtureIds"]), 133)
|
|
37
|
+
|
|
38
|
+
def test_unknown_fixture_operation_is_a_hard_error(self):
|
|
39
|
+
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
40
|
+
fixture["cases"].append({"id": "future-op", "op": "future", "args": [], "expect": None})
|
|
41
|
+
with tempfile.TemporaryDirectory() as temporary:
|
|
42
|
+
path = Path(temporary) / "cert.json"
|
|
43
|
+
path.write_text(json.dumps(fixture), encoding="utf-8")
|
|
44
|
+
with self.assertRaisesRegex(cert.FixtureError, "неизвестная операция"):
|
|
45
|
+
cert.load_fixture(path)
|
|
46
|
+
|
|
47
|
+
def test_unknown_literal_form_is_a_hard_error(self):
|
|
48
|
+
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
49
|
+
fixture["cases"][0]["args"] = [{"futureLiteral": "x"}]
|
|
50
|
+
with tempfile.TemporaryDirectory() as temporary:
|
|
51
|
+
path = Path(temporary) / "cert.json"
|
|
52
|
+
path.write_text(json.dumps(fixture), encoding="utf-8")
|
|
53
|
+
with self.assertRaisesRegex(cert.FixtureError, "literal-теги"):
|
|
54
|
+
cert.load_fixture(path)
|
|
55
|
+
|
|
56
|
+
def test_missing_baseline_fixture_operation_is_a_hard_error(self):
|
|
57
|
+
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
58
|
+
fixture["cases"] = [case for case in fixture["cases"] if case["op"] != "dec"]
|
|
59
|
+
with tempfile.TemporaryDirectory() as temporary:
|
|
60
|
+
path = Path(temporary) / "cert.json"
|
|
61
|
+
path.write_text(json.dumps(fixture), encoding="utf-8")
|
|
62
|
+
with self.assertRaisesRegex(cert.FixtureError, "baseline"):
|
|
63
|
+
cert.load_fixture(path)
|
|
64
|
+
|
|
65
|
+
def test_declared_extension_without_compatible_case_is_a_hard_error(self):
|
|
66
|
+
fixture = self.fixture()
|
|
67
|
+
fixture["cases"] = [case for case in fixture["cases"] if case["op"] != "rat"]
|
|
68
|
+
with self.assertRaisesRegex(cert.FixtureError, "объявленных операций: rat"):
|
|
69
|
+
cert.profile("py", fixture)
|
|
70
|
+
|
|
71
|
+
def test_answer_requires_exactly_the_selected_cases(self):
|
|
72
|
+
cases = cert.profile("lua", self.fixture())["fixture"]["cases"]
|
|
73
|
+
answer = [{"id": cases[0]["id"], "got": cases[0]["expect"]},
|
|
74
|
+
{"id": cases[0]["id"], "got": cases[0]["expect"]},
|
|
75
|
+
{"id": "rat-canonical", "got": "1/2"}]
|
|
76
|
+
errors = cert.validate_answer("lua", cases, answer)
|
|
77
|
+
self.assertTrue(any("повтор" in error for error in errors))
|
|
78
|
+
self.assertTrue(any("невыбранного" in error for error in errors))
|
|
79
|
+
self.assertTrue(any("не исполнен" in error for error in errors))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
unittest.main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Secondary emitters must refuse current admission until separately recertified."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
import deliver
|
|
6
|
+
import plan
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecondaryKeyConflictScopeTests(unittest.TestCase):
|
|
10
|
+
def test_current_release_refuses_before_writing_any_generated_file(self):
|
|
11
|
+
current = plan.build_plan({"semanticVersion": "0.2.11", "schemaVersion": "law.core.ir/0.7", "nodes": []})
|
|
12
|
+
for target in ('cs', 'java', 'kt', 'rs', 'swift'):
|
|
13
|
+
with self.subTest(target=target), tempfile.TemporaryDirectory() as tmp:
|
|
14
|
+
output = Path(tmp) / "project"
|
|
15
|
+
with self.assertRaisesRegex(plan.Refused, "admission:current"):
|
|
16
|
+
deliver.emit_project(current, target, output)
|
|
17
|
+
self.assertFalse(output.exists())
|