You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
5.4 KiB

package cache
import (
"context"
"errors"
"fmt"
"math/rand"
"sync"
"testing"
"time"
)
func BenchmarkCache_ConcurrentUpsertAndGet(b *testing.B) {
ctx := context.Background()
expiredInterval := 10 * time.Minute // Произвольно выбранный интервал истечения срока действия
runGCInterval := 1 * time.Minute // Произвольно выбранный интервал запуска сборщика мусора
// Инициализация кэша
Init(ctx, expiredInterval, runGCInterval)
c := Cache()
var wg sync.WaitGroup
// Количество операций для каждого типа (запись и чтение)
operationsCount := 100000
b.ResetTimer()
// Запуск горутин для записи
for i := 0; i < operationsCount; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", i)
_, err := c.Upsert(key, func() (interface{}, error) {
return fmt.Sprintf("value%d", i), nil
}, 5*time.Minute) // Используем 5 минут как интервал обновления
if err != nil {
b.Errorf("Failed to upsert item: %s", err)
}
}(i)
}
// Запуск горутин для чтения
for i := 0; i < operationsCount; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", i)
_, err := c.Get(key)
if err != nil && !errors.Is(err, ErrorKeyNotFound) {
b.Errorf("Failed to get item: %s", err)
}
}(i)
}
wg.Wait() // Ожидание завершения всех горутин
}
// BenchmarkCache_ConcurrentUpsert бенчмарк для многопоточной операции Upsert.
func BenchmarkCache_ConcurrentUpsert(b *testing.B) {
ctx := context.Background()
Init(ctx, 10*time.Minute, 1*time.Minute)
c := Cache()
b.ResetTimer()
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_, err := c.Upsert(fmt.Sprintf("key%d", i), func() (interface{}, error) {
return fmt.Sprintf("value%d", i), nil
}, 5*time.Minute)
if err != nil {
b.Errorf("Failed to upsert item: %s", err)
}
}(i)
}
wg.Wait()
}
func BenchmarkCache_ConcurrentGet2(b *testing.B) {
ctx := context.Background()
Init(ctx, 10*time.Minute, 1*time.Minute)
c := Cache()
// Предварительно заполняем кэш данными для чтения
for i := 0; i < 100000; i++ {
key := fmt.Sprintf("key%d", i)
c.Upsert(key, func() (interface{}, error) {
return fmt.Sprintf("value%d", i), nil
}, 5*time.Minute)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := c.Get(fmt.Sprintf("key%d", rand.Intn(10000)))
if err != nil {
b.Errorf("Failed to get item: %s", err)
}
}
})
}
func BenchmarkCache_ProductionLike(b *testing.B) {
ctx := context.Background()
expiredInterval := 1 * time.Minute // Более короткий интервал истечения для тестирования GC
runGCInterval := 10 * time.Second // Частый запуск GC для имитации работы
Init(ctx, expiredInterval, runGCInterval)
c := Cache()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var wg sync.WaitGroup
// Чтение данных
wg.Add(1)
go func() {
defer wg.Done()
key := fmt.Sprintf("key%d", rand.Intn(1000))
_, _ = c.Get(key)
}()
// Запись данных
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", i%1000) // Ограничение количества уникальных ключей для имитации повторных записей
_, _ = c.Upsert(key, func() (interface{}, error) {
return fmt.Sprintf("value%d", i), nil
}, time.Duration(rand.Intn(5)+1)*time.Minute) // Рандомный интервал обновления
}(i)
// Обновление данных
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", rand.Intn(1000))
_, _ = c.Upsert(key, func() (interface{}, error) {
return fmt.Sprintf("newValue%d", i), nil
}, time.Duration(rand.Intn(5)+1)*time.Minute)
}(i)
// Удаление данных
if i%10 == 0 { // Не на каждой итерации, чтобы не очищать все сразу
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", rand.Intn(1000))
c.Delete(key)
}(i)
}
wg.Wait()
}
}
func TestUpdate(t *testing.T) {
ctx := context.Background()
expiredInterval := time.Second * 5 // Более короткий интервал истечения для тестирования GC
runGCInterval := 5 * time.Second // Частый запуск GC для имитации работы
key := "key"
val1 := "val1"
val2 := "val2"
get1 := func() (interface{}, error) { return val1, nil }
get2 := func() (interface{}, error) { return val2, nil }
Init(ctx, expiredInterval, runGCInterval)
c := Cache()
c.Upsert(key, get1, expiredInterval)
time.Sleep(expiredInterval * 3)
val, err := c.Get(key)
if err != ErrorKeyNotFound {
t.Error("Key is not deleted", val, err)
}
c.Upsert(key, get1, expiredInterval)
c.Upsert(key, get2, expiredInterval)
val, err = c.Get(key)
if err != nil {
t.Error(err)
}
if val.(string) != val2 {
t.Errorf("new value is %s, expected %s", val, val2)
}
if val.(string) != val2 {
t.Error("value is not updated", val, "!=", val2)
}
}