|
|
- package clist
-
- import (
- "fmt"
- mrand "math/rand"
- "runtime"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
-
- func TestPanicOnMaxLength(t *testing.T) {
- maxLength := 1000
-
- l := newWithMax(maxLength)
- for i := 0; i < maxLength; i++ {
- l.PushBack(1)
- }
- assert.Panics(t, func() {
- l.PushBack(1)
- })
- }
-
- func TestSmall(t *testing.T) {
- l := New()
- el1 := l.PushBack(1)
- el2 := l.PushBack(2)
- el3 := l.PushBack(3)
- if l.Len() != 3 {
- t.Error("Expected len 3, got ", l.Len())
- }
-
- // fmt.Printf("%p %v\n", el1, el1)
- // fmt.Printf("%p %v\n", el2, el2)
- // fmt.Printf("%p %v\n", el3, el3)
-
- r1 := l.Remove(el1)
-
- // fmt.Printf("%p %v\n", el1, el1)
- // fmt.Printf("%p %v\n", el2, el2)
- // fmt.Printf("%p %v\n", el3, el3)
-
- r2 := l.Remove(el2)
-
- // fmt.Printf("%p %v\n", el1, el1)
- // fmt.Printf("%p %v\n", el2, el2)
- // fmt.Printf("%p %v\n", el3, el3)
-
- r3 := l.Remove(el3)
-
- if r1 != 1 {
- t.Error("Expected 1, got ", r1)
- }
- if r2 != 2 {
- t.Error("Expected 2, got ", r2)
- }
- if r3 != 3 {
- t.Error("Expected 3, got ", r3)
- }
- if l.Len() != 0 {
- t.Error("Expected len 0, got ", l.Len())
- }
-
- }
-
- func TestGCFifo(t *testing.T) {
-
- const numElements = 1000000
- l := New()
- gcCount := 0
-
- // SetFinalizer doesn't work well with circular structures,
- // so we construct a trivial non-circular structure to
- // track.
- type value struct {
- Int int
- }
-
- gcCh := make(chan struct{})
- for i := 0; i < numElements; i++ {
- v := new(value)
- v.Int = i
- l.PushBack(v)
- runtime.SetFinalizer(v, func(v *value) {
- gcCh <- struct{}{}
- })
- }
-
- for el := l.Front(); el != nil; {
- l.Remove(el)
- // oldEl := el
- el = el.Next()
- // oldEl.DetachPrev()
- // oldEl.DetachNext()
- }
-
- tickerQuitCh := make(chan struct{})
- tickerDoneCh := make(chan struct{})
- go func() {
- defer close(tickerDoneCh)
- ticker := time.NewTicker(time.Second)
- for {
- select {
- case <-ticker.C:
- runtime.GC()
- case <-tickerQuitCh:
- return
- }
- }
- }()
-
- for i := 0; i < numElements; i++ {
- <-gcCh
- gcCount++
- }
-
- close(tickerQuitCh)
- <-tickerDoneCh
-
- if gcCount != numElements {
- t.Errorf("expected gcCount to be %v, got %v", numElements,
- gcCount)
- }
- }
-
- func TestGCRandom(t *testing.T) {
-
- const numElements = 1000000
- l := New()
- gcCount := 0
-
- // SetFinalizer doesn't work well with circular structures,
- // so we construct a trivial non-circular structure to
- // track.
- type value struct {
- Int int
- }
-
- gcCh := make(chan struct{})
- for i := 0; i < numElements; i++ {
- v := new(value)
- v.Int = i
- l.PushBack(v)
- runtime.SetFinalizer(v, func(v *value) {
- gcCh <- struct{}{}
- })
- }
-
- els := make([]*CElement, 0, numElements)
- for el := l.Front(); el != nil; el = el.Next() {
- els = append(els, el)
- }
-
- for _, i := range mrand.Perm(numElements) {
- el := els[i]
- l.Remove(el)
- _ = el.Next()
- }
-
- tickerQuitCh := make(chan struct{})
- tickerDoneCh := make(chan struct{})
- go func() {
- defer close(tickerDoneCh)
- ticker := time.NewTicker(time.Second)
- for {
- select {
- case <-ticker.C:
- runtime.GC()
- case <-tickerQuitCh:
- return
- }
- }
- }()
-
- for i := 0; i < numElements; i++ {
- <-gcCh
- gcCount++
- }
-
- close(tickerQuitCh)
- <-tickerDoneCh
-
- if gcCount != numElements {
- t.Errorf("expected gcCount to be %v, got %v", numElements,
- gcCount)
- }
- }
-
- func TestScanRightDeleteRandom(t *testing.T) {
-
- const numElements = 1000
- const numTimes = 100
- const numScanners = 10
-
- l := New()
- stop := make(chan struct{})
-
- els := make([]*CElement, numElements)
- for i := 0; i < numElements; i++ {
- el := l.PushBack(i)
- els[i] = el
- }
-
- // Launch scanner routines that will rapidly iterate over elements.
- for i := 0; i < numScanners; i++ {
- go func(scannerID int) {
- var el *CElement
- restartCounter := 0
- counter := 0
- FOR_LOOP:
- for {
- select {
- case <-stop:
- fmt.Println("stopped")
- break FOR_LOOP
- default:
- }
- if el == nil {
- el = l.FrontWait()
- restartCounter++
- }
- el = el.Next()
- counter++
- }
- fmt.Printf("Scanner %v restartCounter: %v counter: %v\n", scannerID, restartCounter, counter)
- }(i)
- }
-
- // Remove an element, push back an element.
- for i := 0; i < numTimes; i++ {
- // Pick an element to remove
- rmElIdx := mrand.Intn(len(els))
- rmEl := els[rmElIdx]
-
- // Remove it
- l.Remove(rmEl)
- // fmt.Print(".")
-
- // Insert a new element
- newEl := l.PushBack(-1*i - 1)
- els[rmElIdx] = newEl
-
- if i%100000 == 0 {
- fmt.Printf("Pushed %vK elements so far...\n", i/1000)
- }
-
- }
-
- // Stop scanners
- close(stop)
- // time.Sleep(time.Second * 1)
-
- // And remove all the elements.
- for el := l.Front(); el != nil; el = el.Next() {
- l.Remove(el)
- }
- if l.Len() != 0 {
- t.Fatal("Failed to remove all elements from CList")
- }
- }
-
- func TestWaitChan(t *testing.T) {
- l := New()
- ch := l.WaitChan()
-
- // 1) add one element to an empty list
- go l.PushBack(1)
- <-ch
-
- // 2) and remove it
- el := l.Front()
- v := l.Remove(el)
- if v != 1 {
- t.Fatal("where is 1 coming from?")
- }
-
- // 3) test iterating forward and waiting for Next (NextWaitChan and Next)
- el = l.PushBack(0)
-
- done := make(chan struct{})
- pushed := 0
- go func() {
- for i := 1; i < 100; i++ {
- l.PushBack(i)
- pushed++
- time.Sleep(time.Duration(mrand.Intn(25)) * time.Millisecond)
- }
- // apply a deterministic pause so the counter has time to catch up
- time.Sleep(25 * time.Millisecond)
- close(done)
- }()
-
- next := el
- seen := 0
- FOR_LOOP:
- for {
- select {
- case <-next.NextWaitChan():
- next = next.Next()
- seen++
- if next == nil {
- t.Fatal("Next should not be nil when waiting on NextWaitChan")
- }
- case <-done:
- break FOR_LOOP
- case <-time.After(10 * time.Second):
- t.Fatal("max execution time")
- }
- }
-
- if pushed != seen {
- t.Fatalf("number of pushed items (%d) not equal to number of seen items (%d)", pushed, seen)
- }
-
- // 4) test iterating backwards (PrevWaitChan and Prev)
- prev := next
- seen = 0
- FOR_LOOP2:
- for {
- select {
- case <-prev.PrevWaitChan():
- prev = prev.Prev()
- seen++
- if prev == nil {
- t.Fatal("expected PrevWaitChan to block forever on nil when reached first elem")
- }
- case <-time.After(3 * time.Second):
- break FOR_LOOP2
- }
- }
-
- if pushed != seen {
- t.Fatalf("number of pushed items (%d) not equal to number of seen items (%d)", pushed, seen)
- }
- }
-
- func TestRemoved(t *testing.T) {
- l := New()
- el1 := l.PushBack(1)
- el2 := l.PushBack(2)
- l.Remove(el1)
- require.True(t, el1.Removed())
- require.False(t, el2.Removed())
- }
-
- func TestNextWaitChan(t *testing.T) {
- l := New()
- el1 := l.PushBack(1)
- t.Run("tail element should not have a closed nextWaitChan", func(t *testing.T) {
- select {
- case <-el1.NextWaitChan():
- t.Fatal("nextWaitChan should not have been closed")
- default:
- }
- })
-
- el2 := l.PushBack(2)
- t.Run("adding element should close tail nextWaitChan", func(t *testing.T) {
- select {
- case <-el1.NextWaitChan():
- require.NotNil(t, el1.Next())
- default:
- t.Fatal("nextWaitChan should have been closed")
- }
-
- select {
- case <-el2.NextWaitChan():
- t.Fatal("nextWaitChan should not have been closed")
- default:
- }
- })
-
- t.Run("removing element should close its nextWaitChan", func(t *testing.T) {
- l.Remove(el2)
- select {
- case <-el2.NextWaitChan():
- require.Nil(t, el2.Next())
- default:
- t.Fatal("nextWaitChan should have been closed")
- }
- })
- }
|