this repo has no description
0
fork

Configure Feed

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

internal/cuetxtar: add @test(allows, sel) directive

Add an `allows` directive that calls `cue.Value.Allows(sel)` to check
whether a selector is allowed by a struct value.

Syntax:
@test(allows, sel) // asserts Allows returns true
@test(allows=false, sel) // asserts Allows returns false

Supported selector forms:
foo or "foo" regular string field
#Def definition field
string any-string pattern (cue.AnyString)
int any-index pattern (cue.AnyIndex)

Negative tests are in TestRunAllowAssertion (inline_test.go),
calling runAllowAssertion directly with failCapture. Positive
end-to-end tests are in TestInlineRunner_Basic (inlinerunner_test.go).

The directive is documented in cue/testdata/readme.md and
doc/specs/inline-test-attributes/spec.md. A new test file
cue/testdata/inlinetest/closedness.txtar exercises the directive
against closed structs and definitions.

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

+235 -23
+4 -23
cue/testdata/definitions/039_augment_closed_optionals.txtar
··· 1 - #name: augment closed optionals 2 - #evalFull 3 - #todo:inline: medium — definitions with closedness; check @test(closed) support 4 1 -- in.cue -- 5 2 #A: { 6 3 [=~"^[a-s]*$"]: int ··· 11 8 #C: { 12 9 #A & #B 13 10 {[=~"^Q*$"]: int} 14 - } 15 - c: #C & {QQ: 3} 11 + } @test(allows, moo) @test(allows, Qoo) @test(allows=false, string) 12 + // TODO: What should this be? @test(allows=false, zoo) 13 + c: #C & {QQ: 3} @test(eq, {QQ: 3}) 16 14 #D: { 17 15 #A 18 16 #B 19 17 } 20 - d: #D & {aaa: 4} 18 + d: #D & {aaa: 4} @test(eq, {aaa: 4}) 21 19 -- out/compile -- 22 20 --- in.cue 23 21 { ··· 56 54 Disjuncts: 0 57 55 58 56 NumCloseIDs: 22 59 - -- out/evalalpha -- 60 - (struct){ 61 - #A: (#struct){ 62 - } 63 - #B: (#struct){ 64 - } 65 - #C: (#struct){ 66 - } 67 - c: (#struct){ 68 - QQ: (int){ 3 } 69 - } 70 - #D: (#struct){ 71 - } 72 - d: (#struct){ 73 - aaa: (int){ 4 } 74 - } 75 - }
+73
cue/testdata/inlinetest/closedness.txtar
··· 1 + -- in.cue -- 2 + t1: close({b: {}}) 3 + t1: _ @test(closed) 4 + t1: b: _ @test(closed=false) 5 + t1: _ @test(allows, "b") 6 + t1: _ @test(allows=false, foo) 7 + 8 + #X: {[=~"^Q*$"]: int} 9 + 10 + #X: _ @test(closed) 11 + #X: _ @test(allows, Q) 12 + #X: _ @test(allows=false, X) 13 + #X: _ @test(allows=false, string) 14 + 15 + t2: #X 16 + t2: _ @test(closed) 17 + t2: _ @test(allows, Q) 18 + t2: _ @test(allows=false, X) 19 + t2: _ @test(allows=false, string) 20 + 21 + #Y: {...} 22 + 23 + t3: #Y 24 + t3: _ @test(closed) 25 + t3: _ @test(allows, Q) 26 + t3: _ @test(allows, X) 27 + t3: _ @test(allows, string) 28 + 29 + // t4: "string" (quoted) targets the literal field named string; 30 + // string (unquoted) targets the any-string pattern constraint. 31 + t4: close({"string": 1}) 32 + t4: _ @test(closed) 33 + t4: _ @test(allows, "string") // quoted: literal field named string 34 + t4: _ @test(allows=false, string) // unquoted: any-string pattern not present 35 + -- out/compile -- 36 + --- in.cue 37 + { 38 + t1: close({ 39 + b: {} 40 + }) 41 + t1: _ 42 + t1: { 43 + b: _ 44 + } 45 + t1: _ 46 + t1: _ 47 + #X: { 48 + [=~"^Q*$"]: int 49 + } 50 + #X: _ 51 + #X: _ 52 + #X: _ 53 + #X: _ 54 + t2: 〈0;#X〉 55 + t2: _ 56 + t2: _ 57 + t2: _ 58 + t2: _ 59 + #Y: { 60 + ... 61 + } 62 + t3: 〈0;#Y〉 63 + t3: _ 64 + t3: _ 65 + t3: _ 66 + t3: _ 67 + t4: close({ 68 + string: 1 69 + }) 70 + t4: _ 71 + t4: _ 72 + t4: _ 73 + }
+20
cue/testdata/readme.md
··· 238 238 closedFalse: {x: 1} @test(closed=false) 239 239 ``` 240 240 241 + ### `allows` — field allowance 242 + 243 + Checks whether `val.Allows(sel)` returns the expected result for the given 244 + selector expression. 245 + 246 + ```cue 247 + openStruct: {a: 1} @test(allows, b) // open: any field allowed 248 + knownField: close({a: 1}) @test(allows, a) // closed: known field allowed 249 + unknownField: close({a: 1}) @test(allows=false, b) // closed: unknown field denied 250 + anyPattern: {[string]: 1} @test(allows, string) // any-string pattern allowed 251 + ``` 252 + 253 + | Selector form | Meaning | 254 + |---------------|---------| 255 + | `foo` | Regular field (by name) | 256 + | `"foo"` | String field (literal name, including reserved words) | 257 + | `#Def` | Definition field | 258 + | `string` | Any-string pattern (`cue.AnyString`) | 259 + | `int` | Any-index pattern (`cue.AnyIndex`) | 260 + 241 261 ### `debugCheck` — debug-printer output 242 262 243 263 ```cue
+31
doc/specs/inline-test-attributes/spec.md
··· 438 438 439 439 --- 440 440 441 + ### Requirement: `allows` directive 442 + The `allows` directive SHALL assert that `val.Allows(sel)` returns the expected 443 + boolean for the given selector expression. `@test(allows, sel)` asserts allowed=true; 444 + `@test(allows=false, sel)` asserts allowed=false. 445 + 446 + The selector is taken from the raw (pre-unquoting) attribute text: 447 + - `string` (unquoted) — any-string pattern (`cue.AnyString`) 448 + - `int` (unquoted) — any-index pattern (`cue.AnyIndex`) 449 + - Anything else is passed to `cue.ParsePath` as a single-selector path: 450 + - A plain identifier (`foo`) — a regular field by name 451 + - A quoted string (`"foo"`) — a string field with literal name `foo`; use `"string"` or `"int"` to select a field literally named `string` or `int` 452 + - A definition name (`#Def`) — a definition field 453 + 454 + #### Scenario: Open struct allows any field 455 + - **WHEN** a field carries `@test(allows, "b")` and the struct is open 456 + - **THEN** the test passes (open structs allow any field) 457 + 458 + #### Scenario: Closed struct allows known field 459 + - **WHEN** a field carries `@test(allows, "a")` and the closed struct allows `a` 460 + - **THEN** the test passes 461 + 462 + #### Scenario: Closed struct denies unknown field 463 + - **WHEN** a field carries `@test(allows=false, "b")` and the closed struct does not allow `b` 464 + - **THEN** the test passes 465 + 466 + #### Scenario: Missing selector argument 467 + - **WHEN** a field carries `@test(allows)` with no selector argument 468 + - **THEN** the test fails with a descriptive error 469 + 470 + --- 471 + 441 472 ### Requirement: `skip` directive 442 473 The `skip` directive SHALL cause the test case or individual assertion to be skipped. An optional `why="reason"` key-value arg provides a human-readable explanation. A versioned form `skip:v3` skips only when running under evaluator version `v3`. 443 474
+48
internal/cuetxtar/inline.go
··· 1183 1183 r.runKindAssertion(t, path, val, pa) 1184 1184 case "closed": 1185 1185 r.runClosedAssertion(t, path, val, pa) 1186 + case "allows": 1187 + r.runAllowsAssertion(t, path, val, pa) 1186 1188 case "skip": 1187 1189 // @test(skip) or @test(skip, why="reason") — skip this test. 1188 1190 reason := "skipped" ··· 1487 1489 got := val.IsClosed() 1488 1490 if got != expected { 1489 1491 t.Errorf("path %s: @test(closed): got closed=%v, want %v", path, got, expected) 1492 + logHint(t, pa.hint) 1493 + } 1494 + } 1495 + 1496 + // runAllowsAssertion checks val.Allows(sel) against the expected result. 1497 + // Syntax: @test(allows, sel) for expected=true, @test(allows=false, sel) for false. 1498 + // sel is the raw attribute value (before quote-unescaping): 1499 + // - Unquoted string or int: the CUE pattern constraints cue.AnyString or cue.AnyIndex. 1500 + // - Anything else (e.g. foo, "foo", #Def): passed to cue.ParsePath as a single-selector 1501 + // path. Quoted values like "foo" or "string" select the literal string field. 1502 + func (r *inlineRunner) runAllowsAssertion(t testing.TB, path cue.Path, val cue.Value, pa parsedTestAttr) { 1503 + t.Helper() 1504 + expected := true 1505 + if len(pa.raw.Fields) >= 1 && pa.raw.Fields[0].Key() == "allows" { 1506 + if pa.raw.Fields[0].Value() == "false" { 1507 + expected = false 1508 + } 1509 + } 1510 + var rawSel string 1511 + for _, kv := range pa.raw.Fields[1:] { 1512 + if kv.Key() == "" { 1513 + rawSel = kv.RawValue() 1514 + break 1515 + } 1516 + } 1517 + if rawSel == "" { 1518 + t.Errorf("path %s: @test(allows): missing selector argument", path) 1519 + return 1520 + } 1521 + var sel cue.Selector 1522 + switch rawSel { 1523 + case "string": 1524 + sel = cue.AnyString 1525 + case "int": 1526 + sel = cue.AnyIndex 1527 + default: 1528 + p := cue.ParsePath(rawSel) 1529 + if err := p.Err(); err != nil || len(p.Selectors()) != 1 { 1530 + t.Errorf("path %s: @test(allows): invalid selector %s: %v", path, rawSel, err) 1531 + return 1532 + } 1533 + sel = p.Selectors()[0] 1534 + } 1535 + got := val.Allows(sel) 1536 + if got != expected { 1537 + t.Errorf("path %s: @test(allows, %s): got Allows=%v, want %v", path, rawSel, got, expected) 1490 1538 logHint(t, pa.hint) 1491 1539 } 1492 1540 }
+59
internal/cuetxtar/inline_test.go
··· 24 24 "cuelang.org/go/cue/cuecontext" 25 25 "cuelang.org/go/cue/parser" 26 26 "cuelang.org/go/cue/token" 27 + "cuelang.org/go/internal" 27 28 "golang.org/x/tools/txtar" 28 29 ) 29 30 ··· 552 553 r.runErrAssertion(rec, cue.MakePath(cue.Str("x")), val, pa) 553 554 if !rec.failed { 554 555 t.Errorf("expected failure when sub-path is not an error") 556 + } 557 + }) 558 + } 559 + 560 + // TestRunAllowsAssertion verifies that @test(allows, sel) and @test(allows=false, sel) 561 + // correctly report failures when the assertion is wrong. 562 + func TestRunAllowsAssertion(t *testing.T) { 563 + ctx := cuecontext.New() 564 + r := &inlineRunner{} 565 + path := cue.MakePath(cue.Str("x")) 566 + 567 + t.Run("allows fails when field is not allowed in closed struct", func(t *testing.T) { 568 + val := ctx.CompileString("x: close({a: 1})") 569 + rec := &failCapture{TB: t} 570 + pa := parsedTestAttr{directive: "allows", raw: internal.ParseAttr(&ast.Attribute{Text: "@test(allows, b)"})} 571 + r.runAllowsAssertion(rec, path, val.LookupPath(path), pa) 572 + if !rec.failed { 573 + t.Errorf("expected failure: closed struct should not allow field b") 574 + } 575 + }) 576 + 577 + t.Run("allows=false fails when field is actually allowed", func(t *testing.T) { 578 + val := ctx.CompileString("x: close({a: 1})") 579 + rec := &failCapture{TB: t} 580 + pa := parsedTestAttr{directive: "allows", raw: internal.ParseAttr(&ast.Attribute{Text: "@test(allows=false, a)"})} 581 + r.runAllowsAssertion(rec, path, val.LookupPath(path), pa) 582 + if !rec.failed { 583 + t.Errorf("expected failure: closed struct should allow known field a") 584 + } 585 + }) 586 + 587 + t.Run("allows fails for int pattern in closed string-keyed struct", func(t *testing.T) { 588 + val := ctx.CompileString("x: close({[string]: 1})") 589 + rec := &failCapture{TB: t} 590 + pa := parsedTestAttr{directive: "allows", raw: internal.ParseAttr(&ast.Attribute{Text: "@test(allows, int)"})} 591 + r.runAllowsAssertion(rec, path, val.LookupPath(path), pa) 592 + if !rec.failed { 593 + t.Errorf("expected failure: string-keyed struct should not allow int pattern") 594 + } 595 + }) 596 + 597 + t.Run("allows passes for open struct", func(t *testing.T) { 598 + val := ctx.CompileString("x: {a: 1}") 599 + rec := &failCapture{TB: t} 600 + pa := parsedTestAttr{directive: "allows", raw: internal.ParseAttr(&ast.Attribute{Text: "@test(allows, b)"})} 601 + r.runAllowsAssertion(rec, path, val.LookupPath(path), pa) 602 + if rec.failed { 603 + t.Errorf("unexpected failure: open struct should allow any field\n%s", rec.msgs.String()) 604 + } 605 + }) 606 + 607 + t.Run("allows=false passes for unknown field in closed struct", func(t *testing.T) { 608 + val := ctx.CompileString("x: close({a: 1})") 609 + rec := &failCapture{TB: t} 610 + pa := parsedTestAttr{directive: "allows", raw: internal.ParseAttr(&ast.Attribute{Text: "@test(allows=false, b)"})} 611 + r.runAllowsAssertion(rec, path, val.LookupPath(path), pa) 612 + if rec.failed { 613 + t.Errorf("unexpected failure: closed struct should deny unknown field b\n%s", rec.msgs.String()) 555 614 } 556 615 }) 557 616 }