package main import ( "encoding/json" "flag" "fmt" "html/template" "log" "net/http" "os" "path" "sort" "strconv" "strings" "text/tabwriter" ) var ( verbose = flag.Bool("verbose", false, "be verbose") web = flag.String("http", "", "open web browser instead") ) const ( statusOK = "OK" statusInformational = "INFORMATIONAL" statusUnimplemented = "UNIMPLEMENTED" statusNonStrict = "NON-STRICT" statusUnclean = "UNCLEAN" statusFailed = "FAILED" ) func failing(behavior string) bool { switch behavior { // case statusUnclean, statusFailed, statusNonStrict: // we should probably fix the nonstrict as well at some point case statusUnclean, statusFailed: return true default: return false } } type statusCounter struct { Total int OK int Informational int Unimplemented int NonStrict int Unclean int Failed int } func (c *statusCounter) Inc(s string) { c.Total++ switch s { case statusOK: c.OK++ case statusInformational: c.Informational++ case statusNonStrict: c.NonStrict++ case statusUnimplemented: c.Unimplemented++ case statusUnclean: c.Unclean++ case statusFailed: c.Failed++ default: panic(fmt.Sprintf("unexpected status %q", s)) } } func main() { log.SetFlags(0) flag.Parse() if flag.NArg() < 1 { log.Fatalf("Usage: %s [options] ", os.Args[0]) } base := path.Dir(flag.Arg(0)) if addr := *web; addr != "" { http.HandleFunc("/", handlerIndex()) http.Handle("/report/", http.StripPrefix("/report/", http.FileServer(http.Dir(base)), )) log.Fatal(http.ListenAndServe(addr, nil)) return } var report report if err := decodeFile(os.Args[1], &report); err != nil { log.Fatal(err) } var servers []string for s := range report { servers = append(servers, s) } sort.Strings(servers) var ( failed bool ) tw := tabwriter.NewWriter(os.Stderr, 0, 4, 1, ' ', 0) for _, server := range servers { var ( srvFailed bool hdrWritten bool counter statusCounter ) var cases []string for id := range report[server] { cases = append(cases, id) } sortBySegment(cases) for _, id := range cases { c := report[server][id] var r entryReport err := decodeFile(path.Join(base, c.ReportFile), &r) if err != nil { log.Fatal(err) } counter.Inc(c.Behavior) bad := failing(c.Behavior) if bad { srvFailed = true failed = true } if *verbose || bad { if !hdrWritten { hdrWritten = true n, _ := fmt.Fprintf(os.Stderr, "AGENT %q\n", server) fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) } fmt.Fprintf(tw, "%s\t%s\t%s\n", server, id, c.Behavior) } if bad { fmt.Fprintf(tw, "\tdesc:\t%s\n", r.Description) fmt.Fprintf(tw, "\texp: \t%s\n", r.Expectation) fmt.Fprintf(tw, "\tact: \t%s\n", r.Result) } } if hdrWritten { fmt.Fprint(tw, "\n") } var status string if srvFailed { status = statusFailed } else { status = statusOK } n, _ := fmt.Fprintf(tw, "AGENT %q SUMMARY (%s)\n", server, status) fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) fmt.Fprintf(tw, "TOTAL:\t%d\n", counter.Total) fmt.Fprintf(tw, "%s:\t%d\n", statusOK, counter.OK) fmt.Fprintf(tw, "%s:\t%d\n", statusInformational, counter.Informational) fmt.Fprintf(tw, "%s:\t%d\n", statusUnimplemented, counter.Unimplemented) fmt.Fprintf(tw, "%s:\t%d\n", statusNonStrict, counter.NonStrict) fmt.Fprintf(tw, "%s:\t%d\n", statusUnclean, counter.Unclean) fmt.Fprintf(tw, "%s:\t%d\n", statusFailed, counter.Failed) fmt.Fprint(tw, "\n") tw.Flush() } var rc int if failed { rc = 1 fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusFailed) } else { fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusOK) } tw.Flush() os.Exit(rc) } type report map[string]server type server map[string]entry type entry struct { Behavior string `json:"behavior"` BehaviorClose string `json:"behaviorClose"` Duration int `json:"duration"` RemoveCloseCode int `json:"removeCloseCode"` ReportFile string `json:"reportFile"` } type entryReport struct { Description string `json:"description"` Expectation string `json:"expectation"` Result string `json:"result"` Duration int `json:"duration"` } func decodeFile(path string, x interface{}) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() d := json.NewDecoder(f) return d.Decode(x) } func compareBySegment(a, b string) int { as := strings.Split(a, ".") bs := strings.Split(b, ".") for i := 0; i < min(len(as), len(bs)); i++ { ax := mustInt(as[i]) bx := mustInt(bs[i]) if ax == bx { continue } return ax - bx } return len(b) - len(a) } func mustInt(s string) int { const bits = 32 << (^uint(0) >> 63) x, err := strconv.ParseInt(s, 10, bits) if err != nil { panic(err) } return int(x) } func min(a, b int) int { if a < b { return a } return b } func handlerIndex() func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if *verbose { log.Printf("reqeust to %s", r.URL) } if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) return } if err := index.Execute(w, nil); err != nil { w.WriteHeader(http.StatusInternalServerError) log.Fatal(err) return } } } var index = template.Must(template.New("").Parse(`

Welcome to WebSocket test server!

Ready to Autobahn!

Reports `))