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