| // 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() |
| } |
| }) |
| } |
| } |