266 lines
5.4 KiB
Go
Raw Normal View History

2021-12-04 16:42:11 +00:00
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] <report-path>", 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(`
<html>
<body>
<h1>Welcome to WebSocket test server!</h1>
<h4>Ready to Autobahn!</h4>
<a href="/report">Reports</a>
</body>
</html>
`))