run tests and linter on every PR
This commit is contained in:
39
.github/workflows/pull_request.yaml
vendored
Normal file
39
.github/workflows/pull_request.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Pull Request
|
||||||
|
on:
|
||||||
|
merge_group:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version-file: './go.mod'
|
||||||
|
check-latest: true
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version-file: './go.mod'
|
||||||
|
check-latest: true
|
||||||
|
- name: Unit Tests
|
||||||
|
run: make t
|
35
.golangci.yaml
Normal file
35
.golangci.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
run:
|
||||||
|
timeout: 3m
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- errname
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- stylecheck
|
||||||
|
- importas
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- mirror
|
||||||
|
- staticcheck
|
||||||
|
- tagalign
|
||||||
|
- testifylint
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- exhaustive
|
||||||
|
- noctx
|
||||||
|
- promlinter
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- shadow
|
||||||
|
- fieldalignment
|
14
Makefile
14
Makefile
@@ -1,18 +1,20 @@
|
|||||||
|
.PHONY: l
|
||||||
|
l: ## Lint Go source files
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest && golangci-lint run
|
||||||
|
|
||||||
.PHONY: t
|
.PHONY: t
|
||||||
t:
|
t: ## Run unit tests
|
||||||
go test -race -count=1 ./...
|
go test -race -count=1 ./...
|
||||||
|
|
||||||
.PHONY: f
|
.PHONY: f
|
||||||
f:
|
f: ## Format code
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|
||||||
|
|
||||||
.PHONY: c
|
.PHONY: c
|
||||||
c:
|
c: ## Measure code coverage
|
||||||
go test -race -covermode=atomic ./... -coverprofile=cover.out && \
|
go test -race -covermode=atomic ./... -coverprofile=cover.out && \
|
||||||
# go tool cover -html=cover.out && \
|
|
||||||
go tool cover -func cover.out \
|
go tool cover -func cover.out \
|
||||||
| grep -vP '[89]\d\.\d%' | grep -v '100.0%' \
|
| grep -vP '[89]\d\.\d%' | grep -v '100.0%' \
|
||||||
|| true
|
|| true
|
||||||
|
|
||||||
rm cover.out
|
rm cover.out
|
34
cache.go
34
cache.go
@@ -7,33 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The cache has a generic 'control' channel that is used to send
|
|
||||||
// messages to the worker. These are the messages that can be sent to it
|
|
||||||
type getDropped struct {
|
|
||||||
res chan int
|
|
||||||
}
|
|
||||||
|
|
||||||
type getSize struct {
|
|
||||||
res chan int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type setMaxSize struct {
|
|
||||||
size int64
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type clear struct {
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncWorker struct {
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type gc struct {
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cache[T any] struct {
|
type Cache[T any] struct {
|
||||||
*Configuration[T]
|
*Configuration[T]
|
||||||
control
|
control
|
||||||
@@ -203,11 +176,6 @@ func (c *Cache[T]) Delete(key string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache[T]) deleteItem(bucket *bucket[T], item *Item[T]) {
|
|
||||||
bucket.delete(item.key) //stop other GETs from getting it
|
|
||||||
c.deletables <- item
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache[T]) set(key string, value T, duration time.Duration, track bool) *Item[T] {
|
func (c *Cache[T]) set(key string, value T, duration time.Duration, track bool) *Item[T] {
|
||||||
item, existing := c.bucket(key).set(key, value, duration, track)
|
item, existing := c.bucket(key).set(key, value, duration, track)
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
@@ -389,7 +357,7 @@ func (c *Cache[T]) gc() int {
|
|||||||
}
|
}
|
||||||
prev := node.Prev
|
prev := node.Prev
|
||||||
item := node.Value
|
item := node.Value
|
||||||
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
if !c.tracking || atomic.LoadInt32(&item.refCount) == 0 {
|
||||||
c.bucket(item.key).delete(item.key)
|
c.bucket(item.key).delete(item.key)
|
||||||
c.size -= item.size
|
c.size -= item.size
|
||||||
c.list.Remove(node)
|
c.list.Remove(node)
|
||||||
|
@@ -124,7 +124,6 @@ func Test_CacheDeletesAFunc(t *testing.T) {
|
|||||||
return key == "d"
|
return key == "d"
|
||||||
}), 1)
|
}), 1)
|
||||||
assert.Equal(t, cache.ItemCount(), 2)
|
assert.Equal(t, cache.ItemCount(), 2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_CacheOnDeleteCallbackCalled(t *testing.T) {
|
func Test_CacheOnDeleteCallbackCalled(t *testing.T) {
|
||||||
|
@@ -37,7 +37,7 @@ func (c *Configuration[T]) MaxSize(max int64) *Configuration[T] {
|
|||||||
// requires a write lock on the bucket). Must be a power of 2 (1, 2, 4, 8, 16, ...)
|
// requires a write lock on the bucket). Must be a power of 2 (1, 2, 4, 8, 16, ...)
|
||||||
// [16]
|
// [16]
|
||||||
func (c *Configuration[T]) Buckets(count uint32) *Configuration[T] {
|
func (c *Configuration[T]) Buckets(count uint32) *Configuration[T] {
|
||||||
if count == 0 || ((count&(^count+1)) == count) == false {
|
if count == 0 || !((count & (^count + 1)) == count) {
|
||||||
count = 16
|
count = 16
|
||||||
}
|
}
|
||||||
c.buckets = int(count)
|
c.buckets = int(count)
|
||||||
|
@@ -32,7 +32,7 @@ func (b *layeredBucket[T]) getSecondaryBucket(primary string) *bucket[T] {
|
|||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return bucket
|
return bucket
|
||||||
@@ -41,7 +41,7 @@ func (b *layeredBucket[T]) getSecondaryBucket(primary string) *bucket[T] {
|
|||||||
func (b *layeredBucket[T]) set(primary, secondary string, value T, duration time.Duration, track bool) (*Item[T], *Item[T]) {
|
func (b *layeredBucket[T]) set(primary, secondary string, value T, duration time.Duration, track bool) (*Item[T], *Item[T]) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
bkt, exists := b.buckets[primary]
|
bkt, exists := b.buckets[primary]
|
||||||
if exists == false {
|
if !exists {
|
||||||
bkt = &bucket[T]{lookup: make(map[string]*Item[T])}
|
bkt = &bucket[T]{lookup: make(map[string]*Item[T])}
|
||||||
b.buckets[primary] = bkt
|
b.buckets[primary] = bkt
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func (b *layeredBucket[T]) delete(primary, secondary string) *Item[T] {
|
|||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return bucket.delete(secondary)
|
return bucket.delete(secondary)
|
||||||
@@ -65,7 +65,7 @@ func (b *layeredBucket[T]) deletePrefix(primary, prefix string, deletables chan
|
|||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return bucket.deletePrefix(prefix, deletables)
|
return bucket.deletePrefix(prefix, deletables)
|
||||||
@@ -75,7 +75,7 @@ func (b *layeredBucket[T]) deleteFunc(primary string, matches func(key string, i
|
|||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return bucket.deleteFunc(matches, deletables)
|
return bucket.deleteFunc(matches, deletables)
|
||||||
@@ -85,7 +85,7 @@ func (b *layeredBucket[T]) deleteAll(primary string, deletables chan *Item[T]) b
|
|||||||
b.RLock()
|
b.RLock()
|
||||||
bucket, exists := b.buckets[primary]
|
bucket, exists := b.buckets[primary]
|
||||||
b.RUnlock()
|
b.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ func Layered[T any](config *Configuration[T]) *LayeredCache[T] {
|
|||||||
deletables: make(chan *Item[T], config.deleteBuffer),
|
deletables: make(chan *Item[T], config.deleteBuffer),
|
||||||
promotables: make(chan *Item[T], config.promoteBuffer),
|
promotables: make(chan *Item[T], config.promoteBuffer),
|
||||||
}
|
}
|
||||||
for i := 0; i < int(config.buckets); i++ {
|
for i := 0; i < config.buckets; i++ {
|
||||||
c.buckets[i] = &layeredBucket[T]{
|
c.buckets[i] = &layeredBucket[T]{
|
||||||
buckets: make(map[string]*bucket[T]),
|
buckets: make(map[string]*bucket[T]),
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ func (c *LayeredCache[T]) gc() int {
|
|||||||
}
|
}
|
||||||
prev := node.Prev
|
prev := node.Prev
|
||||||
item := node.Value
|
item := node.Value
|
||||||
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
if !c.tracking || atomic.LoadInt32(&item.refCount) == 0 {
|
||||||
c.bucket(item.group).delete(item.group, item.key)
|
c.bucket(item.group).delete(item.group, item.key)
|
||||||
c.size -= item.size
|
c.size -= item.size
|
||||||
c.list.Remove(node)
|
c.list.Remove(node)
|
||||||
|
@@ -118,7 +118,6 @@ func Test_LayedCache_DeletesAFunc(t *testing.T) {
|
|||||||
return key == "d"
|
return key == "d"
|
||||||
}), 1)
|
}), 1)
|
||||||
assert.Equal(t, cache.ItemCount(), 3)
|
assert.Equal(t, cache.ItemCount(), 3)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_LayedCache_OnDeleteCallbackCalled(t *testing.T) {
|
func Test_LayedCache_OnDeleteCallbackCalled(t *testing.T) {
|
||||||
|
@@ -85,11 +85,3 @@ func assertList(t *testing.T, list *List[int], expected ...int) {
|
|||||||
node = node.Prev
|
node = node.Prev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listFromInts(ints ...int) *List[int] {
|
|
||||||
l := NewList[int]()
|
|
||||||
for i := len(ints) - 1; i >= 0; i-- {
|
|
||||||
l.Insert(ints[i])
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user