From 5568b7661e10e7f28485ac7a31f5134c30a86b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=20=C4=90ANG?= Date: Tue, 31 Jan 2023 18:30:07 +0700 Subject: [PATCH 1/3] porting red black tree to optimize the key/value --- go.mod | 1 - go.sum | 2 - kvdb/flushable/flushable.go | 28 +- kvdb/redblacktree/comparator.go | 23 + kvdb/redblacktree/iterator.go | 185 +++++++ kvdb/redblacktree/redblacktree.go | 522 ++++++++++++++++++++ kvdb/redblacktree/redblacktree_test.go | 640 +++++++++++++++++++++++++ 7 files changed, 1384 insertions(+), 17 deletions(-) create mode 100644 kvdb/redblacktree/comparator.go create mode 100644 kvdb/redblacktree/iterator.go create mode 100644 kvdb/redblacktree/redblacktree.go create mode 100644 kvdb/redblacktree/redblacktree_test.go diff --git a/go.mod b/go.mod index d77ef680..b32838dc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.20 require ( github.com/JekaMas/go-mutesting v1.1.2 github.com/cockroachdb/pebble v0.0.0-20221111210721-1bda21f14fc2 - github.com/emirpasic/gods v1.12.0 github.com/ethereum/go-ethereum v1.9.22 github.com/golang/mock v1.6.0 github.com/hashicorp/golang-lru v0.5.4 diff --git a/go.sum b/go.sum index 5a6dc001..986bec47 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= diff --git a/kvdb/flushable/flushable.go b/kvdb/flushable/flushable.go index 024411fd..f141e446 100644 --- a/kvdb/flushable/flushable.go +++ b/kvdb/flushable/flushable.go @@ -5,10 +5,10 @@ import ( "errors" "sync" - rbt "github.com/emirpasic/gods/trees/redblacktree" "github.com/ethereum/go-ethereum/common" "github.com/Fantom-foundation/lachesis-base/kvdb" + rbt "github.com/Fantom-foundation/lachesis-base/kvdb/redblacktree" ) var ( @@ -53,7 +53,7 @@ func WrapWithDrop(parent kvdb.Store, drop func()) *Flushable { return &Flushable{ flushableReader: flushableReader{ underlying: parent, - modified: rbt.NewWithStringComparator(), + modified: rbt.New(), }, onDrop: drop, underlying: parent, @@ -78,7 +78,7 @@ func (w *Flushable) Put(key []byte, value []byte) error { } func (w *Flushable) put(key []byte, value []byte) { - w.modified.Put(string(key), common.CopyBytes(value)) + w.modified.Put(key, common.CopyBytes(value)) *w.sizeEstimation += len(key) + len(value) + 128 } @@ -91,7 +91,7 @@ func (w *flushableReader) Has(key []byte) (bool, error) { return false, errClosed } - val, ok := w.modified.Get(string(key)) + val, ok := w.modified.Get(key) if ok { return val != nil, nil } @@ -108,11 +108,11 @@ func (w *flushableReader) Get(key []byte) ([]byte, error) { return nil, errClosed } - if entry, ok := w.modified.Get(string(key)); ok { + if entry, ok := w.modified.Get(key); ok { if entry == nil { return nil, nil } - return common.CopyBytes(entry.([]byte)), nil + return common.CopyBytes(entry), nil } return w.underlying.Get(key) @@ -128,7 +128,7 @@ func (w *Flushable) Delete(key []byte) error { } func (w *Flushable) delete(key []byte) { - w.modified.Put(string(key), nil) + w.modified.Put(key, nil) *w.sizeEstimation += len(key) + 128 // it should be (len(key) - len(old value)), but we'd need to read old value } @@ -203,9 +203,9 @@ func (w *Flushable) flush() error { var err error if it.Value() == nil { - err = batch.Delete([]byte(it.Key().(string))) + err = batch.Delete(it.Key()) } else { - err = batch.Put([]byte(it.Key().(string)), it.Value().([]byte)) + err = batch.Put(it.Key(), it.Value()) } if err != nil { @@ -270,7 +270,7 @@ func nextNode(tree *rbt.Tree, node *rbt.Node) (next *rbt.Node, ok bool) { if node.Parent != nil { for node.Parent != nil { node = node.Parent - if tree.Comparator(origin.Key, node.Key) <= 0 { + if rbt.BytesComparator(origin.Key, node.Key) <= 0 { return node, node != nil } } @@ -283,11 +283,11 @@ func castToPair(node *rbt.Node) (key, val []byte) { if node == nil { return nil, nil } - key = []byte(node.Key.(string)) + key = node.Key if node.Value == nil { val = nil // deleted key } else { - val = node.Value.([]byte) // inserted value + val = node.Value // inserted value } return key, val } @@ -296,7 +296,7 @@ func castToPair(node *rbt.Node) (key, val []byte) { func (it *flushableIterator) init() { it.parentOk = it.parentIt.Next() if len(it.start) != 0 { - it.treeNode, it.treeOk = it.tree.Ceiling(string(it.start)) // not strict >= + it.treeNode, it.treeOk = it.tree.Ceiling(it.start) // not strict >= } else { it.treeNode = it.tree.Left() // lowest key it.treeOk = it.treeNode != nil @@ -411,7 +411,7 @@ func (w *Flushable) GetSnapshot() (kvdb.Snapshot, error) { if err != nil { return nil, err } - modifiedCopy := rbt.NewWithStringComparator() + modifiedCopy := rbt.New() for it := w.modified.Iterator(); it.Next(); { modifiedCopy.Put(it.Key(), it.Value()) } diff --git a/kvdb/redblacktree/comparator.go b/kvdb/redblacktree/comparator.go new file mode 100644 index 00000000..aafd5a03 --- /dev/null +++ b/kvdb/redblacktree/comparator.go @@ -0,0 +1,23 @@ +package redblacktree + +// BytesComparator provides a basic comparison on []byte +func BytesComparator(a, b []byte) int { + min := len(b) + if len(a) < len(b) { + min = len(a) + } + diff := 0 + for i := 0; i < min && diff == 0; i++ { + diff = int(a[i]) - int(b[i]) + } + if diff == 0 { + diff = len(a) - len(b) + } + if diff < 0 { + return -1 + } + if diff > 0 { + return 1 + } + return 0 +} diff --git a/kvdb/redblacktree/iterator.go b/kvdb/redblacktree/iterator.go new file mode 100644 index 00000000..55e7ae3b --- /dev/null +++ b/kvdb/redblacktree/iterator.go @@ -0,0 +1,185 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package redblacktree + +// Iterator holding the iterator's state +type Iterator struct { + tree *Tree + node *Node + position position +} + +type position byte + +const ( + begin, between, end position = 0, 1, 2 +) + +// Iterator returns a stateful iterator whose elements are key/value pairs. +func (tree *Tree) Iterator() Iterator { + return Iterator{tree: tree, node: nil, position: begin} +} + +// IteratorAt returns a stateful iterator whose elements are key/value pairs that is initialised at a particular node. +func (tree *Tree) IteratorAt(node *Node) Iterator { + return Iterator{tree: tree, node: node, position: between} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's key and value can be retrieved by Key() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator) Next() bool { + if iterator.position == end { + goto end + } + if iterator.position == begin { + left := iterator.tree.Left() + if left == nil { + goto end + } + iterator.node = left + goto between + } + if iterator.node.Right != nil { + iterator.node = iterator.node.Right + for iterator.node.Left != nil { + iterator.node = iterator.node.Left + } + goto between + } + for iterator.node.Parent != nil { + node := iterator.node + iterator.node = iterator.node.Parent + if node == iterator.node.Left { + goto between + } + } + +end: + iterator.node = nil + iterator.position = end + return false + +between: + iterator.position = between + return true +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) Prev() bool { + if iterator.position == begin { + goto begin + } + if iterator.position == end { + right := iterator.tree.Right() + if right == nil { + goto begin + } + iterator.node = right + goto between + } + if iterator.node.Left != nil { + iterator.node = iterator.node.Left + for iterator.node.Right != nil { + iterator.node = iterator.node.Right + } + goto between + } + for iterator.node.Parent != nil { + node := iterator.node + iterator.node = iterator.node.Parent + if node == iterator.node.Right { + goto between + } + } + +begin: + iterator.node = nil + iterator.position = begin + return false + +between: + iterator.position = between + return true +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator) Value() []byte { + return iterator.node.Value +} + +// Key returns the current element's key. +// Does not modify the state of the iterator. +func (iterator *Iterator) Key() []byte { + return iterator.node.Key +} + +// Node returns the current element's node. +// Does not modify the state of the iterator. +func (iterator *Iterator) Node() *Node { + return iterator.node +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator) Begin() { + iterator.node = nil + iterator.position = begin +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator) End() { + iterator.node = nil + iterator.position = end +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator +func (iterator *Iterator) First() bool { + iterator.Begin() + return iterator.Next() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) Last() bool { + iterator.End() + return iterator.Prev() +} + +// NextTo moves the iterator to the next element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If NextTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) NextTo(f func(key []byte, value []byte) bool) bool { + for iterator.Next() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} + +// PrevTo moves the iterator to the previous element from current position that satisfies the condition given by the +// passed function, and returns true if there was a next element in the container. +// If PrevTo() returns true, then next element's key and value can be retrieved by Key() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) PrevTo(f func(key []byte, value []byte) bool) bool { + for iterator.Prev() { + key, value := iterator.Key(), iterator.Value() + if f(key, value) { + return true + } + } + return false +} diff --git a/kvdb/redblacktree/redblacktree.go b/kvdb/redblacktree/redblacktree.go new file mode 100644 index 00000000..99febf85 --- /dev/null +++ b/kvdb/redblacktree/redblacktree.go @@ -0,0 +1,522 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package redblacktree implements a red-black tree. +// +// Used by TreeSet and TreeMap. +// +// Structure is not thread safe. +// +// References: http://en.wikipedia.org/wiki/Red%E2%80%93black_tree +package redblacktree + +import ( + "fmt" +) + +type color bool + +const ( + black, red color = true, false +) + +// Tree holds elements of the red-black tree +type Tree struct { + Root *Node + size int +} + +// Node is a single element within the tree +type Node struct { + Key []byte + Value []byte + color color + Left *Node + Right *Node + Parent *Node +} + +// New instantiates a red-black tree with the BytesComparator, i.e. keys are of type []byte. +func New() *Tree { + return &Tree{} +} + +// Put inserts node into the tree. +func (tree *Tree) Put(key []byte, value []byte) { + var insertedNode *Node + if tree.Root == nil { + tree.Root = &Node{Key: key, Value: value, color: red} + insertedNode = tree.Root + } else { + node := tree.Root + loop := true + for loop { + compare := BytesComparator(key, node.Key) + switch { + case compare == 0: + node.Key = key + node.Value = value + return + case compare < 0: + if node.Left == nil { + node.Left = &Node{Key: key, Value: value, color: red} + insertedNode = node.Left + loop = false + } else { + node = node.Left + } + case compare > 0: + if node.Right == nil { + node.Right = &Node{Key: key, Value: value, color: red} + insertedNode = node.Right + loop = false + } else { + node = node.Right + } + } + } + insertedNode.Parent = node + } + tree.insertCase1(insertedNode) + tree.size++ +} + +// Get searches the node in the tree by key and returns its value or nil if key is not found in tree. +// Second return parameter is true if key was found, otherwise false. +func (tree *Tree) Get(key []byte) (value []byte, found bool) { + node := tree.lookup(key) + if node != nil { + return node.Value, true + } + return nil, false +} + +// GetNode searches the node in the tree by key and returns its node or nil if key is not found in tree. +func (tree *Tree) GetNode(key []byte) *Node { + return tree.lookup(key) +} + +// Remove remove the node from the tree by key. +func (tree *Tree) Remove(key []byte) { + var child *Node + node := tree.lookup(key) + if node == nil { + return + } + if node.Left != nil && node.Right != nil { + pred := node.Left.maximumNode() + node.Key = pred.Key + node.Value = pred.Value + node = pred + } + if node.Left == nil || node.Right == nil { + if node.Right == nil { + child = node.Left + } else { + child = node.Right + } + if node.color == black { + node.color = nodeColor(child) + tree.deleteCase1(node) + } + tree.replaceNode(node, child) + if node.Parent == nil && child != nil { + child.color = black + } + } + tree.size-- +} + +// Empty returns true if tree does not contain any nodes +func (tree *Tree) Empty() bool { + return tree.size == 0 +} + +// Size returns number of nodes in the tree. +func (tree *Tree) Size() int { + return tree.size +} + +// Size returns the number of elements stored in the subtree. +// Computed dynamically on each call, i.e. the subtree is traversed to count the number of the nodes. +func (node *Node) Size() int { + if node == nil { + return 0 + } + size := 1 + if node.Left != nil { + size += node.Left.Size() + } + if node.Right != nil { + size += node.Right.Size() + } + return size +} + +// Keys returns all keys in-order +func (tree *Tree) Keys() [][]byte { + keys := make([][]byte, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + keys[i] = it.Key() + } + return keys +} + +// Values returns all values in-order based on the key. +func (tree *Tree) Values() [][]byte { + values := make([][]byte, tree.size) + it := tree.Iterator() + for i := 0; it.Next(); i++ { + values[i] = it.Value() + } + return values +} + +// Left returns the left-most (min) node or nil if tree is empty. +func (tree *Tree) Left() *Node { + var parent *Node + current := tree.Root + for current != nil { + parent = current + current = current.Left + } + return parent +} + +// Right returns the right-most (max) node or nil if tree is empty. +func (tree *Tree) Right() *Node { + var parent *Node + current := tree.Root + for current != nil { + parent = current + current = current.Right + } + return parent +} + +// Floor Finds floor node of the input key, return the floor node or nil if no floor is found. +// Second return parameter is true if floor was found, otherwise false. +// +// Floor node is defined as the largest node that is smaller than or equal to the given node. +// A floor node may not be found, either because the tree is empty, or because +// all nodes in the tree are larger than the given node. +func (tree *Tree) Floor(key []byte) (floor *Node, found bool) { + found = false + node := tree.Root + for node != nil { + compare := BytesComparator(key, node.Key) + switch { + case compare == 0: + return node, true + case compare < 0: + node = node.Left + case compare > 0: + floor, found = node, true + node = node.Right + } + } + if found { + return floor, true + } + return nil, false +} + +// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling is found. +// Second return parameter is true if ceiling was found, otherwise false. +// +// Ceiling node is defined as the smallest node that is larger than or equal to the given node. +// A ceiling node may not be found, either because the tree is empty, or because +// all nodes in the tree are smaller than the given node. +func (tree *Tree) Ceiling(key []byte) (ceiling *Node, found bool) { + found = false + node := tree.Root + for node != nil { + compare := BytesComparator(key, node.Key) + switch { + case compare == 0: + return node, true + case compare < 0: + ceiling, found = node, true + node = node.Left + case compare > 0: + node = node.Right + } + } + if found { + return ceiling, true + } + return nil, false +} + +// Clear removes all nodes from the tree. +func (tree *Tree) Clear() { + tree.Root = nil + tree.size = 0 +} + +// String returns a string representation of container +func (tree *Tree) String() string { + str := "RedBlackTree\n" + if !tree.Empty() { + output(tree.Root, "", true, &str) + } + return str +} + +func (node *Node) String() string { + return fmt.Sprintf("%v", node.Key) +} + +func output(node *Node, prefix string, isTail bool, str *string) { + if node.Right != nil { + newPrefix := prefix + if isTail { + newPrefix += "│ " + } else { + newPrefix += " " + } + output(node.Right, newPrefix, false, str) + } + *str += prefix + if isTail { + *str += "└── " + } else { + *str += "┌── " + } + *str += node.String() + "\n" + if node.Left != nil { + newPrefix := prefix + if isTail { + newPrefix += " " + } else { + newPrefix += "│ " + } + output(node.Left, newPrefix, true, str) + } +} + +func (tree *Tree) lookup(key []byte) *Node { + node := tree.Root + for node != nil { + compare := BytesComparator(key, node.Key) + switch { + case compare == 0: + return node + case compare < 0: + node = node.Left + case compare > 0: + node = node.Right + } + } + return nil +} + +func (node *Node) grandparent() *Node { + if node != nil && node.Parent != nil { + return node.Parent.Parent + } + return nil +} + +func (node *Node) uncle() *Node { + if node == nil || node.Parent == nil || node.Parent.Parent == nil { + return nil + } + return node.Parent.sibling() +} + +func (node *Node) sibling() *Node { + if node == nil || node.Parent == nil { + return nil + } + if node == node.Parent.Left { + return node.Parent.Right + } + return node.Parent.Left +} + +func (tree *Tree) rotateLeft(node *Node) { + right := node.Right + tree.replaceNode(node, right) + node.Right = right.Left + if right.Left != nil { + right.Left.Parent = node + } + right.Left = node + node.Parent = right +} + +func (tree *Tree) rotateRight(node *Node) { + left := node.Left + tree.replaceNode(node, left) + node.Left = left.Right + if left.Right != nil { + left.Right.Parent = node + } + left.Right = node + node.Parent = left +} + +func (tree *Tree) replaceNode(old *Node, new *Node) { + if old.Parent == nil { + tree.Root = new + } else { + if old == old.Parent.Left { + old.Parent.Left = new + } else { + old.Parent.Right = new + } + } + if new != nil { + new.Parent = old.Parent + } +} + +func (tree *Tree) insertCase1(node *Node) { + if node.Parent == nil { + node.color = black + } else { + tree.insertCase2(node) + } +} + +func (tree *Tree) insertCase2(node *Node) { + if nodeColor(node.Parent) == black { + return + } + tree.insertCase3(node) +} + +func (tree *Tree) insertCase3(node *Node) { + uncle := node.uncle() + if nodeColor(uncle) == red { + node.Parent.color = black + uncle.color = black + node.grandparent().color = red + tree.insertCase1(node.grandparent()) + } else { + tree.insertCase4(node) + } +} + +func (tree *Tree) insertCase4(node *Node) { + grandparent := node.grandparent() + if node == node.Parent.Right && node.Parent == grandparent.Left { + tree.rotateLeft(node.Parent) + node = node.Left + } else if node == node.Parent.Left && node.Parent == grandparent.Right { + tree.rotateRight(node.Parent) + node = node.Right + } + tree.insertCase5(node) +} + +func (tree *Tree) insertCase5(node *Node) { + node.Parent.color = black + grandparent := node.grandparent() + grandparent.color = red + if node == node.Parent.Left && node.Parent == grandparent.Left { + tree.rotateRight(grandparent) + } else if node == node.Parent.Right && node.Parent == grandparent.Right { + tree.rotateLeft(grandparent) + } +} + +func (node *Node) maximumNode() *Node { + if node == nil { + return nil + } + for node.Right != nil { + node = node.Right + } + return node +} + +func (tree *Tree) deleteCase1(node *Node) { + if node.Parent == nil { + return + } + tree.deleteCase2(node) +} + +func (tree *Tree) deleteCase2(node *Node) { + sibling := node.sibling() + if nodeColor(sibling) == red { + node.Parent.color = red + sibling.color = black + if node == node.Parent.Left { + tree.rotateLeft(node.Parent) + } else { + tree.rotateRight(node.Parent) + } + } + tree.deleteCase3(node) +} + +func (tree *Tree) deleteCase3(node *Node) { + sibling := node.sibling() + if nodeColor(node.Parent) == black && + nodeColor(sibling) == black && + nodeColor(sibling.Left) == black && + nodeColor(sibling.Right) == black { + sibling.color = red + tree.deleteCase1(node.Parent) + } else { + tree.deleteCase4(node) + } +} + +func (tree *Tree) deleteCase4(node *Node) { + sibling := node.sibling() + if nodeColor(node.Parent) == red && + nodeColor(sibling) == black && + nodeColor(sibling.Left) == black && + nodeColor(sibling.Right) == black { + sibling.color = red + node.Parent.color = black + } else { + tree.deleteCase5(node) + } +} + +func (tree *Tree) deleteCase5(node *Node) { + sibling := node.sibling() + if node == node.Parent.Left && + nodeColor(sibling) == black && + nodeColor(sibling.Left) == red && + nodeColor(sibling.Right) == black { + sibling.color = red + sibling.Left.color = black + tree.rotateRight(sibling) + } else if node == node.Parent.Right && + nodeColor(sibling) == black && + nodeColor(sibling.Right) == red && + nodeColor(sibling.Left) == black { + sibling.color = red + sibling.Right.color = black + tree.rotateLeft(sibling) + } + tree.deleteCase6(node) +} + +func (tree *Tree) deleteCase6(node *Node) { + sibling := node.sibling() + sibling.color = nodeColor(node.Parent) + node.Parent.color = black + if node == node.Parent.Left && nodeColor(sibling.Right) == red { + sibling.Right.color = black + tree.rotateLeft(node.Parent) + } else if nodeColor(sibling.Left) == red { + sibling.Left.color = black + tree.rotateRight(node.Parent) + } +} + +func nodeColor(node *Node) color { + if node == nil { + return black + } + return node.color +} diff --git a/kvdb/redblacktree/redblacktree_test.go b/kvdb/redblacktree/redblacktree_test.go new file mode 100644 index 00000000..381db0f9 --- /dev/null +++ b/kvdb/redblacktree/redblacktree_test.go @@ -0,0 +1,640 @@ +// Copyright (c) 2015, Emir Pasic. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package redblacktree + +import ( + "fmt" + "strings" + "testing" + "unsafe" +) + +func TestRedBlackTreeGet(t *testing.T) { + tree := New() + + if actualValue := tree.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + if actualValue := tree.GetNode([]byte{2}).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } + + tree.Put([]byte{1}, []byte("x")) // 1->x + tree.Put([]byte{2}, []byte("b")) // 1->x, 2->b (in order) + tree.Put([]byte{1}, []byte("a")) // 1->a, 2->b (in order, replacement) + tree.Put([]byte{3}, []byte("c")) // 1->a, 2->b, 3->c (in order) + tree.Put([]byte{4}, []byte("d")) // 1->a, 2->b, 3->c, 4->d (in order) + tree.Put([]byte{5}, []byte("e")) // 1->a, 2->b, 3->c, 4->d, 5->e (in order) + tree.Put([]byte{6}, []byte("f")) // 1->a, 2->b, 3->c, 4->d, 5->e, 6->f (in order) + + fmt.Println(tree) + // + // RedBlackTree + // │ ┌── 6 + // │ ┌── 5 + // │ ┌── 4 + // │ │ └── 3 + // └── 2 + // └── 1 + + if actualValue := tree.Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode([]byte{4}).Size(); actualValue != 4 { + t.Errorf("Got %v expected %v", actualValue, 4) + } + + if actualValue := tree.GetNode([]byte{2}).Size(); actualValue != 6 { + t.Errorf("Got %v expected %v", actualValue, 6) + } + + if actualValue := tree.GetNode([]byte{8}).Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestRedBlackTreePut(t *testing.T) { + tree := New() + tree.Put([]byte{5}, []byte("e")) + tree.Put([]byte{6}, []byte("f")) + tree.Put([]byte{7}, []byte("g")) + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{4}, []byte("d")) + tree.Put([]byte{1}, []byte("x")) + tree.Put([]byte{2}, []byte("b")) + tree.Put([]byte{1}, []byte("a")) //overwrite + + if actualValue := tree.Size(); actualValue != 7 { + t.Errorf("Got %v expected %v", actualValue, 7) + } + + tests1 := [][]interface{}{ + {[]byte{1}, "a", true}, + {[]byte{2}, "b", true}, + {[]byte{3}, "c", true}, + {[]byte{4}, "d", true}, + {[]byte{5}, "e", true}, + {[]byte{6}, "f", true}, + {[]byte{7}, "g", true}, + {[]byte{8}, "", false}, + } + + for _, test := range tests1 { + // retrievals + actualValue, actualFound := tree.Get(test[0].([]byte)) + if string(actualValue) != test[1] || actualFound != test[2] { + t.Errorf("Got %v expected %v", actualValue, test[1]) + } + } + // do more check nil for not found key + actualValue, actualFound := tree.Get([]byte{8}) + if actualFound || actualValue != nil { + t.Errorf("Got %v expected %v", actualValue, nil) + } +} + +func TestRedBlackTreeCeilingAndFloor(t *testing.T) { + tree := New() + + if node, found := tree.Floor([]byte{0}); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + if node, found := tree.Ceiling([]byte{0}); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + tree.Put([]byte{5}, []byte("e")) + tree.Put([]byte{6}, []byte("f")) + tree.Put([]byte{7}, []byte("g")) + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{4}, []byte("d")) + tree.Put([]byte{1}, []byte("x")) + tree.Put([]byte{2}, []byte("b")) + + if node, found := tree.Floor([]byte{4}); int(node.Key[0]) != 4 || !found { + t.Errorf("Got %v expected %v", int(node.Key[0]), 4) + } + if node, found := tree.Floor([]byte{0}); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } + + if node, found := tree.Ceiling([]byte{4}); int(node.Key[0]) != 4 || !found { + t.Errorf("Got %v expected %v", int(node.Key[0]), 4) + } + if node, found := tree.Ceiling([]byte{8}); node != nil || found { + t.Errorf("Got %v expected %v", node, "") + } +} + +func TestRedBlackTreeIteratorNextOnEmpty(t *testing.T) { + tree := New() + it := tree.Iterator() + for it.Next() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestRedBlackTreeIteratorPrevOnEmpty(t *testing.T) { + tree := New() + it := tree.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty tree") + } +} + +func TestRedBlackTreeIterator1Next(t *testing.T) { + tree := New() + tree.Put([]byte{5}, []byte("e")) + tree.Put([]byte{6}, []byte("f")) + tree.Put([]byte{7}, []byte("g")) + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{4}, []byte("d")) + tree.Put([]byte{1}, []byte("x")) + tree.Put([]byte{2}, []byte("b")) + tree.Put([]byte{1}, []byte("a")) //overwrite + + fmt.Println(tree) + // │ ┌── 7 + // └── 6 + // │ ┌── 5 + // └── 4 + // │ ┌── 3 + // └── 2 + // └── 1 + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + key := it.Key() + if actualValue, expectedValue := key, count; int(actualValue[0]) != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIterator1Prev(t *testing.T) { + tree := New() + tree.Put([]byte{5}, []byte("e")) + tree.Put([]byte{6}, []byte("f")) + tree.Put([]byte{7}, []byte("g")) + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{4}, []byte("d")) + tree.Put([]byte{1}, []byte("x")) + tree.Put([]byte{2}, []byte("b")) + tree.Put([]byte{1}, []byte("a")) //overwrite + + fmt.Println(tree) + // │ ┌── 7 + // └── 6 + // │ ┌── 5 + // └── 4 + // │ ┌── 3 + // └── 2 + // └── 1 + it := tree.Iterator() + for it.Next() { + } + countDown := tree.size + for it.Prev() { + key := it.Key() + if actualValue, expectedValue := key, countDown; int(actualValue[0]) != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + countDown-- + } + if actualValue, expectedValue := countDown, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIterator4Next(t *testing.T) { + tree := New() + tree.Put([]byte{13}, []byte{5}) + tree.Put([]byte{8}, []byte{3}) + tree.Put([]byte{17}, []byte{7}) + tree.Put([]byte{1}, []byte{1}) + tree.Put([]byte{11}, []byte{4}) + tree.Put([]byte{15}, []byte{6}) + tree.Put([]byte{25}, []byte{9}) + tree.Put([]byte{6}, []byte{2}) + tree.Put([]byte{22}, []byte{8}) + tree.Put([]byte{27}, []byte{10}) + + fmt.Println(tree) + // │ ┌── 27 + // │ ┌── 25 + // │ │ └── 22 + // │ ┌── 17 + // │ │ └── 15 + // └── 13 + // │ ┌── 11 + // └── 8 + // │ ┌── 6 + // └── 1 + it := tree.Iterator() + count := 0 + for it.Next() { + count++ + value := it.Value() + if actualValue, expectedValue := value, count; int(actualValue[0]) != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, tree.Size(); actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIterator4Prev(t *testing.T) { + tree := New() + tree.Put([]byte{13}, []byte{5}) + tree.Put([]byte{8}, []byte{3}) + tree.Put([]byte{17}, []byte{7}) + tree.Put([]byte{1}, []byte{1}) + tree.Put([]byte{11}, []byte{4}) + tree.Put([]byte{15}, []byte{6}) + tree.Put([]byte{25}, []byte{9}) + tree.Put([]byte{6}, []byte{2}) + tree.Put([]byte{22}, []byte{8}) + tree.Put([]byte{27}, []byte{10}) + + fmt.Println(tree) + // │ ┌── 27 + // │ ┌── 25 + // │ │ └── 22 + // │ ┌── 17 + // │ │ └── 15 + // └── 13 + // │ ┌── 11 + // └── 8 + // │ ┌── 6 + // └── 1 + it := tree.Iterator() + count := tree.Size() + for it.Next() { + } + for it.Prev() { + value := it.Value() + if actualValue, expectedValue := value, count; int(actualValue[0]) != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + count-- + } + if actualValue, expectedValue := count, 0; actualValue != expectedValue { + t.Errorf("Size different. Got %v expected %v", actualValue, expectedValue) + } +} + +func TestRedBlackTreeIteratorBegin(t *testing.T) { + tree := New() + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{1}, []byte("a")) + tree.Put([]byte{2}, []byte("b")) + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + for it.Next() { + } + + it.Begin() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Next() + if key, value := it.Key(), it.Value(); int(key[0]) != 1 || string(value) != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestRedBlackTreeIteratorEnd(t *testing.T) { + tree := New() + it := tree.Iterator() + + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{1}, []byte("a")) + tree.Put([]byte{2}, []byte("b")) + it.End() + if it.node != nil { + t.Errorf("Got %v expected %v", it.node, nil) + } + + it.Prev() + if key, value := it.Key(), it.Value(); int(key[0]) != 3 || string(value) != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestRedBlackTreeIteratorFirst(t *testing.T) { + tree := New() + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{1}, []byte("a")) + tree.Put([]byte{2}, []byte("b")) + it := tree.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); int(key[0]) != 1 || string(value) != "a" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 1, "a") + } +} + +func TestRedBlackTreeIteratorLast(t *testing.T) { + tree := New() + tree.Put([]byte{3}, []byte("c")) + tree.Put([]byte{1}, []byte("a")) + tree.Put([]byte{2}, []byte("b")) + it := tree.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if key, value := it.Key(), it.Value(); int(key[0]) != 3 || string(value) != "c" { + t.Errorf("Got %v,%v expected %v,%v", key, value, 3, "c") + } +} + +func TestRedBlackTreeIteratorNextTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index []byte, value []byte) bool { + return strings.HasSuffix(string(value), "b") + } + + // NextTo (empty) + { + tree := New() + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (not found) + { + tree := New() + tree.Put([]byte{0}, []byte("xx")) + tree.Put([]byte{1}, []byte("yy")) + it := tree.Iterator() + for it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // NextTo (found) + { + tree := New() + tree.Put([]byte{2}, []byte("cc")) + tree.Put([]byte{0}, []byte("aa")) + tree.Put([]byte{1}, []byte("bb")) + it := tree.Iterator() + it.Begin() + if !it.NextTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); int(index[0]) != 1 || string(value) != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Next() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); int(index[0]) != 2 || string(value) != "cc" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 2, "cc") + } + if it.Next() { + t.Errorf("Should not go past last element") + } + } +} + +func TestRedBlackTreeIteratorPrevTo(t *testing.T) { + // Sample seek function, i.e. string starting with "b" + seek := func(index []byte, value []byte) bool { + return strings.HasSuffix(string(value), "b") + } + + // PrevTo (empty) + { + tree := New() + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (not found) + { + tree := New() + tree.Put([]byte{0}, []byte("xx")) + tree.Put([]byte{1}, []byte("yy")) + it := tree.Iterator() + it.End() + for it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + } + + // PrevTo (found) + { + tree := New() + tree.Put([]byte{2}, []byte("cc")) + tree.Put([]byte{0}, []byte("aa")) + tree.Put([]byte{1}, []byte("bb")) + it := tree.Iterator() + it.End() + if !it.PrevTo(seek) { + t.Errorf("Shouldn't iterate on empty tree") + } + if index, value := it.Key(), it.Value(); int(index[0]) != 1 || string(value) != "bb" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 1, "bb") + } + if !it.Prev() { + t.Errorf("Should go to first element") + } + if index, value := it.Key(), it.Value(); int(index[0]) != 0 || string(value) != "aa" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "aa") + } + if it.Prev() { + t.Errorf("Should not go before first element") + } + } +} + +func BenchmarkRedBlackTreeGet100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreeGet100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkGet(b, tree, size) +} + +func BenchmarkRedBlackTreePut100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreePut100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New() + b.StartTimer() + benchmarkPut(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove100(b *testing.B) { + b.StopTimer() + size := 100 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func BenchmarkRedBlackTreeRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + tree := New() + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + b.StartTimer() + benchmarkRemove(b, tree, size) +} + +func benchmarkGet(b *testing.B, tree *Tree, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Get(IntToByteArray(n)) + } + } +} + +func benchmarkPut(b *testing.B, tree *Tree, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Put(IntToByteArray(n), IntToByteArray(n)) + } + } +} + +func benchmarkRemove(b *testing.B, tree *Tree, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + tree.Remove(IntToByteArray(n)) + } + } +} + +func IntToByteArray(num int) []byte { + size := int(unsafe.Sizeof(num)) + arr := make([]byte, size) + for i := 0; i < size; i++ { + byt := *(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(&num)) + uintptr(i))) + arr[size-i-1] = byt + } + return arr +} From 28cf18da20dfb0ee6423e46eaca574347f95ba01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=20=C4=90ANG?= Date: Fri, 19 May 2023 22:18:16 +0700 Subject: [PATCH 2/3] use copy bytes to avoid side effect --- kvdb/flushable/flushable.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kvdb/flushable/flushable.go b/kvdb/flushable/flushable.go index f141e446..339a6a37 100644 --- a/kvdb/flushable/flushable.go +++ b/kvdb/flushable/flushable.go @@ -78,7 +78,7 @@ func (w *Flushable) Put(key []byte, value []byte) error { } func (w *Flushable) put(key []byte, value []byte) { - w.modified.Put(key, common.CopyBytes(value)) + w.modified.Put(common.CopyBytes(key), common.CopyBytes(value)) *w.sizeEstimation += len(key) + len(value) + 128 } @@ -91,7 +91,7 @@ func (w *flushableReader) Has(key []byte) (bool, error) { return false, errClosed } - val, ok := w.modified.Get(key) + val, ok := w.modified.Get(common.CopyBytes(key)) if ok { return val != nil, nil } @@ -108,7 +108,7 @@ func (w *flushableReader) Get(key []byte) ([]byte, error) { return nil, errClosed } - if entry, ok := w.modified.Get(key); ok { + if entry, ok := w.modified.Get(common.CopyBytes(key)); ok { if entry == nil { return nil, nil } @@ -128,7 +128,7 @@ func (w *Flushable) Delete(key []byte) error { } func (w *Flushable) delete(key []byte) { - w.modified.Put(key, nil) + w.modified.Put(common.CopyBytes(key), nil) *w.sizeEstimation += len(key) + 128 // it should be (len(key) - len(old value)), but we'd need to read old value } @@ -296,7 +296,7 @@ func castToPair(node *rbt.Node) (key, val []byte) { func (it *flushableIterator) init() { it.parentOk = it.parentIt.Next() if len(it.start) != 0 { - it.treeNode, it.treeOk = it.tree.Ceiling(it.start) // not strict >= + it.treeNode, it.treeOk = it.tree.Ceiling(common.CopyBytes(it.start)) // not strict >= } else { it.treeNode = it.tree.Left() // lowest key it.treeOk = it.treeNode != nil From 593fcea2436159416731b7dedf358b1a6cd68e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=20=C4=90ANG?= Date: Fri, 19 May 2023 22:35:50 +0700 Subject: [PATCH 3/3] fix lint error for test code --- kvdb/redblacktree/redblacktree_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kvdb/redblacktree/redblacktree_test.go b/kvdb/redblacktree/redblacktree_test.go index 381db0f9..9ab15aa9 100644 --- a/kvdb/redblacktree/redblacktree_test.go +++ b/kvdb/redblacktree/redblacktree_test.go @@ -12,6 +12,7 @@ import ( ) func TestRedBlackTreeGet(t *testing.T) { + t.Parallel() tree := New() if actualValue := tree.Size(); actualValue != 0 { @@ -58,6 +59,7 @@ func TestRedBlackTreeGet(t *testing.T) { } func TestRedBlackTreePut(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{5}, []byte("e")) tree.Put([]byte{6}, []byte("f")) @@ -98,6 +100,7 @@ func TestRedBlackTreePut(t *testing.T) { } func TestRedBlackTreeCeilingAndFloor(t *testing.T) { + t.Parallel() tree := New() if node, found := tree.Floor([]byte{0}); node != nil || found { @@ -131,6 +134,7 @@ func TestRedBlackTreeCeilingAndFloor(t *testing.T) { } func TestRedBlackTreeIteratorNextOnEmpty(t *testing.T) { + t.Parallel() tree := New() it := tree.Iterator() for it.Next() { @@ -139,6 +143,7 @@ func TestRedBlackTreeIteratorNextOnEmpty(t *testing.T) { } func TestRedBlackTreeIteratorPrevOnEmpty(t *testing.T) { + t.Parallel() tree := New() it := tree.Iterator() for it.Prev() { @@ -147,6 +152,7 @@ func TestRedBlackTreeIteratorPrevOnEmpty(t *testing.T) { } func TestRedBlackTreeIterator1Next(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{5}, []byte("e")) tree.Put([]byte{6}, []byte("f")) @@ -180,6 +186,7 @@ func TestRedBlackTreeIterator1Next(t *testing.T) { } func TestRedBlackTreeIterator1Prev(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{5}, []byte("e")) tree.Put([]byte{6}, []byte("f")) @@ -215,6 +222,7 @@ func TestRedBlackTreeIterator1Prev(t *testing.T) { } func TestRedBlackTreeIterator4Next(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{13}, []byte{5}) tree.Put([]byte{8}, []byte{3}) @@ -253,6 +261,7 @@ func TestRedBlackTreeIterator4Next(t *testing.T) { } func TestRedBlackTreeIterator4Prev(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{13}, []byte{5}) tree.Put([]byte{8}, []byte{3}) @@ -293,6 +302,7 @@ func TestRedBlackTreeIterator4Prev(t *testing.T) { } func TestRedBlackTreeIteratorBegin(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{3}, []byte("c")) tree.Put([]byte{1}, []byte("a")) @@ -325,6 +335,7 @@ func TestRedBlackTreeIteratorBegin(t *testing.T) { } func TestRedBlackTreeIteratorEnd(t *testing.T) { + t.Parallel() tree := New() it := tree.Iterator() @@ -352,6 +363,7 @@ func TestRedBlackTreeIteratorEnd(t *testing.T) { } func TestRedBlackTreeIteratorFirst(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{3}, []byte("c")) tree.Put([]byte{1}, []byte("a")) @@ -366,6 +378,7 @@ func TestRedBlackTreeIteratorFirst(t *testing.T) { } func TestRedBlackTreeIteratorLast(t *testing.T) { + t.Parallel() tree := New() tree.Put([]byte{3}, []byte("c")) tree.Put([]byte{1}, []byte("a")) @@ -380,6 +393,7 @@ func TestRedBlackTreeIteratorLast(t *testing.T) { } func TestRedBlackTreeIteratorNextTo(t *testing.T) { + t.Parallel() // Sample seek function, i.e. string starting with "b" seek := func(index []byte, value []byte) bool { return strings.HasSuffix(string(value), "b") @@ -432,6 +446,7 @@ func TestRedBlackTreeIteratorNextTo(t *testing.T) { } func TestRedBlackTreeIteratorPrevTo(t *testing.T) { + t.Parallel() // Sample seek function, i.e. string starting with "b" seek := func(index []byte, value []byte) bool { return strings.HasSuffix(string(value), "b")