update
This commit is contained in:
parent
8c8a794873
commit
a54e8cddbc
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package bytes2 gives you alternate implementations of functionality
|
||||||
|
// similar to go's bytes package
|
||||||
|
|
||||||
|
package bytes2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/siddontang/go/hack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChunkedWriter has the same interface as bytes.Buffer's write functions.
|
||||||
|
// It additionally provides a Reserve function that returns a []byte that
|
||||||
|
// the caller can directly change.
|
||||||
|
type ChunkedWriter struct {
|
||||||
|
bufs [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChunkedWriter(chunkSize int) *ChunkedWriter {
|
||||||
|
cw := &ChunkedWriter{make([][]byte, 1)}
|
||||||
|
cw.bufs[0] = make([]byte, 0, chunkSize)
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes This function can get expensive for large buffers.
|
||||||
|
func (cw *ChunkedWriter) Bytes() (b []byte) {
|
||||||
|
if len(cw.bufs) == 1 {
|
||||||
|
return cw.bufs[0]
|
||||||
|
}
|
||||||
|
b = make([]byte, 0, cw.Len())
|
||||||
|
for _, buf := range cw.bufs {
|
||||||
|
b = append(b, buf...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) Len() int {
|
||||||
|
l := 0
|
||||||
|
for _, buf := range cw.bufs {
|
||||||
|
l += len(buf)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) Reset() {
|
||||||
|
b := cw.bufs[0][:0]
|
||||||
|
cw.bufs = make([][]byte, 1)
|
||||||
|
cw.bufs[0] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) Truncate(n int) {
|
||||||
|
for i, buf := range cw.bufs {
|
||||||
|
if n > len(buf) {
|
||||||
|
n -= len(buf)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cw.bufs[i] = buf[:n]
|
||||||
|
cw.bufs = cw.bufs[:i+1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("bytes.ChunkedBuffer: truncation out of range")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) Write(p []byte) (n int, err error) {
|
||||||
|
return cw.WriteString(hack.String(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) WriteString(p string) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
lastbuf := cw.bufs[len(cw.bufs)-1]
|
||||||
|
for {
|
||||||
|
available := cap(lastbuf) - len(lastbuf)
|
||||||
|
required := len(p)
|
||||||
|
if available >= required {
|
||||||
|
cw.bufs[len(cw.bufs)-1] = append(lastbuf, p...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cw.bufs[len(cw.bufs)-1] = append(lastbuf, p[:available]...)
|
||||||
|
p = p[available:]
|
||||||
|
lastbuf = make([]byte, 0, cap(cw.bufs[0]))
|
||||||
|
cw.bufs = append(cw.bufs, lastbuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) Reserve(n int) (b []byte) {
|
||||||
|
if n > cap(cw.bufs[0]) {
|
||||||
|
panic(fmt.Sprintf("bytes.ChunkedBuffer: Reserve request too high: %d > %d", n, cap(cw.bufs[0])))
|
||||||
|
}
|
||||||
|
lastbuf := cw.bufs[len(cw.bufs)-1]
|
||||||
|
if n > cap(lastbuf)-len(lastbuf) {
|
||||||
|
b = make([]byte, n, cap(cw.bufs[0]))
|
||||||
|
cw.bufs = append(cw.bufs, b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
l := len(lastbuf)
|
||||||
|
b = lastbuf[l : n+l]
|
||||||
|
cw.bufs[len(cw.bufs)-1] = lastbuf[:n+l]
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) WriteByte(c byte) error {
|
||||||
|
cw.Reserve(1)[0] = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) WriteRune(r rune) (n int, err error) {
|
||||||
|
n = utf8.EncodeRune(cw.Reserve(utf8.RuneLen(r)), r)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *ChunkedWriter) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
for _, buf := range cw.bufs {
|
||||||
|
m, err := w.Write(buf)
|
||||||
|
n += int64(m)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if m != len(buf) {
|
||||||
|
return n, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cw.Reset()
|
||||||
|
return n, nil
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package bytes2 gives you alternate implementations of functionality
|
||||||
|
// similar to go's bytes package
|
||||||
|
|
||||||
|
package bytes2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
cw := NewChunkedWriter(5)
|
||||||
|
cw.Write([]byte("1234"))
|
||||||
|
if string(cw.Bytes()) != "1234" {
|
||||||
|
t.Errorf("Expecting 1234, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
cw.WriteString("56")
|
||||||
|
if string(cw.Bytes()) != "123456" {
|
||||||
|
t.Errorf("Expecting 123456, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
if cw.Len() != 6 {
|
||||||
|
t.Errorf("Expecting 6, received %d", cw.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
cw := NewChunkedWriter(3)
|
||||||
|
cw.WriteString("123456789")
|
||||||
|
cw.Truncate(8)
|
||||||
|
if string(cw.Bytes()) != "12345678" {
|
||||||
|
t.Errorf("Expecting 12345678, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
cw.Truncate(5)
|
||||||
|
if string(cw.Bytes()) != "12345" {
|
||||||
|
t.Errorf("Expecting 12345, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
cw.Truncate(2)
|
||||||
|
if string(cw.Bytes()) != "12" {
|
||||||
|
t.Errorf("Expecting 12345, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
cw.Reset()
|
||||||
|
if cw.Len() != 0 {
|
||||||
|
t.Errorf("Expecting 0, received %d", cw.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReserve(t *testing.T) {
|
||||||
|
cw := NewChunkedWriter(4)
|
||||||
|
b := cw.Reserve(2)
|
||||||
|
b[0] = '1'
|
||||||
|
b[1] = '2'
|
||||||
|
cw.WriteByte('3')
|
||||||
|
b = cw.Reserve(2)
|
||||||
|
b[0] = '4'
|
||||||
|
b[1] = '5'
|
||||||
|
if string(cw.Bytes()) != "12345" {
|
||||||
|
t.Errorf("Expecting 12345, received %s", cw.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteTo(t *testing.T) {
|
||||||
|
cw1 := NewChunkedWriter(4)
|
||||||
|
cw1.WriteString("123456789")
|
||||||
|
cw2 := NewChunkedWriter(5)
|
||||||
|
cw1.WriteTo(cw2)
|
||||||
|
if string(cw2.Bytes()) != "123456789" {
|
||||||
|
t.Errorf("Expecting 123456789, received %s", cw2.Bytes())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// cache implements a LRU cache. The implementation borrows heavily
|
||||||
|
// from SmallLRUCache (originally by Nathan Schrenk). The object
|
||||||
|
// maintains a doubly-linked list of elements. When an element is
|
||||||
|
// accessed it is promoted to the head of the list, and when space is
|
||||||
|
// needed the element at the tail of the list (the least recently used
|
||||||
|
// element) is evicted.
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LRUCache is a typical LRU cache implementation. If the cache
|
||||||
|
// reaches the capacity, the least recently used item is deleted from
|
||||||
|
// the cache. Note the capacity is not the number of items, but the
|
||||||
|
// total sum of the Size() of each item.
|
||||||
|
type LRUCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// list & table of *entry objects
|
||||||
|
list *list.List
|
||||||
|
table map[string]*list.Element
|
||||||
|
|
||||||
|
// Our current size. Obviously a gross simplification and
|
||||||
|
// low-grade approximation.
|
||||||
|
size int64
|
||||||
|
|
||||||
|
// How much we are limiting the cache to.
|
||||||
|
capacity int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value is the interface values that go into LRUCache need to satisfy
|
||||||
|
type Value interface {
|
||||||
|
// Size returns how big this value is.
|
||||||
|
Size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item is what is stored in the cache
|
||||||
|
type Item struct {
|
||||||
|
Key string
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
key string
|
||||||
|
value Value
|
||||||
|
size int64
|
||||||
|
time_accessed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLRUCache creates a new empty cache with the given capacity.
|
||||||
|
func NewLRUCache(capacity int64) *LRUCache {
|
||||||
|
return &LRUCache{
|
||||||
|
list: list.New(),
|
||||||
|
table: make(map[string]*list.Element),
|
||||||
|
capacity: capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value from the cache, and marks the entry as most
|
||||||
|
// recently used.
|
||||||
|
func (lru *LRUCache) Get(key string) (v Value, ok bool) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
element := lru.table[key]
|
||||||
|
if element == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
lru.moveToFront(element)
|
||||||
|
return element.Value.(*entry).value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a value in the cache.
|
||||||
|
func (lru *LRUCache) Set(key string, value Value) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
if element := lru.table[key]; element != nil {
|
||||||
|
lru.updateInplace(element, value)
|
||||||
|
} else {
|
||||||
|
lru.addNew(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIfAbsent will set the value in the cache if not present. If the
|
||||||
|
// value exists in the cache, we don't set it.
|
||||||
|
func (lru *LRUCache) SetIfAbsent(key string, value Value) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
if element := lru.table[key]; element != nil {
|
||||||
|
lru.moveToFront(element)
|
||||||
|
} else {
|
||||||
|
lru.addNew(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an entry from the cache, and returns if the entry existed.
|
||||||
|
func (lru *LRUCache) Delete(key string) bool {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
element := lru.table[key]
|
||||||
|
if element == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lru.list.Remove(element)
|
||||||
|
delete(lru.table, key)
|
||||||
|
lru.size -= element.Value.(*entry).size
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear will clear the entire cache.
|
||||||
|
func (lru *LRUCache) Clear() {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
lru.list.Init()
|
||||||
|
lru.table = make(map[string]*list.Element)
|
||||||
|
lru.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity will set the capacity of the cache. If the capacity is
|
||||||
|
// smaller, and the current cache size exceed that capacity, the cache
|
||||||
|
// will be shrank.
|
||||||
|
func (lru *LRUCache) SetCapacity(capacity int64) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
lru.capacity = capacity
|
||||||
|
lru.checkCapacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns a few stats on the cache.
|
||||||
|
func (lru *LRUCache) Stats() (length, size, capacity int64, oldest time.Time) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
if lastElem := lru.list.Back(); lastElem != nil {
|
||||||
|
oldest = lastElem.Value.(*entry).time_accessed
|
||||||
|
}
|
||||||
|
return int64(lru.list.Len()), lru.size, lru.capacity, oldest
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsJSON returns stats as a JSON object in a string.
|
||||||
|
func (lru *LRUCache) StatsJSON() string {
|
||||||
|
if lru == nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
l, s, c, o := lru.Stats()
|
||||||
|
return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length returns how many elements are in the cache
|
||||||
|
func (lru *LRUCache) Length() int64 {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
return int64(lru.list.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the sum of the objects' Size() method.
|
||||||
|
func (lru *LRUCache) Size() int64 {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
return lru.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity returns the cache maximum capacity.
|
||||||
|
func (lru *LRUCache) Capacity() int64 {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
return lru.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oldest returns the insertion time of the oldest element in the cache,
|
||||||
|
// or a IsZero() time if cache is empty.
|
||||||
|
func (lru *LRUCache) Oldest() (oldest time.Time) {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
if lastElem := lru.list.Back(); lastElem != nil {
|
||||||
|
oldest = lastElem.Value.(*entry).time_accessed
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns all the keys for the cache, ordered from most recently
|
||||||
|
// used to last recently used.
|
||||||
|
func (lru *LRUCache) Keys() []string {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
keys := make([]string, 0, lru.list.Len())
|
||||||
|
for e := lru.list.Front(); e != nil; e = e.Next() {
|
||||||
|
keys = append(keys, e.Value.(*entry).key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns all the values for the cache, ordered from most recently
|
||||||
|
// used to last recently used.
|
||||||
|
func (lru *LRUCache) Items() []Item {
|
||||||
|
lru.mu.Lock()
|
||||||
|
defer lru.mu.Unlock()
|
||||||
|
|
||||||
|
items := make([]Item, 0, lru.list.Len())
|
||||||
|
for e := lru.list.Front(); e != nil; e = e.Next() {
|
||||||
|
v := e.Value.(*entry)
|
||||||
|
items = append(items, Item{Key: v.key, Value: v.value})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lru *LRUCache) updateInplace(element *list.Element, value Value) {
|
||||||
|
valueSize := int64(value.Size())
|
||||||
|
sizeDiff := valueSize - element.Value.(*entry).size
|
||||||
|
element.Value.(*entry).value = value
|
||||||
|
element.Value.(*entry).size = valueSize
|
||||||
|
lru.size += sizeDiff
|
||||||
|
lru.moveToFront(element)
|
||||||
|
lru.checkCapacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lru *LRUCache) moveToFront(element *list.Element) {
|
||||||
|
lru.list.MoveToFront(element)
|
||||||
|
element.Value.(*entry).time_accessed = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lru *LRUCache) addNew(key string, value Value) {
|
||||||
|
newEntry := &entry{key, value, int64(value.Size()), time.Now()}
|
||||||
|
element := lru.list.PushFront(newEntry)
|
||||||
|
lru.table[key] = element
|
||||||
|
lru.size += newEntry.size
|
||||||
|
lru.checkCapacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lru *LRUCache) checkCapacity() {
|
||||||
|
// Partially duplicated from Delete
|
||||||
|
for lru.size > lru.capacity {
|
||||||
|
delElem := lru.list.Back()
|
||||||
|
delValue := delElem.Value.(*entry)
|
||||||
|
lru.list.Remove(delElem)
|
||||||
|
delete(lru.table, delValue.key)
|
||||||
|
lru.size -= delValue.size
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheValue struct {
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *CacheValue) Size() int {
|
||||||
|
return cv.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitialState(t *testing.T) {
|
||||||
|
cache := NewLRUCache(5)
|
||||||
|
l, sz, c, _ := cache.Stats()
|
||||||
|
if l != 0 {
|
||||||
|
t.Errorf("length = %v, want 0", l)
|
||||||
|
}
|
||||||
|
if sz != 0 {
|
||||||
|
t.Errorf("size = %v, want 0", sz)
|
||||||
|
}
|
||||||
|
if c != 5 {
|
||||||
|
t.Errorf("capacity = %v, want 5", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetInsertsValue(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
data := &CacheValue{0}
|
||||||
|
key := "key"
|
||||||
|
cache.Set(key, data)
|
||||||
|
|
||||||
|
v, ok := cache.Get(key)
|
||||||
|
if !ok || v.(*CacheValue) != data {
|
||||||
|
t.Errorf("Cache has incorrect value: %v != %v", data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := cache.Keys()
|
||||||
|
if len(k) != 1 || k[0] != key {
|
||||||
|
t.Errorf("Cache.Keys() returned incorrect values: %v", k)
|
||||||
|
}
|
||||||
|
values := cache.Items()
|
||||||
|
if len(values) != 1 || values[0].Key != key {
|
||||||
|
t.Errorf("Cache.Values() returned incorrect values: %v", values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetIfAbsent(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
data := &CacheValue{0}
|
||||||
|
key := "key"
|
||||||
|
cache.SetIfAbsent(key, data)
|
||||||
|
|
||||||
|
v, ok := cache.Get(key)
|
||||||
|
if !ok || v.(*CacheValue) != data {
|
||||||
|
t.Errorf("Cache has incorrect value: %v != %v", data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.SetIfAbsent(key, &CacheValue{1})
|
||||||
|
|
||||||
|
v, ok = cache.Get(key)
|
||||||
|
if !ok || v.(*CacheValue) != data {
|
||||||
|
t.Errorf("Cache has incorrect value: %v != %v", data, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValueWithMultipleTypes(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
data := &CacheValue{0}
|
||||||
|
key := "key"
|
||||||
|
cache.Set(key, data)
|
||||||
|
|
||||||
|
v, ok := cache.Get("key")
|
||||||
|
if !ok || v.(*CacheValue) != data {
|
||||||
|
t.Errorf("Cache has incorrect value for \"key\": %v != %v", data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok = cache.Get(string([]byte{'k', 'e', 'y'}))
|
||||||
|
if !ok || v.(*CacheValue) != data {
|
||||||
|
t.Errorf("Cache has incorrect value for []byte {'k','e','y'}: %v != %v", data, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetUpdatesSize(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
emptyValue := &CacheValue{0}
|
||||||
|
key := "key1"
|
||||||
|
cache.Set(key, emptyValue)
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != 0 {
|
||||||
|
t.Errorf("cache.Size() = %v, expected 0", sz)
|
||||||
|
}
|
||||||
|
someValue := &CacheValue{20}
|
||||||
|
key = "key2"
|
||||||
|
cache.Set(key, someValue)
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != 20 {
|
||||||
|
t.Errorf("cache.Size() = %v, expected 20", sz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetWithOldKeyUpdatesValue(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
emptyValue := &CacheValue{0}
|
||||||
|
key := "key1"
|
||||||
|
cache.Set(key, emptyValue)
|
||||||
|
someValue := &CacheValue{20}
|
||||||
|
cache.Set(key, someValue)
|
||||||
|
|
||||||
|
v, ok := cache.Get(key)
|
||||||
|
if !ok || v.(*CacheValue) != someValue {
|
||||||
|
t.Errorf("Cache has incorrect value: %v != %v", someValue, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetWithOldKeyUpdatesSize(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
emptyValue := &CacheValue{0}
|
||||||
|
key := "key1"
|
||||||
|
cache.Set(key, emptyValue)
|
||||||
|
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != 0 {
|
||||||
|
t.Errorf("cache.Size() = %v, expected %v", sz, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
someValue := &CacheValue{20}
|
||||||
|
cache.Set(key, someValue)
|
||||||
|
expected := int64(someValue.size)
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != expected {
|
||||||
|
t.Errorf("cache.Size() = %v, expected %v", sz, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNonExistent(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
|
||||||
|
if _, ok := cache.Get("crap"); ok {
|
||||||
|
t.Error("Cache returned a crap value after no inserts.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
value := &CacheValue{1}
|
||||||
|
key := "key"
|
||||||
|
|
||||||
|
if cache.Delete(key) {
|
||||||
|
t.Error("Item unexpectedly already in cache.")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Set(key, value)
|
||||||
|
|
||||||
|
if !cache.Delete(key) {
|
||||||
|
t.Error("Expected item to be in cache.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != 0 {
|
||||||
|
t.Errorf("cache.Size() = %v, expected 0", sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cache.Get(key); ok {
|
||||||
|
t.Error("Cache returned a value after deletion.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClear(t *testing.T) {
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
value := &CacheValue{1}
|
||||||
|
key := "key"
|
||||||
|
|
||||||
|
cache.Set(key, value)
|
||||||
|
cache.Clear()
|
||||||
|
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != 0 {
|
||||||
|
t.Errorf("cache.Size() = %v, expected 0 after Clear()", sz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapacityIsObeyed(t *testing.T) {
|
||||||
|
size := int64(3)
|
||||||
|
cache := NewLRUCache(100)
|
||||||
|
cache.SetCapacity(size)
|
||||||
|
value := &CacheValue{1}
|
||||||
|
|
||||||
|
// Insert up to the cache's capacity.
|
||||||
|
cache.Set("key1", value)
|
||||||
|
cache.Set("key2", value)
|
||||||
|
cache.Set("key3", value)
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != size {
|
||||||
|
t.Errorf("cache.Size() = %v, expected %v", sz, size)
|
||||||
|
}
|
||||||
|
// Insert one more; something should be evicted to make room.
|
||||||
|
cache.Set("key4", value)
|
||||||
|
if _, sz, _, _ := cache.Stats(); sz != size {
|
||||||
|
t.Errorf("post-evict cache.Size() = %v, expected %v", sz, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check json stats
|
||||||
|
data := cache.StatsJSON()
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(data), &m); err != nil {
|
||||||
|
t.Errorf("cache.StatsJSON() returned bad json data: %v %v", data, err)
|
||||||
|
}
|
||||||
|
if m["Size"].(float64) != float64(size) {
|
||||||
|
t.Errorf("cache.StatsJSON() returned bad size: %v", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check various other stats
|
||||||
|
if l := cache.Length(); l != size {
|
||||||
|
t.Errorf("cache.StatsJSON() returned bad length: %v", l)
|
||||||
|
}
|
||||||
|
if s := cache.Size(); s != size {
|
||||||
|
t.Errorf("cache.StatsJSON() returned bad size: %v", s)
|
||||||
|
}
|
||||||
|
if c := cache.Capacity(); c != size {
|
||||||
|
t.Errorf("cache.StatsJSON() returned bad length: %v", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks StatsJSON on nil
|
||||||
|
cache = nil
|
||||||
|
if s := cache.StatsJSON(); s != "{}" {
|
||||||
|
t.Errorf("cache.StatsJSON() on nil object returned %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUIsEvicted(t *testing.T) {
|
||||||
|
size := int64(3)
|
||||||
|
cache := NewLRUCache(size)
|
||||||
|
|
||||||
|
cache.Set("key1", &CacheValue{1})
|
||||||
|
cache.Set("key2", &CacheValue{1})
|
||||||
|
cache.Set("key3", &CacheValue{1})
|
||||||
|
// lru: [key3, key2, key1]
|
||||||
|
|
||||||
|
// Look up the elements. This will rearrange the LRU ordering.
|
||||||
|
cache.Get("key3")
|
||||||
|
beforeKey2 := time.Now()
|
||||||
|
cache.Get("key2")
|
||||||
|
afterKey2 := time.Now()
|
||||||
|
cache.Get("key1")
|
||||||
|
// lru: [key1, key2, key3]
|
||||||
|
|
||||||
|
cache.Set("key0", &CacheValue{1})
|
||||||
|
// lru: [key0, key1, key2]
|
||||||
|
|
||||||
|
// The least recently used one should have been evicted.
|
||||||
|
if _, ok := cache.Get("key3"); ok {
|
||||||
|
t.Error("Least recently used element was not evicted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check oldest
|
||||||
|
if o := cache.Oldest(); o.Before(beforeKey2) || o.After(afterKey2) {
|
||||||
|
t.Errorf("cache.Oldest returned an unexpected value: got %v, expected a value between %v and %v", o, beforeKey2, afterKey2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyValue []byte
|
||||||
|
|
||||||
|
func (mv MyValue) Size() int {
|
||||||
|
return cap(mv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGet(b *testing.B) {
|
||||||
|
cache := NewLRUCache(64 * 1024 * 1024)
|
||||||
|
value := make(MyValue, 1000)
|
||||||
|
cache.Set("stuff", value)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
val, ok := cache.Get("stuff")
|
||||||
|
if !ok {
|
||||||
|
panic("error")
|
||||||
|
}
|
||||||
|
_ = val
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package exec
|
package exec2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
|
@ -1,4 +1,4 @@
|
||||||
package exec
|
package exec2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
16
hack/hack.go
16
hack/hack.go
|
@ -25,19 +25,3 @@ func Slice(s string) (b []byte) {
|
||||||
pbytes.Cap = pstring.Len
|
pbytes.Cap = pstring.Len
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Int64Slice(v int64) (b []byte) {
|
|
||||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
|
||||||
pbytes.Data = uintptr(unsafe.Pointer(&v))
|
|
||||||
pbytes.Len = 8
|
|
||||||
pbytes.Cap = 8
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int32Slice(v int32) (b []byte) {
|
|
||||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
|
||||||
pbytes.Data = uintptr(unsafe.Pointer(&v))
|
|
||||||
pbytes.Len = 4
|
|
||||||
pbytes.Cap = 4
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,21 +35,3 @@ func TestByte(t *testing.T) {
|
||||||
t.Fatal(string(b))
|
t.Fatal(string(b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInt(t *testing.T) {
|
|
||||||
if int64(binary.LittleEndian.Uint64(IntSlice(1))) != 1 {
|
|
||||||
t.Fatal("error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if int64(binary.LittleEndian.Uint64(IntSlice(-1))) != -1 {
|
|
||||||
t.Fatal("error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if int64(binary.LittleEndian.Uint64(IntSlice(32768))) != 32768 {
|
|
||||||
t.Fatal(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if int64(binary.LittleEndian.Uint64(IntSlice(-32768))) != -32768 {
|
|
||||||
t.Fatal(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ioext
|
package io2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,4 +1,4 @@
|
||||||
package ioext
|
package io2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2012, Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ioutil2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Write file to temp and atomically move when everything else succeeds.
|
||||||
|
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
|
||||||
|
dir, name := path.Split(filename)
|
||||||
|
f, err := ioutil.TempFile(dir, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := f.Write(data)
|
||||||
|
f.Close()
|
||||||
|
if err == nil && n < len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
} else {
|
||||||
|
err = os.Chmod(f.Name(), perm)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(f.Name(), filename)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package list
|
package list2
|
||||||
|
|
||||||
const defaultSize = 1024
|
const defaultSize = 1024
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package list
|
package list2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
|
@ -1,4 +1,4 @@
|
||||||
package list
|
package list2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
|
@ -1,4 +1,4 @@
|
||||||
package timer
|
package time2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
|
@ -1,4 +1,4 @@
|
||||||
package timer
|
package time2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
|
@ -1,4 +1,4 @@
|
||||||
package timer
|
package time2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
|
@ -1,4 +1,4 @@
|
||||||
package timer
|
package time2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2012, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
Loading…
Reference in New Issue