修复缓存
Build docker and publish / build (20.15.1) (push) Successful in 7m26s

This commit is contained in:
2026-03-06 21:58:29 -08:00
parent 19932bb9f7
commit 4d913c1728
175 changed files with 437 additions and 12104 deletions
-27
View File
@@ -1,27 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAtomicBool(t *testing.T) {
val := ForAtomicBool(true)
assert.True(t, val.True())
val.Set(false)
assert.False(t, val.True())
val.Set(true)
assert.True(t, val.True())
val.Set(false)
assert.False(t, val.True())
ok := val.CompareAndSwap(false, true)
assert.True(t, ok)
assert.True(t, val.True())
ok = val.CompareAndSwap(true, false)
assert.True(t, ok)
assert.False(t, val.True())
ok = val.CompareAndSwap(true, false)
assert.False(t, ok)
assert.False(t, val.True())
}
-19
View File
@@ -1,19 +0,0 @@
package syncx
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestAtomicDuration(t *testing.T) {
d := ForAtomicDuration(time.Duration(100))
assert.Equal(t, time.Duration(100), d.Load())
d.Set(time.Duration(200))
assert.Equal(t, time.Duration(200), d.Load())
assert.True(t, d.CompareAndSwap(time.Duration(200), time.Duration(300)))
assert.Equal(t, time.Duration(300), d.Load())
assert.False(t, d.CompareAndSwap(time.Duration(200), time.Duration(400)))
assert.Equal(t, time.Duration(300), d.Load())
}
-24
View File
@@ -1,24 +0,0 @@
package syncx
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAtomicFloat64(t *testing.T) {
f := ForAtomicFloat64(100)
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
for i := 0; i < 100; i++ {
f.Add(1)
}
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, float64(600), f.Load())
}
-56
View File
@@ -1,56 +0,0 @@
package syncx
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBarrier_Guard(t *testing.T) {
const total = 10000
var barrier Barrier
var count int
var wg sync.WaitGroup
wg.Add(total)
for i := 0; i < total; i++ {
go barrier.Guard(func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
func TestBarrierPtr_Guard(t *testing.T) {
const total = 10000
barrier := new(Barrier)
var count int
wg := new(sync.WaitGroup)
wg.Add(total)
for i := 0; i < total; i++ {
go barrier.Guard(func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
func TestGuard(t *testing.T) {
const total = 10000
var count int
var lock sync.Mutex
wg := new(sync.WaitGroup)
wg.Add(total)
for i := 0; i < total; i++ {
go Guard(&lock, func() {
count++
wg.Done()
})
}
wg.Wait()
assert.Equal(t, total, count)
}
-69
View File
@@ -1,69 +0,0 @@
package syncx
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTimeoutCondWait(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(2)
go func() {
cond.Wait()
wait.Done()
}()
time.Sleep(time.Duration(50) * time.Millisecond)
go func() {
cond.Signal()
wait.Done()
}()
wait.Wait()
}
func TestTimeoutCondWaitTimeout(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(1)
go func() {
cond.WaitWithTimeout(time.Duration(500) * time.Millisecond)
wait.Done()
}()
wait.Wait()
}
func TestTimeoutCondWaitTimeoutRemain(t *testing.T) {
var wait sync.WaitGroup
cond := NewCond()
wait.Add(2)
ch := make(chan time.Duration, 1)
defer close(ch)
timeout := time.Duration(2000) * time.Millisecond
go func() {
remainTimeout, _ := cond.WaitWithTimeout(timeout)
ch <- remainTimeout
wait.Done()
}()
sleep(200)
go func() {
cond.Signal()
wait.Done()
}()
wait.Wait()
remainTimeout := <-ch
assert.True(t, remainTimeout < timeout, "expect remainTimeout %v < %v", remainTimeout, timeout)
assert.True(t, remainTimeout >= time.Duration(200)*time.Millisecond,
"expect remainTimeout %v >= 200 millisecond", remainTimeout)
}
func TestSignalNoWait(t *testing.T) {
cond := NewCond()
cond.Signal()
}
func sleep(millisecond int) {
time.Sleep(time.Duration(millisecond) * time.Millisecond)
}
-31
View File
@@ -1,31 +0,0 @@
package syncx
import (
"sync"
"testing"
)
func TestDoneChanClose(t *testing.T) {
doneChan := NewDoneChan()
for i := 0; i < 5; i++ {
doneChan.Close()
}
}
func TestDoneChanDone(t *testing.T) {
var waitGroup sync.WaitGroup
doneChan := NewDoneChan()
waitGroup.Add(1)
go func() {
<-doneChan.Done()
waitGroup.Done()
}()
for i := 0; i < 5; i++ {
doneChan.Close()
}
waitGroup.Wait()
}
-78
View File
@@ -1,78 +0,0 @@
package syncx
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestImmutableResource(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return "hello", nil
})
res, err := r.Get()
assert.Equal(t, "hello", res)
assert.Equal(t, 1, count)
assert.Nil(t, err)
// again
res, err = r.Get()
assert.Equal(t, "hello", res)
assert.Equal(t, 1, count)
assert.Nil(t, err)
}
func TestImmutableResourceError(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return nil, errors.New("any")
})
res, err := r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
// again
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
r.refreshInterval = 0
time.Sleep(time.Millisecond)
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 2, count)
}
func TestImmutableResourceErrorRefreshAlways(t *testing.T) {
var count int
r := NewImmutableResource(func() (any, error) {
count++
return nil, errors.New("any")
}, WithRefreshIntervalOnFailure(0))
res, err := r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 1, count)
// again
res, err = r.Get()
assert.Nil(t, res)
assert.NotNil(t, err)
assert.Equal(t, "any", err.Error())
assert.Equal(t, 2, count)
}
-17
View File
@@ -1,17 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLimit(t *testing.T) {
limit := NewLimit(2)
limit.Borrow()
assert.True(t, limit.TryBorrow())
assert.False(t, limit.TryBorrow())
assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return())
assert.Equal(t, ErrLimitReturn, limit.Return())
}
-82
View File
@@ -1,82 +0,0 @@
package syncx
import (
"errors"
"fmt"
"sync"
"testing"
"time"
)
func TestLockedCallDo(t *testing.T) {
g := NewLockedCalls()
v, err := g.Do("key", func() (any, error) {
return "bar", nil
})
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
func TestLockedCallDoErr(t *testing.T) {
g := NewLockedCalls()
someErr := errors.New("some error")
v, err := g.Do("key", func() (any, error) {
return nil, someErr
})
if !errors.Is(err, someErr) {
t.Errorf("Do error = %v; want someErr", err)
}
if v != nil {
t.Errorf("unexpected non-nil value %#v", v)
}
}
func TestLockedCallDoDupSuppress(t *testing.T) {
g := NewLockedCalls()
c := make(chan string)
var calls int
fn := func() (any, error) {
calls++
ret := calls
<-c
calls--
return ret, nil
}
const n = 10
var results []int
var lock sync.Mutex
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, err := g.Do("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
lock.Lock()
results = append(results, v.(int))
lock.Unlock()
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
for i := 0; i < n; i++ {
c <- "bar"
}
wg.Wait()
lock.Lock()
defer lock.Unlock()
for _, item := range results {
if item != 1 {
t.Errorf("number of calls = %d; want 1", item)
}
}
}
-22
View File
@@ -1,22 +0,0 @@
package syncx
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestManagedResource(t *testing.T) {
var count int32
resource := NewManagedResource(func() any {
return atomic.AddInt32(&count, 1)
}, func(a, b any) bool {
return a == b
})
assert.Equal(t, resource.Take(), resource.Take())
old := resource.Take()
resource.MarkBroken(old)
assert.NotEqual(t, old, resource.Take())
}
-33
View File
@@ -1,33 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOnce(t *testing.T) {
var v int
add := Once(func() {
v++
})
for i := 0; i < 5; i++ {
add()
}
assert.Equal(t, 1, v)
}
func BenchmarkOnce(b *testing.B) {
var v int
add := Once(func() {
v++
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
add()
}
assert.Equal(b, 1, v)
}
-17
View File
@@ -1,17 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOnceGuard(t *testing.T) {
var guard OnceGuard
assert.False(t, guard.Taken())
assert.True(t, guard.Take())
assert.True(t, guard.Taken())
assert.False(t, guard.Take())
assert.True(t, guard.Taken())
}
-115
View File
@@ -1,115 +0,0 @@
package syncx
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/perfect-panel/server/pkg/lang"
"github.com/stretchr/testify/assert"
)
const limit = 10
func TestPoolGet(t *testing.T) {
stack := NewPool(limit, create, destroy)
ch := make(chan lang.PlaceholderType)
for i := 0; i < limit; i++ {
var fail AtomicBool
go func() {
v := stack.Get()
if v.(int) != 1 {
fail.Set(true)
}
ch <- lang.Placeholder
}()
select {
case <-ch:
case <-time.After(time.Second):
t.Fail()
}
if fail.True() {
t.Fatal("unmatch value")
}
}
}
func TestPoolPopTooMany(t *testing.T) {
stack := NewPool(limit, create, destroy)
ch := make(chan lang.PlaceholderType, 1)
for i := 0; i < limit; i++ {
var wait sync.WaitGroup
wait.Add(1)
go func() {
stack.Get()
ch <- lang.Placeholder
wait.Done()
}()
wait.Wait()
select {
case <-ch:
default:
t.Fail()
}
}
var waitGroup, pushWait sync.WaitGroup
waitGroup.Add(1)
pushWait.Add(1)
go func() {
pushWait.Done()
stack.Get()
waitGroup.Done()
}()
pushWait.Wait()
stack.Put(1)
waitGroup.Wait()
}
func TestPoolPopFirst(t *testing.T) {
var value int32
stack := NewPool(limit, func() any {
return atomic.AddInt32(&value, 1)
}, destroy)
for i := 0; i < 100; i++ {
v := stack.Get().(int32)
assert.Equal(t, 1, int(v))
stack.Put(v)
}
}
func TestPoolWithMaxAge(t *testing.T) {
var value int32
stack := NewPool(limit, func() any {
return atomic.AddInt32(&value, 1)
}, destroy, WithMaxAge(time.Millisecond))
v1 := stack.Get().(int32)
// put nil should not matter
stack.Put(nil)
stack.Put(v1)
time.Sleep(time.Millisecond * 10)
v2 := stack.Get().(int32)
assert.NotEqual(t, v1, v2)
}
func TestNewPoolPanics(t *testing.T) {
assert.Panics(t, func() {
NewPool(0, create, destroy)
})
}
func create() any {
return 1
}
func destroy(_ any) {
}
-27
View File
@@ -1,27 +0,0 @@
package syncx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRefCleaner(t *testing.T) {
var count int
clean := func() {
count += 1
}
cleaner := NewRefResource(clean)
err := cleaner.Use()
assert.Nil(t, err)
err = cleaner.Use()
assert.Nil(t, err)
cleaner.Clean()
cleaner.Clean()
assert.Equal(t, 1, count)
cleaner.Clean()
cleaner.Clean()
assert.Equal(t, 1, count)
assert.Equal(t, ErrUseOfCleaned, cleaner.Use())
}
-99
View File
@@ -1,99 +0,0 @@
package syncx
import (
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
type dummyResource struct {
age int
}
func (dr *dummyResource) Close() error {
return errors.New("close")
}
func TestResourceManager_GetResource(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
var age int
for i := 0; i < 10; i++ {
val, err := manager.GetResource("key", func() (io.Closer, error) {
age++
return &dummyResource{
age: age,
}, nil
})
assert.Nil(t, err)
assert.Equal(t, 1, val.(*dummyResource).age)
}
}
func TestResourceManager_GetResourceError(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
for i := 0; i < 10; i++ {
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
}
}
func TestResourceManager_Close(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
for i := 0; i < 10; i++ {
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
}
if assert.NoError(t, manager.Close()) {
assert.Equal(t, 0, len(manager.resources))
}
}
func TestResourceManager_UseAfterClose(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
_, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
if assert.NoError(t, manager.Close()) {
_, err = manager.GetResource("key", func() (io.Closer, error) {
return nil, errors.New("fail")
})
assert.NotNil(t, err)
assert.Panics(t, func() {
_, err = manager.GetResource("key", func() (io.Closer, error) {
return &dummyResource{age: 123}, nil
})
})
}
}
func TestResourceManager_Inject(t *testing.T) {
manager := NewResourceManager()
defer manager.Close()
manager.Inject("key", &dummyResource{
age: 10,
})
val, err := manager.GetResource("key", func() (io.Closer, error) {
return nil, nil
})
assert.Nil(t, err)
assert.Equal(t, 10, val.(*dummyResource).age)
}
-141
View File
@@ -1,141 +0,0 @@
package syncx
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestExclusiveCallDo(t *testing.T) {
g := NewSingleFlight()
v, err := g.Do("key", func() (any, error) {
return "bar", nil
})
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
func TestExclusiveCallDoErr(t *testing.T) {
g := NewSingleFlight()
someErr := errors.New("some error")
v, err := g.Do("key", func() (any, error) {
return nil, someErr
})
if !errors.Is(err, someErr) {
t.Errorf("Do error = %v; want someErr", err)
}
if v != nil {
t.Errorf("unexpected non-nil value %#v", v)
}
}
func TestExclusiveCallDoDupSuppress(t *testing.T) {
g := NewSingleFlight()
c := make(chan string)
var calls int32
fn := func() (any, error) {
atomic.AddInt32(&calls, 1)
return <-c, nil
}
const n = 10
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, err := g.Do("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
if v.(string) != "bar" {
t.Errorf("got %q; want %q", v, "bar")
}
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
c <- "bar"
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 1 {
t.Errorf("number of calls = %d; want 1", got)
}
}
func TestExclusiveCallDoDiffDupSuppress(t *testing.T) {
g := NewSingleFlight()
broadcast := make(chan struct{})
var calls int32
tests := []string{"e", "a", "e", "a", "b", "c", "b", "a", "c", "d", "b", "c", "d"}
var wg sync.WaitGroup
for _, key := range tests {
wg.Add(1)
go func(k string) {
<-broadcast // get all goroutines ready
_, err := g.Do(k, func() (any, error) {
atomic.AddInt32(&calls, 1)
time.Sleep(10 * time.Millisecond)
return nil, nil
})
if err != nil {
t.Errorf("Do error: %v", err)
}
wg.Done()
}(key)
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
close(broadcast)
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 5 {
// five letters
t.Errorf("number of calls = %d; want 5", got)
}
}
func TestExclusiveCallDoExDupSuppress(t *testing.T) {
g := NewSingleFlight()
c := make(chan string)
var calls int32
fn := func() (any, error) {
atomic.AddInt32(&calls, 1)
return <-c, nil
}
const n = 10
var wg sync.WaitGroup
var freshes int32
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
v, fresh, err := g.DoEx("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
if fresh {
atomic.AddInt32(&freshes, 1)
}
if v.(string) != "bar" {
t.Errorf("got %q; want %q", v, "bar")
}
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
c <- "bar"
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 1 {
t.Errorf("number of calls = %d; want 1", got)
}
if got := atomic.LoadInt32(&freshes); got != 1 {
t.Errorf("freshes = %d; want 1", got)
}
}
-70
View File
@@ -1,70 +0,0 @@
package syncx
import (
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/perfect-panel/server/pkg/lang"
"github.com/stretchr/testify/assert"
)
func TestTryLock(t *testing.T) {
var lock SpinLock
assert.True(t, lock.TryLock())
assert.False(t, lock.TryLock())
lock.Unlock()
assert.True(t, lock.TryLock())
}
func TestSpinLock(t *testing.T) {
var lock SpinLock
lock.Lock()
assert.False(t, lock.TryLock())
lock.Unlock()
assert.True(t, lock.TryLock())
}
func TestSpinLockRace(t *testing.T) {
var lock SpinLock
lock.Lock()
var wait sync.WaitGroup
wait.Add(1)
go func() {
wait.Done()
}()
time.Sleep(time.Millisecond * 100)
lock.Unlock()
wait.Wait()
assert.True(t, lock.TryLock())
}
func TestSpinLock_TryLock(t *testing.T) {
var lock SpinLock
var count int32
var wait sync.WaitGroup
wait.Add(2)
sig := make(chan lang.PlaceholderType)
go func() {
lock.TryLock()
sig <- lang.Placeholder
atomic.AddInt32(&count, 1)
runtime.Gosched()
lock.Unlock()
wait.Done()
}()
go func() {
<-sig
lock.Lock()
atomic.AddInt32(&count, 1)
lock.Unlock()
wait.Done()
}()
wait.Wait()
assert.Equal(t, int32(2), atomic.LoadInt32(&count))
}
-52
View File
@@ -1,52 +0,0 @@
package syncx
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTimeoutLimit(t *testing.T) {
tests := []struct {
name string
interval time.Duration
}{
{
name: "no wait",
},
{
name: "wait",
interval: time.Millisecond * 100,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
limit := NewTimeoutLimit(2)
assert.Nil(t, limit.Borrow(time.Millisecond*200))
assert.Nil(t, limit.Borrow(time.Millisecond*200))
var wait1, wait2, wait3 sync.WaitGroup
wait1.Add(1)
wait2.Add(1)
wait3.Add(1)
go func() {
wait1.Wait()
wait2.Done()
time.Sleep(test.interval)
assert.Nil(t, limit.Return())
wait3.Done()
}()
wait1.Done()
wait2.Wait()
assert.Nil(t, limit.Borrow(time.Second))
wait3.Wait()
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return())
assert.Equal(t, ErrLimitReturn, limit.Return())
})
}
}