diff --git a/client.go b/client.go index 3879ee0..cd2ac45 100644 --- a/client.go +++ b/client.go @@ -25,7 +25,13 @@ func saturateUint16from32(v uint32) uint16 { // saturateUint32 converts v to uint32, clamping to math.MaxUint32 on overflow. func saturateUint32(v int) uint32 { - return uint32(min(max(v, 0), math.MaxUint32)) //nolint:gosec // clamped to [0, MaxUint32] + if v <= 0 { + return 0 + } + if uint64(v) > uint64(math.MaxUint32) { + return math.MaxUint32 + } + return uint32(v) //nolint:gosec // checked to be in [0, MaxUint32] } // Client is the module-side endpoint that connects to a compositor and diff --git a/saturate_test.go b/saturate_test.go new file mode 100644 index 0000000..cfaf676 --- /dev/null +++ b/saturate_test.go @@ -0,0 +1,51 @@ +package compose + +import ( + "math" + "testing" +) + +func TestSaturateUint32(t *testing.T) { + maxUint32 := uint64(math.MaxUint32) + maxInt := int(^uint(0) >> 1) + wantMaxInt := uint32(maxInt) + if uint64(maxInt) > maxUint32 { + wantMaxInt = math.MaxUint32 + } + + tests := []struct { + name string + input int + want uint32 + }{ + {name: "negative", input: -1, want: 0}, + {name: "zero", input: 0, want: 0}, + {name: "one", input: 1, want: 1}, + {name: "max int", input: maxInt, want: wantMaxInt}, + } + + // math.MaxUint32 is wider than int on 32-bit systems, so these cases + // are added only where the boundary is representable as an int. + if uint64(maxInt) >= maxUint32 { + tests = append(tests, struct { + name string + input int + want uint32 + }{name: "max uint32", input: int(maxUint32), want: math.MaxUint32}) + } + if uint64(maxInt) > maxUint32 { + tests = append(tests, struct { + name string + input int + want uint32 + }{name: "above max uint32", input: int(maxUint32 + 1), want: math.MaxUint32}) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := saturateUint32(tt.input); got != tt.want { + t.Fatalf("saturateUint32(%d) = %d, want %d", tt.input, got, tt.want) + } + }) + } +}