this repo has no description
0
fork

Configure Feed

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

internal/cuetxtar: add txtar test

The Go tests are rather hard to read and thus
it is easy to miss whether they actually cover issues.

We add a txtar suite for testing update and force
modes as well as being able to test *negative* tests
and error reporting.

The idea is that most Go tests can move to this
framework. We just add tests for core functionality
now, but intend convert more down the line.

NOTE: looked into using the fs.FS functionality of
txtar, but most of the code modifies the archives,
So it wasn't all that beneficial.

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

+580 -8
+54 -5
internal/cuetxtar/inline.go
··· 130 130 return &InlineRunner{r: &inlineRunner{t: t, m: m, archive: archive, dir: dir}} 131 131 } 132 132 133 + // NewInlineRunnerCapture creates an InlineRunner that captures assertion 134 + // failures into cap without propagating them to t. Infrastructure errors 135 + // (compile errors, write-back failures) still propagate through t. 136 + func NewInlineRunnerCapture(t *testing.T, m *cuetdtest.M, archive *txtar.Archive, dir string, cap *FailCapture) *InlineRunner { 137 + return &InlineRunner{r: &inlineRunner{t: t, m: m, archive: archive, dir: dir, errSink: cap}} 138 + } 139 + 133 140 // Run executes all inline test cases in the archive. 134 141 func (ir *InlineRunner) Run() { 135 142 ir.r.runArchive() ··· 141 148 // inlineRunner handles execution of a single txtar archive in inline mode. 142 149 type inlineRunner struct { 143 150 t *testing.T 151 + errSink testing.TB // if non-nil, assertion errors go here instead of the sub-test t 144 152 m *cuetdtest.M 145 153 archive *txtar.Archive 146 154 dir string ··· 157 165 pendingInlineFillWrites []inlineFillWrite 158 166 } 159 167 168 + // sinkOrSub returns the errSink if set, otherwise the given sub-test t. 169 + // Used in runArchive to route assertion errors in capture mode. 170 + func (r *inlineRunner) sinkOrSub(sub *testing.T) testing.TB { 171 + if r.errSink != nil { 172 + return r.errSink 173 + } 174 + return sub 175 + } 176 + 177 + // sinkOrT returns the errSink if set, otherwise r.t. 178 + // Used for assertions that run outside a per-root sub-test. 179 + func (r *inlineRunner) sinkOrT() testing.TB { 180 + if r.errSink != nil { 181 + return r.errSink 182 + } 183 + return r.t 184 + } 185 + 160 186 // failCapture wraps *testing.T and captures failures without propagating them. 161 187 // It is used for @test(todo) XFAIL mode: all directives run, but failures are 162 188 // logged rather than reported as test errors. ··· 180 206 fmt.Fprintf(&c.msgs, format+"\n", args...) 181 207 } 182 208 209 + // FailCapture wraps a testing.TB and captures Error/Errorf calls without 210 + // propagating failures to the testing framework. All other testing.TB methods 211 + // delegate to the embedded TB. Use with NewInlineRunnerCapture. 212 + type FailCapture struct { 213 + testing.TB 214 + msgs strings.Builder 215 + } 216 + 217 + func (c *FailCapture) Error(args ...any) { 218 + fmt.Fprintln(&c.msgs, args...) 219 + } 220 + 221 + func (c *FailCapture) Errorf(format string, args ...any) { 222 + fmt.Fprintf(&c.msgs, format, args...) 223 + fmt.Fprintln(&c.msgs) 224 + } 225 + 226 + // Failed reports whether any errors were captured. 227 + func (c *FailCapture) Failed() bool { return c.msgs.Len() > 0 } 228 + 229 + // Messages returns the accumulated error text. 230 + func (c *FailCapture) Messages() string { return c.msgs.String() } 231 + 183 232 // runDirectivesForPath runs all directives for a single path, handling 184 233 // @test(skip) and @test(todo) according to their semantics: 185 234 // - skip directives fire first so t.Skip() is called before other assertions ··· 187 236 // 188 237 // path is used both as the argument to runDirective and in log messages; 189 238 // an empty path is displayed as "(file level)". 190 - func (r *inlineRunner) runDirectivesForPath(t *testing.T, path cue.Path, val cue.Value, directives []parsedTestAttr) { 239 + func (r *inlineRunner) runDirectivesForPath(t testing.TB, path cue.Path, val cue.Value, directives []parsedTestAttr) { 191 240 for _, pa := range directives { 192 241 if pa.directive == "skip" { 193 242 r.runDirective(t, path, val, pa) ··· 301 350 } 302 351 seenFilePaths[rec.path.String()] = true 303 352 directives := selectActiveDirectives(allRecords, rec.path, version) 304 - r.runDirectivesForPath(r.t, cue.Path{}, val, directives) 353 + r.runDirectivesForPath(r.sinkOrT(), cue.Path{}, val, directives) 305 354 } 306 355 } 307 356 ··· 331 380 root := root 332 381 name := r.subTestName(root, allRecords) 333 382 r.t.Run(name, func(t *testing.T) { 334 - r.runInline(t, root, val, allRecords) 383 + r.runInline(r.sinkOrSub(t), root, val, allRecords) 335 384 }) 336 385 } 337 386 ··· 341 390 // over all records and check after all subtests run. 342 391 version := r.versionName() 343 392 if fileShareGroups := r.collectDirectShareIDs(allRecords, version); len(fileShareGroups) > 0 { 344 - r.runShareIDChecks(r.t, val, fileShareGroups) 393 + r.runShareIDChecks(r.sinkOrT(), val, fileShareGroups) 345 394 } 346 395 347 396 // After all subtests complete, write back any pending updates. ··· 631 680 // ───────────────────────────────────────────────────────────────────────────── 632 681 633 682 // runInline runs assertions for an inline-form test-case root. 634 - func (r *inlineRunner) runInline(t *testing.T, root testCaseRoot, fileVal cue.Value, records []attrRecord) { 683 + func (r *inlineRunner) runInline(t testing.TB, root testCaseRoot, fileVal cue.Value, records []attrRecord) { 635 684 t.Helper() 636 685 version := r.versionName() 637 686
+403
internal/cuetxtar/inlinetxtar_test.go
··· 1 + // Copyright 2026 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package cuetxtar_test 16 + 17 + // This file tests the CUE_UPDATE=1 / CUE_UPDATE=force write-back behavior of 18 + // the inline runner. Each testdata/inline/*.txtar file describes an update 19 + // scenario: the in/ sections are the input, out/run/errors.txt captures errors 20 + // from a plain run (no update), out/update/ sections are the expected state 21 + // after two CUE_UPDATE=1 passes (for idempotency; omitted when identical to 22 + // input), out/force/ sections are the expected state after CUE_UPDATE=force 23 + // (omitted when identical to update), and out/status.txt records which results 24 + // were identical (proof that the framework actually ran). 25 + // 26 + // CUE_UPDATE=1 adds missing out/ sections (for new tests) but validates 27 + // existing ones without overwriting them. CUE_UPDATE=force fully regenerates 28 + // all out/ sections, and is required when existing section content changes. 29 + 30 + import ( 31 + "bytes" 32 + "fmt" 33 + "os" 34 + "path/filepath" 35 + "slices" 36 + "strings" 37 + "testing" 38 + 39 + "golang.org/x/tools/txtar" 40 + 41 + "cuelang.org/go/internal/cuetest" 42 + "cuelang.org/go/internal/cuetxtar" 43 + ) 44 + 45 + // TestInlineUpdate runs txtar-driven update tests from testdata/inline/. 46 + // Each test verifies that running the inline runner with CUE_UPDATE=1 and/or 47 + // CUE_UPDATE=force produces the expected output, and that a second update pass 48 + // is idempotent. 49 + // 50 + // Testdata format: 51 + // 52 + // -- in/test.cue -- 53 + // ... CUE source with @test directives that may need updating ... 54 + // 55 + // -- out/run/errors.txt -- 56 + // ... errors from a plain run (no update); absent if the run passes ... 57 + // 58 + // -- out/update/test.cue -- 59 + // ... expected source after two CUE_UPDATE=1 passes (idempotent); 60 + // ... absent when update produces no change (i.e. identical to input) ... 61 + // 62 + // -- out/force/test.cue -- 63 + // ... expected source after CUE_UPDATE=force; absent when identical to update ... 64 + // 65 + // -- out/status.txt -- 66 + // ... "update: identical" and/or "force: identical" lines (proof of run) ... 67 + func TestInlineUpdate(t *testing.T) { 68 + matches, err := filepath.Glob("testdata/inline/*.txtar") 69 + if err != nil { 70 + t.Fatal(err) 71 + } 72 + if len(matches) == 0 { 73 + t.Fatal("testdata/inline: no *.txtar files found") 74 + } 75 + for _, path := range matches { 76 + name := strings.TrimSuffix(filepath.Base(path), ".txtar") 77 + t.Run(name, func(t *testing.T) { 78 + runUpdateTest(t, path) 79 + }) 80 + } 81 + } 82 + 83 + func runUpdateTest(t *testing.T, filePath string) { 84 + t.Helper() 85 + outer, err := txtar.ParseFile(filePath) 86 + if err != nil { 87 + t.Fatalf("parse %s: %v", filePath, err) 88 + } 89 + 90 + // Build input archive from in/ sections. 91 + inputArchive := extractTxtarSections(outer, "in/") 92 + if len(inputArchive.Files) == 0 { 93 + t.Fatalf("%s: no in/ sections", filePath) 94 + } 95 + 96 + // ctx holds the values that are constant across all applySection calls. 97 + ctx := &sectionCtx{ 98 + t: t, 99 + outerForce: cuetest.ForceUpdateGoldenFiles, 100 + outerUpdate: cuetest.UpdateGoldenFiles, 101 + outer: outer, 102 + filePath: filePath, 103 + } 104 + // --- Plain run (no update mode) --- 105 + 106 + capR := &cuetxtar.FailCapture{TB: t} 107 + withUpdateMode(false, false, func() { 108 + runner := cuetxtar.NewInlineRunnerCapture(t, nil, cloneTxtarArchive(inputArchive), t.TempDir(), capR) 109 + runner.Run() 110 + }) 111 + runErrors := capR.Messages() 112 + ctx.apply(section{ 113 + prefix: "out/run/errors.txt", 114 + content: singleFile(runErrors), 115 + shouldExist: runErrors != "", 116 + }) 117 + 118 + // --- Regular update (CUE_UPDATE=1) --- 119 + 120 + // Pass 1: run with update mode enabled; capture assertion errors. 121 + update1 := cloneTxtarArchive(inputArchive) 122 + cap1 := &cuetxtar.FailCapture{TB: t} 123 + withUpdateMode(true, false, func() { 124 + runner := cuetxtar.NewInlineRunnerCapture(t, nil, update1, t.TempDir(), cap1) 125 + runner.Run() 126 + }) 127 + 128 + // Pass 2: run again from the pass-1 result to verify idempotency. 129 + update2 := cloneTxtarArchive(update1) 130 + cap2 := &cuetxtar.FailCapture{TB: t} 131 + withUpdateMode(true, false, func() { 132 + runner := cuetxtar.NewInlineRunnerCapture(t, nil, update2, t.TempDir(), cap2) 133 + runner.Run() 134 + }) 135 + 136 + // Both passes must produce the same archive and errors. 137 + if diff := txtarFileDiff(update1, update2); diff != "" { 138 + t.Errorf("CUE_UPDATE=1 is not idempotent (archive):\n%s", diff) 139 + } 140 + if cap1.Messages() != cap2.Messages() { 141 + t.Errorf("CUE_UPDATE=1 is not idempotent (errors):\ngot (pass1):\n%swant (pass2):\n%s", 142 + cap1.Messages(), cap2.Messages()) 143 + } 144 + 145 + // out/update/ sections: omitted when update is identical to input. 146 + updateIdentical := txtarFileDiff(inputArchive, update1) == "" 147 + ctx.apply(section{ 148 + prefix: "out/update/", 149 + content: update1, 150 + shouldExist: !updateIdentical, 151 + staleMsg: "out/update/ sections present but update produces same result as input (run with CUE_UPDATE=1 to remove)", 152 + }) 153 + 154 + // --- Force update (CUE_UPDATE=force) --- 155 + // out/force/ sections: present only when force produces a different result than update. 156 + 157 + forceArchive := cloneTxtarArchive(inputArchive) 158 + capF := &cuetxtar.FailCapture{TB: t} 159 + withUpdateMode(true, true, func() { 160 + runner := cuetxtar.NewInlineRunnerCapture(t, nil, forceArchive, t.TempDir(), capF) 161 + runner.Run() 162 + }) 163 + forceDiffers := txtarFileDiff(update1, forceArchive) != "" || cap1.Messages() != capF.Messages() 164 + forceContent := cloneTxtarArchive(forceArchive) 165 + if msg := capF.Messages(); msg != "" { 166 + forceContent.Files = append(forceContent.Files, txtar.File{Name: "errors.txt", Data: []byte(msg)}) 167 + } 168 + ctx.apply(section{ 169 + prefix: "out/force/", 170 + content: forceContent, 171 + shouldExist: forceDiffers, 172 + staleMsg: "out/force/ sections present but force produces same result as update (run with CUE_UPDATE=1 to remove)", 173 + }) 174 + 175 + // --- Status summary --- 176 + // out/status.txt records which results were identical to their predecessor, 177 + // serving as proof that the framework actually exercised those paths. 178 + var statusLines []string 179 + if updateIdentical { 180 + statusLines = append(statusLines, "update: identical to input") 181 + } 182 + if !forceDiffers { 183 + statusLines = append(statusLines, "force: identical to update") 184 + } 185 + var statusContent string 186 + if len(statusLines) > 0 { 187 + statusContent = strings.Join(statusLines, "\n") + "\n" 188 + } 189 + ctx.apply(section{ 190 + prefix: "out/status.txt", 191 + content: singleFile(statusContent), 192 + shouldExist: statusContent != "", 193 + }) 194 + 195 + // Write the outer testdata file if any sections were updated. 196 + if ctx.modified { 197 + if err := os.WriteFile(filePath, txtar.Format(outer), 0o644); err != nil { 198 + t.Errorf("update %s: %v", filePath, err) 199 + } 200 + } 201 + } 202 + 203 + // singleFile wraps content in a single-file archive with an empty file name. 204 + // Used for sections where the prefix is the full file name (e.g. "out/status.txt"). 205 + func singleFile(content string) *txtar.Archive { 206 + return &txtar.Archive{Files: []txtar.File{{Data: []byte(content)}}} 207 + } 208 + 209 + // sectionCtx holds the values that are constant across all apply calls within 210 + // a single runUpdateTest invocation, and accumulates whether any section was 211 + // modified. 212 + type sectionCtx struct { 213 + t *testing.T 214 + outerForce bool 215 + outerUpdate bool 216 + outer *txtar.Archive 217 + filePath string 218 + modified bool 219 + } 220 + 221 + // section describes one output section group to manage. 222 + type section struct { 223 + prefix string 224 + content *txtar.Archive 225 + shouldExist bool 226 + staleMsg string // optional; ctx.filePath is prepended automatically 227 + } 228 + 229 + // apply manages the lifecycle of one output section group in ctx.outer and 230 + // sets ctx.modified if the archive was changed. 231 + // 232 + // - outerForce: write if shouldExist, remove otherwise; always sets modified. 233 + // - outerUpdate: write if missing and shouldExist; validate if present and 234 + // shouldExist; remove if present but !shouldExist. 235 + // - default: validate if shouldExist; log staleMsg (if non-empty) when the 236 + // section exists but should not. 237 + func (ctx *sectionCtx) apply(s section) { 238 + ctx.t.Helper() 239 + exists := hasTxtarSectionPrefix(ctx.outer, s.prefix) 240 + switch { 241 + case ctx.outerForce: 242 + if s.shouldExist { 243 + replaceTxtarSections(ctx.outer, s.prefix, s.content) 244 + } else { 245 + removeTxtarSectionPrefix(ctx.outer, s.prefix) 246 + } 247 + ctx.modified = true 248 + case ctx.outerUpdate: 249 + switch { 250 + case s.shouldExist && !exists: 251 + replaceTxtarSections(ctx.outer, s.prefix, s.content) 252 + ctx.modified = true 253 + case s.shouldExist && exists: 254 + compareTxtarSections(ctx.t, ctx.outer, s.prefix, s.content, ctx.filePath) 255 + case !s.shouldExist && exists: 256 + removeTxtarSectionPrefix(ctx.outer, s.prefix) 257 + ctx.modified = true 258 + } 259 + default: 260 + if s.shouldExist { 261 + compareTxtarSections(ctx.t, ctx.outer, s.prefix, s.content, ctx.filePath) 262 + } else if exists && s.staleMsg != "" { 263 + ctx.t.Error(ctx.filePath + ": " + s.staleMsg) 264 + } 265 + } 266 + } 267 + 268 + // withUpdateMode temporarily sets cuetest.UpdateGoldenFiles and 269 + // cuetest.ForceUpdateGoldenFiles for the duration of fn, then restores them. 270 + func withUpdateMode(update, force bool, fn func()) { 271 + origUpdate := cuetest.UpdateGoldenFiles 272 + origForce := cuetest.ForceUpdateGoldenFiles 273 + defer func() { 274 + cuetest.UpdateGoldenFiles = origUpdate 275 + cuetest.ForceUpdateGoldenFiles = origForce 276 + }() 277 + cuetest.UpdateGoldenFiles = update 278 + cuetest.ForceUpdateGoldenFiles = force 279 + fn() 280 + } 281 + 282 + // extractTxtarSections returns a new archive built from files in ar whose names 283 + // start with the given prefix; the prefix is stripped from each file name. 284 + func extractTxtarSections(ar *txtar.Archive, prefix string) *txtar.Archive { 285 + var files []txtar.File 286 + for _, f := range ar.Files { 287 + if name, ok := strings.CutPrefix(f.Name, prefix); ok { 288 + files = append(files, txtar.File{Name: name, Data: bytes.Clone(f.Data)}) 289 + } 290 + } 291 + return &txtar.Archive{Files: files} 292 + } 293 + 294 + // cloneTxtarArchive returns a deep copy of ar. 295 + func cloneTxtarArchive(ar *txtar.Archive) *txtar.Archive { 296 + clone := &txtar.Archive{Comment: bytes.Clone(ar.Comment)} 297 + for _, f := range ar.Files { 298 + clone.Files = append(clone.Files, txtar.File{Name: f.Name, Data: bytes.Clone(f.Data)}) 299 + } 300 + return clone 301 + } 302 + 303 + // hasTxtarSectionPrefix reports whether ar has any file whose name starts with prefix. 304 + func hasTxtarSectionPrefix(ar *txtar.Archive, prefix string) bool { 305 + return slices.ContainsFunc(ar.Files, func(f txtar.File) bool { 306 + return strings.HasPrefix(f.Name, prefix) 307 + }) 308 + } 309 + 310 + // removeTxtarSectionPrefix removes all files from ar whose names start with prefix. 311 + func removeTxtarSectionPrefix(ar *txtar.Archive, prefix string) { 312 + ar.Files = slices.DeleteFunc(ar.Files, func(f txtar.File) bool { 313 + return strings.HasPrefix(f.Name, prefix) 314 + }) 315 + } 316 + 317 + // replaceTxtarSections sets all files under prefix in ar to match src: existing 318 + // files with the prefix that are not in src are removed, files that are in src 319 + // are updated in place, and new src files are appended at the end. 320 + func replaceTxtarSections(ar *txtar.Archive, prefix string, src *txtar.Archive) { 321 + // Build the set of names (with prefix) that src provides. 322 + srcByName := make(map[string][]byte, len(src.Files)) 323 + for _, sf := range src.Files { 324 + srcByName[prefix+sf.Name] = sf.Data 325 + } 326 + 327 + var result []txtar.File 328 + inserted := make(map[string]bool) 329 + for _, f := range ar.Files { 330 + if strings.HasPrefix(f.Name, prefix) { 331 + if data, ok := srcByName[f.Name]; ok { 332 + result = append(result, txtar.File{Name: f.Name, Data: data}) 333 + inserted[f.Name] = true 334 + } 335 + // else: stale file — drop it 336 + } else { 337 + result = append(result, f) 338 + } 339 + } 340 + // Append any src files not yet inserted (new sections with no prior entry). 341 + for _, sf := range src.Files { 342 + name := prefix + sf.Name 343 + if !inserted[name] { 344 + result = append(result, txtar.File{Name: name, Data: sf.Data}) 345 + } 346 + } 347 + ar.Files = result 348 + } 349 + 350 + // compareTxtarSections checks that files in src match the sections in ar that 351 + // have the given prefix. hint is the file path used in error messages. 352 + func compareTxtarSections(t *testing.T, ar *txtar.Archive, prefix string, src *txtar.Archive, hint string) { 353 + t.Helper() 354 + want := make(map[string]string) 355 + for _, f := range ar.Files { 356 + if name, ok := strings.CutPrefix(f.Name, prefix); ok { 357 + want[name] = string(f.Data) 358 + } 359 + } 360 + got := make(map[string]string) 361 + for _, f := range src.Files { 362 + got[f.Name] = string(f.Data) 363 + } 364 + for name, g := range got { 365 + w, ok := want[name] 366 + if !ok { 367 + t.Errorf("%s: %s%s: not in testdata (run with CUE_UPDATE=1 to add)\ngot:\n%s", hint, prefix, name, g) 368 + continue 369 + } 370 + if g != w { 371 + t.Errorf("%s: %s%s: mismatch\ngot:\n%swant:\n%s", hint, prefix, name, g, w) 372 + } 373 + } 374 + for name := range want { 375 + if _, ok := got[name]; !ok { 376 + t.Errorf("%s: %s%s: in testdata but not produced by runner", hint, prefix, name) 377 + } 378 + } 379 + } 380 + 381 + // txtarFileDiff returns a human-readable description of file-by-file differences 382 + // between archive a and archive b. Returns "" if they are identical. 383 + func txtarFileDiff(a, b *txtar.Archive) string { 384 + aMap := make(map[string]string) 385 + for _, f := range a.Files { 386 + aMap[f.Name] = string(f.Data) 387 + } 388 + var diffs []string 389 + for _, f := range b.Files { 390 + ac, ok := aMap[f.Name] 391 + if !ok { 392 + diffs = append(diffs, fmt.Sprintf("file %s: only in second archive", f.Name)) 393 + } else if ac != string(f.Data) { 394 + diffs = append(diffs, fmt.Sprintf("file %s: differs\ngot (pass1):\n%swant (pass2):\n%s", 395 + f.Name, ac, string(f.Data))) 396 + } 397 + delete(aMap, f.Name) 398 + } 399 + for name := range aMap { 400 + diffs = append(diffs, fmt.Sprintf("file %s: only in first archive", name)) 401 + } 402 + return strings.Join(diffs, "\n") 403 + }
+3 -3
internal/cuetxtar/permute.go
··· 37 37 // 38 38 // 2. Decl attribute: @test(permute) as a declaration inside a struct means 39 39 // "permute all fields within this struct." 40 - func (r *inlineRunner) runInlinePermutes(t *testing.T, rootPath cue.Path, records []attrRecord, version string) { 40 + func (r *inlineRunner) runInlinePermutes(t testing.TB, rootPath cue.Path, records []attrRecord, version string) { 41 41 type permuteGroup struct { 42 42 parentPath cue.Path 43 43 fields []string // nil means permute all fields ··· 113 113 // checkPermuteCount verifies or auto-updates a @test(permuteCount, N) directive 114 114 // at path after all permutations for a group have run. When CUE_UPDATE=1, the 115 115 // count is filled or replaced with the actual value. 116 - func (r *inlineRunner) checkPermuteCount(t *testing.T, path cue.Path, records []attrRecord, version string, actualCount int) { 116 + func (r *inlineRunner) checkPermuteCount(t testing.TB, path cue.Path, records []attrRecord, version string, actualCount int) { 117 117 t.Helper() 118 118 directives := selectActiveDirectives(records, path, version) 119 119 for _, pa := range directives { ··· 151 151 // Uses Heap's algorithm to enumerate permutations without allocating N! slices. 152 152 // Returns the total number of permutations evaluated (N!), or 0 if skipped 153 153 // (fewer than 2 permutable fields). 154 - func (r *inlineRunner) runPermuteAssertion(t *testing.T, structPath cue.Path, fieldNames []string) int { 154 + func (r *inlineRunner) runPermuteAssertion(t testing.TB, structPath cue.Path, fieldNames []string) int { 155 155 t.Helper() 156 156 if r.cueFiles == nil { 157 157 return 0
+8
internal/cuetxtar/testdata/inline/fail.txtar
··· 1 + -- in/test.cue -- 2 + a: 1 @test(eq, 100) 3 + -- out/run/errors.txt -- 4 + path a: expected 100, got 1 5 + -- out/force/test.cue -- 6 + a: 1 @test(eq, 1) 7 + -- out/status.txt -- 8 + update: identical to input
+22
internal/cuetxtar/testdata/inline/pos_correct.txtar
··· 1 + # Tests that a test with already-correct positions is left unchanged by both 2 + # CUE_UPDATE=1 and CUE_UPDATE=force. Covers different @test(err) placement forms: 3 + # field attribute, nested field inside struct, and nested inside @test(eq). 4 + -- in/test.cue -- 5 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:10]) 6 + 7 + y: { 8 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:11]) 9 + } 10 + 11 + z: { 12 + b: int 13 + c: b & "foo" 14 + 15 + @test(eq, { 16 + b: int 17 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:9]) 18 + }) 19 + } 20 + -- out/status.txt -- 21 + update: identical to input 22 + force: identical to update
+43
internal/cuetxtar/testdata/inline/pos_fill.txtar
··· 1 + # Tests that pos=[] placeholders are filled with actual error positions on 2 + # CUE_UPDATE=1. The input has a conflict error (1 & 2) with an empty pos=[] 3 + # placeholder; after update, the placeholder is replaced with the two source 4 + # positions of the conflicting values. Force update (CUE_UPDATE=force) also 5 + # fills placeholders, producing the same result. 6 + -- in/test.cue -- 7 + x: 100 & 200 @test(err, code=eval, pos=[]) 8 + 9 + y: { 10 + b: 100 & 200 @test(err, code=eval, pos=[]) 11 + } 12 + 13 + z: { 14 + b: int 15 + c: b & "foo" 16 + 17 + @test(eq, { 18 + b: int 19 + c: _|_ @test(err, code=eval, pos=[]) 20 + }) 21 + } 22 + -- out/run/errors.txt -- 23 + path x: @test(err, pos=...): got 2 position(s), want 0; extra positions are often acceptable, and after confirming they are relevant to this error you can add them to pos=[...] 24 + path y.b: @test(err, pos=...): got 2 position(s), want 0; extra positions are often acceptable, and after confirming they are relevant to this error you can add them to pos=[...] 25 + path z: c: @test(err, pos=...): got 3 position(s), want 0; extra positions are often acceptable, and after confirming they are relevant to this error you can add them to pos=[...] [8:5 9:5 9:9] 26 + -- out/update/test.cue -- 27 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:10]) 28 + 29 + y: { 30 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:11]) 31 + } 32 + 33 + z: { 34 + b: int 35 + c: b & "foo" 36 + 37 + @test(eq, { 38 + b: int 39 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:9]) 40 + }) 41 + } 42 + -- out/status.txt -- 43 + force: identical to update
+41
internal/cuetxtar/testdata/inline/pos_force_overwrite.txtar
··· 1 + # Tests that CUE_UPDATE=force overwrites wrong (non-placeholder) positions, while 2 + # CUE_UPDATE=1 leaves them unchanged and reports a position mismatch error. 3 + # The input has pos=[0:4] but the error has two positions; force update corrects it. 4 + -- in/test.cue -- 5 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:999999]) 6 + 7 + y: { 8 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:99999]) 9 + } 10 + 11 + z: { 12 + b: int 13 + c: b & "foo" 14 + 15 + @test(eq, { 16 + b: int 17 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:99999]) 18 + }) 19 + } 20 + -- out/run/errors.txt -- 21 + path x: @test(err, pos=...): unmatched position 1:999999 (spec 0:999999); actual positions: 22 + path y.b: @test(err, pos=...): unmatched position 4:99999 (spec 0:99999); actual positions: 23 + path z: c: @test(err, pos=...): no match for expected position 9:99999 in [8:5 9:5 9:9] 24 + -- out/status.txt -- 25 + update: identical to input 26 + -- out/force/test.cue -- 27 + x: 100 & 200 @test(err, code=eval, pos=[0:4, 0:10]) 28 + 29 + y: { 30 + b: 100 & 200 @test(err, code=eval, pos=[0:5, 0:11]) 31 + } 32 + 33 + z: { 34 + b: int 35 + c: b & "foo" 36 + 37 + @test(eq, { 38 + b: int 39 + c: _|_ @test(err, code=eval, pos=[8:5, 9:5, 9:9]) 40 + }) 41 + }
+6
internal/cuetxtar/testdata/inline/seed.txtar
··· 1 + -- in/test.cue -- 2 + a: 1 @test(eq) 3 + -- out/update/test.cue -- 4 + a: 1 @test(eq, 1) 5 + -- out/status.txt -- 6 + force: identical to update