blob: 457ea85bd963b9658b7024cbf7669c67ff498c3a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package namespace
import (
"fmt"
"testing"
"time"
"v.io/v23/naming"
vdltime "v.io/v23/vdlroot/time"
"v.io/x/ref/test"
)
func compatible(server string, servers []naming.MountedServer) bool {
if len(servers) == 0 {
return server == ""
}
return servers[0].Server == server
}
func future(secs uint32) vdltime.Deadline {
return vdltime.Deadline{Time: time.Now().Add(time.Duration(secs) * time.Second)}
}
// TestCache tests the cache directly rather than via the namespace methods.
func TestCache(t *testing.T) {
ctx, cancel := test.TestContext()
defer cancel()
preload := []struct {
name string
suffix string
server string
}{
{"/h1//a/b/c/d", "c/d", "/h2"},
{"/h2//c/d", "d", "/h3"},
{"/h3//d", "", "/h4:1234"},
}
c := newTTLCache()
for _, p := range preload {
e := &naming.MountEntry{Name: p.suffix, Servers: []naming.MountedServer{{Server: p.server, Deadline: future(30)}}}
c.remember(ctx, p.name, e)
}
tests := []struct {
name string
suffix string
server string
succeed bool
}{
{"/h1//a/b/c/d", "c/d", "/h2", true},
{"/h2//c/d", "d", "/h3", true},
{"/h3//d", "", "/h4:1234", true},
{"/notintcache", "", "", false},
{"/h1//a/b/f//g", "f/g", "/h2", true},
{"/h3//d//e", "e", "/h4:1234", true},
}
for _, p := range tests {
e, err := c.lookup(ctx, p.name)
if (err == nil) != p.succeed {
t.Errorf("%s: lookup failed", p.name)
}
if e.Name != p.suffix || !compatible(p.server, e.Servers) {
t.Errorf("%s: got %v, %s not %s, %s", p.name, e.Name, e.Servers, p.suffix, p.server)
}
}
}
func TestCacheLimit(t *testing.T) {
ctx, cancel := test.TestContext()
defer cancel()
c := newTTLCache().(*ttlCache)
e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "the rain in spain", Deadline: future(3000)}}}
for i := 0; i < maxCacheEntries; i++ {
c.remember(ctx, fmt.Sprintf("%d", i), e)
if len(c.entries) > maxCacheEntries {
t.Errorf("unexpected cache size: got %d not %d", len(c.entries), maxCacheEntries)
}
}
// Adding one more element should reduce us to 3/4 full.
c.remember(ctx, fmt.Sprintf("%d", maxCacheEntries), e)
if len(c.entries) != cacheHisteresisSize {
t.Errorf("cache shrunk wrong amount: got %d not %d", len(c.entries), cacheHisteresisSize)
}
}
func TestCacheTTL(t *testing.T) {
ctx, cancel := test.TestContext()
defer cancel()
before := vdltime.Deadline{Time: time.Now()}
c := newTTLCache().(*ttlCache)
// Fill cache.
e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "the rain in spain", Deadline: future(3000)}}}
for i := 0; i < maxCacheEntries; i++ {
c.remember(ctx, fmt.Sprintf("%d", i), e)
}
// Time out half the entries.
i := len(c.entries) / 2
for k := range c.entries {
c.entries[k].Servers[0].Deadline = before
if i == 0 {
break
}
i--
}
// Add an entry and make sure we now have room.
c.remember(ctx, fmt.Sprintf("%d", maxCacheEntries+2), e)
if len(c.entries) > cacheHisteresisSize {
t.Errorf("entries did not timeout: got %d not %d", len(c.entries), cacheHisteresisSize)
}
}
func TestFlushCacheEntry(t *testing.T) {
ctx, cancel := test.TestContext()
defer cancel()
preload := []struct {
name string
server string
}{
{"/h1//a/b", "/h2//c"},
{"/h2//c", "/h3"},
{"/h3//d", "/h4:1234"},
}
ns, _ := New()
c := ns.resolutionCache.(*ttlCache)
for _, p := range preload {
e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "p.server", Deadline: future(3000)}}}
c.remember(ctx, p.name, e)
}
toflush := "/h1/xyzzy"
if ns.FlushCacheEntry(ctx, toflush) {
t.Errorf("%s should not have caused anything to flush", toflush)
}
toflush = "/h1/a/b/d/e"
if !ns.FlushCacheEntry(ctx, toflush) {
t.Errorf("%s should have caused something to flush", toflush)
}
name := preload[2].name
if _, err := c.lookup(ctx, name); err != nil {
t.Errorf("%s should not have been flushed", name)
}
if len(c.entries) != 2 {
t.Errorf("%s flushed too many entries", toflush)
}
toflush = preload[1].name
if !ns.FlushCacheEntry(ctx, toflush) {
t.Errorf("%s should have caused something to flush", toflush)
}
if _, ok := c.entries[toflush]; ok {
t.Errorf("%s should have been flushed", name)
}
if len(c.entries) != 1 {
t.Errorf("%s flushed too many entries", toflush)
}
}
func disabled(ctls []naming.CacheCtl) bool {
for _, c := range ctls {
if v, ok := c.(naming.DisableCache); ok && bool(v) {
return true
}
}
return false
}
func TestCacheDisableEnable(t *testing.T) {
ctx, cancel := test.TestContext()
defer cancel()
ns, _ := New()
// Default should be working resolution cache.
name := "/h1//a"
serverName := "/h2//"
c := ns.resolutionCache.(*ttlCache)
e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: serverName, Deadline: future(3000)}}}
c.remember(ctx, name, e)
if ne, err := c.lookup(ctx, name); err != nil || ne.Servers[0].Server != serverName {
t.Errorf("should have found the server in the cache")
}
// Turn off the resolution cache.
ctls := ns.CacheCtl(naming.DisableCache(true))
if !disabled(ctls) {
t.Errorf("caching not disabled")
}
nc := ns.resolutionCache.(nullCache)
nc.remember(ctx, name, e)
if _, err := nc.lookup(ctx, name); err == nil {
t.Errorf("should not have found the server in the cache")
}
// Turn on the resolution cache.
ctls = ns.CacheCtl(naming.DisableCache(false))
if disabled(ctls) {
t.Errorf("caching disabled")
}
c = ns.resolutionCache.(*ttlCache)
c.remember(ctx, name, e)
if ne, err := c.lookup(ctx, name); err != nil || ne.Servers[0].Server != serverName {
t.Errorf("should have found the server in the cache")
}
}