blob: 110f51c811ce877d7b887c23c323d7d20a61892e [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"fmt"
"runtime"
"testing"
)
var spanDesc = map[uintptr]struct {
pages uintptr
scav bool
}{
0xc0000000: {2, false},
0xc0006000: {1, false},
0xc0010000: {8, false},
0xc0022000: {7, false},
0xc0034000: {4, true},
0xc0040000: {5, false},
0xc0050000: {5, true},
0xc0060000: {5000, false},
}
// Wrap the Treap one more time because go:notinheap doesn't
// actually follow a structure across package boundaries.
//
//go:notinheap
type treap struct {
runtime.Treap
}
func maskMatchName(mask, match runtime.TreapIterType) string {
return fmt.Sprintf("%0*b-%0*b", runtime.TreapIterBits, uint8(mask), runtime.TreapIterBits, uint8(match))
}
func TestTreapFilter(t *testing.T) {
var iterTypes = [...]struct {
mask, match runtime.TreapIterType
filter runtime.TreapIterFilter // expected filter
}{
{0, 0, 0xf},
{runtime.TreapIterScav, 0, 0x5},
{runtime.TreapIterScav, runtime.TreapIterScav, 0xa},
{runtime.TreapIterScav | runtime.TreapIterHuge, runtime.TreapIterHuge, 0x4},
{runtime.TreapIterScav | runtime.TreapIterHuge, 0, 0x1},
{0, runtime.TreapIterScav, 0x0},
}
for _, it := range iterTypes {
t.Run(maskMatchName(it.mask, it.match), func(t *testing.T) {
if f := runtime.TreapFilter(it.mask, it.match); f != it.filter {
t.Fatalf("got %#x, want %#x", f, it.filter)
}
})
}
}
// This test ensures that the treap implementation in the runtime
// maintains all stated invariants after different sequences of
// insert, removeSpan, find, and erase. Invariants specific to the
// treap data structure are checked implicitly: after each mutating
// operation, treap-related invariants are checked for the entire
// treap.
func TestTreap(t *testing.T) {
// Set up a bunch of spans allocated into mheap_.
// Also, derive a set of typeCounts of each type of span
// according to runtime.TreapIterType so we can verify against
// them later.
spans := make([]runtime.Span, 0, len(spanDesc))
typeCounts := [1 << runtime.TreapIterBits][1 << runtime.TreapIterBits]int{}
for base, de := range spanDesc {
s := runtime.AllocSpan(base, de.pages, de.scav)
defer s.Free()
spans = append(spans, s)
for i := runtime.TreapIterType(0); i < 1<<runtime.TreapIterBits; i++ {
for j := runtime.TreapIterType(0); j < 1<<runtime.TreapIterBits; j++ {
if s.MatchesIter(i, j) {
typeCounts[i][j]++
}
}
}
}
t.Run("TypeCountsSanity", func(t *testing.T) {
// Just sanity check type counts for a few values.
check := func(mask, match runtime.TreapIterType, count int) {
tc := typeCounts[mask][match]
if tc != count {
name := maskMatchName(mask, match)
t.Fatalf("failed a sanity check for mask/match %s counts: got %d, wanted %d", name, tc, count)
}
}
check(0, 0, len(spanDesc))
check(runtime.TreapIterScav, 0, 6)
check(runtime.TreapIterScav, runtime.TreapIterScav, 2)
})
t.Run("Insert", func(t *testing.T) {
tr := treap{}
// Test just a very basic insert/remove for sanity.
tr.Insert(spans[0])
tr.RemoveSpan(spans[0])
})
t.Run("FindTrivial", func(t *testing.T) {
tr := treap{}
// Test just a very basic find operation for sanity.
tr.Insert(spans[0])
i := tr.Find(1)
if i.Span() != spans[0] {
t.Fatal("found unknown span in treap")
}
tr.RemoveSpan(spans[0])
})
t.Run("FindFirstFit", func(t *testing.T) {
// Run this 10 times, recreating the treap each time.
// Because of the non-deterministic structure of a treap,
// we'll be able to test different structures this way.
for i := 0; i < 10; i++ {
tr := runtime.Treap{}
for _, s := range spans {
tr.Insert(s)
}
i := tr.Find(5)
if i.Span().Base() != 0xc0010000 {
t.Fatalf("expected span at lowest address which could fit 5 pages, instead found span at %x", i.Span().Base())
}
for _, s := range spans {
tr.RemoveSpan(s)
}
}
})
t.Run("Iterate", func(t *testing.T) {
for mask := runtime.TreapIterType(0); mask < 1<<runtime.TreapIterBits; mask++ {
for match := runtime.TreapIterType(0); match < 1<<runtime.TreapIterBits; match++ {
iterName := maskMatchName(mask, match)
t.Run(iterName, func(t *testing.T) {
t.Run("StartToEnd", func(t *testing.T) {
// Ensure progressing an iterator actually goes over the whole treap
// from the start and that it iterates over the elements in order.
// Furthermore, ensure that it only iterates over the relevant parts
// of the treap.
// Finally, ensures that Start returns a valid iterator.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
nspans := 0
lastBase := uintptr(0)
for i := tr.Start(mask, match); i.Valid(); i = i.Next() {
nspans++
if lastBase > i.Span().Base() {
t.Fatalf("not iterating in correct order: encountered base %x before %x", lastBase, i.Span().Base())
}
lastBase = i.Span().Base()
if !i.Span().MatchesIter(mask, match) {
t.Fatalf("found non-matching span while iteration over mask/match %s: base %x", iterName, i.Span().Base())
}
}
if nspans != typeCounts[mask][match] {
t.Fatal("failed to iterate forwards over full treap")
}
for _, s := range spans {
tr.RemoveSpan(s)
}
})
t.Run("EndToStart", func(t *testing.T) {
// See StartToEnd tests.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
nspans := 0
lastBase := ^uintptr(0)
for i := tr.End(mask, match); i.Valid(); i = i.Prev() {
nspans++
if lastBase < i.Span().Base() {
t.Fatalf("not iterating in correct order: encountered base %x before %x", lastBase, i.Span().Base())
}
lastBase = i.Span().Base()
if !i.Span().MatchesIter(mask, match) {
t.Fatalf("found non-matching span while iteration over mask/match %s: base %x", iterName, i.Span().Base())
}
}
if nspans != typeCounts[mask][match] {
t.Fatal("failed to iterate backwards over full treap")
}
for _, s := range spans {
tr.RemoveSpan(s)
}
})
})
}
}
t.Run("Prev", func(t *testing.T) {
// Test the iterator invariant that i.prev().next() == i.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
i := tr.Start(0, 0).Next().Next()
p := i.Prev()
if !p.Valid() {
t.Fatal("i.prev() is invalid")
}
if p.Next().Span() != i.Span() {
t.Fatal("i.prev().next() != i")
}
for _, s := range spans {
tr.RemoveSpan(s)
}
})
t.Run("Next", func(t *testing.T) {
// Test the iterator invariant that i.next().prev() == i.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
i := tr.Start(0, 0).Next().Next()
n := i.Next()
if !n.Valid() {
t.Fatal("i.next() is invalid")
}
if n.Prev().Span() != i.Span() {
t.Fatal("i.next().prev() != i")
}
for _, s := range spans {
tr.RemoveSpan(s)
}
})
})
t.Run("EraseOne", func(t *testing.T) {
// Test that erasing one iterator correctly retains
// all relationships between elements.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
i := tr.Start(0, 0).Next().Next().Next()
s := i.Span()
n := i.Next()
p := i.Prev()
tr.Erase(i)
if n.Prev().Span() != p.Span() {
t.Fatal("p, n := i.Prev(), i.Next(); n.prev() != p after i was erased")
}
if p.Next().Span() != n.Span() {
t.Fatal("p, n := i.Prev(), i.Next(); p.next() != n after i was erased")
}
tr.Insert(s)
for _, s := range spans {
tr.RemoveSpan(s)
}
})
t.Run("EraseAll", func(t *testing.T) {
// Test that erasing iterators actually removes nodes from the treap.
tr := treap{}
for _, s := range spans {
tr.Insert(s)
}
for i := tr.Start(0, 0); i.Valid(); {
n := i.Next()
tr.Erase(i)
i = n
}
if size := tr.Size(); size != 0 {
t.Fatalf("should have emptied out treap, %d spans left", size)
}
})
}