mirror of
https://github.com/blindlobstar/go-interview-problems
synced 2025-04-22 01:35:15 +00:00
234 lines
4.6 KiB
Go
234 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSetAndGet(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("key1", "value1", 0)
|
|
val, ok := cache.Get("key1")
|
|
if !ok {
|
|
t.Error("Failed to get value that was just set")
|
|
}
|
|
if val != "value1" {
|
|
t.Errorf("Expected 'value1', got %s", val)
|
|
}
|
|
|
|
val, ok = cache.Get("non-existent")
|
|
if ok {
|
|
t.Error("Get should return false for non-existent key")
|
|
}
|
|
if val != "" {
|
|
t.Errorf("Expected empty string for non-existent key, got %s", val)
|
|
}
|
|
|
|
cache.Set("key1", "updated", 0)
|
|
val, ok = cache.Get("key1")
|
|
if !ok {
|
|
t.Error("Failed to get updated value")
|
|
}
|
|
if val != "updated" {
|
|
t.Errorf("Expected 'updated', got %s", val)
|
|
}
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("key1", "value1", 0)
|
|
_, ok := cache.Get("key1")
|
|
if !ok {
|
|
t.Error("Key should exist before deletion")
|
|
}
|
|
|
|
cache.Delete("key1")
|
|
_, ok = cache.Get("key1")
|
|
if ok {
|
|
t.Error("Key should have been deleted")
|
|
}
|
|
|
|
cache.Delete("non-existent")
|
|
}
|
|
|
|
func TestExpiration(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("short", "shortvalue", 50*time.Millisecond)
|
|
cache.Set("forever", "eternalvalue", 0)
|
|
|
|
_, ok1 := cache.Get("short")
|
|
_, ok2 := cache.Get("forever")
|
|
if !ok1 || !ok2 {
|
|
t.Error("Both keys should exist initially")
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
_, ok1 = cache.Get("short")
|
|
_, ok2 = cache.Get("forever")
|
|
if ok1 {
|
|
t.Error("Key with short TTL should have expired")
|
|
}
|
|
if !ok2 {
|
|
t.Error("Key with no TTL should not expire")
|
|
}
|
|
}
|
|
|
|
func TestAutomaticCleanup(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("expiring", "value", 1*time.Second)
|
|
|
|
val, ok := cache.Get("expiring")
|
|
if !ok || val != "value" {
|
|
t.Error("Key should exist initially")
|
|
}
|
|
|
|
time.Sleep(6 * time.Second)
|
|
|
|
_, ok = cache.Get("expiring")
|
|
if ok {
|
|
t.Error("Expired key should have been automatically cleaned up")
|
|
}
|
|
}
|
|
|
|
func TestConcurrentAccess(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
numOperations := 100
|
|
|
|
for i := range 5 {
|
|
wg.Add(1)
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
for j := range numOperations {
|
|
key := "key" + string(rune('A'+workerID))
|
|
cache.Set(key, "value"+string(rune('0'+j%10)), 500*time.Millisecond)
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
for i := range 5 {
|
|
wg.Add(1)
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
for j := range numOperations {
|
|
key := "key" + string(rune('A'+j%5))
|
|
cache.Get(key)
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
for i := range 2 {
|
|
wg.Add(1)
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
for j := range numOperations / 5 {
|
|
key := "key" + string(rune('A'+j%5))
|
|
cache.Delete(key)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestStopSafety(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
|
|
cache.Stop()
|
|
|
|
cache.Set("key", "value", 0)
|
|
val, ok := cache.Get("key")
|
|
if !ok || val != "value" {
|
|
t.Error("Cache operations should still work after Stop")
|
|
}
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Error("Multiple calls to Stop should not panic")
|
|
}
|
|
}()
|
|
cache.Stop()
|
|
}
|
|
|
|
func TestEmptyStrings(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("", "empty key", 0)
|
|
val, ok := cache.Get("")
|
|
if !ok {
|
|
t.Error("Failed to get value with empty string key")
|
|
}
|
|
if val != "empty key" {
|
|
t.Errorf("Expected 'empty key', got '%s'", val)
|
|
}
|
|
|
|
cache.Set("empty-value", "", 0)
|
|
val, ok = cache.Get("empty-value")
|
|
if !ok {
|
|
t.Error("Failed to get empty string value")
|
|
}
|
|
if val != "" {
|
|
t.Errorf("Expected empty string, got '%s'", val)
|
|
}
|
|
|
|
cache.Delete("")
|
|
_, ok = cache.Get("")
|
|
if ok {
|
|
t.Error("Empty string key should be deleted")
|
|
}
|
|
}
|
|
|
|
func TestTtlUpdates(t *testing.T) {
|
|
cache := NewTtlCache()
|
|
defer cache.Stop()
|
|
|
|
cache.Set("key", "value", 100*time.Millisecond)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
cache.Set("key", "value", 500*time.Millisecond)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
val, ok := cache.Get("key")
|
|
if !ok {
|
|
t.Error("Key should not have expired after TTL update")
|
|
}
|
|
if val != "value" {
|
|
t.Errorf("Expected 'value', got '%s'", val)
|
|
}
|
|
|
|
time.Sleep(400 * time.Millisecond)
|
|
_, ok = cache.Get("key")
|
|
if ok {
|
|
t.Error("Key should have expired after the updated TTL")
|
|
}
|
|
|
|
cache.Set("convert", "value", 100*time.Millisecond)
|
|
time.Sleep(50 * time.Millisecond)
|
|
cache.Set("convert", "permanent", 0)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
val, ok = cache.Get("convert")
|
|
if !ok {
|
|
t.Error("Key should not expire after conversion to non-expiring")
|
|
}
|
|
if val != "permanent" {
|
|
t.Errorf("Expected 'permanent', got '%s'", val)
|
|
}
|
|
}
|