this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/cuetxtar: remove @test(leq)

Test generation led too often to using leq instead
of eq, which was annoying. We don't really need it.

Remove the leq directive and convert all existing
uses to @test(eq), @test(eq) with CUE_UPDATE=1
fill, @test(kind=struct), or @test(eq, V, at=path)
for self-referential cases. Update all
documentation and specs to remove leq references.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I50167c78b80d82ce5c9363b6c8a0b9a0da786301
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1235992
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+125 -133
+8 -12
CLAUDE.md
··· 135 135 format. The inline runner must be able to compile the source. Leave these files 136 136 in their original golden-file format. 137 137 138 - 5. **Definition references in `@test(leq, ...)`**: Definition names (e.g., 139 - `#MyDef`) are not in scope when the expected constraint expression is compiled. 140 - Use the structural equivalent (e.g., `{field: string}`) with `@test(closed)`. 141 - 142 - 6. **Remove `out/eval` and `out/evalalpha` sections** after conversion, but keep 138 + 5. **Remove `out/eval` and `out/evalalpha` sections** after conversion, but keep 143 139 the `out/eval/stats` section (promote v3 stats from `out/evalalpha/stats` if 144 140 needed). 145 141 146 - 7. **Add `out/errors.txt`** if any errors exist in the test (leave empty initially; 142 + 6. **Add `out/errors.txt`** if any errors exist in the test (leave empty initially; 147 143 `CUE_UPDATE=1` fills it in automatically). 148 144 149 - 8. **Add `out/todo.txt`** if there are noteworthy differences 145 + 7. **Add `out/todo.txt`** if there are noteworthy differences 150 146 between the v2 and v3 evaluator results. 151 147 152 - 9. **File header comment**: For files that reference a GitHub issue (e.g., 148 + 8. **File header comment**: For files that reference a GitHub issue (e.g., 153 149 `issue1886.txtar`), add a `#`-prefixed comment block at the very top of the 154 150 txtar file (before the first `-- section --`) explaining what the original 155 151 issue was about and how the test covers its essence. Example: ··· 159 155 # "was already used" errors when a field was referenced before being defined. 160 156 ``` 161 157 162 - 10. **DO NOT** introduce any flags in new @test(err) directives. Only maintainers 158 + 9. **DO NOT** introduce any flags in new @test(err) directives. Only maintainers 163 159 of the CUE project should do so. 164 160 165 - 11. **`hint=` flag**: Any `@test(...)` directive may carry a `hint="..."` flag. 161 + 10. **`hint=` flag**: Any `@test(...)` directive may carry a `hint="..."` flag. 166 162 When a test fails, the runner logs the hint text as an additional note. 167 163 **If you (as an AI) encounter a test failure on a field carrying `hint="..."`, 168 164 read that text before diagnosing or fixing the failure.** The hint may explain ··· 173 169 hint="v3 only reports the direct definition position; see out/todo.txt") 174 170 ``` 175 171 176 - 12. **@test(eq, ...) placement**: Prefer placing the eq directive attribute 172 + 11. **@test(eq, ...) placement**: Prefer placing the eq directive attribute 177 173 either directly after a field for single field test, or as a field decl 178 174 at the end of a struct of a test that is struct based. Do NOT place `@test` 179 175 as a field attribute after a closing `}` — e.g., `} @test(eq, ...)` is 180 176 wrong; move it inside the struct as a trailing decl attribute instead. 181 177 182 - 13. **Structure sharing (`~(field)`)**: When the `out/evalalpha` section shows a 178 + 12. **Structure sharing (`~(field)`)**: When the `out/evalalpha` section shows a 183 179 field as a shared reference (e.g., `y: ~(x)`), add `@test(shareID=name)` to 184 180 both the referencing field (`y`) and the referenced field (`x`), using the same 185 181 name to assert they share the same underlying vertex. Use the `:v3` version suffix
+2 -2
cue/testdata/benchmarks/disjunctelim.txtar
··· 12 12 pat: {[string]: 1} | {a: 2} // 78 conjuncts (+11) 13 13 pat: {[string]: 1} | {a: 2} // 89 conjuncts (+11) 14 14 pat: {[string]: 1} | {a: 2} // 100 conjuncts (+11) 15 - pat: {[string]: 1} | {a: 2} // 111 conjuncts (+11) @test(leq, {} | {a: 2}) 15 + pat: {[string]: 1} | {a: 2} // 111 conjuncts (+11) 16 16 @test(eq, {} | {a: 2}, at=pat) 17 17 18 18 // This also should grow linearly per repeated line. ··· 23 23 list: [1] | [2] | [3] | [4] // 118 conjuncts; 52 disjuncts (+33; +16) 24 24 list: [1] | [2] | [3] | [4] // 151 conjuncts; 68 disjuncts (+33; +16) 25 25 list: [1] | [2] | [3] | [4] // 184 conjuncts; 84 disjuncts (+33; +16) 26 - list: [1] | [2] | [3] | [4] // 217 conjuncts; 100 disjuncts (+33; +16) @test(leq, [1] | [2] | [3] | [4]) 26 + list: [1] | [2] | [3] | [4] // 217 conjuncts; 100 disjuncts (+33; +16) 27 27 @test(eq, [1] | [2] | [3] | [4], at=list) 28 28 29 29 issue3610: {
+1 -1
cue/testdata/benchmarks/listdedup.txtar
··· 9 9 B: #steps: #Script & {mount: [A]} 10 10 C: #steps: #Script & {mount: [B]} 11 11 12 - #Script: {mount: [...#Task]} @test(leq, {mount: []}) 12 + #Script: {mount: [...#Task]} @test(eq, {mount: []}) 13 13 14 14 #Task: { 15 15 // Without the proper constructs, repeating the below results
+102 -43
cue/testdata/cycle/builtins.txtar
··· 35 35 _s: { 36 36 #x: matchN(1, [_s, [..._s]]) 37 37 } 38 - // Cannot use @test(eq) with private field reference _s in expected value. 39 - @test(leq, {#x: _}) 38 + // TODO(inline): inline test outputs references in calls. This was crafted 39 + // by hand. 40 + @test(eq, { 41 + _s: {#x: matchN(1, [_|_, []])} 42 + #x: matchN(1, [_|_, []]) 43 + }) 40 44 } 41 45 issue3420: { 42 46 matches1: { ··· 127 131 a?: matchN(1, [#c]) 128 132 } 129 133 // Cannot use @test(eq) with self-referential #c in expected value. 130 - @test(leq, {data: {a: {b: "foo"}}}) 134 + @test(eq, "foo", at=data.a.b) 135 + @test(debug, """ 136 + (struct){ 137 + data: (#struct){ 138 + a: (#struct){ 139 + b: (string){ "foo" } 140 + } 141 + b?: (string){ string } 142 + } 143 + #c: (#struct){ 144 + b?: (string){ string } 145 + a?: (_){ matchN(1, (#list){ 146 + 0: (_|_){// 〈2;#c〉 147 + } 148 + }) } 149 + } 150 + } 151 + """) 131 152 } 132 153 // Same as above, but with longer paths. 133 154 issue3649: noCycle: t2: { ··· 138 159 y: a?: matchN(1, [{d: c}]) 139 160 } 140 161 // Cannot use @test(eq) with self-referential c in expected value. 141 - @test(leq, {x: {y: {a: {d: {b: "foo"}}}}}) 162 + @test(eq, {a: {d: {b: "foo"}}}, at=x.y) 163 + @test(debug, """ 164 + (struct){ 165 + x: (struct){ 166 + y: (struct){ 167 + a: (struct){ 168 + d: (struct){ 169 + b: (string){ "foo" } 170 + } 171 + } 172 + } 173 + b?: (string){ string } 174 + } 175 + c: (struct){ 176 + b?: (string){ string } 177 + y: (struct){ 178 + a?: (_){ matchN(1, (#list){ 179 + 0: (_|_){// { 180 + // d: 〈4;c〉 181 + // } 182 + } 183 + }) } 184 + } 185 + } 186 + } 187 + """) 142 188 } 143 189 issue3649: cycle: t1: { 144 190 data: #c ··· 241 287 z: x 242 288 z: y: [{}] 243 289 // x.y? is non-concrete (list.MatchN validator); only check z.y which is concrete. 244 - @test(leq, {z: {y: [{}]}}) 290 + @test(eq, [{}], at=z.y) 291 + @test(debug, """ 292 + (struct){ 293 + x: (struct){ 294 + y?: (list){ list.MatchN(1, listMatchN.ok.x) } 295 + } 296 + z: (struct){ 297 + y: (#list){ 298 + 0: (struct){ 299 + } 300 + } 301 + } 302 + } 303 + """) 245 304 } 246 305 -- issue3634.cue -- 247 306 // evalv3 structural cycle regression: using `len(...)` with a `#Schema` ··· 317 376 noCycle.t1.#x.#x: structural cycle: 318 377 ./cycle.cue:6:7 319 378 [eval] issue3649.cycle.t1.data.a: invalid value {b:"foo"} (does not satisfy matchN): 0 matched, expected 1: 320 - ./cycle.cue:38:6 321 - ./cycle.cue:35:11 322 - ./cycle.cue:38:13 379 + ./cycle.cue:80:6 380 + ./cycle.cue:77:11 381 + ./cycle.cue:80:13 323 382 issue3649.cycle.t1.data.a.a: structural cycle: 324 - ./cycle.cue:38:6 383 + ./cycle.cue:80:6 325 384 [eval] 0: cannot use [_|_(0: cannot use value at path '_' (type list) as string in argument 1 to strconv.Atoi)] (type list) as string in argument 1 to strconv.Atoi: 326 - ./cycle.cue:45:24 385 + ./cycle.cue:87:24 327 386 issue4037.cycle.f: invalid value "1233" (does not satisfy matchN): 0 matched, expected 1: 328 - ./cycle.cue:45:14 329 - ./cycle.cue:45:21 330 - ./cycle.cue:46:5 387 + ./cycle.cue:87:14 388 + ./cycle.cue:87:21 389 + ./cycle.cue:88:5 331 390 [structural_cycle] issue3634.reduced.#D.a: structural cycle 332 391 [structural_cycle] jsonCycle.t1.x.y: invalid value "{}" (does not satisfy encoding/json.Validate): structural cycle: 333 392 ./jsoncycle.cue:4:8 ··· 339 398 ./listmatchncycle.cue:5:5 340 399 ./listmatchncycle.cue:5:8 341 400 [eval] issue3443.matchIf.#S: cannot call non-function matchIf (type struct): 342 - ./matchn.cue:21:7 401 + ./matchn.cue:46:7 343 402 [eval] issue3443.cycle1.cycle.s: invalid value {n:{n:_}} (does not satisfy matchN): 0 matched, expected 1: 344 - ./matchn.cue:40:7 345 - ./matchn.cue:40:14 346 - ./matchn.cue:52:7 347 - ./matchn.cue:53:7 403 + ./matchn.cue:65:7 404 + ./matchn.cue:65:14 405 + ./matchn.cue:77:7 406 + ./matchn.cue:78:7 348 407 issue3443.cycle1.cycle.s.n: invalid value {n:_} (does not satisfy matchN): 0 matched, expected 1: 349 - ./matchn.cue:40:7 350 - ./matchn.cue:40:14 351 - ./matchn.cue:40:22 352 - ./matchn.cue:53:10 408 + ./matchn.cue:65:7 409 + ./matchn.cue:65:14 410 + ./matchn.cue:65:22 411 + ./matchn.cue:78:10 353 412 issue3443.cycle1.cycle.s.n.n: structural cycle: 354 - ./matchn.cue:40:7 413 + ./matchn.cue:65:7 355 414 [eval] issue3443.cycle2.fail.#S: invalid value {n:{n:{n:_}}} (does not satisfy matchN): 0 matched, expected 1: 356 - ./matchn.cue:60:13 357 - ./matchn.cue:60:20 415 + ./matchn.cue:85:13 416 + ./matchn.cue:85:20 358 417 issue3443.cycle2.fail.#S.n: invalid value {n:{n:{n:_}}} (does not satisfy matchN): 0 matched, expected 1: 359 - ./matchn.cue:60:13 360 - ./matchn.cue:60:20 361 - ./matchn.cue:60:28 362 - ./matchn.cue:60:40 418 + ./matchn.cue:85:13 419 + ./matchn.cue:85:20 420 + ./matchn.cue:85:28 421 + ./matchn.cue:85:40 363 422 issue3443.cycle2.fail.#S.n.n: invalid value {n:{n:{n:_}}} (does not satisfy matchN): 0 matched, expected 1: 364 - ./matchn.cue:60:13 365 - ./matchn.cue:60:20 366 - ./matchn.cue:60:28 367 - ./matchn.cue:60:40 368 - ./matchn.cue:60:43 423 + ./matchn.cue:85:13 424 + ./matchn.cue:85:20 425 + ./matchn.cue:85:28 426 + ./matchn.cue:85:40 427 + ./matchn.cue:85:43 369 428 issue3443.cycle2.fail.#S.n.n.n: structural cycle: 370 - ./matchn.cue:60:13 429 + ./matchn.cue:85:13 371 430 [incomplete] issue3633.final.data: invalid value {} (does not satisfy matchN): 0 matched, expected 1: 372 - ./matchn.cue:69:6 373 - ./matchn.cue:68:8 374 - ./matchn.cue:69:13 431 + ./matchn.cue:94:6 432 + ./matchn.cue:93:8 433 + ./matchn.cue:94:13 375 434 issue3633.final.data: invalid value {} (does not satisfy matchN): 0 matched, expected 1: 376 - ./matchn.cue:69:17 377 - ./matchn.cue:68:8 378 - ./matchn.cue:69:24 435 + ./matchn.cue:94:17 436 + ./matchn.cue:93:8 437 + ./matchn.cue:94:24 379 438 issue3633.final.data.a: field is required but not present: 380 - ./matchn.cue:69:17 381 - ./matchn.cue:69:29 439 + ./matchn.cue:94:17 440 + ./matchn.cue:94:29 382 441 [eval] selfCycle.t1.c: invalid value {d:{}} (does not satisfy matchN): 0 matched, expected 1: 383 442 ./yamlcycle.cue:24:5 384 443 ./yamlcycle.cue:24:12
+3 -3
cue/testdata/disjunctions/019_ips.txtar
··· 3 3 -- in.cue -- 4 4 import "list" 5 5 6 - IP: list.Repeat([uint8], 4) @test(leq, [...uint8]) 6 + IP: list.Repeat([uint8], 4) 7 7 8 8 Private: 9 9 *[ 192, 168, uint8, uint8] | 10 10 [ 10, uint8, uint8, uint8] | 11 - [ 172, >=16 & <=32, uint8, uint8] @test(leq, [...uint8]) 11 + [ 172, >=16 & <=32, uint8, uint8] 12 12 13 - Inst: Private & [ _, 10, ...] @test(leq, [_, 10, uint8, uint8]) 13 + Inst: Private & [ _, 10, ...] 14 14 15 15 MyIP: Inst & [_, _, 10, 10] @test(eq, [10, 10, 10, 10]) 16 16 @test(eq, {
+1 -1
cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar
··· 20 20 let X = foo 21 21 a: foo: [X]: X 22 22 } 23 - b: #B & {foo: "key"} @test(leq, {foo: "key"}) 23 + b: #B & {foo: "key"} @test(eq, {foo: "key", a: {foo: {}}}) 24 24 -- out/errors.txt -- 25 25 [incomplete] #Foo.X: error in call to or: empty list in call to or: 26 26 ./in.cue:2:10
-7
cue/testdata/inlinetest/structural.txtar
··· 19 19 y: x * 2 20 20 } 21 21 22 - // ── leq: subsumption ────────────────── 23 - 24 - leqBasic: {x: 42} @test(leq, {x: int}) @test(eq, {x: 42}) 25 - 26 22 // ── skip ────────────────────────────── 27 23 28 24 skippedTest: {a: 1} @test(skip, why="demonstrating skip") ··· 66 62 eqComputed: { 67 63 x: 10 68 64 y: (〈0;x〉 * 2) 69 - } 70 - leqBasic: { 71 - x: 42 72 65 } 73 66 skippedTest: { 74 67 a: 1
+2 -2
cue/testdata/lists/019_list_types.txtar
··· 19 19 t0: [...{a: 8}] 20 20 t0: [{}] @test(eq, [{a: 8}]) 21 21 t1: [...] 22 - t1: [...int] @test(leq, [...int]) 22 + t1: [...int] @test(eq, []) 23 23 24 24 e0: list.Repeat([{}], 2) 25 25 e0: [{}] @test(err, code=eval, contains="incompatible list lengths", args=[1, 2], pos=[], hint="missing positions") 26 26 e1: [...int] 27 - e1: [...float] @test(leq, [...int]) 27 + e1: [...float] @test(eq, []) 28 28 -- out/errors.txt -- 29 29 [eval] e0: incompatible list lengths (1 and 2) 30 30 -- out/compile --
-9
cue/testdata/readme.md
··· 117 117 118 118 --- 119 119 120 - ### `leq` — subsumption constraint 121 - 122 - ```cue 123 - count: 5 @test(leq, int) 124 - ``` 125 - 126 - Asserts `evaluate(field) ⊑ constraint`. Useful for type-level assertions 127 - without pinning an exact value. 128 - 129 120 ### `err` — error assertion 130 121 131 122 ```cue
+2 -15
doc/specs/inline-test-attributes/spec.md
··· 81 81 ### Requirement: `guidance=` universal flag 82 82 Any `@test(...)` directive that can produce a test failure MAY carry a `guidance="..."` key-value flag. When an assertion fails, the runner logs the guidance string as an additional note after the failure message. This is intended to provide context for automated tools (such as AI assistants) that inspect test failures, and for human readers who need background on why the expected value was chosen. 83 83 84 - `guidance=` is purely informational — it has no effect on whether a test passes or fails. It does not modify any comparison or matching logic. It is silently accepted by all directives that report failures (`eq`, `leq`, `err`, `kind`, `closed`, `debugCheck`). 84 + `guidance=` is purely informational — it has no effect on whether a test passes or fails. It does not modify any comparison or matching logic. It is silently accepted by all directives that report failures (`eq`, `err`, `kind`, `closed`, `debugCheck`). 85 85 86 86 ```cue 87 87 a: c: 1 @test(err, code=eval, ··· 225 225 #### Scenario: @test(ignore) does not suppress other directives 226 226 - **WHEN** `@test(eq, {b: _ @test(ignore) @test(err, any, code=cycle)})` is declared and `b` in the evaluated value contains a cycle error somewhere in its subtree 227 227 - **THEN** the `eq` check on `b` is skipped, but the `@test(err, any, code=cycle)` assertion still runs and passes 228 - 229 - --- 230 - 231 - ### Requirement: `leq` directive 232 - The `leq` directive SHALL assert that the evaluated value is *subsumed by* the given CUE expression (i.e., the constraint subsumes the result — the result is at least as specific as the constraint). This allows asserting type-level constraints without pinning an exact value. 233 - 234 - #### Scenario: Value subsumed by type constraint 235 - - **WHEN** a field carries `@test(leq, int)` and evaluates to `42` 236 - - **THEN** the test passes because `int` subsumes `42` 237 - 238 - #### Scenario: Value not subsumed 239 - - **WHEN** a field carries `@test(leq, string)` and evaluates to `42` 240 - - **THEN** the test fails 241 228 242 229 --- 243 230 ··· 589 576 --- 590 577 591 578 ### Requirement: `incorrect` universal modifier 592 - Any assertion directive (`eq`, `err`, `leq`, `kind`, `closed`, etc.) MAY carry `incorrect` as a positional flag. This marks the assertion as documenting the current *known-incorrect* behavior of the field — that is, the value or property the evaluator currently produces even though it is wrong. 579 + Any assertion directive (`eq`, `err`, `kind`, `closed`, etc.) MAY carry `incorrect` as a positional flag. This marks the assertion as documenting the current *known-incorrect* behavior of the field — that is, the value or property the evaluator currently produces even though it is wrong. 593 580 594 581 Behavior when the assertion runs: 595 582 - **If the assertion passes** (the documented incorrect value still matches): the test does NOT fail; the runner logs `NOTE: ... matches (documented as known incorrect behavior)`.
-28
internal/cuetxtar/inline.go
··· 814 814 r.runEqInline(t, path, val, pa) 815 815 case "err": 816 816 r.runErrAssertion(t, path, val, pa) 817 - case "leq": 818 - r.runLeqInline(t, path, val, pa) 819 817 case "kind": 820 818 r.runKindAssertion(t, path, val, pa) 821 819 case "closed": ··· 960 958 if !hasSkip { 961 959 t.Errorf("path %s: %v", path, cmpErr) 962 960 logHint(t, pa.hint) 963 - } 964 - } 965 - 966 - // runLeqInline checks that val is subsumed by the constraint in pa. 967 - func (r *inlineRunner) runLeqInline(t testing.TB, path cue.Path, val cue.Value, pa parsedTestAttr) { 968 - t.Helper() 969 - if len(pa.raw.Fields) < 2 { 970 - t.Errorf("path %s: @test(leq) requires a constraint argument", path) 971 - return 972 - } 973 - exprStr := pa.raw.Fields[1].Text() 974 - ctx := r.cueContext() 975 - constraint := ctx.CompileString(exprStr) 976 - if constraint.Err() != nil { 977 - t.Errorf("path %s: @test(leq, ...): cannot compile constraint: %v", path, constraint.Err()) 978 - return 979 - } 980 - r.runLeqAssertion(t, path, val, constraint, pa.hint) 981 - } 982 - 983 - // runLeqAssertion asserts that val is subsumed by constraint (constraint ⊑ val, i.e. val is at least as specific). 984 - func (r *inlineRunner) runLeqAssertion(t testing.TB, path cue.Path, val, constraint cue.Value, hint string) { 985 - t.Helper() 986 - if err := constraint.Subsume(val); err != nil { 987 - t.Errorf("path %s: @test(leq): value %v is not subsumed by constraint %v: %v", path, val, constraint, err) 988 - logHint(t, hint) 989 961 } 990 962 } 991 963
+1 -1
internal/cuetxtar/inline_attr.go
··· 39 39 // parsedTestAttr holds the result of parsing a single @test(...) attribute. 40 40 type parsedTestAttr struct { 41 41 // directive is the primary directive name, e.g. "eq", "err", "kind", 42 - // "closed", "leq", "skip", "permute", "file", "desc". 42 + // "closed", "skip", "permute", "file", "desc". 43 43 directive string 44 44 45 45 // version is the optional version suffix from directive:vN, e.g. "v3".
+3 -9
internal/cuetxtar/testdata/inline/basic.txtar
··· 1 - # Tests basic passing assertions: eq, err, kind=, closed, leq, builtin validators, 1 + # Tests basic passing assertions: eq, err, kind=, closed, builtin validators, 2 2 # and selector references. Each case exercises a distinct directive handler in 3 3 # the inline runner's runDirective dispatcher. 4 4 -- in/test.cue -- ··· 22 22 23 23 // closed: close({}) makes struct closed 24 24 closedStruct: close({a: 1}) @test(closed) 25 - 26 - // leq: 42 satisfies number constraint 27 - leqField: 42 @test(leq, number) 28 25 29 26 // eq: builtin validator call expression 30 27 maxFields: struct.MaxFields(2) & {} @test(eq, struct.MaxFields(2) & {}) ··· 62 59 // closed: close({}) makes struct closed 63 60 closedStruct: close({a: 1}) @test(closed) 64 61 65 - // leq: 42 satisfies number constraint 66 - leqField: 42 @test(leq, number) 67 - 68 62 // eq: builtin validator call expression 69 63 maxFields: struct.MaxFields(2) & {} @test(eq, struct.MaxFields(2) & {}) 70 64 ··· 79 73 ./test.cue:11:11 80 74 ./test.cue:11:15 81 75 [incomplete] incompleteC.d: non-concrete value int in operand to +: 82 - ./test.cue:33:40 83 - ./test.cue:32:18 76 + ./test.cue:30:40 77 + ./test.cue:29:18