1package fastcache
2
3import (
4	"fmt"
5	"runtime"
6	"sync"
7	"testing"
8	"time"
9)
10
11func TestCacheSmall(t *testing.T) {
12	c := New(1)
13	defer c.Reset()
14
15	if v := c.Get(nil, []byte("aaa")); len(v) != 0 {
16		t.Fatalf("unexpected non-empty value obtained from small cache: %q", v)
17	}
18	if v, exist := c.HasGet(nil, []byte("aaa")); exist || len(v) != 0 {
19		t.Fatalf("unexpected non-empty value obtained from small cache: %q", v)
20	}
21
22	c.Set([]byte("key"), []byte("value"))
23	if v := c.Get(nil, []byte("key")); string(v) != "value" {
24		t.Fatalf("unexpected value obtained; got %q; want %q", v, "value")
25	}
26	if v := c.Get(nil, nil); len(v) != 0 {
27		t.Fatalf("unexpected non-empty value obtained from small cache: %q", v)
28	}
29	if v, exist := c.HasGet(nil, nil); exist {
30		t.Fatalf("unexpected nil-keyed value obtained in small cache: %q", v)
31	}
32	if v := c.Get(nil, []byte("aaa")); len(v) != 0 {
33		t.Fatalf("unexpected non-empty value obtained from small cache: %q", v)
34	}
35
36	c.Set([]byte("aaa"), []byte("bbb"))
37	if v := c.Get(nil, []byte("aaa")); string(v) != "bbb" {
38		t.Fatalf("unexpected value obtained; got %q; want %q", v, "bbb")
39	}
40	if v, exist := c.HasGet(nil, []byte("aaa")); !exist || string(v) != "bbb" {
41		t.Fatalf("unexpected value obtained; got %q; want %q", v, "bbb")
42	}
43
44	c.Reset()
45	if v := c.Get(nil, []byte("aaa")); len(v) != 0 {
46		t.Fatalf("unexpected non-empty value obtained from empty cache: %q", v)
47	}
48	if v, exist := c.HasGet(nil, []byte("aaa")); exist || len(v) != 0 {
49		t.Fatalf("unexpected non-empty value obtained from small cache: %q", v)
50	}
51
52	// Test empty value
53	k := []byte("empty")
54	c.Set(k, nil)
55	if v := c.Get(nil, k); len(v) != 0 {
56		t.Fatalf("unexpected non-empty value obtained from empty entry: %q", v)
57	}
58	if v, exist := c.HasGet(nil, k); !exist {
59		t.Fatalf("cannot find empty entry for key %q", k)
60	} else if len(v) != 0 {
61		t.Fatalf("unexpected non-empty value obtained from empty entry: %q", v)
62	}
63	if !c.Has(k) {
64		t.Fatalf("cannot find empty entry for key %q", k)
65	}
66	if c.Has([]byte("foobar")) {
67		t.Fatalf("non-existing entry found in the cache")
68	}
69}
70
71func TestCacheWrap(t *testing.T) {
72	c := New(bucketsCount * chunkSize * 1.5)
73	defer c.Reset()
74
75	calls := uint64(5e6)
76
77	for i := uint64(0); i < calls; i++ {
78		k := []byte(fmt.Sprintf("key %d", i))
79		v := []byte(fmt.Sprintf("value %d", i))
80		c.Set(k, v)
81		vv := c.Get(nil, k)
82		if string(vv) != string(v) {
83			t.Fatalf("unexpected value for key %q; got %q; want %q", k, vv, v)
84		}
85	}
86	for i := uint64(0); i < calls/10; i++ {
87		x := i * 10
88		k := []byte(fmt.Sprintf("key %d", x))
89		v := []byte(fmt.Sprintf("value %d", x))
90		vv := c.Get(nil, k)
91		if len(vv) > 0 && string(v) != string(vv) {
92			t.Fatalf("unexpected value for key %q; got %q; want %q", k, vv, v)
93		}
94	}
95
96	var s Stats
97	c.UpdateStats(&s)
98	getCalls := calls + calls/10
99	if s.GetCalls != getCalls {
100		t.Fatalf("unexpected number of getCalls; got %d; want %d", s.GetCalls, getCalls)
101	}
102	if s.SetCalls != calls {
103		t.Fatalf("unexpected number of setCalls; got %d; want %d", s.SetCalls, calls)
104	}
105	if s.Misses == 0 || s.Misses >= calls/10 {
106		t.Fatalf("unexpected number of misses; got %d; it should be between 0 and %d", s.Misses, calls/10)
107	}
108	if s.Collisions != 0 {
109		t.Fatalf("unexpected number of collisions; got %d; want 0", s.Collisions)
110	}
111	if s.EntriesCount < calls/5 {
112		t.Fatalf("unexpected number of items; got %d; cannot be smaller than %d", s.EntriesCount, calls/5)
113	}
114	if s.BytesSize < 1024 {
115		t.Fatalf("unexpected number of bytesSize; got %d; cannot be smaller than %d", s.BytesSize, 1024)
116	}
117}
118
119func TestCacheDel(t *testing.T) {
120	c := New(1024)
121	defer c.Reset()
122	for i := 0; i < 100; i++ {
123		k := []byte(fmt.Sprintf("key %d", i))
124		v := []byte(fmt.Sprintf("value %d", i))
125		c.Set(k, v)
126		vv := c.Get(nil, k)
127		if string(vv) != string(v) {
128			t.Fatalf("unexpected value for key %q; got %q; want %q", k, vv, v)
129		}
130		c.Del(k)
131		vv = c.Get(nil, k)
132		if len(vv) > 0 {
133			t.Fatalf("unexpected non-empty value got for key %q: %q", k, vv)
134		}
135	}
136}
137
138func TestCacheBigKeyValue(t *testing.T) {
139	c := New(1024)
140	defer c.Reset()
141
142	// Both key and value exceed 64Kb
143	k := make([]byte, 90*1024)
144	v := make([]byte, 100*1024)
145	c.Set(k, v)
146	vv := c.Get(nil, k)
147	if len(vv) > 0 {
148		t.Fatalf("unexpected non-empty value got for key %q: %q", k, vv)
149	}
150
151	// len(key) + len(value) > 64Kb
152	k = make([]byte, 40*1024)
153	v = make([]byte, 40*1024)
154	c.Set(k, v)
155	vv = c.Get(nil, k)
156	if len(vv) > 0 {
157		t.Fatalf("unexpected non-empty value got for key %q: %q", k, vv)
158	}
159}
160
161func TestCacheSetGetSerial(t *testing.T) {
162	itemsCount := 10000
163	c := New(30 * itemsCount)
164	defer c.Reset()
165	if err := testCacheGetSet(c, itemsCount); err != nil {
166		t.Fatalf("unexpected error: %s", err)
167	}
168}
169
170func TestCacheGetSetConcurrent(t *testing.T) {
171	itemsCount := 10000
172	const gorotines = 10
173	c := New(30 * itemsCount * gorotines)
174	defer c.Reset()
175
176	ch := make(chan error, gorotines)
177	for i := 0; i < gorotines; i++ {
178		go func() {
179			ch <- testCacheGetSet(c, itemsCount)
180		}()
181	}
182	for i := 0; i < gorotines; i++ {
183		select {
184		case err := <-ch:
185			if err != nil {
186				t.Fatalf("unexpected error: %s", err)
187			}
188		case <-time.After(5 * time.Second):
189			t.Fatalf("timeout")
190		}
191	}
192}
193
194func testCacheGetSet(c *Cache, itemsCount int) error {
195	for i := 0; i < itemsCount; i++ {
196		k := []byte(fmt.Sprintf("key %d", i))
197		v := []byte(fmt.Sprintf("value %d", i))
198		c.Set(k, v)
199		vv := c.Get(nil, k)
200		if string(vv) != string(v) {
201			return fmt.Errorf("unexpected value for key %q after insertion; got %q; want %q", k, vv, v)
202		}
203	}
204	misses := 0
205	for i := 0; i < itemsCount; i++ {
206		k := []byte(fmt.Sprintf("key %d", i))
207		vExpected := fmt.Sprintf("value %d", i)
208		v := c.Get(nil, k)
209		if string(v) != string(vExpected) {
210			if len(v) > 0 {
211				return fmt.Errorf("unexpected value for key %q after all insertions; got %q; want %q", k, v, vExpected)
212			}
213			misses++
214		}
215	}
216	if misses >= itemsCount/100 {
217		return fmt.Errorf("too many cache misses; got %d; want less than %d", misses, itemsCount/100)
218	}
219	return nil
220}
221
222func TestCacheResetUpdateStatsSetConcurrent(t *testing.T) {
223	c := New(12334)
224
225	stopCh := make(chan struct{})
226
227	// run workers for cache reset
228	var resettersWG sync.WaitGroup
229	for i := 0; i < 10; i++ {
230		resettersWG.Add(1)
231		go func() {
232			defer resettersWG.Done()
233			for {
234				select {
235				case <-stopCh:
236					return
237				default:
238					c.Reset()
239					runtime.Gosched()
240				}
241			}
242		}()
243	}
244
245	// run workers for update cache stats
246	var statsWG sync.WaitGroup
247	for i := 0; i < 10; i++ {
248		statsWG.Add(1)
249		go func() {
250			defer statsWG.Done()
251			var s Stats
252			for {
253				select {
254				case <-stopCh:
255					return
256				default:
257					c.UpdateStats(&s)
258					runtime.Gosched()
259				}
260			}
261		}()
262	}
263
264	// run workers for setting data to cache
265	var settersWG sync.WaitGroup
266	for i := 0; i < 10; i++ {
267		settersWG.Add(1)
268		go func() {
269			defer settersWG.Done()
270			for j := 0; j < 100; j++ {
271				key := []byte(fmt.Sprintf("key_%d", j))
272				value := []byte(fmt.Sprintf("value_%d", j))
273				c.Set(key, value)
274				runtime.Gosched()
275			}
276		}()
277	}
278
279	// wait for setters
280	settersWG.Wait()
281	close(stopCh)
282	statsWG.Wait()
283	resettersWG.Wait()
284}
285