From b1a8e3a1a3d2b855d29c2b864fac61e0c5373440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Z=20Sink=C3=B3?= Date: Sun, 8 Nov 2026 14:43:38 +0100 Subject: [PATCH] tests: the escape-spelling cases really contain escapes Two cases in the merged tests, and a sentence in canonical-json.md, were meant to carry a u-escape and did not: the tool that wrote them had turned the escape into the character it stands for, so the plan-noop 'escaped text' case compared the same text twice, the m4a 'escapes' spelling was identical to the plain one, and the spec said '"a" is "a"'. The canonicalizer was never wrong and the language-neutral vectors (written another way) do contain real escapes, but those two Go cases could not fail. The backslash is now a hex-escaped constant, the escapes row asserts that it carries a u-escape, and the spec sentence is restored. Shown with a mutant that stops decoding u-escapes: the two repaired cases fail, and they passed under the same mutant before. --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEYCIQD15Hiy8IcjBJu4nTEPjqTsuYRv3mcSTUgz6AsdzvipIQIhAI0vYKOleMecnrrbV6/zuitxGCAsMGN1w83wAL9mml6C hash-algorithm = sha256 digest = TVa6vWHN8U7jxEzkCqpS8hnHd2/6h//soXCwxZ7bHcM= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE----- --- MANIFEST.sha256 | 8 ++++---- docs/design/specs/canonical-json.md | 2 +- module/m4a_gate_test.go | 5 ++++- module/m4a_helpers_test.go | 5 +++++ module/plan_noop_exactness_test.go | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 8371d3a..a1388da 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -10,6 +10,7 @@ 11a0fb2cf1ce82ed7df0fe05a6fceb8e20051c48c4e9c80925f34d68dace503d tools/schemalib/validator.py 1264e708b92df67154f5f50bf52d9e7c5b3edb586db583dc47fc10b59bc060ab module/canonical_json.go 134ea29c14da90cff091cb9bd5406522e182a07a86c6c4203e1436df8c26c4bc docs/en/concept/declarative_ecosystem_integration.meta.yaml +139c658d3aa952a54fe5c9b6a8c0c9f3bb83345b1a28551c5c5ad9f2f0c0fdf4 module/m4a_gate_test.go 13b0b2a232d66df4fe26ec751b49b600fc63dbcde26b2bf17c675bfed78d30f8 module/testdata/vcn-v1/cases/partial-read/expected/observed.registry.json 15551f514d13c6d42283a2cf09355f900f862624a51f3e32b6675ef3a24fce4a tools/oci-extract/go.mod 1610f9791c9760be651e499bd3a22f3c65cc5c0521fc77dfe90f08ddbc72f0be module/cicflow_guest.go @@ -17,6 +18,7 @@ 17502a16b64b102182c1e2ffea7b51d9c39461dd8c9dbaad4427ce8ffd213bbf module/testdata/vcn-v1/cases/transport-error/expected/observed.registry.json 1b3e25bf2122e2d44a8cfc13620797fd129911c5c74d8e1c1db96cee6994f1ce docs/hu/concept/git-management.meta.yaml 1b6261e82d3a60e8f5b479fd68b9f097bf750d1e516d345dd50266af3ef085fc tests/test_wasm_fresh_guest_gate.py +1c1ce6b3ef9b99975b9b4653ac85647a930178d9c1ffed50d2a54ddf67db7de9 module/plan_noop_exactness_test.go 1e78f7fcaf38295bd1e923dbb7b0dbf30476393ebffc86273207f2d7e7789df4 requirements.txt 1f3a72bcaf3825d01a6fa6da0b0a781cb4028779181a498311e9c2de073e36fb module/testdata/vcn-v1/cases/desired-absent/expected/observed.oci.json 1f3a72bcaf3825d01a6fa6da0b0a781cb4028779181a498311e9c2de073e36fb module/testdata/vcn-v1/cases/field-absent/expected/observed.oci.json @@ -30,7 +32,6 @@ 247687949482cd6e8645802f2daaaa50d2b0fe546b5b66c97a887830ae6a9e17 module/oci_sign.go 256506d6120cd3ad29d63578dc2c4f8f1dffb8f087270d0a74dd5683deb107cc module/testdata/vcn-v1/cases/desired-absent-observed-present/expected/evidence.claims.json 256506d6120cd3ad29d63578dc2c4f8f1dffb8f087270d0a74dd5683deb107cc module/testdata/vcn-v1/cases/field-absent/expected/evidence.claims.json -2631dbec077e6263a54cf9e2a0389395819859ae0619d5a163e8bd54b8643bff module/m4a_helpers_test.go 2a1559feea3ff94f981c19b44d3e963cb3ff5632ebc7e17f3dace9ef52341624 module/plan_binding.go 2a6bc913a5c877b2b2dfb30f68d165950d9c881606819d6873047f1122c63abf docs/hu/architecture.yaml 2ab20175d0b7d0f2a7ebe960903a08dc7fe69620615f253ae1fa3e584747edaa pytest.ini @@ -55,7 +56,6 @@ 35bd42c0d55ea02ea9701070141dbb7ebc01d6b020e1cea9103bc5f0ec22c243 module/testdata/vcn-v1/cases/field-absent/oci/response.json 374a2c54d3fcd2b684d2a1699fe7e67c69dffa51e302001c13f55b1468c8c0c2 tests/test_tools/test_compiler.py 3766bf4bce6194763f838cc1f2aebff0801d4451956b8446916b17ecf846c2d7 abi.schema.yaml -38cc687d4b502af22a62ea96ac29a45e9a414fe72a4b0d2a584d4a859bd18ba5 module/plan_noop_exactness_test.go 395199128c561de3e991d635b3d93a2f2a8d7809de5ebe7195877ac37c2d92ba README.md 3afc5e1977e9258dd478d592606874b8fad4229f72306b15ef072c2b7fe67665 module/abi_negative_test.go 3c9bd79e75e97207cc669159b980e331ae8f75b36c0e9899b4a66f3deac0846d module/testdata/vcn-v1/cases/partial-read/expected/verdict.fields.json @@ -143,6 +143,7 @@ 9de6268f09ede92c5adcb82ceec4ecd66761ade6e6aa79131cfeb8cc9814f44d tools/wasm_assert_tests_passed.sh a03b3fccfc1f8eb7f76833fdff8a50ea84c80c039db87bc0e41109e96f64420b docs/en/concept/declarative_ecosystem_integration.md a1cba33d61cd932030481da78da553eb0f31b536a7c3879d5bdd9992c69d189a module/testdata/vcn-v1/cases/not-found/expected/observed.oci.json +a1e862a6a87ea57478aca53deaba38d92a70704a26c7b67f84ed0d8959b438c6 module/m4a_helpers_test.go a3f497092a2fe0fc41467edefff241687affff6f01adbbbf0dadbdefd791bd21 tools/compiler.py a44687b269fac17633c80d8e735cf3402d5f65fd0bb2c24e9996d0a89e134c94 docs/contracts/hu/envelope.md a4c522ffc80fc87ff4244ba3da8f19591d8206ed9acb4d1d7896ced9f75714a3 docs/design/specs/state-model.md @@ -173,7 +174,6 @@ c54659558693be5238ff020322eb00ca80ce7d14aea461ee1ae24361e80fe84f tools/releasel c55c74bb201c91330aa8bda11dd256fe35f84b3a3a0e24a1cebe3e487bd60135 requirements.in c637082ac035d4dda23ff27a31dfd7bd9a62437db24b53d48d585897fa1874a7 module/testdata/vcn-v1/not-planned.json c6919cec77cbf13916932f91cdc9b9d32d9290282c5d87244af7adc1a30d441a module/go.sum -c6f4ed120394d5d71b37e9b44293561cc76be2c7a8087d3facb12458c1b08805 module/m4a_gate_test.go c83bb77196b26e0f656d38b38736a488ff2aa316d995908bf9abade63fccdd67 tools/wasm_fresh_guest_test.sh ca7e4a9412ff28a5ca3e3fd9c759a62126564d82e8892b12b6efa1cbf11d5810 tools/oci-extract/client.go cb1301d5e401e05e2c14042cb1af909dc2bc819a636b5b4e1533f4c87ed48831 docs/design/specs/approval-and-execution-state.md @@ -184,7 +184,6 @@ cdc268dd275143f3cbad6b84078f397367c40951812143d227b5490cd007ce82 tools/infra.py d08117cca9ab07e9035ddd59f5e7cb0a610517f95b85542988a3222f08a015da module/testdata/instance-fixture.json d2c71865652ef4c4f9bed86eaeac6e5a63570e8677987b90a97cb756258ab234 feature-list.md d2dfadbd6532e7de6ae35af273fbdc446ffbb3e7f5b24d7092b3dea70ec2001b module/poll_contract_test.go -d3bce87b8745997eb0fb912dcd61c4ee8fd6d6596be6fec41a51649edcf32684 docs/design/specs/canonical-json.md d3ee577820563d1e062dae6243711e4467c2920e1001fad8c4933f5cd4a320be docs/contracts/hu/release-artifact.md d5c7049ae62583fb629ada0f600be689015ffcbaa5290982d989dbe260ff96c5 tools/oci-extract/testdata/compute_client.go d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 module/testdata/vcn-v1/cases/conformant/intent.registry.json @@ -243,5 +242,6 @@ f75b14ade8b810dcd2f4b74008fc1bd5d6fd42bb3a44d3cd72d2c1f82e69ca91 tools/oci-extr fa13c93d96137cd60abaf523186ba2a0fa48ec28df52c55a49767cd58f88ef29 module/testdata/vcn-v1/cases/drift/expected/verdict.fields.json fb5779e0edb8a71595d9d8c81946bfb324b86066c6b94b73b036e3e110591b5c module/testdata/vcn-v1/cases/desired-absent/expected/verdict.fields.json fb885dd678928855a632f1411e364e0674409d8fa15da767ce07d036e2da824f tools/oci-extract/cmd/oci-sweep/main.go +fbe50f259dca8b38a1772df4d5e3f70509cd5293ec7944a08f424713479646c8 docs/design/specs/canonical-json.md fc893fc83e0ba0aac68d4fa7813ec56ac425fe385e4d896fd77532ee94a39509 tools/oci-extract/testdata/vcn.go fd1ca75aba965be594bceb3fe1ad0232750f0ba1a9232dbb5ed64eb280e8c60e docs/design/specs/provider-abi.md diff --git a/docs/design/specs/canonical-json.md b/docs/design/specs/canonical-json.md index 2a1a8c9..7413cf4 100644 --- a/docs/design/specs/canonical-json.md +++ b/docs/design/specs/canonical-json.md @@ -24,7 +24,7 @@ The input is one strict JSON text (RFC 8259). The output is deterministic: - **Whitespace** is dropped (only space, tab, CR and LF are whitespace on input). - **Objects**: members sorted by the **decoded key's UTF-8 bytes**; the keys are - compared after escapes are decoded (`"a"` is `"a"`). A **repeated key is an + compared after escapes are decoded (`"\u0061"` is `"a"`). A **repeated key is an error**, never merged. - **Arrays** keep their order. - **Numbers** are exact, with no floating point: the value is written as diff --git a/module/m4a_gate_test.go b/module/m4a_gate_test.go index ae045fc..36ef805 100644 --- a/module/m4a_gate_test.go +++ b/module/m4a_gate_test.go @@ -490,11 +490,14 @@ func TestEquivalentSpellingsOfDesiredAndObservedGiveOnePlanDigest(t *testing.T) {"plain", `{"displayName":"new","compartmentId":"ocid1.compartment..b"}`, `{"displayName":"old","compartmentId":"ocid1.compartment..a"}`}, {"key order", `{"compartmentId":"ocid1.compartment..b","displayName":"new"}`, `{"compartmentId":"ocid1.compartment..a","displayName":"old"}`}, {"whitespace", ` { "displayName" : "new" , "compartmentId" : "ocid1.compartment..b" } `, "{\n\t\"displayName\": \"old\",\n\t\"compartmentId\": \"ocid1.compartment..a\"\n}"}, - {"escapes", `{"displayName":"new","compartmentId":"ocid1.compartment..b"}`, `{"displayName":"old","compartmentId":"ocid1.compartment..a"}`}, + {"escapes", `{"displayName":"n` + backslash + `u0065w","compartmentId":"ocid1.compartment..b"}`, `{"displayName":"o` + backslash + `u006cd","compartmentId":"ocid1.compartment..a"}`}, } want := m4aPlan(t, bindingKind, spellings[0].desired, spellings[0].observed, b) for _, s := range spellings[1:] { t.Run(s.name, func(t *testing.T) { + if s.name == "escapes" && (!strings.Contains(s.desired, backslash+"u") || !strings.Contains(s.observed, backslash+"u")) { + t.Fatalf("test setup: the escapes spelling carries no u-escape (%q, %q): it would test identical text", s.desired, s.observed) + } got := m4aPlan(t, bindingKind, s.desired, s.observed, b) if got.PlanDigest != want.PlanDigest || got.ExecutionDigest != want.ExecutionDigest { t.Errorf("plan_digest %s, want %s: the same state spelled differently must give one digest", got.PlanDigest, want.PlanDigest) diff --git a/module/m4a_helpers_test.go b/module/m4a_helpers_test.go index 4da0fa4..42085d8 100644 --- a/module/m4a_helpers_test.go +++ b/module/m4a_helpers_test.go @@ -15,6 +15,11 @@ import ( const m4aHash = "abc123" +// backslash is spelled as a hex escape so that no editor or tool between this file and the +// compiler can rewrite a u-escape that a test builds on top of it (that happened once: the +// "escape spelling" cases of this package compared identical text). +const backslash = "\x5c" + func m4aPayload(id, data string) schemaPayload { return schemaPayload{SchemaID: id, SchemaVersion: "v0.1.0", SchemaHash: m4aHash, Encoding: encCanonicalJSON, Data: json.RawMessage(data)} } diff --git a/module/plan_noop_exactness_test.go b/module/plan_noop_exactness_test.go index 4dba931..2731758 100644 --- a/module/plan_noop_exactness_test.go +++ b/module/plan_noop_exactness_test.go @@ -53,7 +53,7 @@ func TestPlanStillCallsEquivalentJSONANoop(t *testing.T) { {"number spelling", `{"shapeConfig":{"ocpus":1},"displayName":"x"}`, `{"shapeConfig":{"ocpus":1.0},"displayName":"x"}`}, {"exponent spelling", `{"shapeConfig":{"ocpus":100},"displayName":"x"}`, `{"shapeConfig":{"ocpus":1e2},"displayName":"x"}`}, {"key order and whitespace", `{"shapeConfig":{"ocpus":2,"memoryInGBs":16},"displayName":"x"}`, ` { "displayName":"x", "shapeConfig":{ "memoryInGBs":16, "ocpus":2 } } `}, - {"escaped text", `{"displayName":"é"}`, "{\"displayName\":\"\xc3\xa9\"}"}, + {"escaped text", `{"displayName":"` + backslash + `u00e9"}`, "{\"displayName\":\"\xc3\xa9\"}"}, } { t.Run(c.name, func(t *testing.T) { res, plan := planInstance(t, c.desired, c.observed)