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