moby/pkg/truncindex/truncindex_test.go
William Martin 57dada5338 Fix non thread-safe Iteration around go-patricia
* Unlike other methods in truncindex, Iterate was not locking before
using the Trie, making it potentially race e.g. Delete could result in
setting a child to nil, while Iterate dereferenced that node
while walking the Trie.

Signed-off-by: Petar Petrov <pppepito86@gmail.com>
2017-01-09 17:06:50 +00:00

453 lines
11 KiB
Go

package truncindex
import (
"math/rand"
"testing"
"time"
"github.com/docker/docker/pkg/stringid"
)
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
func TestTruncIndex(t *testing.T) {
ids := []string{}
index := NewTruncIndex(ids)
// Get on an empty index
if _, err := index.Get("foobar"); err == nil {
t.Fatal("Get on an empty index should return an error")
}
// Spaces should be illegal in an id
if err := index.Add("I have a space"); err == nil {
t.Fatalf("Adding an id with ' ' should return an error")
}
id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
// Add an id
if err := index.Add(id); err != nil {
t.Fatal(err)
}
// Add an empty id (should fail)
if err := index.Add(""); err == nil {
t.Fatalf("Adding an empty id should return an error")
}
// Get a non-existing id
assertIndexGet(t, index, "abracadabra", "", true)
// Get an empty id
assertIndexGet(t, index, "", "", true)
// Get the exact id
assertIndexGet(t, index, id, id, false)
// The first letter should match
assertIndexGet(t, index, id[:1], id, false)
// The first half should match
assertIndexGet(t, index, id[:len(id)/2], id, false)
// The second half should NOT match
assertIndexGet(t, index, id[len(id)/2:], "", true)
id2 := id[:6] + "blabla"
// Add an id
if err := index.Add(id2); err != nil {
t.Fatal(err)
}
// Both exact IDs should work
assertIndexGet(t, index, id, id, false)
assertIndexGet(t, index, id2, id2, false)
// 6 characters or less should conflict
assertIndexGet(t, index, id[:6], "", true)
assertIndexGet(t, index, id[:4], "", true)
assertIndexGet(t, index, id[:1], "", true)
// An ambiguous id prefix should return an error
if _, err := index.Get(id[:4]); err == nil {
t.Fatal("An ambiguous id prefix should return an error")
}
// 7 characters should NOT conflict
assertIndexGet(t, index, id[:7], id, false)
assertIndexGet(t, index, id2[:7], id2, false)
// Deleting a non-existing id should return an error
if err := index.Delete("non-existing"); err == nil {
t.Fatalf("Deleting a non-existing id should return an error")
}
// Deleting an empty id should return an error
if err := index.Delete(""); err == nil {
t.Fatal("Deleting an empty id should return an error")
}
// Deleting id2 should remove conflicts
if err := index.Delete(id2); err != nil {
t.Fatal(err)
}
// id2 should no longer work
assertIndexGet(t, index, id2, "", true)
assertIndexGet(t, index, id2[:7], "", true)
assertIndexGet(t, index, id2[:11], "", true)
// conflicts between id and id2 should be gone
assertIndexGet(t, index, id[:6], id, false)
assertIndexGet(t, index, id[:4], id, false)
assertIndexGet(t, index, id[:1], id, false)
// non-conflicting substrings should still not conflict
assertIndexGet(t, index, id[:7], id, false)
assertIndexGet(t, index, id[:15], id, false)
assertIndexGet(t, index, id, id, false)
assertIndexIterate(t)
assertIndexIterateDoNotPanic(t)
}
func assertIndexIterate(t *testing.T) {
ids := []string{
"19b36c2c326ccc11e726eee6ee78a0baf166ef96",
"28b36c2c326ccc11e726eee6ee78a0baf166ef96",
"37b36c2c326ccc11e726eee6ee78a0baf166ef96",
"46b36c2c326ccc11e726eee6ee78a0baf166ef96",
}
index := NewTruncIndex(ids)
index.Iterate(func(targetId string) {
for _, id := range ids {
if targetId == id {
return
}
}
t.Fatalf("An unknown ID '%s'", targetId)
})
}
func assertIndexIterateDoNotPanic(t *testing.T) {
ids := []string{
"19b36c2c326ccc11e726eee6ee78a0baf166ef96",
"28b36c2c326ccc11e726eee6ee78a0baf166ef96",
}
index := NewTruncIndex(ids)
iterationStarted := make(chan bool, 1)
go func() {
<-iterationStarted
index.Delete("19b36c2c326ccc11e726eee6ee78a0baf166ef96")
}()
index.Iterate(func(targetId string) {
if targetId == "19b36c2c326ccc11e726eee6ee78a0baf166ef96" {
iterationStarted <- true
time.Sleep(100 * time.Millisecond)
}
})
}
func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
if result, err := index.Get(input); err != nil && !expectError {
t.Fatalf("Unexpected error getting '%s': %s", input, err)
} else if err == nil && expectError {
t.Fatalf("Getting '%s' should return an error, not '%s'", input, result)
} else if result != expectedResult {
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
}
}
func BenchmarkTruncIndexAdd100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexAdd250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexAdd500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexGet100(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 100; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}
func BenchmarkTruncIndexGet250(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 250; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}
func BenchmarkTruncIndexGet500(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}
func BenchmarkTruncIndexDelete100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
b.StartTimer()
for _, id := range testSet {
if err := index.Delete(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexDelete250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
b.StartTimer()
for _, id := range testSet {
if err := index.Delete(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexDelete500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
b.StartTimer()
for _, id := range testSet {
if err := index.Delete(id); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkTruncIndexNew100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewTruncIndex(testSet)
}
}
func BenchmarkTruncIndexNew250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewTruncIndex(testSet)
}
}
func BenchmarkTruncIndexNew500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
testSet = append(testSet, stringid.GenerateNonCryptoID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewTruncIndex(testSet)
}
}
func BenchmarkTruncIndexAddGet100(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
id := stringid.GenerateNonCryptoID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}
func BenchmarkTruncIndexAddGet250(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
id := stringid.GenerateNonCryptoID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}
func BenchmarkTruncIndexAddGet500(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
id := stringid.GenerateNonCryptoID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
index := NewTruncIndex([]string{})
for _, id := range testSet {
if err := index.Add(id); err != nil {
b.Fatal(err)
}
}
for _, id := range testKeys {
if res, err := index.Get(id); err != nil {
b.Fatal(res, err)
}
}
}
}