Skip to content

Commit 87a159c

Browse files
committed
utf16: Schema errors and encoding tests
1 parent 806c9cd commit 87a159c

File tree

3 files changed

+151
-54
lines changed

3 files changed

+151
-54
lines changed

gen_testdata.go

+42-43
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,56 @@
11
// +build ignore
22

3-
// generates clones the utf-8 tests data to the other
3+
// gen_testdata clones the utf-8 tests data to the other
44
// unicode encodings and adds BOM variants of each.
55
package main
66

77
import (
8-
"io/ioutil"
9-
"log"
10-
"os"
11-
"path/filepath"
8+
"io/ioutil"
9+
"log"
10+
"os"
11+
"path/filepath"
1212

13-
"golang.org/x/text/encoding"
14-
"golang.org/x/text/encoding/unicode"
13+
"golang.org/x/text/encoding"
14+
"golang.org/x/text/encoding/unicode"
1515
)
1616

17-
1817
func main() {
19-
var xforms = []struct {
20-
dir, bom string
21-
enc encoding.Encoding
22-
} {
23-
{ "testdata/utf-16be", "\xFE\xFF", unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) },
24-
{ "testdata/utf-16le", "\xFF\xFE", unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) },
25-
}
26-
27-
paths, _ := filepath.Glob("testdata/utf-8/*")
28-
for _, p := range paths {
29-
src, err := ioutil.ReadFile(p)
30-
if err != nil {
31-
log.Fatal(err)
32-
}
33-
34-
write("testdata/utf-8_bom", p, "\xEF\xBB\xBF", src)
35-
for _, xform := range xforms {
36-
dst, err := xform.enc.NewEncoder().Bytes(src)
37-
if err != nil {
38-
log.Fatal(err)
39-
}
40-
write(xform.dir, p, "", dst)
41-
write(xform.dir + "_bom", p, xform.bom, dst)
42-
}
43-
}
18+
var xforms = []struct {
19+
dir, bom string
20+
enc encoding.Encoding
21+
}{
22+
{"testdata/utf-16be", "\xFE\xFF", unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)},
23+
{"testdata/utf-16le", "\xFF\xFE", unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)},
24+
}
25+
26+
paths, _ := filepath.Glob("testdata/utf-8/*")
27+
for _, p := range paths {
28+
src, err := ioutil.ReadFile(p)
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
33+
write("testdata/utf-8_bom", p, "\xEF\xBB\xBF", src)
34+
for _, xform := range xforms {
35+
dst, err := xform.enc.NewEncoder().Bytes(src)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
write(xform.dir, p, "", dst)
40+
write(xform.dir+"_bom", p, xform.bom, dst)
41+
}
42+
}
4443
}
4544

4645
func write(dir, orig, bom string, buf []byte) {
47-
f, err := os.Create(filepath.Join(dir, filepath.Base(orig)))
48-
if err != nil {
49-
log.Fatal(err)
50-
}
51-
if _, err = f.Write([]byte(bom)); err != nil {
52-
log.Fatal(err)
53-
}
54-
if _, err = f.Write(buf); err != nil {
55-
log.Fatal(err)
56-
}
46+
f, err := os.Create(filepath.Join(dir, filepath.Base(orig)))
47+
if err != nil {
48+
log.Fatal(err)
49+
}
50+
if _, err = f.Write([]byte(bom)); err != nil {
51+
log.Fatal(err)
52+
}
53+
if _, err = f.Write(buf); err != nil {
54+
log.Fatal(err)
55+
}
5756
}

main.go

+17-11
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func realMain(args []string, w io.Writer) int {
7979
dir := filepath.Dir(list)
8080
f, err := os.Open(list)
8181
if err != nil {
82-
log.Fatalf("%s: %s\n", list, err)
82+
return schemaError("%s: %s", list, err)
8383
}
8484
defer f.Close()
8585

@@ -93,7 +93,7 @@ func realMain(args []string, w io.Writer) int {
9393
docs = append(docs, glob(pattern)...)
9494
}
9595
if err := scanner.Err(); err != nil {
96-
log.Fatalf("%s: invalid file list: %s\n", list, err)
96+
return schemaError("%s: invalid file list: %s", list, err)
9797
}
9898
}
9999
if len(docs) == 0 {
@@ -104,13 +104,13 @@ func realMain(args []string, w io.Writer) int {
104104
sl := gojsonschema.NewSchemaLoader()
105105
schemaPath, err := filepath.Abs(*schemaFlag)
106106
if err != nil {
107-
log.Fatalf("%s: unable to convert to absolute path: %s\n", *schemaFlag, err)
107+
return schemaError("%s: unable to convert to absolute path: %s", *schemaFlag, err)
108108
}
109109
for _, ref := range refFlags {
110110
for _, p := range glob(ref) {
111111
absPath, err := filepath.Abs(p)
112112
if err != nil {
113-
log.Fatalf("%s: unable to convert to absolute path: %s\n", absPath, err)
113+
return schemaError("%s: unable to convert to absolute path: %s", absPath, err)
114114
}
115115

116116
if absPath == schemaPath {
@@ -119,22 +119,22 @@ func realMain(args []string, w io.Writer) int {
119119

120120
loader, err := jsonLoader(absPath)
121121
if err != nil {
122-
log.Fatalf("%s: unable to load schema ref: %s\n", *schemaFlag, err)
122+
return schemaError("%s: unable to load schema ref: %s", *schemaFlag, err)
123123
}
124124

125125
if err := sl.AddSchemas(loader); err != nil {
126-
log.Fatalf("%s: invalid schema: %s\n", p, err)
126+
return schemaError("%s: invalid schema: %s", p, err)
127127
}
128128
}
129129
}
130130

131131
schemaLoader, err := jsonLoader(schemaPath)
132132
if err != nil {
133-
log.Fatalf("%s: unable to load schema: %s\n", *schemaFlag, err)
133+
return schemaError("%s: unable to load schema: %s", *schemaFlag, err)
134134
}
135135
schema, err := sl.Compile(schemaLoader)
136136
if err != nil {
137-
log.Fatalf("%s: invalid schema: %s\n", *schemaFlag, err)
137+
return schemaError("%s: invalid schema: %s", *schemaFlag, err)
138138
}
139139

140140
// Validate the schema against each doc in parallel, limiting simultaneous
@@ -262,8 +262,8 @@ func jsonDecodeCharset(buf []byte) ([]byte, error) {
262262
func printUsage() {
263263
fmt.Fprintf(os.Stderr, `Usage: %s -s schema.(json|yml) [options] document.(json|yml) ...
264264
265-
yajsv validates JSON and YAML document(s) against a schema. One of three statuses are
266-
reported per document:
265+
yajsv validates JSON and YAML document(s) against a schema. One of three status
266+
results are reported per document:
267267
268268
pass: Document is valid relative to the schema
269269
fail: Document is invalid relative to the schema
@@ -273,7 +273,8 @@ func printUsage() {
273273
schema validation failure.
274274
275275
Sets the exit code to 1 on any failures, 2 on any errors, 3 on both, 4 on
276-
invalid usage. Otherwise, 0 is returned if everything passes validation.
276+
invalid usage, 5 on schema definition or file-list errors. Otherwise, 0 is
277+
returned if everything passes validation.
277278
278279
Options:
279280
@@ -288,6 +289,11 @@ func usageError(msg string) int {
288289
return 4
289290
}
290291

292+
func schemaError(format string, args ...interface{}) int {
293+
fmt.Fprintf(os.Stderr, format+"\n", args...)
294+
return 5
295+
}
296+
291297
// glob is a wrapper that also resolves `~` since we may be skipping
292298
// the shell expansion when single-quoting globs at the command line
293299
func glob(pattern string) []string {

main_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
package main
22

33
import (
4+
"fmt"
5+
"os"
46
"path/filepath"
57
"sort"
68
"strings"
79
"testing"
810
)
911

12+
func init() {
13+
// TODO: Cleanup this global monkey-patching
14+
devnull, err := os.Open(os.DevNull)
15+
if err != nil {
16+
panic(err)
17+
}
18+
os.Stderr = devnull
19+
}
20+
1021
func TestMain(t *testing.T) {
1122
tests := []struct {
1223
in string
1324
out []string
1425
exit int
1526
}{
1627
{
28+
"-s testdata/utf-16be_bom/schema.json testdata/utf-16le_bom/data-fail.yml",
29+
[]string{},
30+
5,
31+
}, {
1732
"-s testdata/utf-8/schema.yml testdata/utf-8/data-pass.yml",
1833
[]string{"testdata/utf-8/data-pass.yml: pass"},
1934
0,
@@ -89,3 +104,80 @@ func TestMain(t *testing.T) {
89104
})
90105
}
91106
}
107+
108+
func TestMatrix(t *testing.T) {
109+
// schema.{format} {encoding}{_bom}/data-{expect}.{format}
110+
type testcase struct {
111+
schemaEnc, schemaFmt string
112+
dataEnc, dataFmt, dataRes string
113+
allowBOM bool
114+
}
115+
116+
encodings := []string{"utf-8", "utf-16be", "utf-16le", "utf-8_bom", "utf-16be_bom", "utf-16le_bom"}
117+
formats := []string{"json", "yml"}
118+
results := []string{"pass", "fail", "error"}
119+
tests := []testcase{}
120+
121+
// poor mans cartesian product
122+
for _, senc := range encodings {
123+
for _, sfmt := range formats {
124+
for _, denc := range encodings {
125+
for _, dfmt := range formats {
126+
for _, dres := range results {
127+
tests = append(tests, testcase{senc, sfmt, denc, dfmt, dres, false})
128+
tests = append(tests, testcase{senc, sfmt, denc, dfmt, dres, true})
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
for _, tt := range tests {
136+
schemaBOM := strings.HasSuffix(tt.schemaEnc, "_bom")
137+
schema16 := strings.HasPrefix(tt.schemaEnc, "utf-16")
138+
dataBOM := strings.HasSuffix(tt.dataEnc, "_bom")
139+
data16 := strings.HasPrefix(tt.dataEnc, "utf-16")
140+
141+
schema := fmt.Sprintf("testdata/%s/schema.%s", tt.schemaEnc, tt.schemaFmt)
142+
data := fmt.Sprintf("testdata/%s/data-%s.%s", tt.dataEnc, tt.dataRes, tt.dataFmt)
143+
cmd := fmt.Sprintf("-s %s %s", schema, data)
144+
if tt.allowBOM {
145+
cmd = "-b " + cmd
146+
}
147+
148+
t.Run(cmd, func(t *testing.T) {
149+
want := 0
150+
switch {
151+
// Schema Errors (exit = 5)
152+
// - YAML w/out BOM for UTF-16
153+
// - JSON w/ BOM but missing allowBOM flag
154+
case tt.schemaFmt == "yml" && !schemaBOM && schema16:
155+
want = 5
156+
case tt.schemaFmt == "json" && schemaBOM && !tt.allowBOM:
157+
want = 5
158+
// Data Errors (exit = 2)
159+
// - YAML w/out BOM for UTF-16
160+
// - JSON w/ BOM but missing allowBOM flag
161+
// - standard malformed files (e.g. data-error)
162+
case tt.dataFmt == "yml" && !dataBOM && data16:
163+
want = 2
164+
case tt.dataFmt == "json" && dataBOM && !tt.allowBOM:
165+
want = 2
166+
case tt.dataRes == "error":
167+
want = 2
168+
// Data Failures
169+
case tt.dataRes == "fail":
170+
want = 1
171+
}
172+
173+
// TODO: Cleanup this global monkey-patching
174+
*bomFlag = tt.allowBOM
175+
176+
var w strings.Builder
177+
got := realMain(strings.Split(cmd, " "), &w)
178+
if got != want {
179+
t.Errorf("got(%d) != want(%d) bomflag %t", got, want, *bomFlag)
180+
}
181+
})
182+
}
183+
}

0 commit comments

Comments
 (0)