diff --git a/main.go b/main.go index 44ccc75a19d3343853759de956f7931ade0d96a8..8ee176722d202dd82842a37e424ba339d9566d12 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "bufio" "flag" "fmt" + "io" "io/ioutil" "log" "os" @@ -20,7 +21,7 @@ import ( ) var ( - version = "undefined" + version = "v1.3.0-dev" schemaFlag = flag.String("s", "", "primary JSON schema to validate against, required") quietFlag = flag.Bool("q", false, "quiet, only print validation failures and errors") versionFlag = flag.Bool("v", false, "print version and exit") @@ -37,13 +38,13 @@ func init() { func main() { log.SetFlags(0) - os.Exit(realMain(os.Args[1:])) + os.Exit(realMain(os.Args[1:], os.Stdout)) } -func realMain(args []string) int { +func realMain(args []string, w io.Writer) int { flag.CommandLine.Parse(args) if *versionFlag { - fmt.Println(version) + fmt.Fprintln(w, version) return 0 } if *schemaFlag == "" { @@ -70,7 +71,6 @@ func realMain(args []string) int { if !filepath.IsAbs(pattern) { pattern = filepath.Join(dir, pattern) } - docs = append(docs, glob(pattern)...) } if err := scanner.Err(); err != nil { @@ -89,9 +89,9 @@ func realMain(args []string) int { } for _, ref := range refFlags { for _, p := range glob(ref) { - absPath, absPathErr := filepath.Abs(p) - if absPathErr != nil { - log.Fatalf("%s: unable to convert to absolute path: %s\n", absPath, absPathErr) + absPath, err := filepath.Abs(p) + if err != nil { + log.Fatalf("%s: unable to convert to absolute path: %s\n", absPath, err) } if absPath == schemaPath { @@ -125,17 +125,17 @@ func realMain(args []string) int { failures := make([]string, 0) errors := make([]string, 0) for _, p := range docs { - //fmt.Println(p) wg.Add(1) go func(path string) { defer wg.Done() sem <- 0 defer func() { <-sem }() + loader, err := jsonLoader(path) if err != nil { - msg := fmt.Sprintf("%s: error: load doc %s\n", path, err) - fmt.Println(msg) + msg := fmt.Sprintf("%s: error: load doc: %s", path, err) + fmt.Fprintln(w, msg) errors = append(errors, msg) return } @@ -143,7 +143,7 @@ func realMain(args []string) int { switch { case err != nil: msg := fmt.Sprintf("%s: error: validate: %s", path, err) - fmt.Println(msg) + fmt.Fprintln(w, msg) errors = append(errors, msg) case !result.Valid(): @@ -152,11 +152,11 @@ func realMain(args []string) int { lines[i] = fmt.Sprintf("%s: fail: %s", path, desc) } msg := strings.Join(lines, "\n") - fmt.Println(msg) + fmt.Fprintln(w, msg) failures = append(failures, msg) case !*quietFlag: - fmt.Printf("%s: pass\n", path) + fmt.Fprintf(w, "%s: pass\n", path) } }(p) } @@ -165,12 +165,12 @@ func realMain(args []string) int { // Summarize results (e.g. errors) if !*quietFlag { if len(failures) > 0 { - fmt.Printf("%d of %d failed validation\n", len(failures), len(docs)) - fmt.Println(strings.Join(failures, "\n")) + fmt.Fprintf(w, "%d of %d failed validation\n", len(failures), len(docs)) + fmt.Fprintln(w, strings.Join(failures, "\n")) } if len(errors) > 0 { - fmt.Printf("%d of %d malformed documents\n", len(errors), len(docs)) - fmt.Println(strings.Join(errors, "\n")) + fmt.Fprintf(w, "%d of %d malformed documents\n", len(errors), len(docs)) + fmt.Fprintln(w, strings.Join(errors, "\n")) } } exit := 0 @@ -234,18 +234,14 @@ func glob(pattern string) []string { if err != nil { log.Fatal(err) } - universalPaths := make([]string, 0) paths, err := filepath.Glob(pattern) - for _, mypath := range paths { - universalPaths = append(universalPaths, filepath.ToSlash(mypath)) - } if err != nil { log.Fatal(err) } - if len(universalPaths) == 0 { + if len(paths) == 0 { log.Fatalf("%s: no such file or directory", pattern) } - return universalPaths + return paths } type stringFlags []string diff --git a/main_test.go b/main_test.go index 875ba19dc7967d15ff940e810beb2476a8f25733..a2883d1fe2d19fa91cd2315535b38d912c7a2d9f 100644 --- a/main_test.go +++ b/main_test.go @@ -1,118 +1,92 @@ -// +build windows !windows - package main import ( - "log" + "path/filepath" + "sort" + "strings" + "testing" ) -func ExampleMain_pass_ymlschema_ymldoc() { - exit := realMain([]string{"-s", "testdata/schema.yml", "testdata/data-pass.yml"}) - if exit != 0 { - log.Fatalf("exit: got %d, want 0", exit) - } - // Output: - // testdata/data-pass.yml: pass -} - -func ExampleMain_pass_jsonschema_ymldoc() { - exit := realMain([]string{"-s", "testdata/schema.json", "testdata/data-pass.yml"}) - if exit != 0 { - log.Fatalf("exit: got %d, want 0", exit) - } - // Output: - // testdata/data-pass.yml: pass -} - -func ExampleMain_pass_jsonschema_jsondoc() { - exit := realMain([]string{"-s", "testdata/schema.json", "testdata/data-pass.json"}) - if exit != 0 { - log.Fatalf("exit: got %d, want 0", exit) - } - // Output: - // testdata/data-pass.json: pass -} - -func ExampleMain_pass_ymlschema_jsondoc() { - exit := realMain([]string{"-s", "testdata/schema.yml", "testdata/data-pass.json"}) - if exit != 0 { - log.Fatalf("exit: got %d, want 0", exit) - } - // Output: - // testdata/data-pass.json: pass -} - -func ExampleMain_fail_ymlschema_ymldoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.yml", "testdata/data-fail.yml"}) - if exit != 1 { - log.Fatalf("exit: got %d, want 1", exit) - } - // Output: - // testdata/data-fail.yml: fail: (root): foo is required -} - -func ExampleMain_fail_jsonschema_ymldoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.json", "testdata/data-fail.yml"}) - if exit != 1 { - log.Fatalf("exit: got %d, want 1", exit) - } - // Output: - // testdata/data-fail.yml: fail: (root): foo is required -} - -func ExampleMain_fail_jsonschema_jsondoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.json", "testdata/data-fail.json"}) - if exit != 1 { - log.Fatalf("exit: got %d, want 1", exit) +func TestMain(t *testing.T) { + tests := []struct { + in string + out []string + exit int + } { + { + "-s testdata/schema.yml testdata/data-pass.yml", + []string{"testdata/data-pass.yml: pass"}, + 0, + }, { + "-s testdata/schema.json testdata/data-pass.yml", + []string{"testdata/data-pass.yml: pass"}, + 0, + }, { + "-s testdata/schema.json testdata/data-pass.json", + []string{"testdata/data-pass.json: pass"}, + 0, + }, { + "-s testdata/schema.yml testdata/data-pass.json", + []string{"testdata/data-pass.json: pass"}, + 0, + }, { + "-q -s testdata/schema.yml testdata/data-fail.yml", + []string{"testdata/data-fail.yml: fail: (root): foo is required"}, + 1, + }, { + "-q -s testdata/schema.json testdata/data-fail.yml", + []string{"testdata/data-fail.yml: fail: (root): foo is required"}, + 1, + }, { + "-q -s testdata/schema.json testdata/data-fail.json", + []string{"testdata/data-fail.json: fail: (root): foo is required"}, + 1, + }, { + "-q -s testdata/schema.yml testdata/data-fail.json", + []string{"testdata/data-fail.json: fail: (root): foo is required"}, + 1, + }, { + "-q -s testdata/schema.json testdata/data-error.json", + []string{"testdata/data-error.json: error: validate: invalid character 'o' in literal null (expecting 'u')"}, + 2, + }, { + "-q -s testdata/schema.yml testdata/data-error.yml", + []string{"testdata/data-error.yml: error: load doc: yaml: found unexpected end of stream"}, + 2, + }, { + "-q -s testdata/schema.json testdata/data-*.json", + []string{ + "testdata/data-fail.json: fail: (root): foo is required", + "testdata/data-error.json: error: validate: invalid character 'o' in literal null (expecting 'u')", + }, 3, + }, { + "-q -s testdata/schema.yml testdata/data-*.yml", + []string{ + "testdata/data-error.yml: error: load doc: yaml: found unexpected end of stream", + "testdata/data-fail.yml: fail: (root): foo is required", + }, 3, + }, } - // Output: - // testdata/data-fail.json: fail: (root): foo is required -} -func ExampleMain_fail_ymlschema_jsondoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.yml", "testdata/data-fail.json"}) - if exit != 1 { - log.Fatalf("exit: got %d, want 1", exit) - } - // Output: - // testdata/data-fail.json: fail: (root): foo is required -} + for _, tt := range tests { + in := strings.ReplaceAll(tt.in, "/", string(filepath.Separator)) + sort.Strings(tt.out) + out := strings.Join(tt.out, "\n") + out = strings.ReplaceAll(out, "/", string(filepath.Separator)) -func ExampleMain_error_jsonschema_jsondoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.json", "testdata/data-error.json"}) - if exit != 2 { - log.Fatalf("exit: got %d, want 2", exit) + t.Run(in, func(t *testing.T) { + var w strings.Builder + exit := realMain(strings.Split(in, " "), &w) + if exit != tt.exit { + t.Fatalf("exit: got %d, want %d", exit, tt.exit) + } + lines := strings.Split(w.String(), "\n") + sort.Strings(lines) + got := strings.Join(lines[1:], "\n") + if got != out { + t.Errorf("got\n%s\nwant\n%s", got, out) + } + }) } - // Output: - // testdata/data-error.json: error: validate: invalid character 'o' in literal null (expecting 'u') } -func ExampleMain_error_ymlschema_ymldoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.yml", "testdata/data-error.yml"}) - if exit != 2 { - log.Fatalf("exit: got %d, want 2", exit) - } - // Output: - // testdata/data-error.yml: error: load doc yaml: found unexpected end of stream -} - -func ExampleMain_glob_jsonschema_jsondoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.json", "testdata/data-*.json"}) - if exit != 3 { - log.Fatalf("exit: got %d, want 3", exit) - } - // Unordered output: - // testdata/data-error.json: error: validate: invalid character 'o' in literal null (expecting 'u') - // testdata/data-fail.json: fail: (root): foo is required -} - -func ExampleMain_glob_ymlschema_ymldoc() { - exit := realMain([]string{"-q", "-s", "testdata/schema.yml", "testdata/data-*.yml"}) - if exit != 3 { - log.Fatalf("exit: got %d, want 3", exit) - } - // Unordered output: - // testdata/data-fail.yml: fail: (root): foo is required - // - // testdata/data-error.yml: error: load doc yaml: found unexpected end of stream -}