mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
KV lives in its own org now, like hanzokms / hanzos3 / hanzo-ds. The repo moved hanzoai/kv-go -> hanzokv/go, so the module path follows: a Go module path must match where the repo actually is, or the proxy cannot resolve it. The name is `go`, not `kv-go`, following the hanzo-ds convention — that org holds go, mock, native, sqlbuilder, sqlparser, each named for its language or concern with the ORG supplying the product noun. Repeating "kv" inside a repo that already lives under hanzokv is the compound-word habit we are dropping. 349 occurrences across 188 files: the root module, all four extra/* modules, every example module, imports throughout the tree, and the docs. Builds clean. The package is still `package redis` and the extra dirs are still named redis*. That rename is deliberately NOT bundled here: it is 5,771 occurrences, it breaks all 11 consumers at once, and a large share are WIRE CONTRACTS rather than branding — the redis:// scheme parser, the "redis: nil" error string, RESP command names. Renaming those breaks protocol compatibility with the Valkey server. It needs the same our-vocabulary-vs-the-wire split the argo sweep used, as its own change. GitHub keeps the old path redirecting, and versions already published under github.com/hanzoai/kv-go/v9 stay resolvable, so nothing breaks the moment this lands. Consumers move on their own schedule.
260 lines
7.2 KiB
Go
260 lines
7.2 KiB
Go
package pool
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzokv/go/v9/internal/proto"
|
|
)
|
|
|
|
// TestConn_UsedAtUpdatedOnRead verifies that usedAt is updated when reading from connection
|
|
func TestConn_UsedAtUpdatedOnRead(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
// Get initial usedAt time
|
|
initialUsedAt := cn.UsedAt()
|
|
|
|
// Wait 100ms to ensure time difference (usedAt has ~50ms precision from cached time)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Simulate a read operation by calling WithReader
|
|
ctx := context.Background()
|
|
err := cn.WithReader(ctx, time.Second, func(rd *proto.Reader) error {
|
|
// Don't actually read anything, just trigger the deadline update
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("WithReader failed: %v", err)
|
|
}
|
|
|
|
// Get updated usedAt time
|
|
updatedUsedAt := cn.UsedAt()
|
|
|
|
// Verify that usedAt was updated
|
|
if !updatedUsedAt.After(initialUsedAt) {
|
|
t.Errorf("Expected usedAt to be updated after read. Initial: %v, Updated: %v",
|
|
initialUsedAt, updatedUsedAt)
|
|
}
|
|
|
|
// Verify the difference is reasonable (should be around 100ms, accounting for ~50ms cache precision and ~5ms sleep precision)
|
|
diff := updatedUsedAt.Sub(initialUsedAt)
|
|
if diff < 45*time.Millisecond || diff > 155*time.Millisecond {
|
|
t.Errorf("Expected usedAt difference to be around 100ms (±50ms for cache, ±5ms for sleep), got %v", diff)
|
|
}
|
|
}
|
|
|
|
// TestConn_UsedAtUpdatedOnWrite verifies that usedAt is updated when writing to connection
|
|
func TestConn_UsedAtUpdatedOnWrite(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
// Get initial usedAt time
|
|
initialUsedAt := cn.UsedAt()
|
|
|
|
// Wait at least 100ms to ensure time difference (usedAt has ~50ms precision from cached time)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Simulate a write operation by calling WithWriter
|
|
ctx := context.Background()
|
|
err := cn.WithWriter(ctx, time.Second, func(wr *proto.Writer) error {
|
|
// Don't actually write anything, just trigger the deadline update
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("WithWriter failed: %v", err)
|
|
}
|
|
|
|
// Get updated usedAt time
|
|
updatedUsedAt := cn.UsedAt()
|
|
|
|
// Verify that usedAt was updated
|
|
if !updatedUsedAt.After(initialUsedAt) {
|
|
t.Errorf("Expected usedAt to be updated after write. Initial: %v, Updated: %v",
|
|
initialUsedAt, updatedUsedAt)
|
|
}
|
|
|
|
// Verify the difference is reasonable (should be around 100ms, accounting for ~50ms cache precision)
|
|
diff := updatedUsedAt.Sub(initialUsedAt)
|
|
|
|
// 50 ms is the cache precision, so we allow up to 110ms difference
|
|
if diff < 45*time.Millisecond || diff > 155*time.Millisecond {
|
|
t.Errorf("Expected usedAt difference to be around 100 (±50ms for cache) (+-5ms for sleep precision), got %v", diff)
|
|
}
|
|
}
|
|
|
|
// TestConn_UsedAtUpdatedOnMultipleOperations verifies that usedAt is updated on each operation
|
|
func TestConn_UsedAtUpdatedOnMultipleOperations(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
ctx := context.Background()
|
|
var previousUsedAt time.Time
|
|
|
|
// Perform multiple operations and verify usedAt is updated each time
|
|
// Note: usedAt has ~50ms precision from cached time
|
|
for i := 0; i < 5; i++ {
|
|
currentUsedAt := cn.UsedAt()
|
|
|
|
if i > 0 {
|
|
// Verify usedAt was updated from previous iteration
|
|
if !currentUsedAt.After(previousUsedAt) {
|
|
t.Errorf("Iteration %d: Expected usedAt to be updated. Previous: %v, Current: %v",
|
|
i, previousUsedAt, currentUsedAt)
|
|
}
|
|
}
|
|
|
|
previousUsedAt = currentUsedAt
|
|
|
|
// Wait at least 100ms (accounting for ~50ms cache precision)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Perform a read operation
|
|
err := cn.WithReader(ctx, time.Second, func(rd *proto.Reader) error {
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Iteration %d: WithReader failed: %v", i, err)
|
|
}
|
|
}
|
|
|
|
// Verify final usedAt is significantly later than initial
|
|
finalUsedAt := cn.UsedAt()
|
|
if !finalUsedAt.After(previousUsedAt) {
|
|
t.Errorf("Expected final usedAt to be updated. Previous: %v, Final: %v",
|
|
previousUsedAt, finalUsedAt)
|
|
}
|
|
}
|
|
|
|
// TestConn_UsedAtNotUpdatedWithoutOperation verifies that usedAt is NOT updated without operations
|
|
func TestConn_UsedAtNotUpdatedWithoutOperation(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
// Get initial usedAt time
|
|
initialUsedAt := cn.UsedAt()
|
|
|
|
// Wait without performing any operations
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Get usedAt time again
|
|
currentUsedAt := cn.UsedAt()
|
|
|
|
// Verify that usedAt was NOT updated (should be the same)
|
|
if !currentUsedAt.Equal(initialUsedAt) {
|
|
t.Errorf("Expected usedAt to remain unchanged without operations. Initial: %v, Current: %v",
|
|
initialUsedAt, currentUsedAt)
|
|
}
|
|
}
|
|
|
|
// TestConn_UsedAtConcurrentUpdates verifies that usedAt updates are thread-safe
|
|
func TestConn_UsedAtConcurrentUpdates(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
ctx := context.Background()
|
|
const numGoroutines = 10
|
|
const numIterations = 10
|
|
|
|
// Launch multiple goroutines that perform operations concurrently
|
|
done := make(chan bool, numGoroutines)
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func() {
|
|
for j := 0; j < numIterations; j++ {
|
|
// Alternate between read and write operations
|
|
if j%2 == 0 {
|
|
_ = cn.WithReader(ctx, time.Second, func(rd *proto.Reader) error {
|
|
return nil
|
|
})
|
|
} else {
|
|
_ = cn.WithWriter(ctx, time.Second, func(wr *proto.Writer) error {
|
|
return nil
|
|
})
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < numGoroutines; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Verify that usedAt was updated (should be recent)
|
|
usedAt := cn.UsedAt()
|
|
timeSinceUsed := time.Since(usedAt)
|
|
|
|
// Should be very recent (within last second)
|
|
if timeSinceUsed > time.Second {
|
|
t.Errorf("Expected usedAt to be recent, but it was %v ago", timeSinceUsed)
|
|
}
|
|
}
|
|
|
|
// TestConn_UsedAtPrecision verifies that usedAt has 50ms precision (not nanosecond)
|
|
func TestConn_UsedAtPrecision(t *testing.T) {
|
|
// Create a mock connection
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
cn := NewConn(client)
|
|
defer cn.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Perform an operation
|
|
err := cn.WithReader(ctx, time.Second, func(rd *proto.Reader) error {
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("WithReader failed: %v", err)
|
|
}
|
|
|
|
// Get usedAt time
|
|
usedAt := cn.UsedAt()
|
|
|
|
// Verify that usedAt has nanosecond precision (from the cached time which updates every 50ms)
|
|
// The value should be reasonable (not year 1970 or something)
|
|
if usedAt.Year() < 2020 {
|
|
t.Errorf("Expected usedAt to be a recent time, got %v", usedAt)
|
|
}
|
|
|
|
// The nanoseconds might be non-zero depending on when the cache was updated
|
|
// We just verify the time is stored with full precision (not truncated to seconds)
|
|
initialNanos := usedAt.UnixNano()
|
|
if initialNanos == 0 {
|
|
t.Error("Expected usedAt to have nanosecond precision, got 0")
|
|
}
|
|
}
|