123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- package utils
- import (
- "fmt"
- "runtime"
- "strings"
- "testing"
- )
- // TestObjectPool tests the enhanced object pool functionality
- func TestObjectPool(t *testing.T) {
- pool := NewObjectPool(
- func() *strings.Builder { return &strings.Builder{} },
- func(sb *strings.Builder) { sb.Reset() },
- 10,
- )
- // Test Get/Put cycle
- sb1 := pool.Get()
- if sb1 == nil {
- t.Fatal("Pool returned nil object")
- }
- sb1.WriteString("test data")
- pool.Put(sb1)
- // Get another object (should be reused)
- sb2 := pool.Get()
- if sb2 == nil {
- t.Fatal("Pool returned nil after put")
- }
- // Should be reset
- if sb2.Len() != 0 {
- t.Error("Object not properly reset")
- }
- // Verify stats
- stats := pool.Stats()
- if stats.Reused == 0 {
- t.Error("Pool stats not tracking reuse")
- }
- }
- // TestStringBuilderPool tests the string builder pool
- func TestStringBuilderPool(t *testing.T) {
- pool := NewStringBuilderPool(1024, 10)
- // Test basic functionality
- sb := pool.Get()
- if sb == nil {
- t.Fatal("String builder pool returned nil")
- }
- sb.WriteString("test data")
- if sb.String() != "test data" {
- t.Error("String builder not working correctly")
- }
- pool.Put(sb)
- // Test reuse
- sb2 := pool.Get()
- if sb2.Len() != 0 {
- t.Error("String builder not reset properly")
- }
- pool.Put(sb2)
- }
- // TestByteSlicePool tests the byte slice pool
- func TestByteSlicePool(t *testing.T) {
- pool := NewByteSlicePool()
- // Test different sizes
- sizes := []int{64, 128, 256, 512, 1024}
- slices := make([][]byte, len(sizes))
- for i, size := range sizes {
- slice := pool.Get(size)
- if cap(slice) < size {
- t.Errorf("Slice capacity %d is less than requested size %d", cap(slice), size)
- }
- slices[i] = slice
- }
- // Return all slices
- for _, slice := range slices {
- pool.Put(slice)
- }
- // Test reuse
- for _, size := range sizes {
- slice := pool.Get(size)
- if cap(slice) < size {
- t.Errorf("Reused slice capacity %d is less than requested size %d", cap(slice), size)
- }
- pool.Put(slice)
- }
- }
- // TestMapPool tests the map pool
- func TestMapPool(t *testing.T) {
- pool := NewMapPool[string, int](10, 5)
- // Test Get/Put cycle
- m1 := pool.Get()
- if m1 == nil {
- t.Fatal("Map pool returned nil")
- }
- m1["test"] = 123
- m1["another"] = 456
- if len(m1) != 2 {
- t.Error("Map not working correctly")
- }
- pool.Put(m1)
- // Test reuse and reset
- m2 := pool.Get()
- if len(m2) != 0 {
- t.Error("Map not properly reset")
- }
- pool.Put(m2)
- }
- // TestSlicePool tests the slice pool
- func TestSlicePool(t *testing.T) {
- pool := NewSlicePool[string](10, 5)
- // Test Get/Put cycle
- slice1 := pool.Get()
- if slice1 == nil {
- t.Fatal("Slice pool returned nil")
- }
- slice1 = append(slice1, "test1", "test2")
- if len(slice1) != 2 {
- t.Error("Slice not working correctly")
- }
- pool.Put(slice1)
- // Test reuse and reset
- slice2 := pool.Get()
- if len(slice2) != 0 {
- t.Error("Slice not properly reset")
- }
- pool.Put(slice2)
- }
- // TestPoolManager tests the pool manager functionality
- func TestPoolManager(t *testing.T) {
- manager := NewPoolManager()
- // Register a test pool
- testPool := NewStringBuilderPool(1024, 10)
- manager.RegisterPool("test_pool", testPool)
- // Retrieve the pool
- retrieved, exists := manager.GetPool("test_pool")
- if !exists {
- t.Fatal("Pool not found in manager")
- }
- if retrieved != testPool {
- t.Error("Retrieved pool is not the same as registered")
- }
- // Test non-existent pool
- _, exists = manager.GetPool("non_existent")
- if exists {
- t.Error("Non-existent pool was found")
- }
- // Test stats collection
- stats := manager.GetAllStats()
- if len(stats) == 0 {
- t.Error("No stats returned from manager")
- }
- }
- // TestPooledWorker tests the pooled worker functionality
- func TestPooledWorker(t *testing.T) {
- worker := NewPooledWorker()
- defer worker.Cleanup()
- testData := []byte("test data for processing")
-
- processCount := 0
- processor := func(data []byte, sb *strings.Builder) error {
- sb.WriteString(string(data))
- processCount++
- return nil
- }
- // Process data multiple times
- for i := 0; i < 5; i++ {
- err := worker.ProcessWithPools(testData, processor)
- if err != nil {
- t.Fatalf("Processing failed: %v", err)
- }
- }
- if processCount != 5 {
- t.Errorf("Expected 5 processing calls, got %d", processCount)
- }
- }
- // BenchmarkObjectPool benchmarks the enhanced object pool
- func BenchmarkObjectPool(b *testing.B) {
- pool := NewObjectPool(
- func() *strings.Builder { return &strings.Builder{} },
- func(sb *strings.Builder) { sb.Reset() },
- 100,
- )
- b.ResetTimer()
- b.ReportAllocs()
- b.Run("WithPooling", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- sb := pool.Get()
- sb.WriteString("benchmark test data")
- _ = sb.String()
- pool.Put(sb)
- }
- })
- b.Run("WithoutPooling", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- sb := &strings.Builder{}
- sb.WriteString("benchmark test data")
- _ = sb.String()
- }
- })
- }
- // BenchmarkStringBuilderPool benchmarks the string builder pool
- func BenchmarkStringBuilderPool(b *testing.B) {
- pool := NewStringBuilderPool(1024, 100)
- b.ResetTimer()
- b.ReportAllocs()
- b.Run("PooledStringBuilder", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- sb := pool.Get()
- sb.WriteString("benchmark test data for string building operations")
- sb.WriteString(" with additional content to test performance")
- _ = sb.String()
- pool.Put(sb)
- }
- })
- b.Run("RegularStringBuilder", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- sb := &strings.Builder{}
- sb.Grow(1024)
- sb.WriteString("benchmark test data for string building operations")
- sb.WriteString(" with additional content to test performance")
- _ = sb.String()
- }
- })
- }
- // BenchmarkByteSlicePool benchmarks the byte slice pool
- func BenchmarkByteSlicePool(b *testing.B) {
- pool := NewByteSlicePool()
- testSizes := []int{64, 256, 1024, 4096}
- for _, size := range testSizes {
- b.Run(f("PooledSlice_%d", size), func(b *testing.B) {
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- slice := pool.Get(size)
- // Simulate some work
- for j := 0; j < cap(slice) && j < 100; j++ {
- slice = append(slice, byte(j))
- }
- pool.Put(slice)
- }
- })
- b.Run(f("RegularSlice_%d", size), func(b *testing.B) {
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- slice := make([]byte, 0, size)
- // Simulate some work
- for j := 0; j < cap(slice) && j < 100; j++ {
- slice = append(slice, byte(j))
- }
- }
- })
- }
- }
- // BenchmarkMemoryPoolConcurrency tests concurrent access to memory pools
- func BenchmarkMemoryPoolConcurrency(b *testing.B) {
- pool := NewStringBuilderPool(1024, 100)
- b.ResetTimer()
- b.ReportAllocs()
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- sb := pool.Get()
- sb.WriteString("concurrent benchmark test data")
- _ = sb.String()
- pool.Put(sb)
- }
- })
- }
- // BenchmarkPooledWorkerPerformance benchmarks the pooled worker
- func BenchmarkPooledWorkerPerformance(b *testing.B) {
- worker := NewPooledWorker()
- defer worker.Cleanup()
- testData := []byte("benchmark test data for pooled worker performance testing with longer content")
- processor := func(data []byte, sb *strings.Builder) error {
- sb.WriteString(string(data))
- sb.WriteString(" processed")
- return nil
- }
- b.ResetTimer()
- b.ReportAllocs()
- b.Run("PooledWorker", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- _ = worker.ProcessWithPools(testData, processor)
- }
- })
- b.Run("DirectProcessing", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- sb := &strings.Builder{}
- sb.Grow(1024)
- sb.WriteString(string(testData))
- sb.WriteString(" processed")
- }
- })
- }
- // TestMemoryPoolGCPressure tests that pools reduce GC pressure
- func TestMemoryPoolGCPressure(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping GC pressure test in short mode")
- }
- pool := NewStringBuilderPool(1024, 50)
- // Force GC and get initial stats
- runtime.GC()
- var m1 runtime.MemStats
- runtime.ReadMemStats(&m1)
- // Perform operations with pooling
- for i := 0; i < 10000; i++ {
- sb := pool.Get()
- sb.WriteString("GC pressure test data")
- _ = sb.String()
- pool.Put(sb)
- }
- runtime.GC()
- var m2 runtime.MemStats
- runtime.ReadMemStats(&m2)
- pooledAllocations := m2.TotalAlloc - m1.TotalAlloc
- // Reset and test without pooling
- runtime.GC()
- runtime.ReadMemStats(&m1)
- for i := 0; i < 10000; i++ {
- sb := &strings.Builder{}
- sb.Grow(1024)
- sb.WriteString("GC pressure test data")
- _ = sb.String()
- }
- runtime.GC()
- runtime.ReadMemStats(&m2)
- directAllocations := m2.TotalAlloc - m1.TotalAlloc
- // Pooling should significantly reduce allocations
- if pooledAllocations >= directAllocations {
- t.Logf("Pooled allocations: %d bytes", pooledAllocations)
- t.Logf("Direct allocations: %d bytes", directAllocations)
- t.Error("Pooled allocations should be significantly less than direct allocations")
- }
- reductionRatio := float64(directAllocations-pooledAllocations) / float64(directAllocations)
- t.Logf("Memory allocation reduction: %.2f%%", reductionRatio*100)
- if reductionRatio < 0.5 { // Expect at least 50% reduction
- t.Errorf("Expected at least 50%% allocation reduction, got %.2f%%", reductionRatio*100)
- }
- }
- // TestGlobalPools tests the global pool instances
- func TestGlobalPools(t *testing.T) {
- // Test LogStringBuilderPool
- sb := LogStringBuilderPool.Get()
- if sb == nil {
- t.Error("LogStringBuilderPool returned nil")
- }
- sb.WriteString("global pool test")
- LogStringBuilderPool.Put(sb)
- // Test GlobalByteSlicePool
- slice := GlobalByteSlicePool.Get(1024)
- if cap(slice) < 1024 {
- t.Error("GlobalByteSlicePool returned slice with insufficient capacity")
- }
- GlobalByteSlicePool.Put(slice)
- // Test StringSlicePool
- strSlice := StringSlicePool.Get()
- if strSlice == nil {
- t.Error("StringSlicePool returned nil")
- }
- strSlice = append(strSlice, "test")
- StringSlicePool.Put(strSlice)
- // Test map pools
- stringMap := StringMapPool.Get()
- if stringMap == nil {
- t.Error("StringMapPool returned nil")
- }
- stringMap["test"] = "value"
- StringMapPool.Put(stringMap)
- stringIntMap := StringIntMapPool.Get()
- if stringIntMap == nil {
- t.Error("StringIntMapPool returned nil")
- }
- stringIntMap["test"] = 123
- StringIntMapPool.Put(stringIntMap)
- intStringMap := IntStringMapPool.Get()
- if intStringMap == nil {
- t.Error("IntStringMapPool returned nil")
- }
- intStringMap[123] = "test"
- IntStringMapPool.Put(intStringMap)
- }
- // Helper function for formatted strings
- func f(format string, args ...interface{}) string {
- return fmt.Sprintf(format, args...)
- }
|