| // 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 <https://go.dev/issue/27217>. | |
| setDummyEnv(b, envList) | |
| syscall.Clearenv() | |
| } | |
| }) | |
| } | |
| } |