diff --git a/.github/workflows/continuous_integration.yaml b/.github/workflows/continuous_integration.yaml new file mode 100644 index 0000000000000000000000000000000000000000..96f3ebceb88bf90edd8d92c5539f0a8cd6725332 --- /dev/null +++ b/.github/workflows/continuous_integration.yaml @@ -0,0 +1,53 @@ +name: CI + +on: [pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: '1.14' + - name: Checkout + uses: actions/checkout@v2 + - name: Lint + run: make lint + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: '1.14' + - name: Checkout + uses: actions/checkout@v2 + - name: Test + run: make testci + - name: Code coverage + uses: codecov/codecov-action@v1 + with: + file: ./coverage.txt + + regression_test: + name: Regression Tests + runs-on: ubuntu-latest + strategy: + matrix: + go_version: + - '1.11' + - '1.12' + - '1.13' + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{matrix.go_version}} + - name: Checkout + uses: actions/checkout@v2 + - name: Test + run: make test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2176f307c4b7862535cfa74513683de328e35bbb..0000000000000000000000000000000000000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -language: go -go: - - '1.11' - - '1.12' - - '1.13' - -script: - - make lint - - make testci - -after_success: - # Upload test coverage results to codecov.io - # https://github.com/codecov/example-go/blob/b85638743b972bd0bd2af63421fe513c6f968930/README.md - - bash <(curl -s https://codecov.io/bash) - -# With this filter, we aim to restrict Travis's "Build Pushes" feature to build -# only pushes to master, while allowing the "Build Pull Requests" to build all -# incoming pull requests without redundant double-builds. -# -# This is confusing on Travis's end; this captures the exact problem we're -# trying to solve: -# https://stackoverflow.com/a/31882307 -branches: - only: - - master - -notifications: - email: false diff --git a/Makefile b/Makefile index b2759bd15a935d420a5e88025aba2c681c48c1ba..2ab6c8b7bfd6a470fad385febb940e4f35a5ec21 100644 --- a/Makefile +++ b/Makefile @@ -16,14 +16,16 @@ COVERAGE_PATH ?= coverage.txt COVERAGE_ARGS ?= -covermode=atomic -coverprofile=$(COVERAGE_PATH) TEST_ARGS ?= -race -GENERATED_ASSETS_PATH := httpbin/assets/assets.go - -BIN_DIR := $(GOPATH)/bin -GOLINT := $(BIN_DIR)/golint -GOBINDATA := $(BIN_DIR)/go-bindata +# Tool dependencies +TOOL_BIN_DIR ?= $(shell go env GOPATH)/bin +TOOL_GOBINDATA := $(TOOL_BIN_DIR)/go-bindata +TOOL_GOLINT := $(TOOL_BIN_DIR)/golint +TOOL_STATICCHECK := $(TOOL_BIN_DIR)/staticcheck GO_SOURCES = $(wildcard **/*.go) +GENERATED_ASSETS_PATH := httpbin/assets/assets.go + # ============================================================================= # build # ============================================================================= @@ -38,8 +40,8 @@ assets: $(GENERATED_ASSETS_PATH) clean: rm -rf $(DIST_PATH) $(COVERAGE_PATH) -$(GENERATED_ASSETS_PATH): $(GOBINDATA) static/* - $(GOBINDATA) -o $(GENERATED_ASSETS_PATH) -pkg=assets -prefix=static static +$(GENERATED_ASSETS_PATH): $(TOOL_GOBINDATA) static/* + $(TOOL_GOBINDATA) -o $(GENERATED_ASSETS_PATH) -pkg=assets -prefix=static static # reformat generated code gofmt -s -w $(GENERATED_ASSETS_PATH) # dumb hack to make generate code lint correctly @@ -63,10 +65,11 @@ testci: testcover: testci go tool cover -html=$(COVERAGE_PATH) -lint: $(GOLINT) - test -z "$$(gofmt -d -s -e .)" || (gofmt -d -s -e . ; exit 1) - $(GOLINT) -set_exit_status ./... +lint: $(TOOL_GOLINT) $(TOOL_STATICCHECK) + test -z "$$(gofmt -d -s -e .)" || (echo "Error: gofmt failed"; gofmt -d -s -e . ; exit 1) go vet ./... + $(TOOL_GOLINT) -set_exit_status ./... + $(TOOL_STATICCHECK) ./... # ============================================================================= @@ -94,18 +97,16 @@ imagepush: image # ============================================================================= # dependencies +# +# Deps are installed outside of working dir to avoid polluting go modules # ============================================================================= -deps: $(GOLINT) $(GOBINDATA) +deps: $(TOOL_GOBINDATA) $(TOOL_GOLINT) $(TOOL_STATICCHECK) -# Can't install from working dir because of go mod issues: -# -# go get -u github.com/kevinburke/go-bindata/... -# go: finding github.com/kevinburke/go-bindata/... latest -# go get github.com/kevinburke/go-bindata/...: no matching versions for query "latest" -# -# So we get out of the go modules path to install. -$(GOBINDATA): +$(TOOL_GOBINDATA): cd /tmp && go get -u github.com/kevinburke/go-bindata/... -$(GOLINT): - go get -u golang.org/x/lint/golint +$(TOOL_GOLINT): + cd /tmp && go get -u golang.org/x/lint/golint + +$(TOOL_STATICCHECK): + cd /tmp && go get -u honnef.co/go/tools/cmd/staticcheck diff --git a/httpbin/digest/digest_test.go b/httpbin/digest/digest_test.go index dc25086eee65468dc544bae1effe0328a41496cf..97d55ec2eeeb7530d535fa4f1624237f568abc63 100644 --- a/httpbin/digest/digest_test.go +++ b/httpbin/digest/digest_test.go @@ -13,11 +13,6 @@ const ( exampleUsername = "Mufasa" examplePassword = "Circle Of Life" - exampleChallenge string = `Digest realm="testrealm@host.com", - qop="auth,auth-int", - nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", - opaque="5ccc069c403ebaf9f0171e9517f40e41"` - exampleAuthorization string = `Digest username="Mufasa", realm="testrealm@host.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", diff --git a/httpbin/handlers.go b/httpbin/handlers.go index 5828599b046eb2aedadbad7158543d7c546b54c2..8e0d068f3a8ba76a95e5a0d701e4f5052dd33eeb 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -228,7 +228,7 @@ func (h *HTTPBin) ResponseHeaders(w http.ResponseWriter, r *http.Request) { args := r.URL.Query() for k, vs := range args { for _, v := range vs { - w.Header().Add(http.CanonicalHeaderKey(k), v) + w.Header().Add(k, v) } } body, _ := json.Marshal(args) diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index e554695e8b10de4fe785738cf387b8b9f49a281f..7873586a4ad9fde5cd2a8a307860ee6560427946 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -15,7 +15,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "reflect" "regexp" "strconv" @@ -31,7 +30,7 @@ const alphanumLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012 var app = New( WithMaxBodySize(maxBodySize), WithMaxDuration(maxDuration), - WithObserver(StdLogObserver(log.New(os.Stderr, "", 0))), + WithObserver(StdLogObserver(log.New(ioutil.Discard, "", 0))), ) var handler = app.Handler() @@ -216,7 +215,7 @@ func TestHEAD(t *testing.T) { assertStatusCode(t, w, 200) assertBodyEquals(t, w, "") - contentLengthStr := w.HeaderMap.Get("Content-Length") + contentLengthStr := w.Header().Get("Content-Length") if contentLengthStr == "" { t.Fatalf("missing Content-Length header in response") } @@ -772,7 +771,7 @@ func TestResponseHeaders__OK(t *testing.T) { assertContentType(t, w, jsonContentType) for k, expectedValues := range headers { - values, ok := w.HeaderMap[k] + values, ok := w.Header()[k] if !ok { t.Fatalf("expected header %s in response headers", k) } @@ -1021,7 +1020,7 @@ func TestDeleteCookies(t *testing.T) { for _, c := range w.Result().Cookies() { if c.Name == toDelete { - if time.Now().Sub(c.Expires) < (24*365-1)*time.Hour { + if time.Since(c.Expires) < (24*365-1)*time.Hour { t.Fatalf("expected cookie %s to be deleted; got %#v", toDelete, c) } } @@ -1248,7 +1247,7 @@ func TestGzip(t *testing.T) { assertHeader(t, w, "Content-Encoding", "gzip") assertStatusCode(t, w, http.StatusOK) - zippedContentLengthStr := w.HeaderMap.Get("Content-Length") + zippedContentLengthStr := w.Header().Get("Content-Length") if zippedContentLengthStr == "" { t.Fatalf("missing Content-Length header in response") } @@ -1292,7 +1291,7 @@ func TestDeflate(t *testing.T) { assertHeader(t, w, "Content-Encoding", "deflate") assertStatusCode(t, w, http.StatusOK) - contentLengthHeader := w.HeaderMap.Get("Content-Length") + contentLengthHeader := w.Header().Get("Content-Length") if contentLengthHeader == "" { t.Fatalf("missing Content-Length header in response") } @@ -1415,7 +1414,7 @@ func TestDelay(t *testing.T) { w := httptest.NewRecorder() handler.ServeHTTP(w, r) - elapsed := time.Now().Sub(start) + elapsed := time.Since(start) assertStatusCode(t, w, http.StatusOK) assertHeader(t, w, "Content-Type", jsonContentType) @@ -1515,7 +1514,7 @@ func TestDrip(t *testing.T) { w := httptest.NewRecorder() handler.ServeHTTP(w, r) - elapsed := time.Now().Sub(start) + elapsed := time.Since(start) assertHeader(t, w, "Content-Type", "application/octet-stream") assertStatusCode(t, w, test.code) @@ -1676,7 +1675,7 @@ func TestRange(t *testing.T) { w := httptest.NewRecorder() handler.ServeHTTP(w, r) - t.Logf("headers = %v", w.HeaderMap) + t.Logf("headers = %v", w.Header()) assertStatusCode(t, w, http.StatusPartialContent) assertHeader(t, w, "ETag", "range26") assertHeader(t, w, "Content-Length", "5") @@ -2256,19 +2255,19 @@ func TestBase64(t *testing.T) { }, { "/base64/", - "No input data", + "no input data", }, { "/base64/decode/", - "No input data", + "no input data", }, { "/base64/decode/dmFsaWRfYmFzZTY0X2VuY29kZWRfc3RyaW5n/extra", - "Invalid URL", + "invalid URL", }, { "/base64/unknown/dmFsaWRfYmFzZTY0X2VuY29kZWRfc3RyaW5n", - "Invalid operation: unknown", + "invalid operation: unknown", }, } diff --git a/httpbin/helpers.go b/httpbin/helpers.go index fe62be345069fdec5f985de34f2696442d45d914..3b27f9810b41658c7cb13c6e1ceca117bb19d50f 100644 --- a/httpbin/helpers.go +++ b/httpbin/helpers.go @@ -262,7 +262,7 @@ func newBase64Helper(path string) (*base64Helper, error) { parts := strings.Split(path, "/") if len(parts) != 3 && len(parts) != 4 { - return nil, errors.New("Invalid URL") + return nil, errors.New("invalid URL") } var b base64Helper @@ -277,15 +277,15 @@ func newBase64Helper(path string) (*base64Helper, error) { // - /base64/encode/input_str b.operation = parts[2] if b.operation != "encode" && b.operation != "decode" { - return nil, fmt.Errorf("Invalid operation: %s", b.operation) + return nil, fmt.Errorf("invalid operation: %s", b.operation) } b.data = parts[3] } if len(b.data) == 0 { - return nil, errors.New("No input data") + return nil, errors.New("no input data") } if len(b.data) >= Base64MaxLen { - return nil, fmt.Errorf("Input length - %d, Cannot handle input >= %d", len(b.data), Base64MaxLen) + return nil, fmt.Errorf("input length - %d, Cannot handle input >= %d", len(b.data), Base64MaxLen) } return &b, nil diff --git a/httpbin/middleware.go b/httpbin/middleware.go index 6576610a1b4d90af590bd42a62df6e6c32ff245c..e35a5a9df5ec6b5834c37582fda74801251e9e6d 100644 --- a/httpbin/middleware.go +++ b/httpbin/middleware.go @@ -127,7 +127,7 @@ func observe(o Observer, h http.Handler) http.Handler { Method: r.Method, URI: r.URL.RequestURI(), Size: mw.Size(), - Duration: time.Now().Sub(t), + Duration: time.Since(t), }) }) }