Files
Nick RossiandGitHub b91e844014 util/def,cmd/containerboot: add LookupEnv, simplify env parsing (#20277)
Simplifies cmd/containerboot env var parsing. Most of the private helpers did
not earn their abstraction: defaultEnv(name, "") is just os.Getenv(name), and
the rest collapse into cmp.Or and the existing def.Bool. defaultEnv,
defaultEnvs and defaultBool are gone.

Adds def.LookupEnv, the env companion to def.Bool, for the one case that needs
it: TS_KUBE_SECRET, where an explicit "" disables Kubernetes secret storage and
must stay distinct from unset (cmp.Or cannot express that).

Updates #20018

Signed-off-by: Nick Rossi <[email protected]>
2026-07-17 18:32:52 -07:00

149 lines
4.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package def_test
import (
"os"
"strconv"
"testing"
"time"
"tailscale.com/util/def"
)
func TestLookupEnv(t *testing.T) {
const key = "TS_DEF_TEST_LOOKUPENV"
tests := []struct {
name string
unset bool
value string
def string
want string
}{
{name: "unset", unset: true, def: "fallback", want: "fallback"},
{name: "set", value: "value", def: "fallback", want: "value"},
{name: "empty", value: "", def: "fallback", want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv(key, tt.value)
if tt.unset {
os.Unsetenv(key)
}
if got := def.LookupEnv(key, tt.def); got != tt.want {
t.Errorf("LookupEnv(%q, %q) = %q; want %q", key, tt.def, got, tt.want)
}
})
}
}
func TestBool(t *testing.T) {
tests := []struct {
name string
in string
def bool
want bool
}{
{name: "empty_true", in: "", def: true, want: true},
{name: "empty_false", in: "", def: false, want: false},
{name: "valid_1", in: "1", def: false, want: true},
{name: "valid_t", in: "t", def: false, want: true},
{name: "valid_T", in: "T", def: false, want: true},
{name: "valid_TRUE", in: "TRUE", def: false, want: true},
{name: "valid_true", in: "true", def: false, want: true},
{name: "valid_True", in: "True", def: false, want: true},
{name: "valid_true_default_true", in: "true", def: true, want: true},
{name: "valid_0", in: "0", def: true, want: false},
{name: "valid_f", in: "f", def: true, want: false},
{name: "valid_F", in: "F", def: true, want: false},
{name: "valid_FALSE", in: "FALSE", def: true, want: false},
{name: "valid_false", in: "false", def: true, want: false},
{name: "valid_False", in: "False", def: true, want: false},
{name: "valid_false_default_false", in: "false", def: false, want: false},
{name: "invalid_true", in: "sure", def: true, want: true},
{name: "invalid_false", in: "sure", def: false, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := def.Bool(tt.in, tt.def); got != tt.want {
t.Errorf("Bool(%q, %v) = %v; want %v", tt.in, tt.def, got, tt.want)
}
})
}
}
func TestDuration(t *testing.T) {
tests := []struct {
name string
in string
def time.Duration
want time.Duration
}{
{name: "empty_second", in: "", def: time.Second, want: time.Second},
{name: "empty_zero", in: "", def: 0, want: 0},
{name: "valid", in: "2m30s", def: time.Second, want: 2*time.Minute + 30*time.Second},
{name: "valid_zero", in: "0s", def: time.Second, want: 0},
{name: "invalid_second", in: "soon", def: time.Second, want: time.Second},
{name: "invalid_minute", in: "soon", def: time.Minute, want: time.Minute},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := def.Duration(tt.in, tt.def); got != tt.want {
t.Errorf("Duration(%q, %v) = %v; want %v", tt.in, tt.def, got, tt.want)
}
})
}
}
func FuzzBool(f *testing.F) {
for _, tc := range []struct {
in string
def bool
}{
{in: "", def: true},
{in: "", def: false},
{in: "true", def: false},
{in: "false", def: true},
{in: "sure", def: true},
{in: "sure", def: false},
} {
f.Add(tc.in, tc.def)
}
f.Fuzz(func(t *testing.T, in string, fallback bool) {
got := def.Bool(in, fallback)
want, err := strconv.ParseBool(in)
if in == "" || err != nil {
want = fallback
}
if got != want {
t.Fatalf("Bool(%q, %v) = %v; want %v", in, fallback, got, want)
}
})
}
func FuzzDuration(f *testing.F) {
for _, tc := range []struct {
in string
def time.Duration
}{
{in: "", def: time.Second},
{in: "", def: 0},
{in: "2m30s", def: time.Second},
{in: "soon", def: time.Second},
} {
f.Add(tc.in, int64(tc.def))
}
f.Fuzz(func(t *testing.T, in string, fallbackN int64) {
fallback := time.Duration(fallbackN)
got := def.Duration(in, fallback)
want, err := time.ParseDuration(in)
if in == "" || err != nil {
want = fallback
}
if got != want {
t.Fatalf("Duration(%q, %v) = %v; want %v", in, fallback, got, want)
}
})
}