// Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build unix || (js && wasm) || plan9 || wasip1 package syscall_test import ( "fmt" "strconv" "strings" "syscall" "testing" ) type env struct { name, val string } func genDummyEnv(tb testing.TB, size int) []env { tb.Helper() envList := make([]env, size) for idx := range size { envList[idx] = env{ name: fmt.Sprintf("DUMMY_VAR_%d", idx), val: fmt.Sprintf("val-%d", idx*100), } } return envList } func setDummyEnv(tb testing.TB, envList []env) { tb.Helper() for _, env := range envList { if err := syscall.Setenv(env.name, env.val); err != nil { tb.Fatalf("setenv %s=%q failed: %v", env.name, env.val, err) } } } func setupEnvCleanup(tb testing.TB) { tb.Helper() originalEnv := map[string]string{} for _, env := range syscall.Environ() { fields := strings.SplitN(env, "=", 2) name, val := fields[0], fields[1] originalEnv[name] = val } tb.Cleanup(func() { syscall.Clearenv() for name, val := range originalEnv { if err := syscall.Setenv(name, val); err != nil { tb.Fatalf("could not reset env %s=%q: %v", name, val, err) } } }) } func TestClearenv(t *testing.T) { setupEnvCleanup(t) t.Run("DummyVars-4096", func(t *testing.T) { envList := genDummyEnv(t, 4096) setDummyEnv(t, envList) if env := syscall.Environ(); len(env) < 4096 { t.Fatalf("env is missing dummy variables: %v", env) } for idx := range 4096 { name := fmt.Sprintf("DUMMY_VAR_%d", idx) if _, ok := syscall.Getenv(name); !ok { t.Fatalf("env is missing dummy variable %s", name) } } syscall.Clearenv() if env := syscall.Environ(); len(env) != 0 { t.Fatalf("clearenv should've cleared all variables: %v still set", env) } for idx := range 4096 { name := fmt.Sprintf("DUMMY_VAR_%d", idx) if val, ok := syscall.Getenv(name); ok { t.Fatalf("clearenv should've cleared all variables: %s=%q still set", name, val) } } }) // Test that GODEBUG getting cleared by Clearenv also resets the behaviour. t.Run("GODEBUG", func(t *testing.T) { envList := genDummyEnv(t, 100) setDummyEnv(t, envList) doNilPanic := func() (ret any) { defer func() { ret = recover() }() panic(nil) } // Allow panic(nil). if err := syscall.Setenv("GODEBUG", "panicnil=1"); err != nil { t.Fatalf("setenv GODEBUG=panicnil=1 failed: %v", err) } got := doNilPanic() if got != nil { t.Fatalf("GODEBUG=panicnil=1 did not allow for nil panic: got %#v", got) } // Disallow panic(nil). syscall.Clearenv() if env := syscall.Environ(); len(env) != 0 { t.Fatalf("clearenv should've cleared all variables: %v still set", env) } got = doNilPanic() if got == nil { t.Fatalf("GODEBUG=panicnil=1 being unset didn't reset panicnil behaviour") } if godebug, ok := syscall.Getenv("GODEBUG"); ok { t.Fatalf("GODEBUG still exists in environment despite being unset: GODEBUG=%q", godebug) } }) } func BenchmarkClearenv(b *testing.B) { setupEnvCleanup(b) b.ResetTimer() for _, size := range []int{100, 1000, 10000} { b.Run(strconv.Itoa(size), func(b *testing.B) { envList := genDummyEnv(b, size) for b.Loop() { // Ideally we would use b.StopTimer() for the setDummyEnv // portion, but this causes the benchmark time to get confused // and take forever. See . setDummyEnv(b, envList) syscall.Clearenv() } }) } }