Files
tailscale/tstest/reflect.go
Brad FitzpatrickandBrad Fitzpatrick 2a64c03c95
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / fuzz (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-bench, benchmarks) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-1-2, 1/2) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-2-2, 2/2) (push) Has been cancelled
CI / Windows (win-tool-go) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=darwin, arm64, darwin, macOS) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=linux, amd64, linux, Linux) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=windows, amd64, windows, Windows) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=1/4, amd64, linux, Portable (1/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=2/4, amd64, linux, Portable (2/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=3/4, amd64, linux, Portable (3/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=4/4, amd64, linux, Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
types/ptr: deprecate ptr.To, use Go 1.26 new
Updates #18682

Change-Id: I62f6aa0de2a15ef8c1435032c6aa74a181c25f8f
Signed-off-by: Brad Fitzpatrick <[email protected]>
2026-03-05 20:13:18 -08:00

113 lines
3.2 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package tstest
import (
"net/netip"
"reflect"
"testing"
"time"
)
// IsZeroable is the interface for things with an IsZero method.
type IsZeroable interface {
IsZero() bool
}
var (
netipAddrType = reflect.TypeFor[netip.Addr]()
netipAddrPortType = reflect.TypeFor[netip.AddrPort]()
netipPrefixType = reflect.TypeFor[netip.Prefix]()
timeType = reflect.TypeFor[time.Time]()
timePtrType = reflect.TypeFor[*time.Time]()
)
// CheckIsZero checks that the IsZero method of a given type functions
// correctly, by instantiating a new value of that type, changing a field, and
// then checking that the IsZero method returns false.
//
// The nonzeroValues map should contain non-zero values for each type that
// exists in the type T or any contained types. Basic types like string, bool,
// and numeric types are handled automatically.
func CheckIsZero[T IsZeroable](t testing.TB, nonzeroValues map[reflect.Type]any) {
t.Helper()
var zero T
if !zero.IsZero() {
t.Errorf("zero value of %T is not IsZero", zero)
return
}
var nonEmptyValue func(t reflect.Type) reflect.Value
nonEmptyValue = func(ty reflect.Type) reflect.Value {
if v, ok := nonzeroValues[ty]; ok {
return reflect.ValueOf(v)
}
switch ty {
// Given that we're a networking company, probably fine to have
// a special case for netip.Addr :)
case netipAddrType:
return reflect.ValueOf(netip.MustParseAddr("1.2.3.4"))
case netipAddrPortType:
return reflect.ValueOf(netip.MustParseAddrPort("1.2.3.4:9999"))
case netipPrefixType:
return reflect.ValueOf(netip.MustParsePrefix("1.2.3.4/24"))
case timeType:
return reflect.ValueOf(time.Unix(1704067200, 0))
case timePtrType:
return reflect.ValueOf(new(time.Unix(1704067200, 0)))
}
switch ty.Kind() {
case reflect.String:
return reflect.ValueOf("foo").Convert(ty)
case reflect.Bool:
return reflect.ValueOf(true)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(int64(-42)).Convert(ty)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return reflect.ValueOf(uint64(42)).Convert(ty)
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(float64(3.14)).Convert(ty)
case reflect.Complex64, reflect.Complex128:
return reflect.ValueOf(complex(3.14, 2.71)).Convert(ty)
case reflect.Chan:
return reflect.MakeChan(ty, 1)
// For slices, ensure that the slice is non-empty.
case reflect.Slice:
v := nonEmptyValue(ty.Elem())
sl := reflect.MakeSlice(ty, 1, 1)
sl.Index(0).Set(v)
return sl
case reflect.Map:
// Create a map with a single key-value pair, recursively creating each.
k := nonEmptyValue(ty.Key())
v := nonEmptyValue(ty.Elem())
m := reflect.MakeMap(ty)
m.SetMapIndex(k, v)
return m
default:
panic("unhandled type " + ty.String())
}
}
typ := reflect.TypeFor[T]()
for i, n := 0, typ.NumField(); i < n; i++ {
sf := typ.Field(i)
var nonzero T
rv := reflect.ValueOf(&nonzero).Elem()
rv.Field(i).Set(nonEmptyValue(sf.Type))
if nonzero.IsZero() {
t.Errorf("IsZero = true with %v set; want false\nvalue: %#v", sf.Name, nonzero)
}
}
}