diff --git a/daemon/daemon.go b/daemon/daemon.go index e4078fdfeb..3536678d22 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -182,11 +182,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool, con // don't update the Suffixarray if we're starting up // we'll waste time if we update it for every container - if updateSuffixarray { - daemon.idIndex.Add(container.ID) - } else { - daemon.idIndex.AddWithoutSuffixarrayUpdate(container.ID) - } + daemon.idIndex.Add(container.ID) // FIXME: if the container is supposed to be running but is not, auto restart it? // if so, then we need to restart monitor and init a new lock @@ -377,8 +373,6 @@ func (daemon *Daemon) restore() error { } } - daemon.idIndex.UpdateSuffixarray() - for _, container := range containersToStart { utils.Debugf("Starting container %d", container.ID) if err := container.Start(); err != nil { diff --git a/hack/vendor.sh b/hack/vendor.sh index c4494ffa72..630998e206 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -47,6 +47,8 @@ clone git github.com/gorilla/mux 136d54f81f clone git github.com/syndtr/gocapability 3c85049eae +clone git github.com/tchap/go-patricia v1.0.0 + clone hg code.google.com/p/go.net 84a4013f96e0 clone hg code.google.com/p/gosqlite 74691fb6f837 diff --git a/pkg/truncindex/truncindex.go b/pkg/truncindex/truncindex.go index 78512047a4..811f606661 100644 --- a/pkg/truncindex/truncindex.go +++ b/pkg/truncindex/truncindex.go @@ -1,31 +1,34 @@ package truncindex import ( + "errors" "fmt" - "index/suffixarray" "strings" "sync" + + "github.com/tchap/go-patricia/patricia" +) + +var ( + ErrNoID = errors.New("prefix can't be empty") ) // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. // This is used to retrieve image and container IDs by more convenient shorthand prefixes. type TruncIndex struct { sync.RWMutex - index *suffixarray.Index - ids map[string]bool - bytes []byte + trie *patricia.Trie + ids map[string]struct{} } func NewTruncIndex(ids []string) (idx *TruncIndex) { idx = &TruncIndex{ - ids: make(map[string]bool), - bytes: []byte{' '}, + ids: make(map[string]struct{}), + trie: patricia.NewTrie(), } for _, id := range ids { - idx.ids[id] = true - idx.bytes = append(idx.bytes, []byte(id+" ")...) + idx.addId(id) } - idx.index = suffixarray.New(idx.bytes) return } @@ -33,11 +36,16 @@ func (idx *TruncIndex) addId(id string) error { if strings.Contains(id, " ") { return fmt.Errorf("Illegal character: ' '") } - if _, exists := idx.ids[id]; exists { - return fmt.Errorf("Id already exists: %s", id) + if id == "" { + return ErrNoID + } + if _, exists := idx.ids[id]; exists { + return fmt.Errorf("Id already exists: '%s'", id) + } + idx.ids[id] = struct{}{} + if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted { + return fmt.Errorf("Failed to insert id: %s", id) } - idx.ids[id] = true - idx.bytes = append(idx.bytes, []byte(id+" ")...) return nil } @@ -47,56 +55,46 @@ func (idx *TruncIndex) Add(id string) error { if err := idx.addId(id); err != nil { return err } - idx.index = suffixarray.New(idx.bytes) return nil } -func (idx *TruncIndex) AddWithoutSuffixarrayUpdate(id string) error { - idx.Lock() - defer idx.Unlock() - return idx.addId(id) -} - -func (idx *TruncIndex) UpdateSuffixarray() { - idx.Lock() - defer idx.Unlock() - idx.index = suffixarray.New(idx.bytes) -} - func (idx *TruncIndex) Delete(id string) error { idx.Lock() defer idx.Unlock() - if _, exists := idx.ids[id]; !exists { - return fmt.Errorf("No such id: %s", id) - } - before, after, err := idx.lookup(id) - if err != nil { - return err + if _, exists := idx.ids[id]; !exists || id == "" { + return fmt.Errorf("No such id: '%s'", id) } delete(idx.ids, id) - idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...) - idx.index = suffixarray.New(idx.bytes) - return nil -} - -func (idx *TruncIndex) lookup(s string) (int, int, error) { - offsets := idx.index.Lookup([]byte(" "+s), -1) - //log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes()) - if offsets == nil || len(offsets) == 0 || len(offsets) > 1 { - return -1, -1, fmt.Errorf("No such id: %s", s) + if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted { + return fmt.Errorf("No such id: '%s'", id) } - offsetBefore := offsets[0] + 1 - offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ") - return offsetBefore, offsetAfter, nil + return nil } func (idx *TruncIndex) Get(s string) (string, error) { idx.RLock() defer idx.RUnlock() - before, after, err := idx.lookup(s) - //log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after) - if err != nil { - return "", err + var ( + id string + ) + if s == "" { + return "", ErrNoID } - return string(idx.bytes[before:after]), err + subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error { + if id != "" { + // we haven't found the ID if there are two or more IDs + id = "" + return fmt.Errorf("we've found two entries") + } + id = string(prefix) + return nil + } + + if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil { + return "", fmt.Errorf("No such id: %s", s) + } + if id != "" { + return id, nil + } + return "", fmt.Errorf("No such id: %s", s) } diff --git a/pkg/truncindex/truncindex_test.go b/pkg/truncindex/truncindex_test.go index 5e2a386d74..f88d667d5a 100644 --- a/pkg/truncindex/truncindex_test.go +++ b/pkg/truncindex/truncindex_test.go @@ -26,8 +26,16 @@ func TestTruncIndex(t *testing.T) { 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 @@ -60,6 +68,11 @@ func TestTruncIndex(t *testing.T) { 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) @@ -84,7 +97,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin 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", input) + 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) } diff --git a/vendor/src/github.com/tchap/go-patricia/.gitignore b/vendor/src/github.com/tchap/go-patricia/.gitignore new file mode 100644 index 0000000000..b3971c067a --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/.gitignore @@ -0,0 +1,25 @@ +# Swap files. +*.swp + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/src/github.com/tchap/go-patricia/.travis.yml b/vendor/src/github.com/tchap/go-patricia/.travis.yml new file mode 100644 index 0000000000..66183e2ad4 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.2 + - tip + +branches: + exclude: + - wip diff --git a/vendor/src/github.com/tchap/go-patricia/AUTHORS b/vendor/src/github.com/tchap/go-patricia/AUTHORS new file mode 100644 index 0000000000..e640b0bf51 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/AUTHORS @@ -0,0 +1,3 @@ +This is the complete list of go-patricia copyright holders: + +Ondřej Kupka diff --git a/vendor/src/github.com/tchap/go-patricia/LICENSE b/vendor/src/github.com/tchap/go-patricia/LICENSE new file mode 100644 index 0000000000..e50d398e98 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 The AUTHORS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/src/github.com/tchap/go-patricia/README.md b/vendor/src/github.com/tchap/go-patricia/README.md new file mode 100644 index 0000000000..635e2b8ea7 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/README.md @@ -0,0 +1,112 @@ +# go-patricia # + +**Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)
+**Build Status**: [![Build Status](https://travis-ci.org/tchap/go-patricia.png?branch=master)](https://travis-ci.org/tchap/go-patricia)
+**Test Coverage**: Comming as soon as Drone.io people update their Go. + +## About ## + +A generic patricia trie (also called radix tree) implemented in Go (Golang). + +The patricia trie as implemented in this library enables fast visiting of items +in some particular ways: + +1. visit all items saved in the tree, +2. visit all items matching particular prefix (visit subtree), or +3. given a string, visit all items matching some prefix of that string. + +`[]byte` type is used for keys, `interface{}` for values. + +`Trie` is not thread safe. Synchronize the access yourself. + +### State of the Project ### + +Apparently some people are using this, so the API should not change often. +Any ideas on how to make the library better are still welcome. + +More (unit) testing would be cool as well... + +## Usage ## + +Import the package from GitHub first. + +```go +import "github.com/tchap/go-patricia/patricia" +``` + +Then you can start having fun. + +```go +printItem := func(prefix patricia.Prefix, item patricia.Item) error { + fmt.Printf("%q: %v\n", prefix, item) + return nil +} + +// Create a new tree. +trie := NewTrie() + +// Insert some items. +trie.Insert(Prefix("Pepa Novak"), 1) +trie.Insert(Prefix("Pepa Sindelar"), 2) +trie.Insert(Prefix("Karel Macha"), 3) +trie.Insert(Prefix("Karel Hynek Macha"), 4) + +// Just check if some things are present in the tree. +key := Prefix("Pepa Novak") +fmt.Printf("%q present? %v\n", key, trie.Match(key)) +// "Pepa Novak" present? true +key = Prefix("Karel") +fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key)) +// Anybody called "Karel" here? true + +// Walk the tree. +trie.Visit(printItem) +// "Pepa Novak": 1 +// "Pepa Sindelar": 2 +// "Karel Macha": 3 +// "Karel Hynek Macha": 4 + +// Walk a subtree. +trie.VisitSubtree(Prefix("Pepa"), printItem) +// "Pepa Novak": 1 +// "Pepa Sindelar": 2 + +// Modify an item, then fetch it from the tree. +trie.Set(Prefix("Karel Hynek Macha"), 10) +key = Prefix("Karel Hynek Macha") +fmt.Printf("%q: %v\n", key, trie.Get(key)) +// "Karel Hynek Macha": 10 + +// Walk prefixes. +prefix := Prefix("Karel Hynek Macha je kouzelnik") +trie.VisitPrefixes(prefix, printItem) +// "Karel Hynek Macha": 10 + +// Delete some items. +trie.Delete(Prefix("Pepa Novak")) +trie.Delete(Prefix("Karel Macha")) + +// Walk again. +trie.Visit(printItem) +// "Pepa Sindelar": 2 +// "Karel Hynek Macha": 10 + +// Delete a subtree. +trie.DeleteSubtree(Prefix("Pepa")) + +// Print what is left. +trie.Visit(printItem) +// "Karel Hynek Macha": 10 +``` + +## License ## + +MIT, check the `LICENSE` file. + +[![Gittip +Badge](http://img.shields.io/gittip/alanhamlett.png)](https://www.gittip.com/tchap/ +"Gittip Badge") + +[![Bitdeli +Badge](https://d2weczhvl823v0.cloudfront.net/tchap/go-patricia/trend.png)](https://bitdeli.com/free +"Bitdeli Badge") diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/children.go b/vendor/src/github.com/tchap/go-patricia/patricia/children.go new file mode 100644 index 0000000000..42b0547de6 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/patricia/children.go @@ -0,0 +1,231 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +const ( + // Max prefix length that is kept in a single trie node. + MaxPrefixPerNode = 10 + // Max children to keep in a node in the sparse mode. + MaxChildrenPerSparseNode = 8 +) + +type childList interface { + length() int + head() *Trie + add(child *Trie) childList + replace(b byte, child *Trie) + remove(child *Trie) + next(b byte) *Trie + walk(prefix *Prefix, visitor VisitorFunc) error +} + +type sparseChildList struct { + children []*Trie +} + +func newSparseChildList() childList { + return &sparseChildList{ + children: make([]*Trie, 0, MaxChildrenPerSparseNode), + } +} + +func (list *sparseChildList) length() int { + return len(list.children) +} + +func (list *sparseChildList) head() *Trie { + return list.children[0] +} + +func (list *sparseChildList) add(child *Trie) childList { + // Search for an empty spot and insert the child if possible. + if len(list.children) != cap(list.children) { + list.children = append(list.children, child) + return list + } + + // Otherwise we have to transform to the dense list type. + return newDenseChildList(list, child) +} + +func (list *sparseChildList) replace(b byte, child *Trie) { + // Seek the child and replace it. + for i, node := range list.children { + if node.prefix[0] == b { + list.children[i] = child + return + } + } +} + +func (list *sparseChildList) remove(child *Trie) { + for i, node := range list.children { + if node.prefix[0] == child.prefix[0] { + list.children = append(list.children[:i], list.children[i+1:]...) + return + } + } + + // This is not supposed to be reached. + panic("removing non-existent child") +} + +func (list *sparseChildList) next(b byte) *Trie { + for _, child := range list.children { + if child.prefix[0] == b { + return child + } + } + return nil +} + +func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error { + for _, child := range list.children { + *prefix = append(*prefix, child.prefix...) + if child.item != nil { + err := visitor(*prefix, child.item) + if err != nil { + if err == SkipSubtree { + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + continue + } + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + return err + } + } + + err := child.children.walk(prefix, visitor) + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + if err != nil { + return err + } + } + + return nil +} + +type denseChildList struct { + min int + max int + children []*Trie +} + +func newDenseChildList(list *sparseChildList, child *Trie) childList { + var ( + min int = 255 + max int = 0 + ) + for _, child := range list.children { + b := int(child.prefix[0]) + if b < min { + min = b + } + if b > max { + max = b + } + } + + b := int(child.prefix[0]) + if b < min { + min = b + } + if b > max { + max = b + } + + children := make([]*Trie, max-min+1) + for _, child := range list.children { + children[int(child.prefix[0])-min] = child + } + children[int(child.prefix[0])-min] = child + + return &denseChildList{min, max, children} +} + +func (list *denseChildList) length() int { + return list.max - list.min + 1 +} + +func (list *denseChildList) head() *Trie { + return list.children[0] +} + +func (list *denseChildList) add(child *Trie) childList { + b := int(child.prefix[0]) + + switch { + case list.min <= b && b <= list.max: + if list.children[b-list.min] != nil { + panic("dense child list collision detected") + } + list.children[b-list.min] = child + + case b < list.min: + children := make([]*Trie, list.max-b+1) + children[0] = child + copy(children[list.min-b:], list.children) + list.children = children + list.min = b + + default: // b > list.max + children := make([]*Trie, b-list.min+1) + children[b-list.min] = child + copy(children, list.children) + list.children = children + list.max = b + } + + return list +} + +func (list *denseChildList) replace(b byte, child *Trie) { + list.children[int(b)-list.min] = nil + list.children[int(child.prefix[0])-list.min] = child +} + +func (list *denseChildList) remove(child *Trie) { + i := int(child.prefix[0]) - list.min + if list.children[i] == nil { + // This is not supposed to be reached. + panic("removing non-existent child") + } + list.children[i] = nil +} + +func (list *denseChildList) next(b byte) *Trie { + i := int(b) + if i < list.min || list.max < i { + return nil + } + return list.children[i-list.min] +} + +func (list *denseChildList) walk(prefix *Prefix, visitor VisitorFunc) error { + for _, child := range list.children { + if child == nil { + continue + } + *prefix = append(*prefix, child.prefix...) + if child.item != nil { + if err := visitor(*prefix, child.item); err != nil { + if err == SkipSubtree { + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + continue + } + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + return err + } + } + + err := child.children.walk(prefix, visitor) + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go new file mode 100644 index 0000000000..8fcbcdf426 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go @@ -0,0 +1,432 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "errors" +) + +//------------------------------------------------------------------------------ +// Trie +//------------------------------------------------------------------------------ + +type ( + Prefix []byte + Item interface{} + VisitorFunc func(prefix Prefix, item Item) error +) + +// Trie is a generic patricia trie that allows fast retrieval of items by prefix. +// and other funky stuff. +// +// Trie is not thread-safe. +type Trie struct { + prefix Prefix + item Item + + children childList +} + +// Public API ------------------------------------------------------------------ + +// Trie constructor. +func NewTrie() *Trie { + return &Trie{ + children: newSparseChildList(), + } +} + +// Item returns the item stored in the root of this trie. +func (trie *Trie) Item() Item { + return trie.item +} + +// Insert inserts a new item into the trie using the given prefix. Insert does +// not replace existing items. It returns false if an item was already in place. +func (trie *Trie) Insert(key Prefix, item Item) (inserted bool) { + return trie.put(key, item, false) +} + +// Set works much like Insert, but it always sets the item, possibly replacing +// the item previously inserted. +func (trie *Trie) Set(key Prefix, item Item) { + trie.put(key, item, true) +} + +// Get returns the item located at key. +// +// This method is a bit dangerous, because Get can as well end up in an internal +// node that is not really representing any user-defined value. So when nil is +// a valid value being used, it is not possible to tell if the value was inserted +// into the tree by the user or not. A possible workaround for this is not to use +// nil interface as a valid value, even using zero value of any type is enough +// to prevent this bad behaviour. +func (trie *Trie) Get(key Prefix) (item Item) { + _, node, found, leftover := trie.findSubtree(key) + if !found || len(leftover) != 0 { + return nil + } + return node.item +} + +// Match returns what Get(prefix) != nil would return. The same warning as for +// Get applies here as well. +func (trie *Trie) Match(prefix Prefix) (matchedExactly bool) { + return trie.Get(prefix) != nil +} + +// MatchSubtree returns true when there is a subtree representing extensions +// to key, that is if there are any keys in the tree which have key as prefix. +func (trie *Trie) MatchSubtree(key Prefix) (matched bool) { + _, _, matched, _ = trie.findSubtree(key) + return +} + +// Visit calls visitor on every node containing a non-nil item. +// +// If an error is returned from visitor, the function stops visiting the tree +// and returns that error, unless it is a special error - SkipSubtree. In that +// case Visit skips the subtree represented by the current node and continues +// elsewhere. +func (trie *Trie) Visit(visitor VisitorFunc) error { + return trie.walk(nil, visitor) +} + +// VisitSubtree works much like Visit, but it only visits nodes matching prefix. +func (trie *Trie) VisitSubtree(prefix Prefix, visitor VisitorFunc) error { + // Nil prefix not allowed. + if prefix == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return nil + } + + // Locate the relevant subtree. + _, root, found, leftover := trie.findSubtree(prefix) + if !found { + return nil + } + prefix = append(prefix, leftover...) + + // Visit it. + return root.walk(prefix, visitor) +} + +// VisitPrefixes visits only nodes that represent prefixes of key. +// To say the obvious, returning SkipSubtree from visitor makes no sense here. +func (trie *Trie) VisitPrefixes(key Prefix, visitor VisitorFunc) error { + // Nil key not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return nil + } + + // Walk the path matching key prefixes. + node := trie + prefix := key + offset := 0 + for { + // Compute what part of prefix matches. + common := node.longestCommonPrefixLength(key) + key = key[common:] + offset += common + + // Partial match means that there is no subtree matching prefix. + if common < len(node.prefix) { + return nil + } + + // Call the visitor. + if item := node.item; item != nil { + if err := visitor(prefix[:offset], item); err != nil { + return err + } + } + + if len(key) == 0 { + // This node represents key, we are finished. + return nil + } + + // There is some key suffix left, move to the children. + child := node.children.next(key[0]) + if child == nil { + // There is nowhere to continue, return. + return nil + } + + node = child + } +} + +// Delete deletes the item represented by the given prefix. +// +// True is returned if the matching node was found and deleted. +func (trie *Trie) Delete(key Prefix) (deleted bool) { + // Nil prefix not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return false + } + + // Find the relevant node. + parent, node, _, leftover := trie.findSubtree(key) + if len(leftover) != 0 { + return false + } + + // If the item is already set to nil, there is nothing to do. + if node.item == nil { + return false + } + + // Delete the item. + node.item = nil + + // Compact since that might be possible now. + if compacted := node.compact(); compacted != node { + if parent == nil { + *node = *compacted + } else { + parent.children.replace(node.prefix[0], compacted) + *parent = *parent.compact() + } + } + + return true +} + +// DeleteSubtree finds the subtree exactly matching prefix and deletes it. +// +// True is returned if the subtree was found and deleted. +func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) { + // Nil prefix not allowed. + if prefix == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return false + } + + // Locate the relevant subtree. + parent, root, found, _ := trie.findSubtree(prefix) + if !found { + return false + } + + // If we are in the root of the trie, reset the trie. + if parent == nil { + root.prefix = nil + root.children = newSparseChildList() + return true + } + + // Otherwise remove the root node from its parent. + parent.children.remove(root) + return true +} + +// Internal helper methods ----------------------------------------------------- + +func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) { + // Nil prefix not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + var ( + common int + node *Trie = trie + child *Trie + ) + + if node.prefix == nil { + if len(key) <= MaxPrefixPerNode { + node.prefix = key + goto InsertItem + } + node.prefix = key[:MaxPrefixPerNode] + key = key[MaxPrefixPerNode:] + goto AppendChild + } + + for { + // Compute the longest common prefix length. + common = node.longestCommonPrefixLength(key) + key = key[common:] + + // Only a part matches, split. + if common < len(node.prefix) { + goto SplitPrefix + } + + // common == len(node.prefix) since never (common > len(node.prefix)) + // common == len(former key) <-> 0 == len(key) + // -> former key == node.prefix + if len(key) == 0 { + goto InsertItem + } + + // Check children for matching prefix. + child = node.children.next(key[0]) + if child == nil { + goto AppendChild + } + node = child + } + +SplitPrefix: + // Split the prefix if necessary. + child = new(Trie) + *child = *node + *node = *NewTrie() + node.prefix = child.prefix[:common] + child.prefix = child.prefix[common:] + child = child.compact() + node.children = node.children.add(child) + +AppendChild: + // Keep appending children until whole prefix is inserted. + // This loop starts with empty node.prefix that needs to be filled. + for len(key) != 0 { + child := NewTrie() + if len(key) <= MaxPrefixPerNode { + child.prefix = key + node.children = node.children.add(child) + node = child + goto InsertItem + } else { + child.prefix = key[:MaxPrefixPerNode] + key = key[MaxPrefixPerNode:] + node.children = node.children.add(child) + node = child + } + } + +InsertItem: + // Try to insert the item if possible. + if replace || node.item == nil { + node.item = item + return true + } + return false +} + +func (trie *Trie) compact() *Trie { + // Only a node with a single child can be compacted. + if trie.children.length() != 1 { + return trie + } + + child := trie.children.head() + + // If any item is set, we cannot compact since we want to retain + // the ability to do searching by key. This makes compaction less usable, + // but that simply cannot be avoided. + if trie.item != nil || child.item != nil { + return trie + } + + // Make sure the combined prefixes fit into a single node. + if len(trie.prefix)+len(child.prefix) > MaxPrefixPerNode { + return trie + } + + // Concatenate the prefixes, move the items. + child.prefix = append(trie.prefix, child.prefix...) + if trie.item != nil { + child.item = trie.item + } + + return child +} + +func (trie *Trie) findSubtree(prefix Prefix) (parent *Trie, root *Trie, found bool, leftover Prefix) { + // Find the subtree matching prefix. + root = trie + for { + // Compute what part of prefix matches. + common := root.longestCommonPrefixLength(prefix) + prefix = prefix[common:] + + // We used up the whole prefix, subtree found. + if len(prefix) == 0 { + found = true + leftover = root.prefix[common:] + return + } + + // Partial match means that there is no subtree matching prefix. + if common < len(root.prefix) { + leftover = root.prefix[common:] + return + } + + // There is some prefix left, move to the children. + child := root.children.next(prefix[0]) + if child == nil { + // There is nowhere to continue, there is no subtree matching prefix. + return + } + + parent = root + root = child + } +} + +func (trie *Trie) walk(actualRootPrefix Prefix, visitor VisitorFunc) error { + var prefix Prefix + // Allocate a bit more space for prefix at the beginning. + if actualRootPrefix == nil { + prefix = make(Prefix, 32+len(trie.prefix)) + copy(prefix, trie.prefix) + prefix = prefix[:len(trie.prefix)] + } else { + prefix = make(Prefix, 32+len(actualRootPrefix)) + copy(prefix, actualRootPrefix) + prefix = prefix[:len(actualRootPrefix)] + } + + // Visit the root first. Not that this works for empty trie as well since + // in that case item == nil && len(children) == 0. + if trie.item != nil { + if err := visitor(prefix, trie.item); err != nil { + if err == SkipSubtree { + return nil + } + return err + } + } + + // Then continue to the children. + return trie.children.walk(&prefix, visitor) +} + +func (trie *Trie) longestCommonPrefixLength(prefix Prefix) (i int) { + for ; i < len(prefix) && i < len(trie.prefix) && prefix[i] == trie.prefix[i]; i++ { + } + return +} + +// Errors ---------------------------------------------------------------------- + +var ( + SkipSubtree = errors.New("Skip this subtree") + ErrNilPrefix = errors.New("Nil prefix passed into a method call") +) diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go new file mode 100644 index 0000000000..346e9a66cb --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go @@ -0,0 +1,161 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "testing" +) + +// Tests ----------------------------------------------------------------------- + +func TestTrie_InsertDense(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"aba", 0, success}, + {"abb", 1, success}, + {"abc", 2, success}, + {"abd", 3, success}, + {"abe", 4, success}, + {"abf", 5, success}, + {"abg", 6, success}, + {"abh", 7, success}, + {"abi", 8, success}, + {"abj", 9, success}, + {"abk", 0, success}, + {"abl", 1, success}, + {"abm", 2, success}, + {"abn", 3, success}, + {"abo", 4, success}, + {"abp", 5, success}, + {"abq", 6, success}, + {"abr", 7, success}, + {"abs", 8, success}, + {"abt", 9, success}, + {"abu", 0, success}, + {"abv", 1, success}, + {"abw", 2, success}, + {"abx", 3, success}, + {"aby", 4, success}, + {"abz", 5, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestTrie_InsertDensePreceeding(t *testing.T) { + trie := NewTrie() + start := byte(70) + // create a dense node + for i := byte(0); i <= MaxChildrenPerSparseNode; i++ { + if !trie.Insert(Prefix([]byte{start + i}), true) { + t.Errorf("insert failed, prefix=%v", start+i) + } + } + // insert some preceeding keys + for i := byte(1); i < start; i *= i + 1 { + if !trie.Insert(Prefix([]byte{start - i}), true) { + t.Errorf("insert failed, prefix=%v", start-i) + } + } +} + +func TestTrie_InsertDenseDuplicatePrefixes(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"aba", 0, success}, + {"abb", 1, success}, + {"abc", 2, success}, + {"abd", 3, success}, + {"abe", 4, success}, + {"abf", 5, success}, + {"abg", 6, success}, + {"abh", 7, success}, + {"abi", 8, success}, + {"abj", 9, success}, + {"abk", 0, success}, + {"abl", 1, success}, + {"abm", 2, success}, + {"abn", 3, success}, + {"abo", 4, success}, + {"abp", 5, success}, + {"abq", 6, success}, + {"abr", 7, success}, + {"abs", 8, success}, + {"abt", 9, success}, + {"abu", 0, success}, + {"abv", 1, success}, + {"abw", 2, success}, + {"abx", 3, success}, + {"aby", 4, success}, + {"abz", 5, success}, + {"aba", 0, failure}, + {"abb", 1, failure}, + {"abc", 2, failure}, + {"abd", 3, failure}, + {"abe", 4, failure}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestTrie_DeleteDense(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"aba", 0, success}, + {"abb", 1, success}, + {"abc", 2, success}, + {"abd", 3, success}, + {"abe", 4, success}, + {"abf", 5, success}, + {"abg", 6, success}, + {"abh", 7, success}, + {"abi", 8, success}, + {"abj", 9, success}, + {"abk", 0, success}, + {"abl", 1, success}, + {"abm", 2, success}, + {"abn", 3, success}, + {"abo", 4, success}, + {"abp", 5, success}, + {"abq", 6, success}, + {"abr", 7, success}, + {"abs", 8, success}, + {"abt", 9, success}, + {"abu", 0, success}, + {"abv", 1, success}, + {"abw", 2, success}, + {"abx", 3, success}, + {"aby", 4, success}, + {"abz", 5, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range data { + t.Logf("DELETE word=%v, success=%v", v.key, v.retVal) + if ok := trie.Delete([]byte(v.key)); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go new file mode 100644 index 0000000000..27f3c878b5 --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go @@ -0,0 +1,659 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "bytes" + "errors" + "fmt" + "strings" + "testing" +) + +const ( + success = true + failure = false +) + +type testData struct { + key string + value interface{} + retVal bool +} + +// Tests ----------------------------------------------------------------------- + +func TestTrie_InsertDifferentPrefixes(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepaneeeeeeeeeeeeee", "Pepan Zdepan", success}, + {"Honzooooooooooooooo", "Honza Novak", success}, + {"Jenikuuuuuuuuuuuuuu", "Jenik Poustevnicek", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestTrie_InsertDuplicatePrefixes(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Pepan", "Pepan Zdepan", failure}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestTrie_InsertVariousPrefixes(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Pepin", "Pepin Omacka", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + {"Pepan", "Pepan Dupan", failure}, + {"Karel", "Karel Pekar", success}, + {"Jenik", "Jenik Poustevnicek", failure}, + {"Pepanek", "Pepanek Zemlicka", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestTrie_InsertAndMatchPrefix(t *testing.T) { + trie := NewTrie() + t.Log("INSERT prefix=by week") + trie.Insert(Prefix("by week"), 2) + t.Log("INSERT prefix=by") + trie.Insert(Prefix("by"), 1) + + if !trie.Match(Prefix("by")) { + t.Error("MATCH prefix=by, expected=true, got=false") + } +} + +func TestTrie_SetGet(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Pepin", "Pepin Omacka", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + {"Pepan", "Pepan Dupan", failure}, + {"Karel", "Karel Pekar", success}, + {"Jenik", "Jenik Poustevnicek", failure}, + {"Pepanek", "Pepanek Zemlicka", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range data { + t.Logf("SET %q to 10", v.key) + trie.Set(Prefix(v.key), 10) + } + + for _, v := range data { + value := trie.Get(Prefix(v.key)) + t.Logf("GET %q => %v", v.key, value) + if value.(int) != 10 { + t.Errorf("Unexpected return value, != 10", value) + } + } + + if value := trie.Get(Prefix("random crap")); value != nil { + t.Errorf("Unexpected return value, %v != ", value) + } +} + +func TestTrie_Match(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Pepin", "Pepin Omacka", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + {"Pepan", "Pepan Dupan", failure}, + {"Karel", "Karel Pekar", success}, + {"Jenik", "Jenik Poustevnicek", failure}, + {"Pepanek", "Pepanek Zemlicka", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range data { + matched := trie.Match(Prefix(v.key)) + t.Logf("MATCH %q => %v", v.key, matched) + if !matched { + t.Errorf("Inserted key %q was not matched", v.key) + } + } + + if trie.Match(Prefix("random crap")) { + t.Errorf("Key that was not inserted matched: %q", "random crap") + } +} + +func TestTrie_MatchFalsePositive(t *testing.T) { + trie := NewTrie() + + if ok := trie.Insert(Prefix("A"), 1); !ok { + t.Fatal("INSERT prefix=A, item=1 not ok") + } + + resultMatchSubtree := trie.MatchSubtree(Prefix("A extra")) + resultMatch := trie.Match(Prefix("A extra")) + + if resultMatchSubtree != false { + t.Error("MatchSubtree returned false positive") + } + + if resultMatch != false { + t.Error("Match returned false positive") + } +} + +func TestTrie_MatchSubtree(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Pepin", "Pepin Omacka", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + {"Pepan", "Pepan Dupan", failure}, + {"Karel", "Karel Pekar", success}, + {"Jenik", "Jenik Poustevnicek", failure}, + {"Pepanek", "Pepanek Zemlicka", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range data { + key := Prefix(v.key[:3]) + matched := trie.MatchSubtree(key) + t.Logf("MATCH_SUBTREE %q => %v", key, matched) + if !matched { + t.Errorf("Subtree %q was not matched", v.key) + } + } +} + +func TestTrie_Visit(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepa", 0, success}, + {"Pepa Zdepa", 1, success}, + {"Pepa Kuchar", 2, success}, + {"Honza", 3, success}, + {"Jenik", 4, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + if err := trie.Visit(func(prefix Prefix, item Item) error { + name := data[item.(int)].key + t.Logf("VISITING prefix=%q, item=%v", prefix, item) + if !strings.HasPrefix(string(prefix), name) { + t.Errorf("Unexpected prefix encountered, %q not a prefix of %q", prefix, name) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestTrie_VisitSkipSubtree(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepa", 0, success}, + {"Pepa Zdepa", 1, success}, + {"Pepa Kuchar", 2, success}, + {"Honza", 3, success}, + {"Jenik", 4, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + if err := trie.Visit(func(prefix Prefix, item Item) error { + t.Logf("VISITING prefix=%q, item=%v", prefix, item) + if item.(int) == 0 { + t.Logf("SKIP %q", prefix) + return SkipSubtree + } + if strings.HasPrefix(string(prefix), "Pepa") { + t.Errorf("Unexpected prefix encountered, %q", prefix) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestTrie_VisitReturnError(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepa", 0, success}, + {"Pepa Zdepa", 1, success}, + {"Pepa Kuchar", 2, success}, + {"Honza", 3, success}, + {"Jenik", 4, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + someErr := errors.New("Something exploded") + if err := trie.Visit(func(prefix Prefix, item Item) error { + t.Logf("VISITING prefix=%q, item=%v", prefix, item) + if item.(int) == 0 { + return someErr + } + if item.(int) != 0 { + t.Errorf("Unexpected prefix encountered, %q", prefix) + } + return nil + }); err != nil && err != someErr { + t.Fatal(err) + } +} + +func TestTrie_VisitSubtree(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepa", 0, success}, + {"Pepa Zdepa", 1, success}, + {"Pepa Kuchar", 2, success}, + {"Honza", 3, success}, + {"Jenik", 4, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + var counter int + subtreePrefix := []byte("Pep") + t.Log("VISIT Pep") + if err := trie.VisitSubtree(subtreePrefix, func(prefix Prefix, item Item) error { + t.Logf("VISITING prefix=%q, item=%v", prefix, item) + if !bytes.HasPrefix(prefix, subtreePrefix) { + t.Errorf("Unexpected prefix encountered, %q does not extend %q", + prefix, subtreePrefix) + } + if len(prefix) > len(data[item.(int)].key) { + t.Fatalf("Something is rather fishy here, prefix=%q", prefix) + } + counter++ + return nil + }); err != nil { + t.Fatal(err) + } + + if counter != 3 { + t.Error("Unexpected number of nodes visited") + } +} + +func TestTrie_VisitPrefixes(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"P", 0, success}, + {"Pe", 1, success}, + {"Pep", 2, success}, + {"Pepa", 3, success}, + {"Pepa Zdepa", 4, success}, + {"Pepa Kuchar", 5, success}, + {"Honza", 6, success}, + {"Jenik", 7, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + var counter int + word := []byte("Pepa") + if err := trie.VisitPrefixes(word, func(prefix Prefix, item Item) error { + t.Logf("VISITING prefix=%q, item=%v", prefix, item) + if !bytes.HasPrefix(word, prefix) { + t.Errorf("Unexpected prefix encountered, %q is not a prefix of %q", + prefix, word) + } + counter++ + return nil + }); err != nil { + t.Fatal(err) + } + + if counter != 4 { + t.Error("Unexpected number of nodes visited") + } +} + +func TestParticiaTrie_Delete(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range data { + t.Logf("DELETE word=%v, success=%v", v.key, v.retVal) + if ok := trie.Delete([]byte(v.key)); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestParticiaTrie_DeleteNonExistent(t *testing.T) { + trie := NewTrie() + + insertData := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Honza", "Honza Novak", success}, + {"Jenik", "Jenik Poustevnicek", success}, + } + deleteData := []testData{ + {"Pepan", "Pepan Zdepan", success}, + {"Honza", "Honza Novak", success}, + {"Pepan", "Pepan Zdepan", failure}, + {"Jenik", "Jenik Poustevnicek", success}, + {"Honza", "Honza Novak", failure}, + } + + for _, v := range insertData { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range deleteData { + t.Logf("DELETE word=%v, success=%v", v.key, v.retVal) + if ok := trie.Delete([]byte(v.key)); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +func TestParticiaTrie_DeleteSubtree(t *testing.T) { + trie := NewTrie() + + insertData := []testData{ + {"P", 0, success}, + {"Pe", 1, success}, + {"Pep", 2, success}, + {"Pepa", 3, success}, + {"Pepa Zdepa", 4, success}, + {"Pepa Kuchar", 5, success}, + {"Honza", 6, success}, + {"Jenik", 7, success}, + } + deleteData := []testData{ + {"Pe", -1, success}, + {"Pe", -1, failure}, + {"Honzik", -1, failure}, + {"Honza", -1, success}, + {"Honza", -1, failure}, + {"Pep", -1, failure}, + {"P", -1, success}, + {"Nobody", -1, failure}, + {"", -1, success}, + } + + for _, v := range insertData { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + for _, v := range deleteData { + t.Logf("DELETE_SUBTREE prefix=%v, success=%v", v.key, v.retVal) + if ok := trie.DeleteSubtree([]byte(v.key)); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } +} + +/* +func TestTrie_Dump(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"Honda", nil, success}, + {"Honza", nil, success}, + {"Jenik", nil, success}, + {"Pepan", nil, success}, + {"Pepin", nil, success}, + } + + for i, v := range data { + if _, ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal { + t.Logf("INSERT %v %v", v.key, v.value) + t.Fatalf("Unexpected return value, expected=%v, got=%v", i, ok) + } + } + + dump := ` ++--+--+ Hon +--+--+ da + | | + | +--+ za + | + +--+ Jenik + | + +--+ Pep +--+--+ an + | + +--+ in +` + + var buf bytes.Buffer + trie.Dump(buf) + + if !bytes.Equal(buf.Bytes(), dump) { + t.Logf("DUMP") + t.Fatalf("Unexpected dump generated, expected\n\n%v\ngot\n\n%v", dump, buf.String()) + } +} +*/ + +func TestTrie_compact(t *testing.T) { + trie := NewTrie() + + trie.Insert(Prefix("a"), 0) + trie.Insert(Prefix("ab"), 0) + trie.Insert(Prefix("abc"), 0) + trie.Insert(Prefix("abcd"), 0) + trie.Insert(Prefix("abcde"), 0) + trie.Insert(Prefix("abcdef"), 0) + trie.Insert(Prefix("abcdefg"), 0) + trie.Insert(Prefix("abcdefgi"), 0) + trie.Insert(Prefix("abcdefgij"), 0) + trie.Insert(Prefix("abcdefgijk"), 0) + + trie.Delete(Prefix("abcdef")) + trie.Delete(Prefix("abcde")) + trie.Delete(Prefix("abcdefg")) + + trie.Delete(Prefix("a")) + trie.Delete(Prefix("abc")) + trie.Delete(Prefix("ab")) + + trie.Visit(func(prefix Prefix, item Item) error { + // 97 ~~ 'a', + for ch := byte(97); ch <= 107; ch++ { + if c := bytes.Count(prefix, []byte{ch}); c > 1 { + t.Errorf("%q appeared in %q %v times", ch, prefix, c) + } + } + return nil + }) +} + +func TestTrie_longestCommonPrefixLenght(t *testing.T) { + trie := NewTrie() + trie.prefix = []byte("1234567890") + + switch { + case trie.longestCommonPrefixLength([]byte("")) != 0: + t.Fail() + case trie.longestCommonPrefixLength([]byte("12345")) != 5: + t.Fail() + case trie.longestCommonPrefixLength([]byte("123789")) != 3: + t.Fail() + case trie.longestCommonPrefixLength([]byte("12345678901")) != 10: + t.Fail() + } +} + +// Examples -------------------------------------------------------------------- + +func ExampleTrie() { + // Create a new tree. + trie := NewTrie() + + // Insert some items. + trie.Insert(Prefix("Pepa Novak"), 1) + trie.Insert(Prefix("Pepa Sindelar"), 2) + trie.Insert(Prefix("Karel Macha"), 3) + trie.Insert(Prefix("Karel Hynek Macha"), 4) + + // Just check if some things are present in the tree. + key := Prefix("Pepa Novak") + fmt.Printf("%q present? %v\n", key, trie.Match(key)) + key = Prefix("Karel") + fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key)) + + // Walk the tree. + trie.Visit(printItem) + // "Pepa Novak": 1 + // "Pepa Sindelar": 2 + // "Karel Macha": 3 + // "Karel Hynek Macha": 4 + + // Walk a subtree. + trie.VisitSubtree(Prefix("Pepa"), printItem) + // "Pepa Novak": 1 + // "Pepa Sindelar": 2 + + // Modify an item, then fetch it from the tree. + trie.Set(Prefix("Karel Hynek Macha"), 10) + key = Prefix("Karel Hynek Macha") + fmt.Printf("%q: %v\n", key, trie.Get(key)) + // "Karel Hynek Macha": 10 + + // Walk prefixes. + prefix := Prefix("Karel Hynek Macha je kouzelnik") + trie.VisitPrefixes(prefix, printItem) + // "Karel Hynek Macha": 10 + + // Delete some items. + trie.Delete(Prefix("Pepa Novak")) + trie.Delete(Prefix("Karel Macha")) + + // Walk again. + trie.Visit(printItem) + // "Pepa Sindelar": 2 + // "Karel Hynek Macha": 10 + + // Delete a subtree. + trie.DeleteSubtree(Prefix("Pepa")) + + // Print what is left. + trie.Visit(printItem) + // "Karel Hynek Macha": 10 + + // Output: + // "Pepa Novak" present? true + // Anybody called "Karel" here? true + // "Pepa Novak": 1 + // "Pepa Sindelar": 2 + // "Karel Macha": 3 + // "Karel Hynek Macha": 4 + // "Pepa Novak": 1 + // "Pepa Sindelar": 2 + // "Karel Hynek Macha": 10 + // "Karel Hynek Macha": 10 + // "Pepa Sindelar": 2 + // "Karel Hynek Macha": 10 + // "Karel Hynek Macha": 10 +} + +// Helpers --------------------------------------------------------------------- + +func printItem(prefix Prefix, item Item) error { + fmt.Printf("%q: %v\n", prefix, item) + return nil +} diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go new file mode 100644 index 0000000000..ce5ae378fa --- /dev/null +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "crypto/rand" + "reflect" + "testing" +) + +// Tests ----------------------------------------------------------------------- + +func TestTrie_GetNonexistentPrefix(t *testing.T) { + trie := NewTrie() + + data := []testData{ + {"aba", 0, success}, + } + + for _, v := range data { + t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal) + if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal { + t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok) + } + } + + t.Logf("GET prefix=baa, expect item=nil") + if item := trie.Get(Prefix("baa")); item != nil { + t.Errorf("Unexpected return value, expected=, got=%v", item) + } +} + +func TestTrie_RandomKitchenSink(t *testing.T) { + if testing.Short() { + t.Skip() + } + const count, size = 750000, 16 + b := make([]byte, count+size+1) + if _, err := rand.Read(b); err != nil { + t.Fatal("error generating random bytes", err) + } + m := make(map[string]string) + for i := 0; i < count; i++ { + m[string(b[i:i+size])] = string(b[i+1 : i+size+1]) + } + trie := NewTrie() + getAndDelete := func(k, v string) { + i := trie.Get(Prefix(k)) + if i == nil { + t.Fatalf("item not found, prefix=%v", []byte(k)) + } else if s, ok := i.(string); !ok { + t.Fatalf("unexpected item type, expecting=%v, got=%v", reflect.TypeOf(k), reflect.TypeOf(i)) + } else if s != v { + t.Fatalf("unexpected item, expecting=%v, got=%v", []byte(k), []byte(s)) + } else if !trie.Delete(Prefix(k)) { + t.Fatalf("delete failed, prefix=%v", []byte(k)) + } else if i = trie.Get(Prefix(k)); i != nil { + t.Fatalf("unexpected item, expecting=, got=%v", i) + } else if trie.Delete(Prefix(k)) { + t.Fatalf("extra delete succeeded, prefix=%v", []byte(k)) + } + } + for k, v := range m { + if !trie.Insert(Prefix(k), v) { + t.Fatalf("insert failed, prefix=%v", []byte(k)) + } + if byte(k[size/2]) < 128 { + getAndDelete(k, v) + delete(m, k) + } + } + for k, v := range m { + getAndDelete(k, v) + } +}