mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 11:27:02 +02:00
overlay/checksum: test each arch implementation directly
The correctness sweeps only exercised the public Checksum dispatcher, so wherever it resolved to the gvisor fallback (non-AVX2 amd64, fallback architectures) the suite compared gvisor against itself and the AVX2 assembly went untested -- silently green. Per-arch export_test.go files now enumerate the hand-written implementations and every sweep runs against the dispatcher plus each of them, skipping with an explicit message when the running CPU can't execute one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,37 +8,69 @@ import (
|
|||||||
gvisorchecksum "gvisor.dev/gvisor/pkg/tcpip/checksum"
|
gvisorchecksum "gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestChecksumMatchesGvisor walks lengths from 0 to 4096, with several initial
|
// archImpl names one checksum function under test. The per-arch
|
||||||
// seeds and a handful of starting alignments, asserting that our local
|
// export_*_test.go files enumerate the hand-written implementations so the
|
||||||
// Checksum matches gvisor's reference bit-for-bit.
|
// suite compares each one against gvisor directly, regardless of which one
|
||||||
func TestChecksumMatchesGvisor(t *testing.T) {
|
// the public Checksum dispatches to on the running CPU. Testing only the
|
||||||
rng := rand.New(rand.NewPCG(1, 2))
|
// dispatcher was tautological wherever it resolved to the gvisor fallback
|
||||||
const padFront = 16
|
// (non-AVX2 amd64, fallback architectures) — gvisor compared with itself,
|
||||||
|
// assembly untested, suite green.
|
||||||
|
type archImpl struct {
|
||||||
|
name string
|
||||||
|
fn func([]byte, uint16) uint16
|
||||||
|
available bool
|
||||||
|
}
|
||||||
|
|
||||||
// Random pool large enough for the longest case + alignment slop.
|
// implsUnderTest is the public dispatcher plus every arch implementation.
|
||||||
pool := make([]byte, 4096+padFront)
|
func implsUnderTest() []archImpl {
|
||||||
for i := range pool {
|
return append([]archImpl{{name: "dispatch", fn: Checksum, available: true}}, archImpls...)
|
||||||
pool[i] = byte(rng.Uint32())
|
}
|
||||||
|
|
||||||
|
// requireAvailable skips loudly when the running CPU can't execute an
|
||||||
|
// implementation — visible in test output, unlike the old silent tautology.
|
||||||
|
func requireAvailable(t *testing.T, impl archImpl) {
|
||||||
|
t.Helper()
|
||||||
|
if !impl.available {
|
||||||
|
t.Skipf("%s not supported on this CPU; its assembly is NOT tested in this run", impl.name)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
seeds := []uint16{0, 0x0001, 0xabcd, 0xffff, 0x1234, 0xfedc}
|
// TestChecksumMatchesGvisor walks lengths from 0 to 4096, with several initial
|
||||||
offsets := []int{0, 1, 2, 3, 4, 5, 7, 8, 15, 16}
|
// seeds and a handful of starting alignments, asserting that each local
|
||||||
|
// implementation matches gvisor's reference bit-for-bit.
|
||||||
|
func TestChecksumMatchesGvisor(t *testing.T) {
|
||||||
|
for _, impl := range implsUnderTest() {
|
||||||
|
t.Run(impl.name, func(t *testing.T) {
|
||||||
|
requireAvailable(t, impl)
|
||||||
|
rng := rand.New(rand.NewPCG(1, 2))
|
||||||
|
const padFront = 16
|
||||||
|
|
||||||
for length := 0; length <= 4096; length++ {
|
// Random pool large enough for the longest case + alignment slop.
|
||||||
for _, seed := range seeds {
|
pool := make([]byte, 4096+padFront)
|
||||||
for _, off := range offsets {
|
for i := range pool {
|
||||||
if off+length > len(pool) {
|
pool[i] = byte(rng.Uint32())
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
buf := pool[off : off+length]
|
seeds := []uint16{0, 0x0001, 0xabcd, 0xffff, 0x1234, 0xfedc}
|
||||||
want := gvisorchecksum.Checksum(buf, seed)
|
offsets := []int{0, 1, 2, 3, 4, 5, 7, 8, 15, 16}
|
||||||
got := Checksum(buf, seed)
|
|
||||||
if got != want {
|
for length := 0; length <= 4096; length++ {
|
||||||
t.Fatalf("len=%d off=%d seed=%#x: got %#04x want %#04x",
|
for _, seed := range seeds {
|
||||||
length, off, seed, got, want)
|
for _, off := range offsets {
|
||||||
|
if off+length > len(pool) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf := pool[off : off+length]
|
||||||
|
want := gvisorchecksum.Checksum(buf, seed)
|
||||||
|
got := impl.fn(buf, seed)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("len=%d off=%d seed=%#x: got %#04x want %#04x",
|
||||||
|
length, off, seed, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,23 +78,28 @@ func TestChecksumMatchesGvisor(t *testing.T) {
|
|||||||
// historically tripped up checksum implementations: all-zero, all-0xff,
|
// historically tripped up checksum implementations: all-zero, all-0xff,
|
||||||
// alternating, and ascending sequences.
|
// alternating, and ascending sequences.
|
||||||
func TestChecksumPatternedBuffers(t *testing.T) {
|
func TestChecksumPatternedBuffers(t *testing.T) {
|
||||||
for length := 0; length <= 256; length++ {
|
for _, impl := range implsUnderTest() {
|
||||||
patterns := map[string][]byte{
|
t.Run(impl.name, func(t *testing.T) {
|
||||||
"zeros": make([]byte, length),
|
requireAvailable(t, impl)
|
||||||
"ones": bytes(length, 0xff),
|
for length := 0; length <= 256; length++ {
|
||||||
"alternating": pattern(length, []byte{0xa5, 0x5a}),
|
patterns := map[string][]byte{
|
||||||
"ascending": ascending(length),
|
"zeros": make([]byte, length),
|
||||||
}
|
"ones": bytes(length, 0xff),
|
||||||
for name, buf := range patterns {
|
"alternating": pattern(length, []byte{0xa5, 0x5a}),
|
||||||
for _, seed := range []uint16{0, 0xffff, 0x8000} {
|
"ascending": ascending(length),
|
||||||
want := gvisorchecksum.Checksum(buf, seed)
|
}
|
||||||
got := Checksum(buf, seed)
|
for name, buf := range patterns {
|
||||||
if got != want {
|
for _, seed := range []uint16{0, 0xffff, 0x8000} {
|
||||||
t.Fatalf("%s len=%d seed=%#x: got %#04x want %#04x",
|
want := gvisorchecksum.Checksum(buf, seed)
|
||||||
name, length, seed, got, want)
|
got := impl.fn(buf, seed)
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("%s len=%d seed=%#x: got %#04x want %#04x",
|
||||||
|
name, length, seed, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,36 +135,41 @@ func ascending(n int) []byte {
|
|||||||
// and k=1 (one main loop iter, then tail). It's explicit coverage for
|
// and k=1 (one main loop iter, then tail). It's explicit coverage for
|
||||||
// payload sizes that are odd, not divisible by 4, by 8, or by 32.
|
// payload sizes that are odd, not divisible by 4, by 8, or by 32.
|
||||||
func TestChecksumTailPaths(t *testing.T) {
|
func TestChecksumTailPaths(t *testing.T) {
|
||||||
rng := rand.New(rand.NewPCG(42, 17))
|
for _, impl := range implsUnderTest() {
|
||||||
const padFront = 16
|
t.Run(impl.name, func(t *testing.T) {
|
||||||
const maxK = 8
|
requireAvailable(t, impl)
|
||||||
|
rng := rand.New(rand.NewPCG(42, 17))
|
||||||
|
const padFront = 16
|
||||||
|
const maxK = 8
|
||||||
|
|
||||||
pool := make([]byte, 64*maxK+padFront+64)
|
pool := make([]byte, 64*maxK+padFront+64)
|
||||||
for i := range pool {
|
for i := range pool {
|
||||||
pool[i] = byte(rng.Uint32())
|
pool[i] = byte(rng.Uint32())
|
||||||
}
|
}
|
||||||
|
|
||||||
seeds := []uint16{0, 0xffff, 0xabcd}
|
seeds := []uint16{0, 0xffff, 0xabcd}
|
||||||
offsets := []int{0, 1, 3, 7, 15} // mix of aligned and odd starts
|
offsets := []int{0, 1, 3, 7, 15} // mix of aligned and odd starts
|
||||||
|
|
||||||
for k := 0; k <= maxK; k++ {
|
for k := 0; k <= maxK; k++ {
|
||||||
for tail := 0; tail < 64; tail++ {
|
for tail := 0; tail < 64; tail++ {
|
||||||
length := 64*k + tail
|
length := 64*k + tail
|
||||||
for _, seed := range seeds {
|
for _, seed := range seeds {
|
||||||
for _, off := range offsets {
|
for _, off := range offsets {
|
||||||
if off+length > len(pool) {
|
if off+length > len(pool) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
buf := pool[off : off+length]
|
buf := pool[off : off+length]
|
||||||
want := gvisorchecksum.Checksum(buf, seed)
|
want := gvisorchecksum.Checksum(buf, seed)
|
||||||
got := Checksum(buf, seed)
|
got := impl.fn(buf, seed)
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("k=%d tail=%d (len=%d) off=%d seed=%#x: got %#04x want %#04x",
|
t.Fatalf("k=%d tail=%d (len=%d) off=%d seed=%#x: got %#04x want %#04x",
|
||||||
k, tail, length, off, seed, got, want)
|
k, tail, length, off, seed, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package checksum
|
||||||
|
|
||||||
|
// archImpls exposes every hand-written implementation on this architecture
|
||||||
|
// so the tests exercise them directly, independent of what the public
|
||||||
|
// Checksum dispatches to on the running CPU. Without this, running the
|
||||||
|
// suite on a non-AVX2 machine compared gvisor against itself and left the
|
||||||
|
// assembly untested — silently. available=false makes the test skip loudly
|
||||||
|
// instead.
|
||||||
|
var archImpls = []archImpl{
|
||||||
|
{name: "avx2", fn: checksumAVX2, available: hasAVX2},
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package checksum
|
||||||
|
|
||||||
|
// archImpls exposes every hand-written implementation on this architecture
|
||||||
|
// for direct testing; see export_amd64_test.go for the rationale. NEON is
|
||||||
|
// mandatory in armv8, so it is always available.
|
||||||
|
var archImpls = []archImpl{
|
||||||
|
{name: "neon", fn: checksumNEON, available: true},
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !amd64 && !arm64
|
||||||
|
|
||||||
|
package checksum
|
||||||
|
|
||||||
|
// No hand-written implementations on this architecture; the dispatcher is
|
||||||
|
// pure gvisor and there is nothing separate to test.
|
||||||
|
var archImpls []archImpl
|
||||||
Reference in New Issue
Block a user