blob: 9b19d8732e832483e863250e4b6be86870f1c6f2 [file] [edit]
// Copyright 2026 Google LLC
//
// 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 layout
import (
"testing"
_ "github.com/bazelbuild/rsclient/internal/pkg/testsetup"
)
func TestLinearMap(t *testing.T) {
// Setup: 3 items with sizes 10, 20, 30.
// Boundary array should be: [0, 10, 30, 60]
m := NewLinearMap([]int{10, 20, 30})
t.Run("Basic spans", func(t *testing.T) {
// Verify that the prefix-sum math correctly calculated offsets for
// each element in the sequence.
if s := m.Span(0); s.Offset != 0 || s.Size != 10 {
t.Errorf("Item 0: got %+v", s)
}
if s := m.Span(1); s.Offset != 10 || s.Size != 20 {
t.Errorf("Item 1: got %+v", s)
}
if s := m.Span(2); s.Offset != 30 || s.Size != 30 {
t.Errorf("Item 2: got %+v", s)
}
// The total extent must equal the sum of all item sizes.
if m.Total() != 60 {
t.Errorf("Total = %d, want 60", m.Total())
}
})
t.Run("At coordinate", func(t *testing.T) {
// Verify that we can resolve a specific coordinate back to an element index.
// This uses binary search (O(log N)) on the cached boundaries.
tests := []struct {
coord int
want int
}{
{-1, -1}, // Out-of-bounds (negative)
{0, 0}, // Start of first element
{9, 0}, // Last unit of first element
{10, 1}, // Start of second element
{29, 1}, // Last unit of second element
{30, 2}, // Start of third element
{59, 2}, // End of third element
{60, -1}, // Out-of-bounds (beyond total)
}
for _, tc := range tests {
if got := m.At(tc.coord); got != tc.want {
t.Errorf("At(%d) = %d, want %d", tc.coord, got, tc.want)
}
}
})
t.Run("Intersects window", func(t *testing.T) {
// Verify that given a sliding window (like a viewport), we correctly
// find the range of indices [start, end) for all elements that have
// at least one unit of space inside that window.
tests := []struct {
window Span
want Interval
}{
// Window [0, 5) sits entirely inside Item 0 [0, 10).
{Span{0, 5}, Interval{0, 1}},
// Window [5, 15) starts in Item 0 and ends in Item 1 [10, 30).
{Span{5, 10}, Interval{0, 2}},
// Window [35, 45) sits entirely inside Item 2 [30, 60).
{Span{35, 10}, Interval{2, 3}},
// Window [0, 60) covers the entire range.
{Span{0, 60}, Interval{0, 3}},
// Window [65, 75) is entirely past the end.
{Span{65, 10}, Interval{3, 3}},
// Unbounded window starting at 15 overlaps Item 1 [10, 30) and Item 2 [30, 60).
{Span{15, unbounded}, Interval{1, 3}},
}
for _, tc := range tests {
got := m.Intersects(tc.window)
if got != tc.want {
t.Errorf("Intersects(%+v) = %+v, want %+v", tc.window, got, tc.want)
}
}
})
}
func TestNewLinearMap_Panic(t *testing.T) {
// Verify that the constructor panics if any size is negative.
defer func() {
if r := recover(); r == nil {
t.Errorf("NewLinearMap should have panicked on negative size")
}
}()
NewLinearMap([]int{10, -5, 20})
}