diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..69602e9 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,34 @@ +--- +name: golangci-lint +on: + push: + paths: + - "go.sum" + - "go.mod" + - "**.go" + - ".github/workflows/golangci-lint.yml" + - ".golangci.yml" + pull_request: + +permissions: # added using https://github.com/step-security/secure-repo + contents: read + +jobs: + golangci: + permissions: + contents: read # for actions/checkout to fetch code + pull-requests: read # for golangci/golangci-lint-action to fetch pull requests + name: lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Install Go + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + with: + go-version: 1.24.x + - name: Lint + uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 + with: + args: --verbose + version: v2.0.2 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..95aad69 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,29 @@ +version: "2" +run: + timeout: 15m +output: + show-stats: false + +formatters: + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - default + gofumpt: + extra-rules: true + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +linters: + # Keep this list sorted alphabetically + disable: + # TODO: Cleanup errcheck issues. + - errcheck + enable: + - revive diff --git a/memcache/fakeserver_test.go b/memcache/fakeserver_test.go index 4508c9e..025043b 100644 --- a/memcache/fakeserver_test.go +++ b/memcache/fakeserver_test.go @@ -151,9 +151,8 @@ func (c *testConn) handleRequestLine(line string) bool { if noReply == "" { if ok { return c.reply("TOUCHED") - } else { - return c.reply("NOT_FOUND") } + return c.reply("NOT_FOUND") } return true } @@ -163,7 +162,7 @@ func (c *testConn) handleRequestLine(line string) bool { flags, _ := strconv.ParseUint(flagsStr, 10, 32) exptimeVal, _ := strconv.ParseInt(exptimeStr, 10, 64) itemLen, _ := strconv.ParseInt(lenStr, 10, 32) - //log.Printf("got %q flags=%q exp=%d %d len=%d cas=%q noreply=%q", verb, key, flags, exptimeVal, itemLen, casUniq, noReply) + // log.Printf("got %q flags=%q exp=%d %d len=%d cas=%q noreply=%q", verb, key, flags, exptimeVal, itemLen, casUniq, noReply) if c.s.m == nil { c.s.m = make(map[string]serverItem) } @@ -274,7 +273,6 @@ func (c *testConn) handleRequestLine(line string) bool { } return false - } func computeExpTime(n int64) time.Time { diff --git a/memcache/memcache.go b/memcache/memcache.go index 6f48caa..0bb52ac 100644 --- a/memcache/memcache.go +++ b/memcache/memcache.go @@ -49,7 +49,7 @@ var ( // CompareAndSwap) failed because the condition was not satisfied. ErrNotStored = errors.New("memcache: item not stored") - // ErrServer means that a server error occurred. + // ErrServerError means that a server error occurred. ErrServerError = errors.New("memcache: server error") // ErrNoStats means that no statistics were available. @@ -57,7 +57,6 @@ var ( // ErrMalformedKey is returned when an invalid key is used. // Keys must be at maximum 250 bytes long and not - // contain whitespace or control characters. ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters") // ErrNoServers is returned when no servers are configured or available. @@ -101,7 +100,6 @@ func legalKey(key string) bool { var ( crlf = []byte("\r\n") - space = []byte(" ") resultOK = []byte("OK\r\n") resultStored = []byte("STORED\r\n") resultNotStored = []byte("NOT_STORED\r\n") @@ -326,6 +324,7 @@ func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) e return nil } +// FlushAll send the flush_all command. func (c *Client) FlushAll() error { return c.selector.Each(c.flushAllFromAddr) } @@ -460,7 +459,8 @@ func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) e } switch { case bytes.Equal(line, resultTouched): - break + // TODO: SA4011: ineffective break statement. Did you mean to break out of the outer loop? + break //nolint:staticcheck case bytes.Equal(line, resultNotFound): return ErrCacheMiss default: @@ -504,7 +504,7 @@ func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { } var err error - for _ = range keyMap { + for range keyMap { if ge := <-ch; ge != nil { err = ge } @@ -750,7 +750,7 @@ func (c *Client) DeleteAll() error { }) } -// Get and Touch the item with the provided key. The error ErrCacheMiss is +// GetAndTouch Get and Touch the item with the provided key. The error ErrCacheMiss is // returned if the item didn't already exist in the cache. func (c *Client) GetAndTouch(key string, expiration int32) (item *Item, err error) { err = c.withKeyAddr(key, func(addr net.Addr) error { diff --git a/memcache/memcache_test.go b/memcache/memcache_test.go index a0fa746..bdfa5b1 100644 --- a/memcache/memcache_test.go +++ b/memcache/memcache_test.go @@ -103,10 +103,10 @@ func TestTLS(t *testing.T) { } t.Logf("version: %s", bytes.TrimSpace(out)) - if err := os.WriteFile(filepath.Join(td, "/cert.pem"), LocalhostCert, 0644); err != nil { + if err := os.WriteFile(filepath.Join(td, "/cert.pem"), LocalhostCert, 0o644); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(td, "/key.pem"), LocalhostKey, 0644); err != nil { + if err := os.WriteFile(filepath.Join(td, "/key.pem"), LocalhostKey, 0o644); err != nil { t.Fatal(err) } @@ -155,7 +155,6 @@ func TestTLS(t *testing.T) { InsecureSkipVerify: true, } return td.DialContext(ctx, network, addr) - } testWithClient(t, c) } @@ -248,16 +247,16 @@ func testWithClient(t *testing.T, c *Client) { } // Append - append := &Item{Key: "append", Value: []byte("appendval")} - if err := c.Append(append); err != ErrNotStored { + appendItem := &Item{Key: "append", Value: []byte("appendval")} + if err := c.Append(appendItem); err != ErrNotStored { t.Fatalf("first append(append) want ErrNotStored, got %v", err) } - c.Set(append) + c.Set(appendItem) err = c.Append(&Item{Key: "append", Value: []byte("1")}) checkErr(err, "second append(append): %v", err) appended, err := c.Get("append") checkErr(err, "third append(append): %v", err) - if string(appended.Value) != string(append.Value)+"1" { + if string(appended.Value) != string(appendItem.Value)+"1" { t.Fatalf("Append: want=append1, got=%s", string(appended.Value)) } @@ -305,7 +304,7 @@ func testWithClient(t *testing.T, c *Client) { // Delete err = c.Delete("foo") checkErr(err, "Delete: %v", err) - it, err = c.Get("foo") + _, err = c.Get("foo") if err != ErrCacheMiss { t.Errorf("post-Delete want ErrCacheMiss, got %v", err) } @@ -324,12 +323,12 @@ func testWithClient(t *testing.T, c *Client) { } err = c.Delete("num") checkErr(err, "delete num: %v", err) - n, err = c.Increment("num", 1) + _, err = c.Increment("num", 1) if err != ErrCacheMiss { t.Fatalf("increment post-delete: want ErrCacheMiss, got %v", err) } mustSet(&Item{Key: "num", Value: []byte("not-numeric")}) - n, err = c.Increment("num", 1) + _, err = c.Increment("num", 1) if err == nil || !strings.Contains(err.Error(), "client error") { t.Fatalf("increment non-number: want client error, got %v", err) } @@ -338,7 +337,7 @@ func testWithClient(t *testing.T, c *Client) { // Test Delete All err = c.DeleteAll() checkErr(err, "DeleteAll: %v", err) - it, err = c.Get("bar") + _, err = c.Get("bar") if err != ErrCacheMiss { t.Errorf("post-DeleteAll want ErrCacheMiss, got %v", err) } @@ -387,7 +386,7 @@ func testTouchWithClient(t *testing.T, c *Client) { _, err = c.Get("bar") if err == nil { - t.Fatalf("item bar did not expire within %v seconds", time.Now().Sub(setTime).Seconds()) + t.Fatalf("item bar did not expire within %v seconds", time.Since(setTime).Seconds()) } else { if err != ErrCacheMiss { t.Fatalf("unexpected error retrieving bar: %v", err.Error()) @@ -446,32 +445,58 @@ func TestScanGetResponseLine(t *testing.T) { wantSize int wantErr bool }{ - {name: "blank", line: "", - wantErr: true}, - {name: "malformed1", line: "VALU foobar1234 1 4096\r\n", - wantErr: true}, - {name: "malformed2", line: "VALUEfoobar1234 1 4096\r\n", - wantErr: true}, - {name: "malformed3", line: "VALUE foobar1234 14096\r\n", - wantErr: true}, - {name: "malformed4", line: "VALUE foobar123414096\r\n", - wantErr: true}, - {name: "no-eol", line: "VALUE foobar1234 1 4096", - wantErr: true}, - {name: "basic", line: "VALUE foobar1234 1 4096\r\n", - wantKey: "foobar1234", wantFlags: 1, wantSize: 4096}, - {name: "casid", line: "VALUE foobar1234 1 4096 1234\r\n", - wantKey: "foobar1234", wantFlags: 1, wantSize: 4096, wantCasid: 1234}, - {name: "flags-max-uint32", line: "VALUE key 4294967295 1\r\n", - wantKey: "key", wantFlags: 4294967295, wantSize: 1}, - {name: "flags-overflow", line: "VALUE key 4294967296 1\r\n", - wantErr: true}, - {name: "size-max-uint32", line: "VALUE key 1 2147483647\r\n", - wantKey: "key", wantFlags: 1, wantSize: 2147483647}, - {name: "size-overflow", line: "VALUE key 1 4294967296\r\n", - wantErr: true}, - {name: "casid-overflow", line: "VALUE key 1 4096 18446744073709551616\r\n", - wantErr: true}, + { + name: "blank", line: "", + wantErr: true, + }, + { + name: "malformed1", line: "VALU foobar1234 1 4096\r\n", + wantErr: true, + }, + { + name: "malformed2", line: "VALUEfoobar1234 1 4096\r\n", + wantErr: true, + }, + { + name: "malformed3", line: "VALUE foobar1234 14096\r\n", + wantErr: true, + }, + { + name: "malformed4", line: "VALUE foobar123414096\r\n", + wantErr: true, + }, + { + name: "no-eol", line: "VALUE foobar1234 1 4096", + wantErr: true, + }, + { + name: "basic", line: "VALUE foobar1234 1 4096\r\n", + wantKey: "foobar1234", wantFlags: 1, wantSize: 4096, + }, + { + name: "casid", line: "VALUE foobar1234 1 4096 1234\r\n", + wantKey: "foobar1234", wantFlags: 1, wantSize: 4096, wantCasid: 1234, + }, + { + name: "flags-max-uint32", line: "VALUE key 4294967295 1\r\n", + wantKey: "key", wantFlags: 4294967295, wantSize: 1, + }, + { + name: "flags-overflow", line: "VALUE key 4294967296 1\r\n", + wantErr: true, + }, + { + name: "size-max-uint32", line: "VALUE key 1 2147483647\r\n", + wantKey: "key", wantFlags: 1, wantSize: 2147483647, + }, + { + name: "size-overflow", line: "VALUE key 1 4294967296\r\n", + wantErr: true, + }, + { + name: "casid-overflow", line: "VALUE key 1 4096 18446744073709551616\r\n", + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/memcache/selector.go b/memcache/selector.go index 964dbdb..f002c9c 100644 --- a/memcache/selector.go +++ b/memcache/selector.go @@ -111,6 +111,7 @@ var keyBufPool = sync.Pool{ }, } +// PickServer returns the address for a server from the ServerList. func (ss *ServerList) PickServer(key string) (net.Addr, error) { ss.mu.RLock() defer ss.mu.RUnlock()