Feat: LockableList (pushpop)
This commit is contained in:
parent
c7b1d5eba5
commit
c167129c6d
@ -93,7 +93,7 @@ const charsetWithUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12
|
||||
func RandStr(size int) string {
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i != size; i++ {
|
||||
buf[i] = charset[uint32(RNG(36))%uint32(len(charset))]
|
||||
buf[i] = charset[GetOptimizedRand().Uint32()%uint32(len(charset))]
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.tcp.direct/kayos/common
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/crypto v0.7.0
|
||||
nullprogram.com/x/rng v1.1.0
|
||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
|
310
list/list.go
Normal file
310
list/list.go
Normal file
@ -0,0 +1,310 @@
|
||||
// Package list implements a locking list.l
|
||||
package list
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrElementNotInList = errors.New("element not in list")
|
||||
ErrMarkNotInList = errors.New("mark not in list")
|
||||
ErrNilValue = errors.New("nil element")
|
||||
ErrUninitialized = errors.New("uninitialized list")
|
||||
)
|
||||
|
||||
type LockingList struct {
|
||||
l *list.List
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
func (ll *LockingList) Lock() error {
|
||||
if ll == nil || ll.RWMutex == nil {
|
||||
return ErrUninitialized
|
||||
}
|
||||
ll.RWMutex.Lock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) Unlock() {
|
||||
if ll.RWMutex != nil {
|
||||
ll.RWMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (ll *LockingList) RLock() error {
|
||||
switch {
|
||||
case
|
||||
ll == nil,
|
||||
ll.RWMutex == nil,
|
||||
ll.l == nil:
|
||||
return ErrUninitialized
|
||||
default:
|
||||
//
|
||||
}
|
||||
ll.RWMutex.RLock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) RUnlock() {
|
||||
if ll.RWMutex != nil {
|
||||
ll.RWMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func New() *LockingList {
|
||||
ll := &LockingList{
|
||||
l: list.New(),
|
||||
RWMutex: &sync.RWMutex{},
|
||||
}
|
||||
return ll
|
||||
}
|
||||
|
||||
func (ll *LockingList) wrapElement(e *list.Element) *Element {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return &Element{
|
||||
list: ll,
|
||||
Element: e,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes or clears list l.
|
||||
func (ll *LockingList) Init() *LockingList {
|
||||
if ll.l != nil {
|
||||
_ = ll.Lock()
|
||||
ll.l.Init()
|
||||
ll.Unlock()
|
||||
return ll
|
||||
}
|
||||
ll.l = list.New()
|
||||
ll.RWMutex = &sync.RWMutex{}
|
||||
return ll
|
||||
}
|
||||
|
||||
func (ll *LockingList) InsertAfter(v any, mark *Element) (*Element, error) {
|
||||
if err := ll.check(v, mark, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = ll.Lock()
|
||||
res := ll.l.InsertAfter(v, mark.Element)
|
||||
ll.Unlock()
|
||||
return ll.wrapElement(res), nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) InsertBefore(v any, mark *Element) (*Element, error) {
|
||||
if err := ll.check(v, mark, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = ll.Lock()
|
||||
res := ll.wrapElement(
|
||||
ll.l.InsertBefore(v, mark.Element),
|
||||
)
|
||||
ll.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) Len() int {
|
||||
_ = ll.RLock()
|
||||
l := ll.l.Len()
|
||||
ll.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
func (ll *LockingList) MoveAfter(e, mark *Element) error {
|
||||
_ = ll.Lock()
|
||||
ll.l.MoveAfter(e.Element, mark.Element)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) Front() *Element {
|
||||
_ = ll.RLock()
|
||||
e := ll.l.Front()
|
||||
ll.RUnlock()
|
||||
return ll.wrapElement(e)
|
||||
}
|
||||
|
||||
func (ll *LockingList) Back() *Element {
|
||||
_ = ll.RLock()
|
||||
e := ll.l.Back()
|
||||
ll.RUnlock()
|
||||
return ll.wrapElement(e)
|
||||
}
|
||||
|
||||
func (ll *LockingList) MoveToBack(e *Element) error {
|
||||
_ = ll.Lock()
|
||||
ll.l.MoveToBack(e.Element)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) MoveToFront(e *Element) error {
|
||||
_ = ll.Lock()
|
||||
ll.l.MoveToFront(e.Element)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) PushBack(v any) *Element {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
_ = ll.Lock()
|
||||
e := ll.l.PushBack(v)
|
||||
ll.Unlock()
|
||||
return ll.wrapElement(e)
|
||||
}
|
||||
|
||||
func (ll *LockingList) PushFront(v any) *Element {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
if err := ll.Lock(); err != nil {
|
||||
return nil
|
||||
}
|
||||
e := ll.l.PushFront(v)
|
||||
ll.Unlock()
|
||||
return ll.wrapElement(e)
|
||||
}
|
||||
|
||||
func (ll *LockingList) Remove(elm *Element) error {
|
||||
if ll.l == nil {
|
||||
return ErrUninitialized
|
||||
}
|
||||
if err := ll.check(elm, nil, false); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = ll.Lock()
|
||||
_ = ll.l.Remove(elm.Element)
|
||||
elm.list = nil // avoid memory leaks
|
||||
elm.Element = nil // avoid memory leaks
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rotate moves the first element to the back of the list and returns it.
|
||||
func (ll *LockingList) Rotate() *Element {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
if ll.Len() < 1 {
|
||||
return nil
|
||||
}
|
||||
_ = ll.Lock()
|
||||
e := ll.l.Front()
|
||||
ll.l.MoveToBack(e)
|
||||
ll.Unlock()
|
||||
return ll.wrapElement(e)
|
||||
}
|
||||
|
||||
func (ll *LockingList) Push(item any) (err error) {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
if err = ll.Lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
ll.l.PushBack(item)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) Pop() any {
|
||||
if ll.Len() < 1 {
|
||||
return nil
|
||||
}
|
||||
_ = ll.Lock()
|
||||
e := ll.l.Back()
|
||||
ll.l.Remove(e)
|
||||
ll.Unlock()
|
||||
return e.Value
|
||||
}
|
||||
|
||||
func (ll *LockingList) PushBackList(other *LockingList) error {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
_ = ll.Lock()
|
||||
ll.l.PushBackList(other.l)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LockingList) PushFrontList(other *LockingList) error {
|
||||
if ll.l == nil {
|
||||
ll.Init()
|
||||
}
|
||||
_ = ll.Lock()
|
||||
ll.l.PushFrontList(other.l)
|
||||
ll.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
*list.Element
|
||||
list *LockingList
|
||||
}
|
||||
|
||||
func (e *Element) Value() any {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if e.Element == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Element.Value
|
||||
}
|
||||
|
||||
func (e *Element) Next() *Element {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if err := e.list.RLock(); err != nil {
|
||||
return nil
|
||||
}
|
||||
ne := e.list.wrapElement(e.Element.Next())
|
||||
e.list.RUnlock()
|
||||
return ne
|
||||
}
|
||||
|
||||
func (e *Element) Prev() *Element {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if err := e.list.RLock(); err != nil {
|
||||
return nil
|
||||
}
|
||||
pe := e.list.wrapElement(e.Element.Prev())
|
||||
e.list.RUnlock()
|
||||
return pe
|
||||
}
|
||||
|
||||
func (ll *LockingList) check(item any, mark *Element, needsMark bool) (err error) {
|
||||
var elm *Element
|
||||
var isElement bool
|
||||
|
||||
elm, isElement = item.(*Element)
|
||||
|
||||
if err = ll.RLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case
|
||||
needsMark && mark == nil,
|
||||
needsMark && mark.list != ll:
|
||||
err = ErrMarkNotInList
|
||||
|
||||
case
|
||||
isElement && elm.Element == nil,
|
||||
isElement && elm.list != ll:
|
||||
err = ErrElementNotInList
|
||||
|
||||
default:
|
||||
err = nil
|
||||
}
|
||||
ll.RUnlock()
|
||||
return
|
||||
}
|
562
list/list_test.go
Normal file
562
list/list_test.go
Normal file
@ -0,0 +1,562 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Adapted from golang source
|
||||
func checkListLen(t *testing.T, l *LockingList, length int) bool {
|
||||
t.Helper()
|
||||
if n := l.Len(); n != length {
|
||||
t.Errorf("l.Len() = %d, want %d", n, length)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Adapted from golang source
|
||||
func checkList(t *testing.T, l *LockingList, es []any) {
|
||||
t.Helper()
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
le := e.Value()
|
||||
if le != es[i] {
|
||||
t.Errorf("\uF630 elt[%d].Value = %v, want %v", i, le, es[i])
|
||||
}
|
||||
// t.Logf("\uF634 elt[%d].Value = %v, want %v", i, le, es[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func GetUnexportedField(field reflect.Value) interface{} {
|
||||
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
|
||||
}
|
||||
|
||||
func checkListPointers(t *testing.T, ll *LockingList, es []*list.Element) {
|
||||
if !checkListLen(t, ll, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
newList := reflect.New(reflect.TypeOf(*ll.l)).Elem()
|
||||
root := newList.FieldByName("root")
|
||||
|
||||
// zero length lists must be the zero value or properly initialized (sentinel circle)
|
||||
if len(es) == 0 {
|
||||
next := root.FieldByName("next")
|
||||
prev := root.FieldByName("prev")
|
||||
if !next.IsNil() && next != root || !prev.IsNil() && prev != root {
|
||||
t.Errorf("l.root.next = %v; should be nil or %v",
|
||||
next, root.Type(),
|
||||
)
|
||||
}
|
||||
if !prev.IsNil() && next != root || !prev.IsNil() && prev != root {
|
||||
t.Errorf("l.root.prev = %p; should be nil or %v",
|
||||
prev.Type(), root.Type(),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from golang source
|
||||
func TestExtending(t *testing.T) { //nolint:funlen,gocyclo
|
||||
t.Parallel()
|
||||
l1 := New()
|
||||
l2 := New()
|
||||
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
l1.PushBack(3)
|
||||
|
||||
l2.PushBack(4)
|
||||
l2.PushBack(5)
|
||||
|
||||
l3 := New()
|
||||
if err := l3.PushBackList(l1); err != nil {
|
||||
t.Error(errors.New("PushBackList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3})
|
||||
if err := l3.PushBackList(l2); err != nil {
|
||||
t.Error(errors.New("PushBackList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3, 4, 5})
|
||||
|
||||
l3 = New()
|
||||
if err := l3.PushFrontList(l2); err != nil {
|
||||
t.Error(errors.New("PushFrontList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{4, 5})
|
||||
if err := l3.PushFrontList(l1); err != nil {
|
||||
t.Error(errors.New("PushFrontList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3, 4, 5})
|
||||
|
||||
checkList(t, l1, []any{1, 2, 3})
|
||||
checkList(t, l2, []any{4, 5})
|
||||
|
||||
l3 = New()
|
||||
if err := l3.PushBackList(l1); err != nil {
|
||||
t.Error(errors.New("PushBackList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3})
|
||||
if err := l3.PushBackList(l3); err != nil {
|
||||
t.Error(errors.New("PushBackList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
if err := l3.PushFrontList(l1); err != nil {
|
||||
return
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3})
|
||||
if err := l3.PushFrontList(l3); err != nil {
|
||||
t.Error(errors.New("PushFrontList failed"))
|
||||
}
|
||||
checkList(t, l3, []any{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
if err := l1.PushBackList(l3); err != nil {
|
||||
t.Error(errors.New("PushBackList failed"))
|
||||
}
|
||||
checkList(t, l1, []any{1, 2, 3})
|
||||
if err := l1.PushFrontList(l3); err != nil {
|
||||
t.Error(errors.New("PushFrontList failed"))
|
||||
}
|
||||
checkList(t, l1, []any{1, 2, 3})
|
||||
}
|
||||
|
||||
// Adapted from golang source
|
||||
func TestIssue4103(t *testing.T) {
|
||||
t.Parallel()
|
||||
l1 := New()
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
|
||||
l2 := New()
|
||||
l2.PushBack(3)
|
||||
l2.PushBack(4)
|
||||
|
||||
e := l1.Front()
|
||||
err := l2.Remove(e)
|
||||
if err == nil {
|
||||
t.Errorf("l2.Remove(e) = %v, want ErrElementNotInList", err)
|
||||
}
|
||||
if !errors.Is(err, ErrElementNotInList) {
|
||||
t.Errorf("l2.Remove(e) = %v, want ErrElementNotInList", err)
|
||||
}
|
||||
|
||||
// l2 should not change because e is not an element of l2
|
||||
if n := l2.Len(); n != 2 {
|
||||
t.Errorf("l2.Len() = %d, want 2", n)
|
||||
}
|
||||
|
||||
var ne *Element
|
||||
|
||||
if ne, err = l1.InsertBefore(8, e); err != nil {
|
||||
t.Errorf("l1.InsertBefore(8, e) = %v, want nil", err)
|
||||
}
|
||||
|
||||
//goland:noinspection GoCommentLeadingSpace (lol)
|
||||
if ne == nil { // nolint:SA5011
|
||||
t.Fatalf("l1.InsertBefore(8, e) = nil, want non-nil")
|
||||
}
|
||||
|
||||
//goland:noinspection GoCommentLeadingSpace (lol)
|
||||
if ne.Element == nil { // nolint:SA5011
|
||||
t.Errorf("l1.InsertBefore(8, e) = nil, want non-nil")
|
||||
}
|
||||
if ne.Value() != 8 {
|
||||
t.Errorf("l1.InsertBefore(8, e) = %v, want 8", ne.Value())
|
||||
}
|
||||
|
||||
if n := l1.Len(); n != 3 {
|
||||
t.Errorf("l1.Len() = %d, want 3", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from golang source
|
||||
func TestIssue6349(t *testing.T) {
|
||||
t.Parallel()
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
|
||||
e := l.Front()
|
||||
if e.Value() != 1 {
|
||||
t.Errorf("e.value = %d, want 1", e.Value())
|
||||
}
|
||||
if err := l.Remove(e); err != nil {
|
||||
t.Errorf("l.Remove(e) = %v, want nil", err)
|
||||
}
|
||||
if e.Next() != nil {
|
||||
t.Errorf("e.Next() != nil")
|
||||
}
|
||||
if e.Prev() != nil {
|
||||
t.Errorf("e.Prev() != nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
|
||||
// Adapted from golang source.
|
||||
func TestZeroList(t *testing.T) {
|
||||
t.Parallel()
|
||||
var l1 = new(LockingList)
|
||||
l1.PushFront(1)
|
||||
checkList(t, l1, []any{1})
|
||||
|
||||
var l2 = new(LockingList)
|
||||
l2.PushBack(1)
|
||||
checkList(t, l2, []any{1})
|
||||
|
||||
var l3 = new(LockingList)
|
||||
if err := l3.PushFrontList(l1); err != nil {
|
||||
t.Errorf("l3.PushFrontList(l1) = %v, want nil", err)
|
||||
}
|
||||
checkList(t, l3, []any{1})
|
||||
|
||||
var l4 = new(LockingList)
|
||||
if err := l4.PushBackList(l2); err != nil {
|
||||
t.Errorf("l4.PushBackList(l2) = %v, want nil", err)
|
||||
}
|
||||
checkList(t, l4, []any{1})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
|
||||
// Adapted from golang source.
|
||||
func TestInsertBeforeUnknownMark(t *testing.T) {
|
||||
t.Parallel()
|
||||
var l = New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
_, err := l.InsertBefore(1, new(Element))
|
||||
if err == nil || !errors.Is(err, ErrMarkNotInList) {
|
||||
t.Errorf("l.InsertBefore(1, new(Element)) = %v, want ErrMarkNotInList", err)
|
||||
}
|
||||
checkList(t, l, []any{1, 2, 3})
|
||||
}
|
||||
|
||||
// TestList tests the list implementation.
|
||||
// Mostly adapted from golang source
|
||||
func TestList(t *testing.T) {
|
||||
l := New()
|
||||
checkListPointers(t, l, []*list.Element{})
|
||||
|
||||
// Single element list
|
||||
e := l.PushFront("a")
|
||||
checkListPointers(t, l, []*list.Element{e.Element})
|
||||
if err := l.MoveToFront(e); err != nil {
|
||||
t.Errorf("MoveToFront(e) = %v, want nil", err)
|
||||
}
|
||||
if err := l.MoveAfter(e, e); err != nil {
|
||||
t.Errorf("MoveAfter(e, e) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e.Element})
|
||||
if err := l.MoveToBack(e); err != nil {
|
||||
t.Errorf("MoveToBack(e) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e.Element})
|
||||
if err := l.Remove(e); err != nil {
|
||||
t.Errorf("Remove(e) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{})
|
||||
|
||||
// Bigger list
|
||||
e2 := l.PushFront(2)
|
||||
e1 := l.PushFront(1)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack("banana")
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e3.Element, e4.Element})
|
||||
|
||||
var err error
|
||||
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e3.Element, e4.Element})
|
||||
|
||||
// move from middle
|
||||
if err = l.MoveToFront(e3); err != nil {
|
||||
t.Errorf("MoveToFront(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
|
||||
|
||||
if err = l.MoveToFront(e1); err != nil {
|
||||
t.Errorf("MoveToFront(e1) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// move from middle
|
||||
if err = l.MoveToBack(e3); err != nil {
|
||||
t.Errorf("MoveToBack(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
|
||||
|
||||
// move from back
|
||||
if err = l.MoveToFront(e3); err != nil {
|
||||
t.Errorf("MoveToFront(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
|
||||
|
||||
// should be no-op
|
||||
if err = l.MoveToFront(e3); err != nil {
|
||||
t.Errorf("MoveToFront(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e3.Element, e1.Element, e4.Element})
|
||||
|
||||
// move from front
|
||||
if err = l.MoveToBack(e3); err != nil {
|
||||
t.Errorf("MoveToBack(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
|
||||
|
||||
// should be no-op
|
||||
if err = l.MoveToBack(e3); err != nil {
|
||||
t.Errorf("MoveToBack(e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element})
|
||||
|
||||
// insert before front
|
||||
if e2, err = l.InsertBefore(2, e1); err != nil {
|
||||
t.Errorf("InsertBefore(2, e1) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e2.Element, e1.Element, e4.Element, e3.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// insert before middle
|
||||
if e2, err = l.InsertBefore(2, e4); err != nil {
|
||||
t.Errorf("InsertBefore(2, e4) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e4.Element, e3.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// insert before back
|
||||
if e2, err = l.InsertBefore(2, e3); err != nil {
|
||||
t.Errorf("InsertBefore(2, e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e2.Element, e3.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// insert after front
|
||||
if e2, err = l.InsertAfter(2, e1); err != nil {
|
||||
t.Errorf("InsertAfter(2, e1) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e2.Element, e4.Element, e3.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// insert after middle
|
||||
if e2, err = l.InsertAfter(2, e4); err != nil {
|
||||
t.Errorf("InsertAfter(2, e4) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e2.Element, e3.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// insert after back
|
||||
if e2, err = l.InsertAfter(2, e3); err != nil {
|
||||
t.Errorf("InsertAfter(2, e3) = %v, want nil", err)
|
||||
}
|
||||
checkListPointers(t, l, []*list.Element{e1.Element, e4.Element, e3.Element, e2.Element})
|
||||
if err = l.Remove(e2); err != nil {
|
||||
t.Errorf("Remove(e2) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// Check standard iteration.
|
||||
sum := 0
|
||||
for e = l.Front(); e != nil; e = e.Next() {
|
||||
if i, ok := e.Element.Value.(int); ok {
|
||||
sum += i
|
||||
}
|
||||
}
|
||||
if sum != 4 {
|
||||
t.Errorf("sum over l = %d, want 4", sum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThePlanet(t *testing.T) {
|
||||
t.Parallel()
|
||||
var err error
|
||||
var nl = New()
|
||||
|
||||
if nl.Len() != 0 {
|
||||
t.Errorf("Init() failed to reset list length to 0")
|
||||
}
|
||||
|
||||
nl.PushFront(1)
|
||||
if nl.Pop() != 1 {
|
||||
t.Errorf("Pop() failed to return first element")
|
||||
}
|
||||
if nl.Len() != 0 {
|
||||
t.Errorf("Pop() failed to remove first element")
|
||||
}
|
||||
|
||||
if err = nl.Push(1); err != nil {
|
||||
t.Errorf("Push(1) = %v, want nil", err)
|
||||
}
|
||||
|
||||
nl.l = nil
|
||||
if err = nl.Push(1); err != nil {
|
||||
t.Errorf("Push(1) = %v, want %v", err, nil)
|
||||
}
|
||||
if nl.Pop() != 1 {
|
||||
t.Errorf("Pop() = %v, want %v", err, 1)
|
||||
}
|
||||
if nl.Pop() != nil {
|
||||
t.Errorf("Pop() = %v, want %v", err, nil)
|
||||
}
|
||||
|
||||
t.Run("PushPop", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
pl := New()
|
||||
for i := 1; i < 5; i++ {
|
||||
if err = pl.Push(i); err != nil {
|
||||
t.Errorf("Push(%d) = %v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
for i := 4; i > 0; i-- {
|
||||
if pl.Pop() != i {
|
||||
t.Errorf("Pop() = %d, want %d", pl.Pop(), i)
|
||||
}
|
||||
}
|
||||
for i := 1; i < 5; i++ {
|
||||
if err = pl.Push(i); err != nil {
|
||||
t.Errorf("Push(%d) = %v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Concurrent", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cl := New()
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
go func(n int) {
|
||||
if cerr := cl.Push(n); cerr != nil {
|
||||
t.Errorf("Push(%d) err = %v, want nil", i, cerr)
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if cl.Len() != 1000 {
|
||||
t.Errorf("Len() = %d, want 1000", cl.Len())
|
||||
}
|
||||
for i := 0; i < 1000; i++ {
|
||||
if cl.Pop() == nil {
|
||||
t.Errorf("Pop() = %d, want %d", cl.Pop(), i)
|
||||
}
|
||||
if cl.Len() != 999-i {
|
||||
t.Errorf("Len() = %d, want %d", cl.Len(), 999-i)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
nl.Init()
|
||||
for i := 1; i < 5; i++ {
|
||||
if err = nl.Push(i); err != nil {
|
||||
t.Errorf("Push(%d) = %v, want nil", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i < 100; i++ {
|
||||
for j := 1; j < 5; j++ {
|
||||
res := nl.Rotate()
|
||||
t.Logf("Rotate() = %d", res.Value())
|
||||
if res.Value() != j {
|
||||
t.Errorf("Rotate() = %d, want %d", res.Value(), j)
|
||||
}
|
||||
if j > 4 && res.Next() == nil {
|
||||
t.Fatalf("%d.Next is nil", res.Value())
|
||||
}
|
||||
if j < 2 && res.Prev() == nil {
|
||||
t.Errorf("Prev is nil")
|
||||
}
|
||||
if res.Next() != nil && res.Next().Value() != j+1 {
|
||||
t.Errorf("Next = %d, want %d", res.Next().Value(), j+1)
|
||||
}
|
||||
if res.Prev() != nil && j-1 != 0 && res.Prev().Value() != j-1 {
|
||||
t.Errorf("Prev = %d, want %d", res.Prev().Value(), j-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("CoverageStuff", func(t *testing.T) {
|
||||
nl.RWMutex = nil
|
||||
if !errors.Is(nl.Lock(), ErrUninitialized) {
|
||||
t.Errorf("Lock() = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
var yeet *Element
|
||||
if yeet, err = nl.InsertAfter(1, nil); !errors.Is(err, ErrUninitialized) {
|
||||
t.Errorf("InsertAfter(1, nil) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
if yeet.Next() != nil {
|
||||
t.Errorf("Next() = %v, want %v", yeet.Next(), nil)
|
||||
}
|
||||
if yeet.Prev() != nil {
|
||||
t.Errorf("Prev() = %v, want %v", yeet.Prev(), nil)
|
||||
}
|
||||
if _, err = nl.InsertBefore(1, nil); !errors.Is(err, ErrUninitialized) {
|
||||
t.Errorf("InsertAfter(1, nil) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
if err = nl.Remove(nil); !errors.Is(err, ErrUninitialized) {
|
||||
t.Errorf("Remove(nil) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
if err = nl.Push(1); !errors.Is(err, ErrUninitialized) {
|
||||
t.Errorf("Push(1) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
if e := nl.PushFront(1); e != nil {
|
||||
t.Errorf("PushFront(1) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
nl.l = nil
|
||||
nl.Rotate()
|
||||
if nl.wrapElement(nil) != nil {
|
||||
t.Errorf("wrapElement(e) = %v, want nil", err)
|
||||
}
|
||||
if nl.wrapElement(nil).Value() != nil {
|
||||
t.Errorf("wrapElement(e).Value() = %v, want nil", err)
|
||||
}
|
||||
el := &Element{}
|
||||
if el.Value() != nil {
|
||||
t.Errorf("el.Value() = %v, want nil", err)
|
||||
}
|
||||
if _, err = nl.InsertAfter(1, nil); err == nil {
|
||||
t.Errorf("InsertAfter(1, nil) = %v, want error", err)
|
||||
}
|
||||
nl.Init()
|
||||
nl.Init()
|
||||
nl.l = nil
|
||||
if !errors.Is(nl.Remove(nil), ErrUninitialized) {
|
||||
t.Errorf("Remove(nil) = %v, want %v", err, ErrUninitialized)
|
||||
}
|
||||
nl.Init()
|
||||
_ = nl.Push(1)
|
||||
_ = nl.Push(2)
|
||||
if nl.Back().Value() == 1 {
|
||||
t.Errorf("Back() = %v, want %v", err, 1)
|
||||
}
|
||||
})
|
||||
|
||||
// Clear all elements by iterating
|
||||
for nl.Len() > 0 {
|
||||
nl.Pop()
|
||||
}
|
||||
checkListPointers(t, nl, []*list.Element{})
|
||||
}
|
Loading…
Reference in New Issue
Block a user