diff options
Diffstat (limited to 'pkg/segment/test')
-rw-r--r-- | pkg/segment/test/BUILD | 68 | ||||
-rw-r--r-- | pkg/segment/test/segment_test.go | 865 | ||||
-rw-r--r-- | pkg/segment/test/set_functions.go | 54 |
3 files changed, 987 insertions, 0 deletions
diff --git a/pkg/segment/test/BUILD b/pkg/segment/test/BUILD new file mode 100644 index 000000000..131bf09b9 --- /dev/null +++ b/pkg/segment/test/BUILD @@ -0,0 +1,68 @@ +load("//tools:defs.bzl", "go_library", "go_test") +load("//tools/go_generics:defs.bzl", "go_template_instance") + +package( + default_visibility = ["//visibility:private"], + licenses = ["notice"], +) + +go_template_instance( + name = "int_range", + out = "int_range.go", + package = "segment", + template = "//pkg/segment:generic_range", + types = { + "T": "int", + }, +) + +go_template_instance( + name = "int_set", + out = "int_set.go", + package = "segment", + template = "//pkg/segment:generic_set", + types = { + "Key": "int", + "Range": "Range", + "Value": "int", + "Functions": "setFunctions", + }, +) + +go_template_instance( + name = "gap_set", + out = "gap_set.go", + consts = { + "trackGaps": "1", + }, + package = "segment", + prefix = "gap", + template = "//pkg/segment:generic_set", + types = { + "Key": "int", + "Range": "Range", + "Value": "int", + "Functions": "gapSetFunctions", + }, +) + +go_library( + name = "segment", + testonly = 1, + srcs = [ + "gap_set.go", + "int_range.go", + "int_set.go", + "set_functions.go", + ], + deps = [ + "//pkg/state", + ], +) + +go_test( + name = "segment_test", + size = "small", + srcs = ["segment_test.go"], + library = ":segment", +) diff --git a/pkg/segment/test/segment_test.go b/pkg/segment/test/segment_test.go new file mode 100644 index 000000000..85fa19096 --- /dev/null +++ b/pkg/segment/test/segment_test.go @@ -0,0 +1,865 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package segment + +import ( + "fmt" + "math/rand" + "reflect" + "testing" +) + +const ( + // testSize is the baseline number of elements inserted into sets under + // test, and is chosen to be large enough to ensure interesting amounts of + // tree rebalancing. + // + // Note that because checkSet is called between each insertion/removal in + // some tests that use it, tests may be quadratic in testSize. + testSize = 8000 + + // valueOffset is the difference between the value and start of test + // segments. + valueOffset = 100000 + + // intervalLength is the interval used by random gap tests. + intervalLength = 10 +) + +func shuffle(xs []int) { + rand.Shuffle(len(xs), func(i, j int) { xs[i], xs[j] = xs[j], xs[i] }) +} + +func randIntervalPermutation(size int) []int { + p := make([]int, size) + for i := range p { + p[i] = intervalLength * i + } + shuffle(p) + return p +} + +// validate can be passed to Check. +func validate(nr int, r Range, v int) error { + if got, want := v, r.Start+valueOffset; got != want { + return fmt.Errorf("segment %d has key %d, value %d (expected %d)", nr, r.Start, got, want) + } + return nil +} + +// checkSetMaxGap returns an error if maxGap inside all nodes of s is not well +// maintained. +func checkSetMaxGap(s *gapSet) error { + n := s.root + return checkNodeMaxGap(&n) +} + +// checkNodeMaxGap returns an error if maxGap inside the subtree rooted by n is +// not well maintained. +func checkNodeMaxGap(n *gapnode) error { + var max int + if !n.hasChildren { + max = n.calculateMaxGapLeaf() + } else { + for i := 0; i <= n.nrSegments; i++ { + child := n.children[i] + if err := checkNodeMaxGap(child); err != nil { + return err + } + if temp := child.maxGap.Get(); i == 0 || temp > max { + max = temp + } + } + } + if max != n.maxGap.Get() { + return fmt.Errorf("maxGap wrong in node\n%vexpected: %d got: %d", n, max, n.maxGap) + } + return nil +} + +func TestAddRandom(t *testing.T) { + var s Set + order := rand.Perm(testSize) + var nrInsertions int + for i, j := range order { + if !s.AddWithoutMerging(Range{j, j + 1}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + nrInsertions++ + if err := s.segmentTestCheck(nrInsertions, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Insertion order: %v", order[:nrInsertions]) + t.Logf("Set contents:\n%v", &s) + } +} + +func TestRemoveRandom(t *testing.T) { + var s Set + for i := 0; i < testSize; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i+valueOffset) { + t.Fatalf("Failed to insert segment %d", i) + } + } + order := rand.Perm(testSize) + var nrRemovals int + for i, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + t.Errorf("Iteration %d: failed to find segment with key %d", i, j) + break + } + s.Remove(seg) + nrRemovals++ + if err := s.segmentTestCheck(testSize-nrRemovals, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + } + if got, want := s.countSegments(), testSize-nrRemovals; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Removal order: %v", order[:nrRemovals]) + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestMaxGapAddRandom(t *testing.T) { + var s gapSet + order := rand.Perm(testSize) + var nrInsertions int + for i, j := range order { + if !s.AddWithoutMerging(Range{j, j + 1}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + nrInsertions++ + if err := s.segmentTestCheck(nrInsertions, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Insertion order: %v", order[:nrInsertions]) + t.Logf("Set contents:\n%v", &s) + } +} + +func TestMaxGapAddRandomWithRandomInterval(t *testing.T) { + var s gapSet + order := randIntervalPermutation(testSize) + var nrInsertions int + for i, j := range order { + if !s.AddWithoutMerging(Range{j, j + rand.Intn(intervalLength-1) + 1}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + nrInsertions++ + if err := s.segmentTestCheck(nrInsertions, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Insertion order: %v", order[:nrInsertions]) + t.Logf("Set contents:\n%v", &s) + } +} + +func TestMaxGapAddRandomWithMerge(t *testing.T) { + var s gapSet + order := randIntervalPermutation(testSize) + nrInsertions := 1 + for i, j := range order { + if !s.Add(Range{j, j + intervalLength}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Insertion order: %v", order) + t.Logf("Set contents:\n%v", &s) + } +} + +func TestMaxGapRemoveRandom(t *testing.T) { + var s gapSet + for i := 0; i < testSize; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i+valueOffset) { + t.Fatalf("Failed to insert segment %d", i) + } + } + order := rand.Perm(testSize) + var nrRemovals int + for i, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + t.Errorf("Iteration %d: failed to find segment with key %d", i, j) + break + } + temprange := seg.Range() + s.Remove(seg) + nrRemovals++ + if err := s.segmentTestCheck(testSize-nrRemovals, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When removing %v: %v", temprange, err) + break + } + } + if got, want := s.countSegments(), testSize-nrRemovals; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Removal order: %v", order[:nrRemovals]) + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestMaxGapRemoveHalfRandom(t *testing.T) { + var s gapSet + for i := 0; i < testSize; i++ { + if !s.AddWithoutMerging(Range{intervalLength * i, intervalLength*i + rand.Intn(intervalLength-1) + 1}, intervalLength*i+valueOffset) { + t.Fatalf("Failed to insert segment %d", i) + } + } + order := randIntervalPermutation(testSize) + order = order[:testSize/2] + var nrRemovals int + for i, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + t.Errorf("Iteration %d: failed to find segment with key %d", i, j) + break + } + temprange := seg.Range() + s.Remove(seg) + nrRemovals++ + if err := s.segmentTestCheck(testSize-nrRemovals, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When removing %v: %v", temprange, err) + break + } + } + if got, want := s.countSegments(), testSize-nrRemovals; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Removal order: %v", order[:nrRemovals]) + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestMaxGapAddRandomRemoveRandomHalfWithMerge(t *testing.T) { + var s gapSet + order := randIntervalPermutation(testSize * 2) + order = order[:testSize] + for i, j := range order { + if !s.Add(Range{j, j + intervalLength}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + shuffle(order) + var nrRemovals int + for _, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + continue + } + temprange := seg.Range() + s.Remove(seg) + nrRemovals++ + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When removing %v: %v", temprange, err) + break + } + } + if t.Failed() { + t.Logf("Removal order: %v", order[:nrRemovals]) + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestNextLargeEnoughGap(t *testing.T) { + var s gapSet + order := randIntervalPermutation(testSize * 2) + order = order[:testSize] + for i, j := range order { + if !s.Add(Range{j, j + rand.Intn(intervalLength-1) + 1}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + shuffle(order) + order = order[:testSize/2] + for _, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + continue + } + temprange := seg.Range() + s.Remove(seg) + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When removing %v: %v", temprange, err) + break + } + } + minSize := 7 + var gapArr1 []int + for gap := s.LowerBoundGap(0).NextLargeEnoughGap(minSize); gap.Ok(); gap = gap.NextLargeEnoughGap(minSize) { + if gap.Range().Length() < minSize { + t.Errorf("NextLargeEnoughGap wrong, gap %v has length %d, wanted %d", gap.Range(), gap.Range().Length(), minSize) + } else { + gapArr1 = append(gapArr1, gap.Range().Start) + } + } + var gapArr2 []int + for gap := s.LowerBoundGap(0).NextGap(); gap.Ok(); gap = gap.NextGap() { + if gap.Range().Length() >= minSize { + gapArr2 = append(gapArr2, gap.Range().Start) + } + } + + if !reflect.DeepEqual(gapArr2, gapArr1) { + t.Errorf("Search result not correct, got: %v, wanted: %v", gapArr1, gapArr2) + } + if t.Failed() { + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestPrevLargeEnoughGap(t *testing.T) { + var s gapSet + order := randIntervalPermutation(testSize * 2) + order = order[:testSize] + for i, j := range order { + if !s.Add(Range{j, j + rand.Intn(intervalLength-1) + 1}, j+valueOffset) { + t.Errorf("Iteration %d: failed to insert segment with key %d", i, j) + break + } + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When inserting %d: %v", j, err) + break + } + } + end := s.LastSegment().End() + shuffle(order) + order = order[:testSize/2] + for _, j := range order { + seg := s.FindSegment(j) + if !seg.Ok() { + continue + } + temprange := seg.Range() + s.Remove(seg) + if err := checkSetMaxGap(&s); err != nil { + t.Errorf("When removing %v: %v", temprange, err) + break + } + } + minSize := 7 + var gapArr1 []int + for gap := s.UpperBoundGap(end + intervalLength).PrevLargeEnoughGap(minSize); gap.Ok(); gap = gap.PrevLargeEnoughGap(minSize) { + if gap.Range().Length() < minSize { + t.Errorf("PrevLargeEnoughGap wrong, gap length %d, wanted %d", gap.Range().Length(), minSize) + } else { + gapArr1 = append(gapArr1, gap.Range().Start) + } + } + var gapArr2 []int + for gap := s.UpperBoundGap(end + intervalLength).PrevGap(); gap.Ok(); gap = gap.PrevGap() { + if gap.Range().Length() >= minSize { + gapArr2 = append(gapArr2, gap.Range().Start) + } + } + if !reflect.DeepEqual(gapArr2, gapArr1) { + t.Errorf("Search result not correct, got: %v, wanted: %v", gapArr1, gapArr2) + } + if t.Failed() { + t.Logf("Set contents:\n%v", &s) + t.FailNow() + } +} + +func TestAddSequentialAdjacent(t *testing.T) { + var s Set + var nrInsertions int + for i := 0; i < testSize; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i+valueOffset) { + t.Fatalf("Failed to insert segment %d", i) + } + nrInsertions++ + if err := s.segmentTestCheck(nrInsertions, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Set contents:\n%v", &s) + } + + first := s.FirstSegment() + gotSeg, gotGap := first.PrevNonEmpty() + if wantGap := s.FirstGap(); gotSeg.Ok() || gotGap != wantGap { + t.Errorf("FirstSegment().PrevNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", gotSeg, gotGap, wantGap) + } + gotSeg, gotGap = first.NextNonEmpty() + if wantSeg := first.NextSegment(); gotSeg != wantSeg || gotGap.Ok() { + t.Errorf("FirstSegment().NextNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", gotSeg, gotGap, wantSeg) + } + + last := s.LastSegment() + gotSeg, gotGap = last.PrevNonEmpty() + if wantSeg := last.PrevSegment(); gotSeg != wantSeg || gotGap.Ok() { + t.Errorf("LastSegment().PrevNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", gotSeg, gotGap, wantSeg) + } + gotSeg, gotGap = last.NextNonEmpty() + if wantGap := s.LastGap(); gotSeg.Ok() || gotGap != wantGap { + t.Errorf("LastSegment().NextNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", gotSeg, gotGap, wantGap) + } + + for seg := first.NextSegment(); seg != last; seg = seg.NextSegment() { + gotSeg, gotGap = seg.PrevNonEmpty() + if wantSeg := seg.PrevSegment(); gotSeg != wantSeg || gotGap.Ok() { + t.Errorf("%v.PrevNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", seg, gotSeg, gotGap, wantSeg) + } + gotSeg, gotGap = seg.NextNonEmpty() + if wantSeg := seg.NextSegment(); gotSeg != wantSeg || gotGap.Ok() { + t.Errorf("%v.NextNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", seg, gotSeg, gotGap, wantSeg) + } + } +} + +func TestAddSequentialNonAdjacent(t *testing.T) { + var s Set + var nrInsertions int + for i := 0; i < testSize; i++ { + // The range here differs from TestAddSequentialAdjacent so that + // consecutive segments are not adjacent. + if !s.AddWithoutMerging(Range{2 * i, 2*i + 1}, 2*i+valueOffset) { + t.Fatalf("Failed to insert segment %d", i) + } + nrInsertions++ + if err := s.segmentTestCheck(nrInsertions, validate); err != nil { + t.Errorf("Iteration %d: %v", i, err) + break + } + } + if got, want := s.countSegments(), nrInsertions; got != want { + t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want) + } + if t.Failed() { + t.Logf("Set contents:\n%v", &s) + } + + for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { + gotSeg, gotGap := seg.PrevNonEmpty() + if wantGap := seg.PrevGap(); gotSeg.Ok() || gotGap != wantGap { + t.Errorf("%v.PrevNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", seg, gotSeg, gotGap, wantGap) + } + gotSeg, gotGap = seg.NextNonEmpty() + if wantGap := seg.NextGap(); gotSeg.Ok() || gotGap != wantGap { + t.Errorf("%v.NextNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", seg, gotSeg, gotGap, wantGap) + } + } +} + +func TestMergeSplit(t *testing.T) { + tests := []struct { + name string + initial []Range + split bool + splitAddr int + final []Range + }{ + { + name: "Add merges after existing segment", + initial: []Range{{1000, 1100}, {1100, 1200}}, + final: []Range{{1000, 1200}}, + }, + { + name: "Add merges before existing segment", + initial: []Range{{1100, 1200}, {1000, 1100}}, + final: []Range{{1000, 1200}}, + }, + { + name: "Add merges between existing segments", + initial: []Range{{1000, 1100}, {1200, 1300}, {1100, 1200}}, + final: []Range{{1000, 1300}}, + }, + { + name: "SplitAt does nothing at a free address", + initial: []Range{{100, 200}}, + split: true, + splitAddr: 300, + final: []Range{{100, 200}}, + }, + { + name: "SplitAt does nothing at the beginning of a segment", + initial: []Range{{100, 200}}, + split: true, + splitAddr: 100, + final: []Range{{100, 200}}, + }, + { + name: "SplitAt does nothing at the end of a segment", + initial: []Range{{100, 200}}, + split: true, + splitAddr: 200, + final: []Range{{100, 200}}, + }, + { + name: "SplitAt splits in the middle of a segment", + initial: []Range{{100, 200}}, + split: true, + splitAddr: 150, + final: []Range{{100, 150}, {150, 200}}, + }, + } +Tests: + for _, test := range tests { + var s Set + for _, r := range test.initial { + if !s.Add(r, 0) { + t.Errorf("%s: Add(%v) failed; set contents:\n%v", test.name, r, &s) + continue Tests + } + } + if test.split { + s.SplitAt(test.splitAddr) + } + var i int + for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { + if i > len(test.final) { + t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, s.countSegments(), len(test.final), &s) + continue Tests + } + if got, want := seg.Range(), test.final[i]; got != want { + t.Errorf("%s: Segment %d mismatch: got %v, wanted %v; set contents:\n%v", test.name, i, got, want, &s) + continue Tests + } + i++ + } + if i < len(test.final) { + t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, i, len(test.final), &s) + } + } +} + +func TestIsolate(t *testing.T) { + tests := []struct { + name string + initial Range + bounds Range + final []Range + }{ + { + name: "Isolate does not split a segment that falls inside bounds", + initial: Range{100, 200}, + bounds: Range{100, 200}, + final: []Range{{100, 200}}, + }, + { + name: "Isolate splits at beginning of segment", + initial: Range{50, 200}, + bounds: Range{100, 200}, + final: []Range{{50, 100}, {100, 200}}, + }, + { + name: "Isolate splits at end of segment", + initial: Range{100, 250}, + bounds: Range{100, 200}, + final: []Range{{100, 200}, {200, 250}}, + }, + { + name: "Isolate splits at beginning and end of segment", + initial: Range{50, 250}, + bounds: Range{100, 200}, + final: []Range{{50, 100}, {100, 200}, {200, 250}}, + }, + } +Tests: + for _, test := range tests { + var s Set + seg := s.Insert(s.FirstGap(), test.initial, 0) + seg = s.Isolate(seg, test.bounds) + if !test.bounds.IsSupersetOf(seg.Range()) { + t.Errorf("%s: Isolated segment %v lies outside bounds %v; set contents:\n%v", test.name, seg.Range(), test.bounds, &s) + } + var i int + for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { + if i > len(test.final) { + t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, s.countSegments(), len(test.final), &s) + continue Tests + } + if got, want := seg.Range(), test.final[i]; got != want { + t.Errorf("%s: Segment %d mismatch: got %v, wanted %v; set contents:\n%v", test.name, i, got, want, &s) + continue Tests + } + i++ + } + if i < len(test.final) { + t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, i, len(test.final), &s) + } + } +} + +func benchmarkAddSequential(b *testing.B, size int) { + for n := 0; n < b.N; n++ { + var s Set + for i := 0; i < size; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + } +} + +func benchmarkAddRandom(b *testing.B, size int) { + order := rand.Perm(size) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + var s Set + for _, i := range order { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + } +} + +func benchmarkFindSequential(b *testing.B, size int) { + var s Set + for i := 0; i < size; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + for i := 0; i < size; i++ { + if seg := s.FindSegment(i); !seg.Ok() { + b.Fatalf("Failed to find segment %d", i) + } + } + } +} + +func benchmarkFindRandom(b *testing.B, size int) { + var s Set + for i := 0; i < size; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + order := rand.Perm(size) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + for _, i := range order { + if si := s.FindSegment(i); !si.Ok() { + b.Fatalf("Failed to find segment %d", i) + } + } + } +} + +func benchmarkIteration(b *testing.B, size int) { + var s Set + for i := 0; i < size; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + + b.ResetTimer() + var count uint64 + for n := 0; n < b.N; n++ { + for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { + count++ + } + } + if got, want := count, uint64(size)*uint64(b.N); got != want { + b.Fatalf("Iterated wrong number of segments: got %d, wanted %d", got, want) + } +} + +func benchmarkAddFindRemoveSequential(b *testing.B, size int) { + for n := 0; n < b.N; n++ { + var s Set + for i := 0; i < size; i++ { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + for i := 0; i < size; i++ { + seg := s.FindSegment(i) + if !seg.Ok() { + b.Fatalf("Failed to find segment %d", i) + } + s.Remove(seg) + } + if !s.IsEmpty() { + b.Fatalf("Set not empty after all removals:\n%v", &s) + } + } +} + +func benchmarkAddFindRemoveRandom(b *testing.B, size int) { + order := rand.Perm(size) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + var s Set + for _, i := range order { + if !s.AddWithoutMerging(Range{i, i + 1}, i) { + b.Fatalf("Failed to insert segment %d", i) + } + } + for _, i := range order { + seg := s.FindSegment(i) + if !seg.Ok() { + b.Fatalf("Failed to find segment %d", i) + } + s.Remove(seg) + } + if !s.IsEmpty() { + b.Fatalf("Set not empty after all removals:\n%v", &s) + } + } +} + +// Although we don't generally expect our segment sets to get this big, they're +// useful for emulating the effect of cache pressure. +var testSizes = []struct { + desc string + size int +}{ + {"64", 1 << 6}, + {"256", 1 << 8}, + {"1K", 1 << 10}, + {"4K", 1 << 12}, + {"16K", 1 << 14}, + {"64K", 1 << 16}, +} + +func BenchmarkAddSequential(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkAddSequential(b, test.size) + }) + } +} + +func BenchmarkAddRandom(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkAddRandom(b, test.size) + }) + } +} + +func BenchmarkFindSequential(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkFindSequential(b, test.size) + }) + } +} + +func BenchmarkFindRandom(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkFindRandom(b, test.size) + }) + } +} + +func BenchmarkIteration(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkIteration(b, test.size) + }) + } +} + +func BenchmarkAddFindRemoveSequential(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkAddFindRemoveSequential(b, test.size) + }) + } +} + +func BenchmarkAddFindRemoveRandom(b *testing.B) { + for _, test := range testSizes { + b.Run(test.desc, func(b *testing.B) { + benchmarkAddFindRemoveRandom(b, test.size) + }) + } +} diff --git a/pkg/segment/test/set_functions.go b/pkg/segment/test/set_functions.go new file mode 100644 index 000000000..7cd895cc7 --- /dev/null +++ b/pkg/segment/test/set_functions.go @@ -0,0 +1,54 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package segment + +type setFunctions struct{} + +// MinKey returns the minimum key for the set. +func (s setFunctions) MinKey() int { + return -s.MaxKey() - 1 +} + +// MaxKey returns the maximum key for the set. +func (setFunctions) MaxKey() int { + return int(^uint(0) >> 1) +} + +func (setFunctions) ClearValue(*int) {} + +func (setFunctions) Merge(_ Range, val1 int, _ Range, _ int) (int, bool) { + return val1, true +} + +func (setFunctions) Split(_ Range, val int, _ int) (int, int) { + return val, val +} + +type gapSetFunctions struct { + setFunctions +} + +// MinKey is adjusted to make sure no add overflow would happen in test cases. +// e.g. A gap with range {MinInt32, 2} would cause overflow in Range().Length(). +// +// Normally Keys should be unsigned to avoid these issues. +func (s gapSetFunctions) MinKey() int { + return s.setFunctions.MinKey() / 2 +} + +// MaxKey returns the maximum key for the set. +func (s gapSetFunctions) MaxKey() int { + return s.setFunctions.MaxKey() / 2 +} |