You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/envknob/envknob_test.go

329 lines
7.4 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package envknob
import (
"os"
"testing"
"time"
"tailscale.com/types/opt"
)
func TestBool(t *testing.T) {
tests := []struct {
name string
envVar string
value string
want bool
wantSet bool
}{
{name: "true", envVar: "TEST_BOOL_TRUE", value: "true", want: true, wantSet: true},
{name: "false", envVar: "TEST_BOOL_FALSE", value: "false", want: false, wantSet: true},
{name: "1", envVar: "TEST_BOOL_1", value: "1", want: true, wantSet: true},
{name: "0", envVar: "TEST_BOOL_0", value: "0", want: false, wantSet: true},
{name: "unset", envVar: "TEST_BOOL_UNSET", value: "", want: false, wantSet: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.value != "" {
os.Setenv(tt.envVar, tt.value)
defer os.Unsetenv(tt.envVar)
}
got := Bool(tt.envVar)
if got != tt.want {
t.Errorf("Bool(%q) = %v, want %v", tt.envVar, got, tt.want)
}
})
}
}
func TestBoolDefaultTrue(t *testing.T) {
envVar := "TEST_BOOL_DEFAULT_TRUE"
// Unset - should return true
os.Unsetenv(envVar)
if got := BoolDefaultTrue(envVar); !got {
t.Errorf("BoolDefaultTrue(%q) with unset = %v, want true", envVar, got)
}
// Set to false - should return false
os.Setenv(envVar, "false")
defer os.Unsetenv(envVar)
if got := BoolDefaultTrue(envVar); got {
t.Errorf("BoolDefaultTrue(%q) with false = %v, want false", envVar, got)
}
}
func TestGOOS(t *testing.T) {
// Should return a non-empty string
if got := GOOS(); got == "" {
t.Error("GOOS() returned empty string")
}
// By default should match runtime.GOOS
if got := GOOS(); got != os.Getenv("GOOS") && os.Getenv("GOOS") == "" {
// If GOOS env var not set, should use runtime
// Can't test exact value as it's platform-dependent
}
}
func TestString(t *testing.T) {
tests := []struct {
name string
envVar string
value string
want string
}{
{name: "set", envVar: "TEST_STRING", value: "hello", want: "hello"},
{name: "empty", envVar: "TEST_STRING_EMPTY", value: "", want: ""},
{name: "spaces", envVar: "TEST_STRING_SPACES", value: " value ", want: " value "},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.value != "" {
os.Setenv(tt.envVar, tt.value)
defer os.Unsetenv(tt.envVar)
}
got := String(tt.envVar)
if got != tt.want {
t.Errorf("String(%q) = %q, want %q", tt.envVar, got, tt.want)
}
})
}
}
func TestOptBool(t *testing.T) {
tests := []struct {
name string
envVar string
value string
wantSet bool
wantVal bool
}{
{name: "true", envVar: "TEST_OPT_TRUE", value: "true", wantSet: true, wantVal: true},
{name: "false", envVar: "TEST_OPT_FALSE", value: "false", wantSet: true, wantVal: false},
{name: "unset", envVar: "TEST_OPT_UNSET", value: "", wantSet: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.value != "" {
os.Setenv(tt.envVar, tt.value)
defer os.Unsetenv(tt.envVar)
} else {
os.Unsetenv(tt.envVar)
}
got := OptBool(tt.envVar)
if _, ok := got.Get(); ok != tt.wantSet {
t.Errorf("OptBool(%q).Get() set = %v, want %v", tt.envVar, ok, tt.wantSet)
}
if tt.wantSet {
if val, _ := got.Get(); val != tt.wantVal {
t.Errorf("OptBool(%q).Get() value = %v, want %v", tt.envVar, val, tt.wantVal)
}
}
})
}
}
func TestSetenv(t *testing.T) {
envVar := "TEST_SETENV"
value := "test_value"
defer os.Unsetenv(envVar)
Setenv(envVar, value)
// Verify it's actually set in the environment
if got := os.Getenv(envVar); got != value {
t.Errorf("After Setenv, os.Getenv(%q) = %q, want %q", envVar, got, value)
}
// Verify String retrieves it
if got := String(envVar); got != value {
t.Errorf("After Setenv, String(%q) = %q, want %q", envVar, got, value)
}
}
func TestRegisterString(t *testing.T) {
envVar := "TEST_REGISTER_STRING"
value := "registered"
os.Setenv(envVar, value)
defer os.Unsetenv(envVar)
var target string
RegisterString(&target, envVar)
if target != value {
t.Errorf("After RegisterString, target = %q, want %q", target, value)
}
}
func TestRegisterBool(t *testing.T) {
envVar := "TEST_REGISTER_BOOL"
os.Setenv(envVar, "true")
defer os.Unsetenv(envVar)
var target bool
RegisterBool(&target, envVar)
if !target {
t.Error("After RegisterBool with true, target = false, want true")
}
}
func TestRegisterOptBool(t *testing.T) {
envVar := "TEST_REGISTER_OPTBOOL"
os.Setenv(envVar, "true")
defer os.Unsetenv(envVar)
var target opt.Bool
RegisterOptBool(&target, envVar)
if val, ok := target.Get(); !ok || !val {
t.Errorf("After RegisterOptBool, target = (%v, %v), want (true, true)", val, ok)
}
}
func TestLogCurrent(t *testing.T) {
// Set a test env var
os.Setenv("TEST_LOG_CURRENT", "test")
defer os.Unsetenv("TEST_LOG_CURRENT")
// Force it to be noted
Setenv("TEST_LOG_CURRENT", "test")
logged := false
logf := func(format string, args ...any) {
logged = true
}
LogCurrent(logf)
if !logged {
t.Error("LogCurrent did not call logf")
}
}
func TestUseRunningUserForAuth(t *testing.T) {
// This just tests that the function runs without panicking
defer func() {
if r := recover(); r != nil {
t.Errorf("UseRunningUserForAuth() panicked: %v", r)
}
}()
_ = UseRunningUserForAuth()
}
func TestDERPConncap(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("DERPConncap() panicked: %v", r)
}
}()
got := DERPConncap()
if got < 0 {
t.Errorf("DERPConncap() = %d, want >= 0", got)
}
}
// Test some known environment variables
func TestKnownVariables(t *testing.T) {
// These functions should not panic
_ = CrashMonitorSupport()
_ = NoLogsNoSupport()
_ = AllowRemoteUpdate()
_ = DisablePortMapper()
}
// Benchmark common operations
func BenchmarkBool(b *testing.B) {
os.Setenv("BENCH_BOOL", "true")
defer os.Unsetenv("BENCH_BOOL")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Bool("BENCH_BOOL")
}
}
func BenchmarkString(b *testing.B) {
os.Setenv("BENCH_STRING", "value")
defer os.Unsetenv("BENCH_STRING")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = String("BENCH_STRING")
}
}
func BenchmarkOptBool(b *testing.B) {
os.Setenv("BENCH_OPTBOOL", "true")
defer os.Unsetenv("BENCH_OPTBOOL")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = OptBool("BENCH_OPTBOOL")
}
}
// Integration test for registering variables
func TestRegisterIntegration(t *testing.T) {
// Test registering multiple types
var (
strVal string
boolVal bool
optVal opt.Bool
durVal time.Duration
intVal int
)
os.Setenv("TEST_INT_STR", "hello")
os.Setenv("TEST_INT_BOOL", "true")
os.Setenv("TEST_INT_OPT", "false")
os.Setenv("TEST_INT_DUR", "5s")
os.Setenv("TEST_INT_INT", "42")
defer func() {
os.Unsetenv("TEST_INT_STR")
os.Unsetenv("TEST_INT_BOOL")
os.Unsetenv("TEST_INT_OPT")
os.Unsetenv("TEST_INT_DUR")
os.Unsetenv("TEST_INT_INT")
}()
RegisterString(&strVal, "TEST_INT_STR")
RegisterBool(&boolVal, "TEST_INT_BOOL")
RegisterOptBool(&optVal, "TEST_INT_OPT")
RegisterDuration(&durVal, "TEST_INT_DUR")
RegisterInt(&intVal, "TEST_INT_INT")
if strVal != "hello" {
t.Errorf("strVal = %q, want %q", strVal, "hello")
}
if !boolVal {
t.Error("boolVal = false, want true")
}
if val, ok := optVal.Get(); !ok || val {
t.Errorf("optVal = (%v, %v), want (false, true)", val, ok)
}
if durVal != 5*time.Second {
t.Errorf("durVal = %v, want 5s", durVal)
}
if intVal != 42 {
t.Errorf("intVal = %d, want 42", intVal)
}
}