Skip to content

Commit 1a2e26e

Browse files
committed
encoding/jsonschema: add location information to test output
It's useful to know exactly where in the test data a test is, so log that information. Also print a txtar file to make it easier to reproduce test failures. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Ibef118835a429437825eb443e265066737c5fcb1 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200518 TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent c0fdf75 commit 1a2e26e

File tree

2 files changed

+89
-13
lines changed

2 files changed

+89
-13
lines changed

encoding/jsonschema/external_test.go

+65-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package jsonschema_test
1616

1717
import (
18+
stdjson "encoding/json"
1819
"fmt"
1920
"os"
2021
"path"
@@ -28,6 +29,7 @@ import (
2829
"cuelang.org/go/cue/cuecontext"
2930
"cuelang.org/go/cue/errors"
3031
"cuelang.org/go/cue/format"
32+
"cuelang.org/go/cue/token"
3133
"cuelang.org/go/encoding/json"
3234
"cuelang.org/go/encoding/jsonschema"
3335
"cuelang.org/go/encoding/jsonschema/internal/externaltest"
@@ -100,23 +102,32 @@ func runExternalSchemaTests(t *testing.T, filename string, s *externaltest.Schem
100102
qt.Assert(t, qt.IsNil(err))
101103
schemaValue = ctx.CompileBytes(b, cue.Filename("generated.cue"))
102104
if err := schemaValue.Err(); err != nil {
105+
t.Logf("extracted schema: %q", b)
103106
extractErr = fmt.Errorf("cannot compile resulting schema: %v", errors.Details(err, nil))
104107
}
105108
}
106109

107110
if extractErr != nil {
111+
t.Logf("location: %v", testdataPos(s))
112+
t.Logf("txtar:\n%s", schemaFailureTxtar(s))
108113
for _, test := range s.Tests {
109114
t.Run("", func(t *testing.T) {
110-
testFailed(t, &test.Skip, "could not compile schema")
115+
testFailed(t, &test.Skip, test, "could not compile schema")
111116
})
112117
}
113-
testFailed(t, &s.Skip, fmt.Sprintf("extract error: %v", extractErr))
118+
testFailed(t, &s.Skip, s, fmt.Sprintf("extract error: %v", extractErr))
114119
return
115120
}
116-
testSucceeded(t, &s.Skip)
121+
testSucceeded(t, &s.Skip, s)
117122

118123
for _, test := range s.Tests {
119124
t.Run(testName(test.Description), func(t *testing.T) {
125+
defer func() {
126+
if t.Failed() || testing.Verbose() {
127+
t.Logf("txtar:\n%s", testCaseTxtar(s, test))
128+
}
129+
}()
130+
t.Logf("location: %v", testdataPos(test))
120131
instAST, err := json.Extract("instance.json", test.Data)
121132
if err != nil {
122133
t.Fatal(err)
@@ -129,21 +140,60 @@ func runExternalSchemaTests(t *testing.T, filename string, s *externaltest.Schem
129140
err = instValue.Unify(schemaValue).Err()
130141
if test.Valid {
131142
if err != nil {
132-
testFailed(t, &test.Skip, errors.Details(err, nil))
143+
testFailed(t, &test.Skip, test, errors.Details(err, nil))
133144
} else {
134-
testSucceeded(t, &test.Skip)
145+
testSucceeded(t, &test.Skip, test)
135146
}
136147
} else {
137148
if err == nil {
138-
testFailed(t, &test.Skip, "unexpected success")
149+
testFailed(t, &test.Skip, test, "unexpected success")
139150
} else {
140-
testSucceeded(t, &test.Skip)
151+
testSucceeded(t, &test.Skip, test)
141152
}
142153
}
143154
})
144155
}
145156
}
146157

158+
// testCaseTxtar returns a testscript that runs the given test.
159+
func testCaseTxtar(s *externaltest.Schema, test *externaltest.Test) string {
160+
var buf strings.Builder
161+
fmt.Fprintf(&buf, "env CUE_EXPERIMENT=evalv3\n")
162+
fmt.Fprintf(&buf, "exec cue def json+jsonschema: schema.json\n")
163+
if !test.Valid {
164+
buf.WriteString("! ")
165+
}
166+
// TODO add $schema when one isn't already present?
167+
fmt.Fprintf(&buf, "exec cue vet -c instance.json json+jsonschema: schema.json\n")
168+
fmt.Fprintf(&buf, "\n")
169+
fmt.Fprintf(&buf, "-- schema.json --\n%s\n", indentJSON(s.Schema))
170+
fmt.Fprintf(&buf, "-- instance.json --\n%s\n", indentJSON(test.Data))
171+
return buf.String()
172+
}
173+
174+
// testCaseTxtar returns a testscript that decodes the given schema.
175+
func schemaFailureTxtar(s *externaltest.Schema) string {
176+
var buf strings.Builder
177+
fmt.Fprintf(&buf, "env CUE_EXPERIMENT=evalv3\n")
178+
fmt.Fprintf(&buf, "exec cue def -o schema.cue json+jsonschema: schema.json\n")
179+
fmt.Fprintf(&buf, "exec cat schema.cue\n")
180+
fmt.Fprintf(&buf, "exec cue vet schema.cue\n")
181+
fmt.Fprintf(&buf, "-- schema.json --\n%s\n", indentJSON(s.Schema))
182+
return buf.String()
183+
}
184+
185+
func indentJSON(x stdjson.RawMessage) []byte {
186+
data, err := stdjson.MarshalIndent(x, "", "\t")
187+
if err != nil {
188+
panic(err)
189+
}
190+
return data
191+
}
192+
193+
type positioner interface {
194+
Pos() token.Pos
195+
}
196+
147197
// testName returns a test name that doesn't contain any
148198
// slashes because slashes muck with matching.
149199
func testName(s string) string {
@@ -153,7 +203,7 @@ func testName(s string) string {
153203
// testFailed marks the current test as failed with the
154204
// given error message, and updates the
155205
// skip field pointed to by skipField if necessary.
156-
func testFailed(t *testing.T, skipField *string, errStr string) {
206+
func testFailed(t *testing.T, skipField *string, p positioner, errStr string) {
157207
if cuetest.UpdateGoldenFiles {
158208
if *skipField == "" && !allowRegressions() {
159209
t.Fatalf("test regression; was succeeding, now failing: %v", errStr)
@@ -169,7 +219,7 @@ func testFailed(t *testing.T, skipField *string, errStr string) {
169219

170220
// testFails marks the current test as succeeded and updates the
171221
// skip field pointed to by skipField if necessary.
172-
func testSucceeded(t *testing.T, skipField *string) {
222+
func testSucceeded(t *testing.T, skipField *string, p positioner) {
173223
if cuetest.UpdateGoldenFiles {
174224
*skipField = ""
175225
return
@@ -179,6 +229,12 @@ func testSucceeded(t *testing.T, skipField *string) {
179229
}
180230
}
181231

232+
func testdataPos(p positioner) token.Position {
233+
pp := p.Pos().Position()
234+
pp.Filename = path.Join(testDir, pp.Filename)
235+
return pp
236+
}
237+
182238
func allowRegressions() bool {
183239
return os.Getenv("CUE_ALLOW_REGRESSIONS") != ""
184240
}

encoding/jsonschema/internal/externaltest/tests.go

+24-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import (
1212
"cuelang.org/go/cue/cuecontext"
1313
"cuelang.org/go/cue/interpreter/embed"
1414
"cuelang.org/go/cue/load"
15+
"cuelang.org/go/cue/token"
1516
)
1617

1718
type Schema struct {
19+
location
1820
Description string `json:"description"`
1921
Comment string `json:"comment,omitempty"`
2022
Schema stdjson.RawMessage `json:"schema"`
@@ -23,13 +25,23 @@ type Schema struct {
2325
}
2426

2527
type Test struct {
28+
location
2629
Description string `json:"description"`
2730
Comment string `json:"comment,omitempty"`
2831
Data stdjson.RawMessage `json:"data"`
2932
Valid bool `json:"valid"`
3033
Skip string `json:"skip,omitempty"`
3134
}
3235

36+
type location struct {
37+
root cue.Value
38+
path cue.Path
39+
}
40+
41+
func (loc location) Pos() token.Pos {
42+
return loc.root.LookupPath(loc.path).Pos()
43+
}
44+
3345
func ParseTestData(data []byte) ([]*Schema, error) {
3446
var schemas []*Schema
3547
if err := json.Unmarshal(data, &schemas); err != nil {
@@ -81,7 +93,7 @@ func ReadTestDir(dir string) (tests map[string][]*Schema, err error) {
8193
inst := load.Instances([]string{"."}, &load.Config{
8294
Dir: dir,
8395
})[0]
84-
if err != nil {
96+
if err := inst.Err; err != nil {
8597
return nil, err
8698
}
8799
ctx := cuecontext.New(cuecontext.Interpreter(embed.New()))
@@ -97,9 +109,17 @@ func ReadTestDir(dir string) (tests map[string][]*Schema, err error) {
97109
return nil, err
98110
}
99111
// Fix up the raw JSON data to avoid running into some decode issues.
100-
for _, schemas := range tests {
101-
for _, schema := range schemas {
102-
for _, test := range schema.Tests {
112+
for filename, schemas := range tests {
113+
for i, schema := range schemas {
114+
schema.location = location{
115+
root: val,
116+
path: cue.MakePath(cue.Str(filename), cue.Index(i)),
117+
}
118+
for j, test := range schema.Tests {
119+
test.location = location{
120+
root: val,
121+
path: cue.MakePath(cue.Str(filename), cue.Index(i), cue.Str("tests"), cue.Index(j)),
122+
}
103123
if len(test.Data) == 0 {
104124
// See https://github.com/cue-lang/cue/issues/3397
105125
test.Data = []byte("null")

0 commit comments

Comments
 (0)