Skip to content

Commit 158c690

Browse files
authored
Initial import from go-httpbin (#1)
Initial import of websocket library code and commit history from these go-httpbin pull requests: - mccutchen/go-httpbin#155 - mccutchen/go-httpbin#156 - mccutchen/go-httpbin#161
1 parent 9d2be89 commit 158c690

File tree

8 files changed

+1298
-22
lines changed

8 files changed

+1298
-22
lines changed

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ test:
1515
# based on codecov.io's documentation:
1616
# https://github.com/codecov/example-go/blob/b85638743b972bd0bd2af63421fe513c6f968930/README.md
1717
testci:
18-
go test $(TEST_ARGS) $(COVERAGE_ARGS) ./...
18+
AUTOBAHN_TESTS=1 go test $(TEST_ARGS) $(COVERAGE_ARGS) ./...
1919
.PHONY: testci
2020

2121
testcover: testci
2222
go tool cover -html=$(COVERAGE_PATH)
2323
.PHONY: testcover
2424

25+
# Run the autobahn fuzzingclient test suite
26+
testautobahn:
27+
AUTOBAHN_TESTS=1 AUTOBAHN_OPEN_REPORT=1 go test -v -run ^TestWebSocketServer$$ $(TEST_ARGS) ./...
28+
.PHONY: autobahntests
29+
30+
2531
lint:
2632
test -z "$$(gofmt -d -s -e .)" || (echo "Error: gofmt failed"; gofmt -d -s -e . ; exit 1)
2733
go vet ./...

README.md

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
1-
# go-pkg-template
1+
# github.com/mccutchen/websocket
22

3-
[![Documentation](https://pkg.go.dev/badge/github.com/mccutchen/go-pkg-template)](https://pkg.go.dev/github.com/mccutchen/go-pkg-template)
4-
[![Build status](https://github.com/mccutchen/go-pkg-template/actions/workflows/test.yaml/badge.svg)](https://github.com/mccutchen/go-pkg-template/actions/workflows/test.yaml)
5-
[![Code coverage](https://codecov.io/gh/mccutchen/go-pkg-template/branch/main/graph/badge.svg)](https://codecov.io/gh/mccutchen/go-pkg-template)
6-
[![Go report card](http://goreportcard.com/badge/github.com/mccutchen/go-pkg-template)](https://goreportcard.com/report/github.com/mccutchen/go-pkg-template)
3+
[![Documentation](https://pkg.go.dev/badge/github.com/mccutchen/websocket)](https://pkg.go.dev/github.com/mccutchen/websocket)
4+
[![Build status](https://github.com/mccutchen/websocket/actions/workflows/test.yaml/badge.svg)](https://github.com/mccutchen/websocket/actions/workflows/test.yaml)
5+
[![Code coverage](https://codecov.io/gh/mccutchen/websocket/branch/main/graph/badge.svg)](https://codecov.io/gh/mccutchen/websocket)
6+
[![Go report card](http://goreportcard.com/badge/github.com/mccutchen/websocket)](https://goreportcard.com/report/github.com/mccutchen/websocket)
77

8-
Template repo for Go packages/libraries
9-
10-
TODO:
11-
- Replace go-pkg-template with new repo name
12-
- Add high level description
13-
- Add usage examples
14-
- Set up branch protection rules
15-
- Require linear history
16-
- Require pull requests
17-
- Require lint/test CI checks to pass
18-
- Set up security scanning
19-
- Maybe:
20-
- Update go version in go.mod
21-
- Update action versions in .github/workflows/*.yaml
8+
A minimal Golang implementation of the websocket protocol, extracted from [mccutchen/go-httpbin][].
229

2310
## Usage
2411

@@ -29,3 +16,5 @@ TK
2916
```bash
3017
make test
3118
```
19+
20+
[mccutchen/go-httpbin]: https://github.com/mccutchen/go-httpbin

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/mccutchen/go-pkg-template
1+
module github.com/mccutchen/websocket
22

3-
go 1.23.0
3+
go 1.21.0

internal/testing/assert/assert.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Package assert implements common assertions used in go-httbin's unit tests.
2+
package assert
3+
4+
import (
5+
"fmt"
6+
"net/http"
7+
"reflect"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/mccutchen/websocket/internal/testing/must"
13+
)
14+
15+
// Equal asserts that two values are equal.
16+
func Equal[T comparable](t *testing.T, got, want T, msg string, arg ...any) {
17+
t.Helper()
18+
if got != want {
19+
if msg == "" {
20+
msg = "expected values to match"
21+
}
22+
msg = fmt.Sprintf(msg, arg...)
23+
t.Fatalf("%s:\nwant: %#v\n got: %#v", msg, want, got)
24+
}
25+
}
26+
27+
// DeepEqual asserts that two values are deeply equal.
28+
func DeepEqual[T any](t *testing.T, got, want T, msg string, arg ...any) {
29+
t.Helper()
30+
if !reflect.DeepEqual(got, want) {
31+
if msg == "" {
32+
msg = "expected values to match"
33+
}
34+
msg = fmt.Sprintf(msg, arg...)
35+
t.Fatalf("%s:\nwant: %#v\n got: %#v", msg, want, got)
36+
}
37+
}
38+
39+
// NilError asserts that an error is nil.
40+
func NilError(t *testing.T, err error) {
41+
t.Helper()
42+
if err != nil {
43+
t.Fatalf("expected nil error, got %s (%T)", err, err)
44+
}
45+
}
46+
47+
// Error asserts that an error is not nil.
48+
func Error(t *testing.T, got, expected error) {
49+
t.Helper()
50+
if got != expected {
51+
if got != nil && expected != nil {
52+
if got.Error() == expected.Error() {
53+
return
54+
}
55+
}
56+
t.Fatalf("expected error %v, got %v", expected, got)
57+
}
58+
}
59+
60+
// StatusCode asserts that a response has a specific status code.
61+
func StatusCode(t *testing.T, resp *http.Response, code int) {
62+
t.Helper()
63+
if resp.StatusCode != code {
64+
t.Fatalf("expected status code %d, got %d", code, resp.StatusCode)
65+
}
66+
if resp.StatusCode >= 400 {
67+
// Ensure our error responses are never served as HTML, so that we do
68+
// not need to worry about XSS or other attacks in error responses.
69+
if ct := resp.Header.Get("Content-Type"); !isSafeContentType(ct) {
70+
t.Errorf("HTTP %s error served with dangerous content type: %s", resp.Status, ct)
71+
}
72+
}
73+
}
74+
75+
func isSafeContentType(ct string) bool {
76+
return strings.HasPrefix(ct, "application/json") || strings.HasPrefix(ct, "text/plain") || strings.HasPrefix(ct, "application/octet-stream")
77+
}
78+
79+
// Header asserts that a header key has a specific value in a response.
80+
func Header(t *testing.T, resp *http.Response, key, want string) {
81+
t.Helper()
82+
got := resp.Header.Get(key)
83+
if want != got {
84+
t.Fatalf("expected header %s=%#v, got %#v", key, want, got)
85+
}
86+
}
87+
88+
// ContentType asserts that a response has a specific Content-Type header
89+
// value.
90+
func ContentType(t *testing.T, resp *http.Response, contentType string) {
91+
t.Helper()
92+
Header(t, resp, "Content-Type", contentType)
93+
}
94+
95+
// Contains asserts that needle is found in the given string.
96+
func Contains(t *testing.T, s string, needle string, description string) {
97+
t.Helper()
98+
if !strings.Contains(s, needle) {
99+
t.Fatalf("expected string %q in %s %q", needle, description, s)
100+
}
101+
}
102+
103+
// BodyContains asserts that a response body contains a specific substring.
104+
func BodyContains(t *testing.T, resp *http.Response, needle string) {
105+
t.Helper()
106+
body := must.ReadAll(t, resp.Body)
107+
Contains(t, body, needle, "body")
108+
}
109+
110+
// BodyEquals asserts that a response body is equal to a specific string.
111+
func BodyEquals(t *testing.T, resp *http.Response, want string) {
112+
t.Helper()
113+
got := must.ReadAll(t, resp.Body)
114+
Equal(t, got, want, "incorrect response body")
115+
}
116+
117+
// BodySize asserts that a response body is a specific size.
118+
func BodySize(t *testing.T, resp *http.Response, want int) {
119+
t.Helper()
120+
got := must.ReadAll(t, resp.Body)
121+
Equal(t, len(got), want, "incorrect response body size")
122+
}
123+
124+
// DurationRange asserts that a duration is within a specific range.
125+
func DurationRange(t *testing.T, got, minVal, maxVal time.Duration) {
126+
t.Helper()
127+
if got < minVal || got > maxVal {
128+
t.Fatalf("expected duration between %s and %s, got %s", minVal, maxVal, got)
129+
}
130+
}
131+
132+
type number interface {
133+
~int64 | ~float64
134+
}
135+
136+
// RoughlyEqual asserts that a numeric value is within a certain tolerance.
137+
func RoughlyEqual[T number](t *testing.T, got, want T, epsilon T) {
138+
t.Helper()
139+
if got < want-epsilon || got > want+epsilon {
140+
t.Fatalf("expected value between %v and %v, got %v", want-epsilon, want+epsilon, got)
141+
}
142+
}

internal/testing/must/must.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Package must implements helper functions for testing to eliminate some error
2+
// checking boilerplate.
3+
package must
4+
5+
import (
6+
"encoding/json"
7+
"io"
8+
"net/http"
9+
"testing"
10+
"time"
11+
)
12+
13+
// DoReq makes an HTTP request and fails the test if there is an error.
14+
func DoReq(t *testing.T, client *http.Client, req *http.Request) *http.Response {
15+
t.Helper()
16+
start := time.Now()
17+
resp, err := client.Do(req)
18+
if err != nil {
19+
t.Fatalf("error making HTTP request: %s %s: %s", req.Method, req.URL, err)
20+
}
21+
t.Logf("HTTP request: %s %s => %s (%s)", req.Method, req.URL, resp.Status, time.Since(start))
22+
return resp
23+
}
24+
25+
// ReadAll reads all bytes from an io.Reader and fails the test if there is an
26+
// error.
27+
func ReadAll(t *testing.T, r io.Reader) string {
28+
t.Helper()
29+
body, err := io.ReadAll(r)
30+
if err != nil {
31+
t.Fatalf("error reading: %s", err)
32+
}
33+
if rc, ok := r.(io.ReadCloser); ok {
34+
rc.Close()
35+
}
36+
return string(body)
37+
}
38+
39+
// Unmarshal unmarshals JSON from an io.Reader into a value and fails the test
40+
// if there is an error.
41+
func Unmarshal[T any](t *testing.T, r io.Reader) T {
42+
t.Helper()
43+
var v T
44+
if err := json.NewDecoder(r).Decode(&v); err != nil {
45+
t.Fatal(err)
46+
}
47+
return v
48+
}

0 commit comments

Comments
 (0)