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") } }) }