github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/autobahn/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "html/template" 8 "log" 9 "net/http" 10 "os" 11 "path" 12 "sort" 13 "strconv" 14 "strings" 15 "text/tabwriter" 16 ) 17 18 var ( 19 verbose = flag.Bool("verbose", false, "be verbose") 20 web = flag.String("http", "", "open web browser instead") 21 ) 22 23 const ( 24 statusOK = "OK" 25 statusInformational = "INFORMATIONAL" 26 statusUnimplemented = "UNIMPLEMENTED" 27 statusNonStrict = "NON-STRICT" 28 statusUnclean = "UNCLEAN" 29 statusFailed = "FAILED" 30 ) 31 32 func failing(behavior string) bool { 33 switch behavior { 34 case statusUnclean, statusFailed, statusNonStrict: 35 return true 36 default: 37 return false 38 } 39 } 40 41 type statusCounter struct { 42 Total int 43 OK int 44 Informational int 45 Unimplemented int 46 NonStrict int 47 Unclean int 48 Failed int 49 } 50 51 func (c *statusCounter) Inc(s string) { 52 c.Total++ 53 switch s { 54 case statusOK: 55 c.OK++ 56 case statusInformational: 57 c.Informational++ 58 case statusNonStrict: 59 c.NonStrict++ 60 case statusUnimplemented: 61 c.Unimplemented++ 62 case statusUnclean: 63 c.Unclean++ 64 case statusFailed: 65 c.Failed++ 66 default: 67 panic(fmt.Sprintf("unexpected status %q", s)) 68 } 69 } 70 71 func main() { 72 log.SetFlags(0) 73 flag.Parse() 74 75 if flag.NArg() < 1 { 76 log.Fatalf("Usage: %s [options] <report-path>", os.Args[0]) 77 } 78 79 base := path.Dir(flag.Arg(0)) 80 81 if addr := *web; addr != "" { 82 http.HandleFunc("/", handlerIndex()) 83 http.Handle("/report/", http.StripPrefix("/report/", 84 http.FileServer(http.Dir(base)), 85 )) 86 log.Fatal(http.ListenAndServe(addr, nil)) 87 return 88 } 89 90 var report report 91 if err := decodeFile(os.Args[1], &report); err != nil { 92 log.Fatal(err) 93 } 94 95 var servers []string 96 for s := range report { 97 servers = append(servers, s) 98 } 99 sort.Strings(servers) 100 101 var ( 102 failed bool 103 ) 104 tw := tabwriter.NewWriter(os.Stderr, 0, 4, 1, ' ', 0) 105 for _, server := range servers { 106 var ( 107 srvFailed bool 108 hdrWritten bool 109 counter statusCounter 110 ) 111 112 var cases []string 113 for id := range report[server] { 114 cases = append(cases, id) 115 } 116 sortBySegment(cases) 117 for _, id := range cases { 118 c := report[server][id] 119 120 var r entryReport 121 err := decodeFile(path.Join(base, c.ReportFile), &r) 122 if err != nil { 123 log.Fatal(err) 124 } 125 counter.Inc(c.Behavior) 126 bad := failing(c.Behavior) 127 if bad { 128 srvFailed = true 129 failed = true 130 } 131 if *verbose || bad { 132 if !hdrWritten { 133 hdrWritten = true 134 n, _ := fmt.Fprintf(os.Stderr, "AGENT %q\n", server) 135 fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) 136 } 137 fmt.Fprintf(tw, "%s\t%s\t%s\n", server, id, c.Behavior) 138 } 139 if bad { 140 fmt.Fprintf(tw, "\tdesc:\t%s\n", r.Description) 141 fmt.Fprintf(tw, "\texp: \t%s\n", r.Expectation) 142 fmt.Fprintf(tw, "\tact: \t%s\n", r.Result) 143 } 144 } 145 if hdrWritten { 146 fmt.Fprint(tw, "\n") 147 } 148 var status string 149 if srvFailed { 150 status = statusFailed 151 } else { 152 status = statusOK 153 } 154 n, _ := fmt.Fprintf(tw, "AGENT %q SUMMARY (%s)\n", server, status) 155 fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) 156 157 fmt.Fprintf(tw, "TOTAL:\t%d\n", counter.Total) 158 fmt.Fprintf(tw, "%s:\t%d\n", statusOK, counter.OK) 159 fmt.Fprintf(tw, "%s:\t%d\n", statusInformational, counter.Informational) 160 fmt.Fprintf(tw, "%s:\t%d\n", statusUnimplemented, counter.Unimplemented) 161 fmt.Fprintf(tw, "%s:\t%d\n", statusNonStrict, counter.NonStrict) 162 fmt.Fprintf(tw, "%s:\t%d\n", statusUnclean, counter.Unclean) 163 fmt.Fprintf(tw, "%s:\t%d\n", statusFailed, counter.Failed) 164 fmt.Fprint(tw, "\n") 165 tw.Flush() 166 } 167 var rc int 168 if failed { 169 rc = 1 170 fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusFailed) 171 } else { 172 fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusOK) 173 } 174 175 tw.Flush() 176 os.Exit(rc) 177 } 178 179 type spec struct { 180 OutDir string `json:"outdir"` 181 } 182 183 type report map[string]server 184 185 type server map[string]entry 186 187 type entry struct { 188 Behavior string `json:"behavior"` 189 BehaviorClose string `json:"behaviorClose"` 190 Duration int `json:"duration"` 191 RemoveCloseCode int `json:"removeCloseCode"` 192 ReportFile string `json:"reportFile"` 193 } 194 195 type entryReport struct { 196 Description string `json:"description"` 197 Expectation string `json:"expectation"` 198 Result string `json:"result"` 199 Duration int `json:"duration"` 200 } 201 202 func decodeFile(path string, x interface{}) error { 203 f, err := os.Open(path) 204 if err != nil { 205 return err 206 } 207 defer f.Close() 208 209 d := json.NewDecoder(f) 210 return d.Decode(x) 211 } 212 213 func compareBySegment(a, b string) int { 214 as := strings.Split(a, ".") 215 bs := strings.Split(b, ".") 216 for i := 0; i < min(len(as), len(bs)); i++ { 217 ax := mustInt(as[i]) 218 bx := mustInt(bs[i]) 219 if ax == bx { 220 continue 221 } 222 return ax - bx 223 } 224 return len(b) - len(a) 225 } 226 227 func mustInt(s string) int { 228 const bits = 32 << (^uint(0) >> 63) 229 x, err := strconv.ParseInt(s, 10, bits) 230 if err != nil { 231 panic(err) 232 } 233 return int(x) 234 } 235 236 func min(a, b int) int { 237 if a < b { 238 return a 239 } 240 return b 241 } 242 243 func handlerIndex() func(w http.ResponseWriter, r *http.Request) { 244 return func(w http.ResponseWriter, r *http.Request) { 245 if *verbose { 246 log.Printf("reqeust to %s", r.URL) 247 } 248 if r.URL.Path != "/" { 249 w.WriteHeader(http.StatusNotFound) 250 return 251 } 252 if err := index.Execute(w, nil); err != nil { 253 w.WriteHeader(http.StatusInternalServerError) 254 log.Fatal(err) 255 return 256 } 257 } 258 } 259 260 var index = template.Must(template.New("").Parse(` 261 <html> 262 <body> 263 <h1>Welcome to WebSocket test server!</h1> 264 <h4>Ready to Autobahn!</h4> 265 <a href="/report">Reports</a> 266 </body> 267 </html> 268 `))