mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +00:00
Three orthogonal additions, one wire format. v2 package — generic ad-hoc schema dispatch (~/work/lux/zap/v2): - README + IMPOSSIBILITY + BENCH_RESULTS document the v1↔v2 split. - builder/field/iter/list/pool/registry/schema/view + codegen package emit v1-equivalent fast paths from declarative .zap sources. - _compile_fail_test/ + examples/ cover negative and positive cases. - v2_test.go exercises the generic dispatch path. - doc.go in v1 marks v1 deprecated for *new* schema authoring; existing v1 buffers stay parseable by both v1 and v2. bufpool — pooled read slabs (bufpool.go + bufpool_test.go): - Message gains optional *bufRef; Release() returns the slab to its pool when sourced from the pooled read path, GC-managed otherwise. - ParseHeader returns (data, rootOffset) without allocating *Message, used by zapv2 to avoid two allocations per parse. - bench_read_test.go + node_quic_stream_test.go cover both paths. Transport allocator + UMA/RDMA cgo (transport/*): - Transport.Caps()/Buffer interface: capability + slab-typed buffer contract. Bytes() vs DevicePtr() decoupled so UMA and GPUDirect can share the same call surface. - DPDK PMD + GPUDirect RDMA + CUDA UMA real cgo backends gated behind build tags; stub paths remain default on darwin / non-Linux. - transport_test.go covers Caps reporting + Buffer lifecycle on the default transport. LLM.md + go.mod follow the additions.
235 lines
6.3 KiB
Go
235 lines
6.3 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package transport
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPickAutoSelectsBestAvailable(t *testing.T) {
|
|
// Auto returns the best-available transport on this host. On a
|
|
// pure-CPU host that's "default"; on a host where the cuda /
|
|
// gpudirect / dpdk build tags brought live implementations
|
|
// online, auto picks one of those.
|
|
//
|
|
// We accept any name from Registered() — the contract is "Pick
|
|
// MUST succeed when no preference is given", not "the result MUST
|
|
// be default".
|
|
t.Setenv(envPreference, "")
|
|
tr, err := Pick("")
|
|
if err != nil {
|
|
t.Fatalf("Pick: %v", err)
|
|
}
|
|
defer tr.Close()
|
|
registered := map[string]bool{}
|
|
for _, n := range Registered() {
|
|
registered[n] = true
|
|
}
|
|
if !registered[tr.Name()] {
|
|
t.Errorf("Pick auto=%q; want one of %v", tr.Name(), Registered())
|
|
}
|
|
}
|
|
|
|
func TestPickExplicitDefault(t *testing.T) {
|
|
tr, err := Pick("default")
|
|
if err != nil {
|
|
t.Fatalf("Pick(default): %v", err)
|
|
}
|
|
defer tr.Close()
|
|
if tr.Name() != "default" {
|
|
t.Errorf("name=%q", tr.Name())
|
|
}
|
|
}
|
|
|
|
func TestPickExplicitUnavailable(t *testing.T) {
|
|
// Explicit gpudirect on a host without GPUDirect must return the
|
|
// sentinel error — no silent fallback when the operator asked
|
|
// specifically.
|
|
_, err := Pick("gpudirect")
|
|
if err == nil {
|
|
t.Fatal("Pick(gpudirect) succeeded on a host without GPUDirect; expected error")
|
|
}
|
|
if !errors.Is(err, ErrNotAvailable) {
|
|
t.Errorf("err=%v; want ErrNotAvailable", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "gpudirect") {
|
|
t.Errorf("err message missing transport name: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDefaultRoundTrip(t *testing.T) {
|
|
tr, err := Pick("default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tr.Close()
|
|
dt := tr.(*defaultTransport)
|
|
|
|
// Wire outbox → inbox manually so the round-trip test doesn't need
|
|
// an external relay.
|
|
go func() {
|
|
for env := range dt.outboxChan() {
|
|
dt.inboxChan() <- env
|
|
}
|
|
}()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
if err := tr.Send(ctx, "peer-1", []byte("hello")); err != nil {
|
|
t.Fatalf("Send: %v", err)
|
|
}
|
|
peer, msg, err := tr.Recv(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Recv: %v", err)
|
|
}
|
|
if peer != "peer-1" || string(msg) != "hello" {
|
|
t.Errorf("got peer=%q msg=%q", peer, string(msg))
|
|
}
|
|
}
|
|
|
|
func TestPickEnvOverride(t *testing.T) {
|
|
t.Setenv(envPreference, "default")
|
|
tr, err := Pick("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tr.Close()
|
|
if tr.Name() != "default" {
|
|
t.Errorf("env override ignored: got %q", tr.Name())
|
|
}
|
|
}
|
|
|
|
func TestPickUnknownName(t *testing.T) {
|
|
_, err := Pick("nonsense-transport")
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown transport name")
|
|
}
|
|
}
|
|
|
|
func TestDefaultCaps(t *testing.T) {
|
|
// The default transport is CPU-only, not GPU-resident, not zero-copy.
|
|
// Callers that need GPU-resident bytes (FHE precompile, BLS batch
|
|
// verify) gate on these flags at construction so they fall back to
|
|
// heap allocation when the chosen transport can't deliver.
|
|
tr, err := Pick("default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tr.Close()
|
|
caps := tr.Caps()
|
|
if caps.GPUResident {
|
|
t.Errorf("default transport reports GPUResident=true; want false")
|
|
}
|
|
if caps.ZeroCopy {
|
|
t.Errorf("default transport reports ZeroCopy=true; want false")
|
|
}
|
|
}
|
|
|
|
func TestDefaultNotBufferAllocator(t *testing.T) {
|
|
// The default transport intentionally does NOT implement
|
|
// BufferAllocator — callers that need managed buffers must pick a
|
|
// transport that can deliver them (uma, gpudirect). The type
|
|
// assertion below documents the contract.
|
|
tr, err := Pick("default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tr.Close()
|
|
if _, ok := tr.(BufferAllocator); ok {
|
|
t.Errorf("default transport unexpectedly implements BufferAllocator")
|
|
}
|
|
}
|
|
|
|
func TestRegistered(t *testing.T) {
|
|
got := Registered()
|
|
want := map[string]bool{"default": true, "uma": true, "gpudirect": true, "dpdk": true}
|
|
if len(got) != len(want) {
|
|
t.Errorf("Registered: got %d entries, want %d", len(got), len(want))
|
|
}
|
|
for _, n := range got {
|
|
if !want[n] {
|
|
t.Errorf("Registered: unexpected %q", n)
|
|
}
|
|
delete(want, n)
|
|
}
|
|
if len(want) > 0 {
|
|
t.Errorf("Registered: missing %v", want)
|
|
}
|
|
}
|
|
|
|
func TestUMABufferAllocator(t *testing.T) {
|
|
// Skip when UMA isn't compiled in (cuda build tag missing).
|
|
tr, err := Pick("uma")
|
|
if err != nil {
|
|
t.Skipf("UMA not available on this host: %v", err)
|
|
}
|
|
defer tr.Close()
|
|
caps := tr.Caps()
|
|
if !caps.GPUResident {
|
|
t.Errorf("uma transport reports GPUResident=false; want true")
|
|
}
|
|
if !caps.ZeroCopy {
|
|
t.Errorf("uma transport reports ZeroCopy=false; want true")
|
|
}
|
|
alloc, ok := tr.(BufferAllocator)
|
|
if !ok {
|
|
t.Fatal("uma transport does not implement BufferAllocator")
|
|
}
|
|
// Hit every slab class plus one cold-path size.
|
|
for _, size := range []int{1, 256, 1024, 4096, 16384, 65536, 1 << 20, 4 << 20} {
|
|
buf, err := alloc.AllocBuffer(size)
|
|
if err != nil {
|
|
t.Errorf("AllocBuffer(%d): %v", size, err)
|
|
continue
|
|
}
|
|
b := buf.Bytes()
|
|
if len(b) != size {
|
|
t.Errorf("AllocBuffer(%d): Bytes len=%d", size, len(b))
|
|
}
|
|
// Write + read must round-trip through unified memory.
|
|
for i := range b {
|
|
b[i] = byte(i & 0xFF)
|
|
}
|
|
for i := range b {
|
|
if b[i] != byte(i&0xFF) {
|
|
t.Errorf("buffer corruption at i=%d: got %d", i, b[i])
|
|
break
|
|
}
|
|
}
|
|
// DevicePtr must be non-nil for GPU launches.
|
|
if buf.DevicePtr() == nil {
|
|
t.Errorf("AllocBuffer(%d): DevicePtr is nil", size)
|
|
}
|
|
buf.Release()
|
|
// Double-Release must be a no-op (not a panic).
|
|
buf.Release()
|
|
}
|
|
}
|
|
|
|
func TestPickAllUnavailableOnNonGPUHost(t *testing.T) {
|
|
// On a host without CUDA / DPDK / ibverbs, every native transport
|
|
// must report an error individually. This is the inverse of
|
|
// TestPickAutoFallsBackToDefault — auto must fall through, but
|
|
// explicit requests must error.
|
|
for _, name := range []string{"uma", "gpudirect", "dpdk"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
_, err := Pick(name)
|
|
if err == nil {
|
|
// On a GPU host this CAN succeed — that's fine. The
|
|
// test self-skips when running on real GPU hardware.
|
|
t.Skipf("Pick(%s) succeeded — GPU hardware present, skipping the negative case", name)
|
|
return
|
|
}
|
|
if !errors.Is(err, ErrNotAvailable) {
|
|
t.Errorf("Pick(%s) err=%v; want wrapped ErrNotAvailable", name, err)
|
|
}
|
|
})
|
|
}
|
|
}
|