blob: 64ca0ab4e74b7e405ce81f951052a2d18ca88baf [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 (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
_ "github.com/bazelbuild/rsclient/internal/pkg/testsetup"
)
func TestBoundedSize(t *testing.T) {
tests := []struct {
name string
in int
want int
}{
{"Finite positive", 10, 10},
{"Zero", 0, 0},
{"unbounded", unbounded, 0},
{"Negative other", -100, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := BoundedSize(tt.in); got != tt.want {
t.Errorf("BoundedSize(%d) = %d, want %d", tt.in, got, tt.want)
}
})
}
}
func TestResolveSize(t *testing.T) {
tests := []struct {
name string
assigned int
intrinsic int
want int
}{
{"Assigned finite", 10, 50, 10},
{"Assigned zero", 0, 50, 0},
{"Assigned Unlimited (uses intrinsic)", unbounded, 50, 50},
{"Assigned Unlimited (uses zero intrinsic)", unbounded, 0, 0},
{"Assigned negative (clamped to zero)", -5, 50, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ResolveSize(tt.assigned, tt.intrinsic); got != tt.want {
t.Errorf("ResolveSize(%d, %d) = %d, want %d", tt.assigned, tt.intrinsic, got, tt.want)
}
})
}
}
func TestConstraint_IsUnbounded(t *testing.T) {
tests := []struct {
name string
c Constraint
want bool
}{
{"Finite", Constraint{Min: 10, Max: 20}, false},
{"Zero", Constraint{Min: 0, Max: 0}, false},
{"Unlimited", Constraint{Min: 10, Max: unbounded}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.c.IsUnbounded(); got != tt.want {
t.Errorf("IsUnbounded() = %v, want %v", got, tt.want)
}
})
}
}
func TestConstraint_Sum(t *testing.T) {
tests := []struct {
name string
a, b Constraint
want Constraint
}{
{
name: "Finite sum",
a: Constraint{Min: 1, Max: 5, Weight: 1},
b: Constraint{Min: 2, Max: 10, Weight: 2},
want: Constraint{Min: 3, Max: 15, Weight: 3},
},
{
name: "One unlimited",
a: Constraint{Min: 1, Max: unbounded, Weight: 1},
b: Constraint{Min: 2, Max: 10, Weight: 2},
want: Constraint{Min: 3, Max: unbounded, Weight: 3},
},
{
name: "Both unlimited",
a: Constraint{Min: 1, Max: unbounded, Weight: 1},
b: Constraint{Min: 2, Max: unbounded, Weight: 2},
want: Constraint{Min: 3, Max: unbounded, Weight: 3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.Sum(tt.b); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Sum() = %v, want %v", got, tt.want)
}
})
}
}
func TestConstraintSum(t *testing.T) {
tests := []struct {
name string
constraints []Constraint
want Constraint
}{
{
name: "Empty",
constraints: nil,
want: Constraint{},
},
{
name: "Multiple finite",
constraints: []Constraint{
{Min: 1, Max: 5, Weight: 1},
{Min: 2, Max: 6, Weight: 2},
{Min: 3, Max: 7, Weight: 3},
},
want: Constraint{Min: 6, Max: 18, Weight: 6},
},
{
name: "Mixed with unlimited",
constraints: []Constraint{
{Min: 1, Max: 5, Weight: 1},
{Min: 2, Max: unbounded, Weight: 2},
{Min: 3, Max: 7, Weight: 3},
},
want: Constraint{Min: 6, Max: unbounded, Weight: 6},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ConstraintSum(tt.constraints); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ConstraintSum() = %v, want %v", got, tt.want)
}
})
}
}
func TestDistributeProportionally(t *testing.T) {
tests := []struct {
name string
total int
naturalWidths []int
want []int
}{
{
name: "equal distribution",
total: 10,
naturalWidths: []int{5, 5},
want: []int{5, 5},
},
{
name: "weighted distribution",
total: 12,
naturalWidths: []int{10, 20},
want: []int{5, 7},
},
{
name: "rounding surplus",
total: 10,
naturalWidths: []int{3, 3, 3},
want: []int{4, 3, 3}, // 3.33 each, first one gets the remainder
},
{
name: "minimum 1 requirement",
total: 5,
naturalWidths: []int{100, 100, 100, 100, 100},
want: []int{1, 1, 1, 1, 1},
},
{
name: "empty",
total: 10,
naturalWidths: nil,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := DistributeProportionally(tt.total, tt.naturalWidths)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("DistributeProportionally() mismatch (-want +got):\n%s", diff)
}
if got != nil {
sum := 0
for _, w := range got {
sum += w
}
if sum != tt.total {
t.Errorf("Sum = %d, want %d", sum, tt.total)
}
}
})
}
}
func TestDistribute(t *testing.T) {
tests := []struct {
name string
total int
constraints []Constraint
want []int
}{
{
name: "even split no weights",
total: 10,
constraints: []Constraint{
{Min: 1, Max: unbounded},
{Min: 1, Max: unbounded},
},
want: []int{5, 5},
},
{
name: "uneven split no weights",
total: 11,
constraints: []Constraint{
{Min: 1, Max: unbounded},
{Min: 1, Max: unbounded},
},
want: []int{6, 5},
},
{
name: "weighted split",
total: 100,
constraints: []Constraint{
{Min: 1, Max: unbounded, Weight: 1},
{Min: 1, Max: unbounded, Weight: 3},
},
want: []int{26, 74},
},
{
name: "respect minimums",
total: 10,
constraints: []Constraint{
{Min: 8, Max: unbounded, Weight: 1},
{Min: 1, Max: unbounded, Weight: 1},
},
want: []int{9, 1},
},
{
name: "oversubscribed total",
total: 5,
constraints: []Constraint{
{Min: 4, Max: unbounded},
{Min: 4, Max: unbounded},
},
want: []int{4, 1}, // Squashes second tile to fit total.
},
{
name: "respect maximums",
total: 100,
constraints: []Constraint{
{Min: 10, Max: 20, Weight: 1},
{Min: 10, Max: 100, Weight: 1},
},
want: []int{20, 80},
},
{
name: "all at maximums",
total: 100,
constraints: []Constraint{
{Min: 10, Max: 20, Weight: 1},
{Min: 10, Max: 30, Weight: 1},
},
want: []int{20, 30}, // Total sum 50 < 100.
},
{
name: "iterative locking with weights",
total: 100,
constraints: []Constraint{
{Min: 0, Max: 10, Weight: 1}, // Hits Max fast
{Min: 0, Max: 40, Weight: 2}, // Hits Max later
{Min: 0, Max: 100, Weight: 2},
},
want: []int{10, 40, 50},
},
{
name: "remainder redistribution respects max",
total: 11,
constraints: []Constraint{
{Min: 5, Max: 5, Weight: 0},
{Min: 5, Max: 10, Weight: 0},
},
want: []int{5, 6},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Distribute(tt.total, tt.constraints); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Distribute() = %v, want %v", got, tt.want)
}
})
}
}