this repo has no description
0
fork

Configure Feed

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

internal/cuetxtar: auto-manage out/errors.txt

With CUE_UPDATE=1, the inline runner now automatically adds
out/errors.txt when CUE evaluation errors are present and removes it
when it becomes empty. The section is always inserted after the last
input file and before any out/ section (e.g. out/compile).

The meta-test framework is updated to only run the "plain run of
update output" check when source (non-out/) files actually changed.
If only out/ sections changed (e.g. because out/errors.txt was
auto-created), the @test assertions are unchanged so no new failures
can be introduced.

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

+609 -73
+7
cue/testdata/basicrewrite/000_errors.txtar
··· 6 6 e: _|_ == _|_ @test(eq, true) 7 7 f: ({} & false) == _|_ @test(eq, true) 8 8 g: _|_ != ({} & false) @test(eq, false) 9 + -- out/errors.txt -- 10 + [user] explicit error (_|_ literal) in source: 11 + ./in.cue:1:4 12 + explicit error (_|_ literal) in source: 13 + ./in.cue:1:10 14 + [user] explicit error (_|_ literal) in source: 15 + ./in.cue:2:11 9 16 -- out/compile -- 10 17 --- in.cue 11 18 {
+13
cue/testdata/basicrewrite/001_regexp.txtar
··· 24 24 contains="cannot use true", pos=[0:5, 0:14]) 25 25 e3: !="a" & <5 @test(err, code=eval, 26 26 contains="conflicting values", pos=[0:5, 0:13]) 27 + -- out/errors.txt -- 28 + [eval] b3: invalid value "foo" (out of bound =~"[a-z]{4}"): 29 + ./in.cue:10:5 30 + ./in.cue:11:5 31 + [eval] e1: cannot use 1 (type int) as type (string|bytes): 32 + ./in.cue:20:5 33 + ./in.cue:20:14 34 + [eval] e2: cannot use true (type bool) as type (string|bytes): 35 + ./in.cue:22:5 36 + ./in.cue:22:14 37 + [eval] e3: conflicting values !="a" and <5 (mismatched types string and number): 38 + ./in.cue:24:5 39 + ./in.cue:24:13 27 40 -- out/compile -- 28 41 --- in.cue 29 42 {
+7
cue/testdata/basicrewrite/007_strings_and_bytes.txtar
··· 10 10 // TODO: consider the semantics of this and perhaps allow this. 11 11 e0: "a" + '' @test(err, code=eval) 12 12 e1: 'b' + "c" @test(err, code=eval) 13 + -- out/errors.txt -- 14 + [eval] e0: invalid operands "a" and '' to '+' (type string and bytes): 15 + ./in.cue:10:5 16 + ./in.cue:10:11 17 + [eval] e1: invalid operands 'b' and "c" to '+' (type bytes and string): 18 + ./in.cue:11:5 19 + ./in.cue:11:11 13 20 -- out/compile -- 14 21 --- in.cue 15 22 {
+3
cue/testdata/benchmarks/typocheck.txtar
··· 24 24 _checkB1: out.B1 @test(eq, "name1") 25 25 _checkB50: out.B50 @test(eq, "name50") 26 26 _checkB99: out.B99 @test(eq, "name99") 27 + -- out/errors.txt -- 28 + [incomplete] #Output: cannot range over #input (incomplete type _): 29 + ./in.cue:15:17 27 30 -- out/eval/stats -- 28 31 Leaks: 0 29 32 Freed: 700
-1
cue/testdata/builtins/list/sort.txtar
··· 2 2 import "list" 3 3 4 4 a: list.Sort([2, 3, 1, 4], {x: _, y: _, less: x < y}) @test(eq, [1, 2, 3, 4]) 5 - -- out/errors.txt -- 6 5 -- out/eval/stats -- 7 6 Leaks: 0 8 7 Freed: 15
-1
cue/testdata/builtins/or.txtar
··· 9 9 10 10 // do not unique embedded scalars with differing values for child definitions. 11 11 embed1: or([{2, #x: 1}, {2, #x: 2}]) @test(eq, {2, #x: 1} | {2, #x: 2}) 12 - -- out/errors.txt -- 13 12 -- out/compile -- 14 13 --- in.cue 15 14 {
-1
cue/testdata/choosedefault/002_associativity_of_defaults.txtar
··· 70 70 #Schema: field: *null | string 71 71 }) 72 72 } 73 - -- out/errors.txt -- 74 73 -- out/eval/stats -- 75 74 Leaks: 30 76 75 Freed: 352
-1
cue/testdata/comprehensions/else.txtar
··· 184 184 for x in [1] { for y in [] { y } fallback { inner: true } } fallback { outer: true } 185 185 @test(eq, {inner: true}) 186 186 } 187 - -- out/errors.txt -- 188 187 -- out/eval/stats -- 189 188 Leaks: 16 190 189 Freed: 114
+3
cue/testdata/comprehensions/issue2481.txtar
··· 80 80 } 81 81 @test(eq, {out: {role: "member"}, #github_team_membership: {team_id: string, role: string}, values: {role: "member", username: "foo"}}) 82 82 } 83 + -- out/errors.txt -- 84 + [eval] out: cannot combine regular field "val" with "b": 85 + ./in.cue:35:2 83 86 -- out/eval/stats -- 84 87 Leaks: 3 85 88 Freed: 23
-1
cue/testdata/comprehensions/issue3762.txtar
··· 121 121 ok: true 122 122 }) 123 123 } 124 - -- out/errors.txt -- 125 124 -- out/eval/stats -- 126 125 Leaks: 6 127 126 Freed: 74
-1
cue/testdata/comprehensions/issue3763.txtar
··· 97 97 ports: [] 98 98 }) 99 99 } 100 - -- out/errors.txt -- 101 100 -- out/todo.txt -- 102 101 full.k.n.n1._cs.l: v3 uses structure sharing (~(full._cl.l)) where v2 expands 103 102 the full _ports struct inline.
-1
cue/testdata/comprehensions/issue3929.txtar
··· 193 193 // @test(closed, at=full.objects.cronjob.foobar) 194 194 195 195 } 196 - -- out/errors.txt -- 197 196 -- out/todo.txt -- 198 197 reduced.out.def2._in.age and reduced.out.def2.def3: v3 shows disjunction 199 198 "one"|"two" where v2 resolves to "one". Also, reduced.out._in is a (#struct)
-1
cue/testdata/comprehensions/issue436.txtar
··· 24 24 not: a & { 25 25 val: "other" 26 26 } @test(eq, {val: "other", result: *"not-matched" | string}) @test(closed) 27 - -- out/errors.txt -- 28 27 -- out/eval/stats -- 29 28 Leaks: 3 30 29 Freed: 22
-1
cue/testdata/comprehensions/issue507.txtar
··· 27 27 "hello bar: \(e)" 28 28 }, 29 29 ] @test(eq, ["hello bar: something"]) 30 - -- out/errors.txt -- 31 30 -- out/eval/stats -- 32 31 Leaks: 0 33 32 Freed: 15
-1
cue/testdata/comprehensions/issue843.txtar
··· 70 70 from: #c1 71 71 @test(eq, {do: "f2", from: {_, #c: []}}) 72 72 } 73 - -- out/errors.txt -- 74 73 -- out/todo.txt -- 75 74 Field ordering differs between v2 and v3 in #d1.#c.0 and #d2.#c.0: 76 75 v3 puts `as` before `do`, while v2 puts `do` before `as`.
-1
cue/testdata/comprehensions/multi.txtar
··· 30 30 message2: ["how are you doing?", "bye"] 31 31 }) 32 32 } 33 - -- out/errors.txt -- 34 33 -- out/eval/stats -- 35 34 Leaks: 0 36 35 Freed: 24
-1
cue/testdata/comprehensions/nestembed.txtar
··· 20 20 } 21 21 @test(eq, {gcp: {cluters: 10}, aws: {clusters: 20}}) 22 22 } 23 - -- out/errors.txt -- 24 23 -- out/todo.txt -- 25 24 In v3, New_infra.gcp and New_infra.aws are shared references (~) to useful_infra.gcp 26 25 and useful_infra.aws respectively, while in v2 they are expanded inline.
-1
cue/testdata/cycle/chain.txtar
··· 496 496 } 497 497 }) 498 498 } 499 - -- out/errors.txt -- 500 499 -- out/todo.txt -- 501 500 chain.t2.p2 produces an incomplete error in v2 (cycle error referencing X) 502 501 but succeeds in v3. issue2052.reduced.t1 and t2 show structure sharing
-1
cue/testdata/cycle/compbottom.txtar
··· 100 100 }) 101 101 } 102 102 } 103 - -- out/errors.txt -- 104 103 -- out/todo.txt -- 105 104 medium.p1-p4: field ordering of userinfo/host in #Y differs between v2 (host first) and v3 (userinfo first). 106 105 -- out/eval/stats --
-1
cue/testdata/cycle/patterns.txtar
··· 25 25 x: [1] 26 26 @test(eq, {_self: {x: []}, x: [1], a: int}) 27 27 } 28 - -- out/errors.txt -- 29 28 -- out/todo.txt -- 30 29 issue3326 is present in v3 (evalalpha) but not in v2 (eval) — v2 did not evaluate it. 31 30 -- out/eval/stats --
-1
cue/testdata/cycle/with_defaults.txtar
··· 17 17 max: 8 18 18 @test(eq, {max: 8, min: *1 | int}) 19 19 } 20 - -- out/errors.txt -- 21 20 -- out/todo.txt -- 22 21 rg field ordering differs between v2 and v3: v2 has min before max, v3 has max before min. 23 22 -- out/eval/stats --
-1
cue/testdata/definitions/028_recursive_closing_starting_at_non-definition.txtar
··· 9 9 } 10 10 } 11 11 A: z & {a: {#B: {c: e: 2}}} @test(eq, {a: #B: c: {e: 2, d: 1, f: 1}}) 12 - -- out/errors.txt -- 13 12 -- out/compile -- 14 13 --- in.cue 15 14 {
-1
cue/testdata/definitions/issue317.txtar
··· 58 58 } 59 59 }] 60 60 }) 61 - -- out/errors.txt -- 62 61 -- out/eval/stats -- 63 62 Leaks: 0 64 63 Freed: 52
-1
cue/testdata/definitions/issue3834.txtar
··· 147 147 out: true 148 148 }) 149 149 } 150 - -- out/errors.txt -- 151 150 -- out/eval/stats -- 152 151 Leaks: 60 153 152 Freed: 279
-1
cue/testdata/definitions/issue4000.txtar
··· 67 67 spec: [...#RuleGroup] 68 68 } 69 69 #TypeMeta: kind: string | *"default" 70 - -- out/errors.txt -- 71 70 -- out/compile -- 72 71 --- in.cue 73 72 {
-1
cue/testdata/definitions/issue419.txtar
··· 36 36 Disjuncts: 4 37 37 38 38 NumCloseIDs: 5 39 - -- out/errors.txt -- 40 39 -- out/compile -- 41 40 --- in.cue 42 41 {
-1
cue/testdata/definitions/issue471.txtar
··· 45 45 Disjuncts: 2 46 46 47 47 NumCloseIDs: 4 48 - -- out/errors.txt -- 49 48 -- out/compile -- 50 49 --- in.cue 51 50 {
-1
cue/testdata/definitions/issue539.txtar
··· 18 18 hostname: *"localhost" | string 19 19 _#foo: {hello: "world"} 20 20 }) 21 - -- out/errors.txt -- 22 21 -- out/eval/stats -- 23 22 Leaks: 0 24 23 Freed: 14
-1
cue/testdata/definitions/root3.txtar
··· 8 8 #D: b: _ 9 9 b: _ 10 10 }) 11 - -- out/errors.txt -- 12 11 -- out/eval/stats -- 13 12 Leaks: 0 14 13 Freed: 5
-1
cue/testdata/definitions/root7.txtar
··· 3 3 {root.input.#deps} 4 4 5 5 @test(eq, { root: input: #deps: root: {} }) 6 - -- out/errors.txt -- 7 6 -- out/eval/stats -- 8 7 Leaks: 0 9 8 Freed: 5
-1
cue/testdata/definitions/visibility.txtar
··· 14 14 #name: "baz" 15 15 name: string 16 16 }) 17 - -- out/errors.txt -- 18 17 -- out/eval/stats -- 19 18 Leaks: 0 20 19 Freed: 8
-1
cue/testdata/disjunctions/elimination.txtar
··· 1169 1169 Notifications: 4 1170 1170 1171 1171 NumCloseIDs: 323 1172 - -- out/errors.txt -- 1173 1172 -- out/todo.txt -- 1174 1173 issue2685: only evaluated by v3 evaluator. 1175 1174 -- out/compile --
+7
cue/testdata/eval/issue2235.txtar
··· 134 134 } 135 135 }, at=clusters) 136 136 137 + -- out/errors.txt -- 138 + [incomplete] #Cluster.shorewallPrefix: invalid interpolation: error in call to strings.ToUpper: non-concrete value string: 139 + ./issue2235.cue:14:19 140 + ./issue2235.cue:13:19 141 + ./issue2235.cue:14:33 142 + [incomplete] #GlobalIngressController.objects.namespaced.ingress.Service: key value of dynamic field must be concrete, found _|_(#GlobalIngressController.objects.namespaced.ingress.Service: invalid interpolation: non-concrete value string (type string)): 143 + ./issue2235.cue:43:12 137 144 -- out/eval/stats -- 138 145 Leaks: 2 139 146 Freed: 68
+14
cue/testdata/eval/issue3801.txtar
··· 165 165 #Base: {val0: string, value: string} 166 166 }) 167 167 } 168 + -- out/errors.txt -- 169 + [incomplete] issue3801.let.full.#Resource.pickMsg.msg: invalid interpolation: non-concrete value string (type string): 170 + ./nested.cue:58:25 171 + ./nested.cue:54:12 172 + [incomplete] issue3801.let.full.#JSONPatch.output: cannot reference optional field: namespace: 173 + ./nested.cue:72:24 174 + [incomplete] issue3801.let.full.#Main.jsonPatch.output: key value of dynamic field must be concrete, found string: 175 + ./nested.cue:72:24 176 + ./nested.cue:70:15 177 + [incomplete] issue3801.nonRooted.full.#Output.output: key value of dynamic field must be concrete, found string: 178 + ./nested.cue:138:12 179 + ./nested.cue:135:15 180 + [incomplete] issue3801.nonRooted.full.#SubOutput.output: cannot reference optional field: namespace: 181 + ./nested.cue:138:12 168 182 -- out/eval/stats -- 169 183 Leaks: 40 170 184 Freed: 200
-1
cue/testdata/eval/letjoin.txtar
··· 185 185 _input: {A: {p: "A"}, B: {p: "B"}} 186 186 }) 187 187 } 188 - -- out/errors.txt -- 189 188 -- out/eval/stats -- 190 189 Leaks: 54 191 190 Freed: 139
+4
cue/testdata/fulleval/018_recursive_evaluation_within_list.txtar
··· 13 13 d: "s" + c @test(err, code=incomplete) 14 14 c: string 15 15 } 16 + -- out/errors.txt -- 17 + [incomplete] b1.d: non-concrete value string in operand to +: 18 + ./in.cue:10:5 19 + ./in.cue:11:5 16 20 -- out/compile -- 17 21 --- in.cue 18 22 {
+5
cue/testdata/fulleval/020_complex_interaction_of_groundness.txtar
··· 11 11 s: _|_ @test(err, code=incomplete, pos=[4:34, 4:23, 5:13]) 12 12 } 13 13 }) 14 + -- out/errors.txt -- 15 + [incomplete] a.b.c.s: non-concrete value string in operand to +: 16 + ./in.cue:4:34 17 + ./in.cue:4:23 18 + ./in.cue:5:13 14 19 -- out/compile -- 15 20 --- in.cue 16 21 {
+3
cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar
··· 18 18 a: foo: [X]: X 19 19 } 20 20 b: #B & {foo: "key"} @test(leq, {foo: "key"}) 21 + -- out/errors.txt -- 22 + [incomplete] #Foo.X: error in call to or: empty list in call to or: 23 + ./in.cue:2:10 21 24 -- out/eval/stats -- 22 25 Leaks: 10 23 26 Freed: 19
+7
cue/testdata/inlinetest/structural.txtar
··· 47 47 // ── mixed inline ────────────────────── 48 48 49 49 mixedInline: 42 @test(eq, 42) 50 + -- out/errors.txt -- 51 + [eval] errOnly: conflicting values 2 and 1: 52 + ./in.cue:5:10 53 + ./in.cue:5:14 54 + [eval] errWithCode: conflicting values string and 1 (mismatched types string and int): 55 + ./in.cue:7:14 56 + ./in.cue:7:23 50 57 -- out/compile -- 51 58 --- in.cue 52 59 {
-1
cue/testdata/interpolation/scalars.txtar
··· 18 18 19 19 // but normalize representation 20 20 n2: "\(1e2)" @test(eq, "1E+2") 21 - -- out/errors.txt -- 22 21 -- out/eval/stats -- 23 22 Leaks: 0 24 23 Freed: 11
+74 -30
internal/cuetxtar/inline.go
··· 410 410 } 411 411 412 412 // handleErrorsTxtSection manages the out/errors.txt documentary section. 413 - // The section is only processed if it already exists in the archive: 414 - // - CUE_UPDATE=1: updates the section with current error output 415 - // - otherwise: silently skips any difference 413 + // 414 + // On CUE_UPDATE=1: 415 + // - If the section is absent and there are errors → insert it after the 416 + // last input file (before any out/ section such as out/compile). 417 + // - If the section is present and there are no errors → remove it. 418 + // - If the section is present and content differs → update it in place. 419 + // 420 + // On CUE_UPDATE=diff: 421 + // - Report a diff if present content or absent-but-needed differs. 422 + // 423 + // Otherwise: silently skip any difference. 416 424 func (r *inlineRunner) handleErrorsTxtSection(val cue.Value) { 417 425 const sectionName = "out/errors.txt" 418 426 419 - // Find the section in the archive. 420 - sectionIdx := -1 421 - for i, f := range r.archive.Files { 422 - if f.Name == sectionName { 423 - sectionIdx = i 424 - break 425 - } 426 - } 427 - // Never auto-create the section. 428 - if sectionIdx < 0 { 429 - return 430 - } 431 - 432 427 // Collect all errors (including incomplete) from the evaluated value. 433 428 // Do not pass Cwd: cueerrors.Print prepends "./" to relative paths for 434 429 // IDE compatibility, which we don't want in the golden section. Strip the ··· 448 443 } 449 444 resultBytes := []byte(result) 450 445 451 - existing := r.archive.Files[sectionIdx].Data 452 - if bytes.Equal(existing, resultBytes) { 453 - return 446 + // Find the section in the archive. 447 + sectionIdx := -1 448 + for i, f := range r.archive.Files { 449 + if f.Name == sectionName { 450 + sectionIdx = i 451 + break 452 + } 453 + } 454 + 455 + // Determine the existing content (nil if absent, []byte{} if present-but-empty). 456 + var existing []byte 457 + if sectionIdx >= 0 { 458 + existing = r.archive.Files[sectionIdx].Data 454 459 } 455 460 456 - if cuetest.UpdateGoldenFiles { 457 - r.archive.Files[sectionIdx].Data = resultBytes 458 - if r.filePath != "" { 459 - out := txtar.Format(r.archive) 460 - if err := os.WriteFile(r.filePath, out, 0o644); err != nil { 461 - r.t.Errorf("inline: errors.txt write-back to %s: %v", r.filePath, err) 462 - } 463 - } 461 + // No change needed only when section exists with matching non-empty content. 462 + if sectionIdx >= 0 && len(resultBytes) > 0 && bytes.Equal(existing, resultBytes) { 463 + return // already up to date 464 + } 465 + // No section and no errors: nothing to do. 466 + if sectionIdx < 0 && len(resultBytes) == 0 { 464 467 return 465 468 } 466 469 470 + // Section needs to be added, removed, or updated. 467 471 if cuetest.DiffGoldenFiles { 468 472 r.t.Errorf("result for %s differs: (-want +got)\n%s", 469 - sectionName, 470 - cmp.Diff(string(existing), result), 471 - ) 473 + sectionName, cmp.Diff(string(existing), result)) 474 + return 472 475 } 476 + if !cuetest.UpdateGoldenFiles { 477 + return // silently skip 478 + } 479 + 480 + changed := false 481 + switch { 482 + case sectionIdx >= 0 && len(resultBytes) == 0: 483 + // Section exists but should be absent — remove it. 484 + r.archive.Files = slices.Delete(r.archive.Files, sectionIdx, sectionIdx+1) 485 + changed = true 486 + case sectionIdx >= 0: 487 + // Section exists with wrong content — update in place. 488 + r.archive.Files[sectionIdx].Data = resultBytes 489 + changed = true 490 + default: 491 + // Section absent but should exist — insert after last input file. 492 + insertIdx := r.errorsInsertIdx() 493 + r.archive.Files = slices.Insert(r.archive.Files, insertIdx, 494 + txtar.File{Name: sectionName, Data: resultBytes}) 495 + changed = true 496 + } 497 + 498 + if changed && r.filePath != "" { 499 + out := txtar.Format(r.archive) 500 + if err := os.WriteFile(r.filePath, out, 0o644); err != nil { 501 + r.t.Errorf("inline: errors.txt write-back to %s: %v", r.filePath, err) 502 + } 503 + } 504 + } 505 + 506 + // errorsInsertIdx returns the index at which to insert out/errors.txt: 507 + // directly after the last input file (name not starting with "out/"), 508 + // which places it before any out/ sections such as out/compile. 509 + func (r *inlineRunner) errorsInsertIdx() int { 510 + last := -1 511 + for i, f := range r.archive.Files { 512 + if !strings.HasPrefix(f.Name, "out/") { 513 + last = i 514 + } 515 + } 516 + return last + 1 473 517 } 474 518 475 519 // subTestName returns the sub-test name for a root.
+43 -5
internal/cuetxtar/inlinetxtar_test.go
··· 152 152 }) 153 153 154 154 // --- Plain run on update output --- 155 - // When update changed the source, run the updated files as a fresh test to 156 - // verify the update output is itself a passing test. This replaces the need 157 - // for separate *_run.txtar files that duplicate the update output as input. 158 - updateOutputPasses := updateIdentical // trivially true when nothing changed 159 - if !updateIdentical { 155 + // When update changed source (non-out/) files, run the updated files as a 156 + // fresh test to verify the update output is itself a passing test. This 157 + // replaces the need for separate *_run.txtar files that duplicate the 158 + // update output as input. 159 + // 160 + // We only check source files here because changes to out/ sections (e.g. 161 + // auto-creation of out/errors.txt) are documentary and do not affect 162 + // @test assertion behavior. If only out/ sections changed, the assertions 163 + // are unchanged from before update, so the plain run would produce the 164 + // same result as the original run — no new failures are possible. 165 + updateSourceIdentical := txtarSourceFileDiff(inputArchive, update1) == "" 166 + updateOutputPasses := updateIdentical || updateSourceIdentical 167 + if !updateSourceIdentical { 160 168 capUR := &cuetxtar.FailCapture{TB: t} 161 169 withUpdateMode(false, false, func() { 162 170 runner := cuetxtar.NewInlineRunnerCapture(t, nil, cloneTxtarArchive(update1), t.TempDir(), capUR) ··· 396 404 t.Errorf("%s: %s%s: in testdata but not produced by runner", hint, prefix, name) 397 405 } 398 406 } 407 + } 408 + 409 + // txtarSourceFileDiff returns a human-readable diff of non-out/ files between 410 + // a and b. Returns "" if all source files are identical. Changes to out/ 411 + // sections are ignored since they are documentary and do not affect @test 412 + // assertion behavior. 413 + func txtarSourceFileDiff(a, b *txtar.Archive) string { 414 + aMap := make(map[string]string) 415 + for _, f := range a.Files { 416 + if !strings.HasPrefix(f.Name, "out/") { 417 + aMap[f.Name] = string(f.Data) 418 + } 419 + } 420 + var diffs []string 421 + for _, f := range b.Files { 422 + if strings.HasPrefix(f.Name, "out/") { 423 + continue 424 + } 425 + ac, ok := aMap[f.Name] 426 + if !ok { 427 + diffs = append(diffs, fmt.Sprintf("file %s: only in second archive", f.Name)) 428 + } else if ac != string(f.Data) { 429 + diffs = append(diffs, fmt.Sprintf("file %s: differs", f.Name)) 430 + } 431 + delete(aMap, f.Name) 432 + } 433 + for name := range aMap { 434 + diffs = append(diffs, fmt.Sprintf("file %s: only in first archive", name)) 435 + } 436 + return strings.Join(diffs, "\n") 399 437 } 400 438 401 439 // txtarFileDiff returns a human-readable description of file-by-file differences
+42 -1
internal/cuetxtar/testdata/inline/basic.txtar
··· 36 36 incompleteA: {b: int} 37 37 incompleteC: incompleteA & {b: 100, d: incompleteA.b + 3} @test(eq, {b: 100, d: _|_}) 38 38 -- out/status.txt -- 39 - update: identical to input 39 + update: output passes run 40 40 force: identical to update 41 41 -- out/run/errors.txt -- 42 42 path kindIntFail: @test(kind=string): got kind int, want string 43 + -- out/update/test.cue -- 44 + import ( 45 + "math" 46 + "struct" 47 + ) 48 + 49 + // eq: concrete values 50 + intEq: 42 @test(eq, 42) 51 + strEq: "hello" @test(eq, "hello") 52 + 53 + // err: conflict error detected 54 + errField: 1 & 2 @test(err, code=eval, 55 + contains="conflicting values %s and %s", args=[2, 1], pos=[0:11, 0:15] 56 + ) 57 + 58 + // kind=: abstract type check 59 + kindInt: int @test(kind=int) 60 + kindIntFail: int @test(kind=string) 61 + 62 + // closed: close({}) makes struct closed 63 + closedStruct: close({a: 1}) @test(closed) 64 + 65 + // leq: 42 satisfies number constraint 66 + leqField: 42 @test(leq, number) 67 + 68 + // eq: builtin validator call expression 69 + maxFields: struct.MaxFields(2) & {} @test(eq, struct.MaxFields(2) & {}) 70 + 71 + // eq: selector reference (math.Pi) 72 + piField: math.Pi @test(eq, math.Pi) 73 + 74 + // eq: incomplete field in struct — incompleteA.b is abstract, so incompleteA.b+3 is _|_ 75 + incompleteA: {b: int} 76 + incompleteC: incompleteA & {b: 100, d: incompleteA.b + 3} @test(eq, {b: 100, d: _|_}) 77 + -- out/update/out/errors.txt -- 78 + [eval] errField: conflicting values 2 and 1: 79 + ./test.cue:11:11 80 + ./test.cue:11:15 81 + [incomplete] incompleteC.d: non-concrete value int in operand to +: 82 + ./test.cue:33:40 83 + ./test.cue:32:18
+4
internal/cuetxtar/testdata/inline/eq_fill_err_annotation.txtar
··· 17 17 -- out/status.txt -- 18 18 update: output passes run 19 19 force: identical to update 20 + -- out/update/out/errors.txt -- 21 + [incomplete] x.b: non-concrete value int in operand to +: 22 + ./test.cue:3:5 23 + ./test.cue:2:5
+4
internal/cuetxtar/testdata/inline/eq_nested_err_full.txtar
··· 19 19 b: 100 20 20 d: a.b + 3 21 21 } @test(eq, {b: 100, d: _|_ @test(err, code=incomplete, contains="non-concrete value int in operand to +", pos=[4:5, 1:8])}) 22 + -- out/update/out/errors.txt -- 23 + [incomplete] c.d: non-concrete value int in operand to +: 24 + ./test.cue:4:5 25 + ./test.cue:1:8
+11 -1
internal/cuetxtar/testdata/inline/err.txtar
··· 2 2 args: 1 & 2 @test(err, code=eval, args=[2, 1]) 3 3 argsReverse: 1 & 2 @test(err, code=eval, args=[1, 2]) 4 4 -- out/status.txt -- 5 - update: identical to input 5 + update: output passes run 6 6 force: identical to update 7 + -- out/update/test.cue -- 8 + args: 1 & 2 @test(err, code=eval, args=[2, 1]) 9 + argsReverse: 1 & 2 @test(err, code=eval, args=[1, 2]) 10 + -- out/update/out/errors.txt -- 11 + [eval] args: conflicting values 2 and 1: 12 + ./test.cue:1:14 13 + ./test.cue:1:18 14 + [eval] argsReverse: conflicting values 2 and 1: 15 + ./test.cue:2:14 16 + ./test.cue:2:18
+11 -1
internal/cuetxtar/testdata/inline/err_args.txtar
··· 7 7 -- out/run/errors.txt -- 8 8 path wrongArgs: @test(err, args=...): args: expected "WRONG" in Msg() args, got [1 "s" int string] 9 9 -- out/status.txt -- 10 - update: identical to input 10 + update: output passes run 11 11 force: identical to update 12 + -- out/update/test.cue -- 13 + rightArgs: 1 & "s" @test(err, args=[int]) 14 + wrongArgs: 1 & "s" @test(err, args=[WRONG]) 15 + -- out/update/out/errors.txt -- 16 + [eval] rightArgs: conflicting values 1 and "s" (mismatched types int and string): 17 + ./test.cue:1:12 18 + ./test.cue:1:16 19 + [eval] wrongArgs: conflicting values 1 and "s" (mismatched types int and string): 20 + ./test.cue:2:12 21 + ./test.cue:2:16
+54 -1
internal/cuetxtar/testdata/inline/err_contains.txtar
··· 40 40 path wrong: @test(err): expected error message to contain "WRONG_SUBSTRING", got "wrong: conflicting values 2 and 1" 41 41 path inAlias.bad: @test(err): expected error message to contain "WRONG", got "inAlias.bad: conflicting values 1 and {bad:(1 & x)} (mismatched types int and struct)" 42 42 -- out/status.txt -- 43 - update: identical to input 43 + update: output passes run 44 + -- out/update/test.cue -- 45 + right: 1 & 2 @test(err, contains="conflicting values") 46 + wrong: 1 & 2 @test(err, contains="WRONG_SUBSTRING") 47 + 48 + rightEq: {b: 1, a: null & string} @test(eq, {b: 1, a: _|_ @test(err, contains="conflicting values")}) 49 + wrongEq: {b: 1, a: null & string} @test(eq, {b: 1, a: _|_ @test(err, contains="WRONG_SUBSTRING")}) 50 + 51 + inAlias: x={ 52 + bad: 1 & x @test(err, contains="WRONG") 53 + } 54 + 55 + rightFmt: int & string @test(err, contains="conflicting values %s and %s (mismatched types %s and %s)") 56 + 57 + -- out/update/out/errors.txt -- 58 + [eval] right: conflicting values 2 and 1: 59 + ./test.cue:1:8 60 + ./test.cue:1:12 61 + [eval] wrong: conflicting values 2 and 1: 62 + ./test.cue:2:8 63 + ./test.cue:2:12 64 + [eval] rightEq.a: conflicting values null and string (mismatched types null and string): 65 + ./test.cue:4:20 66 + ./test.cue:4:27 67 + [eval] wrongEq.a: conflicting values null and string (mismatched types null and string): 68 + ./test.cue:5:20 69 + ./test.cue:5:27 70 + [eval] inAlias.bad: conflicting values 1 and {bad:(1 & x)} (mismatched types int and struct): 71 + ./test.cue:7:12 72 + ./test.cue:8:7 73 + ./test.cue:8:11 74 + [eval] rightFmt: conflicting values int and string (mismatched types int and string): 75 + ./test.cue:11:11 76 + ./test.cue:11:17 77 + -- out/force/out/errors.txt -- 78 + [eval] right: conflicting values 2 and 1: 79 + ./test.cue:1:8 80 + ./test.cue:1:12 81 + [eval] wrong: conflicting values 2 and 1: 82 + ./test.cue:2:8 83 + ./test.cue:2:12 84 + [eval] rightEq.a: conflicting values null and string (mismatched types null and string): 85 + ./test.cue:4:20 86 + ./test.cue:4:27 87 + [eval] wrongEq.a: conflicting values null and string (mismatched types null and string): 88 + ./test.cue:5:20 89 + ./test.cue:5:27 90 + [eval] inAlias.bad: conflicting values 1 and {bad:(1 & x)} (mismatched types int and struct): 91 + ./test.cue:7:12 92 + ./test.cue:8:7 93 + ./test.cue:8:11 94 + [eval] rightFmt: conflicting values int and string (mismatched types int and string): 95 + ./test.cue:11:11 96 + ./test.cue:11:17
+7
internal/cuetxtar/testdata/inline/err_fill.txtar
··· 10 10 -- out/status.txt -- 11 11 update: output passes run 12 12 force: identical to update 13 + -- out/update/out/errors.txt -- 14 + [eval] x: conflicting values int and string (mismatched types int and string): 15 + ./test.cue:1:4 16 + ./test.cue:1:10 17 + [eval] atBare.b: conflicting values int and string (mismatched types int and string): 18 + ./test.cue:2:13 19 + ./test.cue:2:19
+8
internal/cuetxtar/testdata/inline/err_fill_multi.txtar
··· 9 9 -- out/status.txt -- 10 10 update: output passes run 11 11 force: identical to update 12 + -- out/update/out/errors.txt -- 13 + [eval] x: 2 errors in empty disjunction: 14 + x.a: conflicting values int and string (mismatched types int and string): 15 + ./test.cue:1:9 16 + ./test.cue:1:15 17 + x.b: conflicting values int and bool (mismatched types int and bool): 18 + ./test.cue:1:29 19 + ./test.cue:1:35
+36 -1
internal/cuetxtar/testdata/inline/err_path.txtar
··· 16 16 path xWrong: @test(err, path=...): expected error path "wrong", got "y" 17 17 path suberrWrong: @test(err, suberr=...): no sub-error matched "string" 18 18 -- out/status.txt -- 19 - update: identical to input 19 + update: output passes run 20 20 force: identical to update 21 + -- out/update/test.cue -- 22 + xRight: {y: int & string} @test(err, path=y) 23 + xAbsolute: {y: int & string} @test(err, path=xAbsolute.y) 24 + xWrong: {y: int & string} @test(err, path=wrong) 25 + 26 + suberrRight: ({a: int & string} | {b: int & bool}) @test(err, 27 + suberr=(path=a, contains="string"), 28 + suberr=(path=b, contains="bool")) 29 + suberrWrong: ({a: int & string} | {b: int & bool}) @test(err, 30 + suberr=(path=WRONG, contains="string"), 31 + suberr=(path=b, contains="bool")) 32 + -- out/update/out/errors.txt -- 33 + [eval] xRight.y: conflicting values int and string (mismatched types int and string): 34 + ./test.cue:1:13 35 + ./test.cue:1:19 36 + [eval] xAbsolute.y: conflicting values int and string (mismatched types int and string): 37 + ./test.cue:2:16 38 + ./test.cue:2:22 39 + [eval] xWrong.y: conflicting values int and string (mismatched types int and string): 40 + ./test.cue:3:13 41 + ./test.cue:3:19 42 + [eval] suberrRight: 2 errors in empty disjunction: 43 + suberrRight.a: conflicting values int and string (mismatched types int and string): 44 + ./test.cue:5:19 45 + ./test.cue:5:25 46 + suberrRight.b: conflicting values int and bool (mismatched types int and bool): 47 + ./test.cue:5:39 48 + ./test.cue:5:45 49 + [eval] suberrWrong: 2 errors in empty disjunction: 50 + suberrWrong.a: conflicting values int and string (mismatched types int and string): 51 + ./test.cue:8:19 52 + ./test.cue:8:25 53 + suberrWrong.b: conflicting values int and bool (mismatched types int and bool): 54 + ./test.cue:8:39 55 + ./test.cue:8:45
+40 -1
internal/cuetxtar/testdata/inline/err_pos_passes.txtar
··· 25 25 // Relative pos= form (delta from field line) 26 26 absPos: 1 & 2 @test(err, code=eval, pos=[0:9, 0:13]) 27 27 -- out/status.txt -- 28 - update: identical to input 28 + update: output passes run 29 29 force: identical to update 30 + -- out/update/test.cue -- 31 + // Field attr on same line: "1 & 2" has 1 at col 10, 2 at col 14 in "oneLine: 1 & 2" 32 + oneLine: 1 & 2 @test(err, code=eval, pos=[0:10, 0:14]) 33 + 34 + // Field attr with conflict below: a:1 and a:2 values at col 5 (after tab) 35 + nested: { 36 + a: 1 37 + a: 2 38 + } @test(err, code=eval, pos=[1:5, 2:5]) 39 + 40 + // Decl attr inside struct: baseLine = opening brace line; a:1 at delta 2, a:2 at delta 3 41 + declAttr: { 42 + @test(err, code=eval, pos=[2:5, 3:5]) 43 + a: 1 44 + a: 2 45 + } 46 + 47 + // Prior passing field doesn't shift the baseLine of subsequent fields 48 + prior: 1 @test(eq, 1) 49 + afterPrior: 1 & 2 @test(err, code=eval, pos=[0:13, 0:17]) 50 + 51 + // Relative pos= form (delta from field line) 52 + absPos: 1 & 2 @test(err, code=eval, pos=[0:9, 0:13]) 53 + -- out/update/out/errors.txt -- 54 + [eval] oneLine: conflicting values 2 and 1: 55 + ./test.cue:2:10 56 + ./test.cue:2:14 57 + [eval] nested.a: conflicting values 2 and 1: 58 + ./test.cue:6:5 59 + ./test.cue:7:5 60 + [eval] declAttr.a: conflicting values 2 and 1: 61 + ./test.cue:13:5 62 + ./test.cue:14:5 63 + [eval] afterPrior: conflicting values 2 and 1: 64 + ./test.cue:19:13 65 + ./test.cue:19:17 66 + [eval] absPos: conflicting values 2 and 1: 67 + ./test.cue:22:9 68 + ./test.cue:22:13
+47 -1
internal/cuetxtar/testdata/inline/pos_correct.txtar
··· 31 31 }) 32 32 } 33 33 -- out/status.txt -- 34 - update: identical to input 34 + update: output passes run 35 35 force: identical to update 36 + -- out/update/test.cue -- 37 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:10]) 38 + 39 + y: { 40 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:11]) 41 + } 42 + 43 + z: { 44 + b: int 45 + c: b & "foo" 46 + 47 + @test(eq, { 48 + b: int 49 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:9]) 50 + }) 51 + } 52 + 53 + 54 + // w tests two wrong pos= specs inside the same @test(eq, {...}), requiring 55 + // both nested rewrites to be merged before the outer attribute is written back. 56 + w: { 57 + a: 100 & 200 58 + b: 100 & 200 59 + 60 + @test(eq, { 61 + a: _|_ @test(err, code=eval, pos=[21:5, 21:11]) 62 + b: _|_ @test(err, code=eval, pos=[22:5, 22:11]) 63 + }) 64 + } 65 + -- out/update/out/errors.txt -- 66 + [eval] x: conflicting values 200 and 100: 67 + ./test.cue:1:4 68 + ./test.cue:1:10 69 + [eval] y.b: conflicting values 200 and 100: 70 + ./test.cue:4:5 71 + ./test.cue:4:11 72 + [eval] z.c: conflicting values "foo" and int (mismatched types string and int): 73 + ./test.cue:8:5 74 + ./test.cue:9:5 75 + ./test.cue:9:9 76 + [eval] w.a: conflicting values 200 and 100: 77 + ./test.cue:21:5 78 + ./test.cue:21:11 79 + [eval] w.b: conflicting values 200 and 100: 80 + ./test.cue:22:5 81 + ./test.cue:22:11
+17
internal/cuetxtar/testdata/inline/pos_fill.txtar
··· 72 72 -- out/status.txt -- 73 73 update: output passes run 74 74 force: identical to update 75 + -- out/update/out/errors.txt -- 76 + [eval] x: conflicting values 200 and 100: 77 + ./test.cue:1:4 78 + ./test.cue:1:10 79 + [eval] y.b: conflicting values 200 and 100: 80 + ./test.cue:4:5 81 + ./test.cue:4:11 82 + [eval] z.c: conflicting values "foo" and int (mismatched types string and int): 83 + ./test.cue:8:5 84 + ./test.cue:9:5 85 + ./test.cue:9:9 86 + [eval] w.a: conflicting values 200 and 100: 87 + ./test.cue:21:5 88 + ./test.cue:21:11 89 + [eval] w.b: conflicting values 200 and 100: 90 + ./test.cue:22:5 91 + ./test.cue:22:11
+63 -1
internal/cuetxtar/testdata/inline/pos_force_overwrite.txtar
··· 37 37 path z: c: @test(err, pos=...): no match for expected position 9:99999 in [test.cue:8:5 test.cue:9:5 test.cue:9:9] 38 38 path w: a: @test(err, pos=...): got 2 position(s), want 1; extra positions are often acceptable, and after confirming they are relevant to this error you can add them to pos=[...] [test.cue:20:5 test.cue:20:11] 39 39 -- out/status.txt -- 40 - update: identical to input 40 + update: output passes run 41 41 -- out/force/test.cue -- 42 42 x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:10]) 43 43 ··· 66 66 b: _|_ @test(err, code=eval, pos=[21:5, 21:11]) 67 67 }) 68 68 } 69 + -- out/update/test.cue -- 70 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:999999]) 71 + 72 + y: { 73 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:99999]) 74 + } 75 + 76 + z: { 77 + b: int 78 + c: b & "foo" 79 + 80 + @test(eq, { 81 + b: int 82 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:99999]) 83 + }) 84 + } 85 + 86 + // w tests two wrong pos= specs inside the same @test(eq, {...}), requiring 87 + // both nested rewrites to be merged before the outer attribute is written back. 88 + w: { 89 + a: 100 & 200 90 + b: 100 & 200 91 + 92 + @test(eq, { 93 + a: _|_ @test(err, code=eval, pos=[22:99999]) 94 + b: _|_ @test(err, code=eval, pos=[23:99999]) 95 + }) 96 + } 97 + -- out/update/out/errors.txt -- 98 + [eval] x: conflicting values 200 and 100: 99 + ./test.cue:1:4 100 + ./test.cue:1:10 101 + [eval] y.b: conflicting values 200 and 100: 102 + ./test.cue:4:5 103 + ./test.cue:4:11 104 + [eval] z.c: conflicting values "foo" and int (mismatched types string and int): 105 + ./test.cue:8:5 106 + ./test.cue:9:5 107 + ./test.cue:9:9 108 + [eval] w.a: conflicting values 200 and 100: 109 + ./test.cue:20:5 110 + ./test.cue:20:11 111 + [eval] w.b: conflicting values 200 and 100: 112 + ./test.cue:21:5 113 + ./test.cue:21:11 114 + -- out/force/out/errors.txt -- 115 + [eval] x: conflicting values 200 and 100: 116 + ./test.cue:1:4 117 + ./test.cue:1:10 118 + [eval] y.b: conflicting values 200 and 100: 119 + ./test.cue:4:5 120 + ./test.cue:4:11 121 + [eval] z.c: conflicting values "foo" and int (mismatched types string and int): 122 + ./test.cue:8:5 123 + ./test.cue:9:5 124 + ./test.cue:9:9 125 + [eval] w.a: conflicting values 200 and 100: 126 + ./test.cue:20:5 127 + ./test.cue:20:11 128 + [eval] w.b: conflicting values 200 and 100: 129 + ./test.cue:21:5 130 + ./test.cue:21:11
+24
internal/cuetxtar/testdata/inline/suberrors.txtar
··· 46 46 seed: #empty @test(err, code=eval, 47 47 suberr=(code=eval, contains="conflicting values %s and %s (mismatched types %s and %s)", args=[{}, null, struct, null], pos=[-18:9, -1:7, 0:7]), 48 48 suberr=(code=eval, path=n, contains="field not allowed", pos=[-1:15])) 49 + -- out/update/out/errors.txt -- 50 + [eval] x: 2 errors in empty disjunction: 51 + x: conflicting values {n:3} and null (mismatched types struct and null): 52 + ./test.cue:4:4 53 + ./test.cue:5:4 54 + ./test.cue:5:13 55 + x.n: field not allowed: 56 + ./test.cue:4:12 57 + ./test.cue:5:14 58 + [eval] y: 2 errors in empty disjunction: 59 + y: conflicting values {n:3} and null (mismatched types struct and null): 60 + ./test.cue:11:4 61 + ./test.cue:12:4 62 + ./test.cue:12:13 63 + y.n: field not allowed: 64 + ./test.cue:11:12 65 + ./test.cue:12:14 66 + [eval] seed: 2 errors in empty disjunction: 67 + seed: conflicting values {} and null (mismatched types struct and null): 68 + ./test.cue:1:9 69 + ./test.cue:18:7 70 + ./test.cue:19:7 71 + seed.n: field not allowed: 72 + ./test.cue:18:15
+51 -1
internal/cuetxtar/testdata/inline/todo.txtar
··· 48 48 // @test(eq, X, incorrect) and @test(err:todo) coexist 49 49 eqIncorrectAndErrTodo: 42 @test(eq, 42, incorrect) @test(err:todo, p=1, code=eval) 50 50 -- out/status.txt -- 51 - update: identical to input 51 + update: output passes run 52 52 -- out/force/test.cue -- 53 53 // @test(todo) suppresses a failing @test(eq) — 42 != 99 normally fails 54 54 suppressFailingEq: 42 @test(eq, 42) @test(todo) ··· 91 91 92 92 // @test(eq, X, incorrect) and @test(err:todo) coexist 93 93 eqIncorrectAndErrTodo: 42 @test(eq, 42, incorrect) @test(err:todo, p=1, code=eval) 94 + -- out/update/test.cue -- 95 + // @test(todo) suppresses a failing @test(eq) — 42 != 99 normally fails 96 + suppressFailingEq: 42 @test(eq, 99) @test(todo) 97 + 98 + // @test(todo) with a passing @test(eq) — passes normally, warning logged 99 + todoPassingEq: 42 @test(eq, 42) @test(todo) 100 + 101 + // @test(todo) with priority and why flags 102 + todoWithFlags: 42 @test(eq, 99) @test(todo, p=1, why="known issue") 103 + 104 + // @test(todo) suppresses a failing @test(err) — 42 is not an error 105 + suppressFailingErr: 42 @test(err) @test(todo) 106 + 107 + // @test(eq:todo, X) where value != X: silent non-failure 108 + eqTodoStillFailing: 42 @test(eq, 42) @test(eq:todo, 99) 109 + 110 + // @test(eq:todo, X) where value == X: warning logged, no failure 111 + eqTodoPassing: 42 @test(eq, 42) @test(eq:todo, 42) 112 + 113 + // @test(eq, ...) and @test(eq:todo, ...) coexist independently 114 + eqAndEqTodoCoexist: 42 @test(eq, 42) @test(eq:todo, 99) 115 + 116 + // @test(err:todo) where value is not an error: silent non-failure 117 + errTodoStillFailing: 42 @test(err:todo, code=eval) 118 + 119 + // @test(err:todo) where value IS an error: warning logged, no failure 120 + errTodoPassing: 1 & 2 @test(err:todo, code=eval) 121 + 122 + // @test(err:todo) with priority flag 123 + errTodoWithPriority: 42 @test(err:todo, p=1, code=eval) 124 + 125 + // @test(kind:todo=int) where kind is float: silent non-failure 126 + kindTodoStillFailing: 1.0 @test(kind:todo=int) 127 + 128 + // @test(kind:todo=int) where kind matches: warning logged, no failure 129 + kindTodoPassing: int @test(kind:todo=int) 130 + 131 + // @test(eq, X, incorrect) where value == X: logs note, no failure 132 + eqIncorrectPassing: 42 @test(eq, 42, incorrect) 133 + 134 + // @test(eq, X, incorrect) and @test(err:todo) coexist 135 + eqIncorrectAndErrTodo: 42 @test(eq, 42, incorrect) @test(err:todo, p=1, code=eval) 136 + -- out/update/out/errors.txt -- 137 + [eval] errTodoPassing: conflicting values 2 and 1: 138 + ./test.cue:26:17 139 + ./test.cue:26:21 140 + -- out/force/out/errors.txt -- 141 + [eval] errTodoPassing: conflicting values 2 and 1: 142 + ./test.cue:26:17 143 + ./test.cue:26:21