1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package os_test
6
7import (
8	. "os"
9	"reflect"
10	"strings"
11	"testing"
12)
13
14// testGetenv gives us a controlled set of variables for testing Expand.
15func testGetenv(s string) string {
16	switch s {
17	case "*":
18		return "all the args"
19	case "#":
20		return "NARGS"
21	case "$":
22		return "PID"
23	case "1":
24		return "ARGUMENT1"
25	case "HOME":
26		return "/usr/gopher"
27	case "H":
28		return "(Value of H)"
29	case "home_1":
30		return "/usr/foo"
31	case "_":
32		return "underscore"
33	}
34	return ""
35}
36
37var expandTests = []struct {
38	in, out string
39}{
40	{"", ""},
41	{"$*", "all the args"},
42	{"$$", "PID"},
43	{"${*}", "all the args"},
44	{"$1", "ARGUMENT1"},
45	{"${1}", "ARGUMENT1"},
46	{"now is the time", "now is the time"},
47	{"$HOME", "/usr/gopher"},
48	{"$home_1", "/usr/foo"},
49	{"${HOME}", "/usr/gopher"},
50	{"${H}OME", "(Value of H)OME"},
51	{"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
52	{"start$+middle$^end$", "start$+middle$^end$"},
53	{"mixed$|bag$$$", "mixed$|bagPID$"},
54	{"$", "$"},
55	{"$}", "$}"},
56	{"${", ""},  // invalid syntax; eat up the characters
57	{"${}", ""}, // invalid syntax; eat up the characters
58}
59
60func TestExpand(t *testing.T) {
61	for _, test := range expandTests {
62		result := Expand(test.in, testGetenv)
63		if result != test.out {
64			t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out)
65		}
66	}
67}
68
69var global any
70
71func BenchmarkExpand(b *testing.B) {
72	b.Run("noop", func(b *testing.B) {
73		var s string
74		b.ReportAllocs()
75		for i := 0; i < b.N; i++ {
76			s = Expand("tick tick tick tick", func(string) string { return "" })
77		}
78		global = s
79	})
80	b.Run("multiple", func(b *testing.B) {
81		var s string
82		b.ReportAllocs()
83		for i := 0; i < b.N; i++ {
84			s = Expand("$a $a $a $a", func(string) string { return "boom" })
85		}
86		global = s
87	})
88}
89
90func TestConsistentEnviron(t *testing.T) {
91	e0 := Environ()
92	for i := 0; i < 10; i++ {
93		e1 := Environ()
94		if !reflect.DeepEqual(e0, e1) {
95			t.Fatalf("environment changed")
96		}
97	}
98}
99
100func TestUnsetenv(t *testing.T) {
101	const testKey = "GO_TEST_UNSETENV"
102	set := func() bool {
103		prefix := testKey + "="
104		for _, key := range Environ() {
105			if strings.HasPrefix(key, prefix) {
106				return true
107			}
108		}
109		return false
110	}
111	if err := Setenv(testKey, "1"); err != nil {
112		t.Fatalf("Setenv: %v", err)
113	}
114	if !set() {
115		t.Error("Setenv didn't set TestUnsetenv")
116	}
117	if err := Unsetenv(testKey); err != nil {
118		t.Fatalf("Unsetenv: %v", err)
119	}
120	if set() {
121		t.Fatal("Unsetenv didn't clear TestUnsetenv")
122	}
123}
124
125func TestClearenv(t *testing.T) {
126	const testKey = "GO_TEST_CLEARENV"
127	const testValue = "1"
128
129	// reset env
130	defer func(origEnv []string) {
131		for _, pair := range origEnv {
132			// Environment variables on Windows can begin with =
133			// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
134			i := strings.Index(pair[1:], "=") + 1
135			if err := Setenv(pair[:i], pair[i+1:]); err != nil {
136				t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err)
137			}
138		}
139	}(Environ())
140
141	if err := Setenv(testKey, testValue); err != nil {
142		t.Fatalf("Setenv(%q, %q) failed: %v", testKey, testValue, err)
143	}
144	if _, ok := LookupEnv(testKey); !ok {
145		t.Errorf("Setenv(%q, %q) didn't set $%s", testKey, testValue, testKey)
146	}
147	Clearenv()
148	if val, ok := LookupEnv(testKey); ok {
149		t.Errorf("Clearenv() didn't clear $%s, remained with value %q", testKey, val)
150	}
151}
152
153func TestLookupEnv(t *testing.T) {
154	const smallpox = "SMALLPOX"      // No one has smallpox.
155	value, ok := LookupEnv(smallpox) // Should not exist.
156	if ok || value != "" {
157		t.Fatalf("%s=%q", smallpox, value)
158	}
159	defer Unsetenv(smallpox)
160	err := Setenv(smallpox, "virus")
161	if err != nil {
162		t.Fatalf("failed to release smallpox virus")
163	}
164	_, ok = LookupEnv(smallpox)
165	if !ok {
166		t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken")
167	}
168}
169
170// On Windows, Environ was observed to report keys with a single leading "=".
171// Check that they are properly reported by LookupEnv and can be set by SetEnv.
172// See https://golang.org/issue/49886.
173func TestEnvironConsistency(t *testing.T) {
174	t.Parallel()
175
176	for _, kv := range Environ() {
177		i := strings.Index(kv, "=")
178		if i == 0 {
179			// We observe in practice keys with a single leading "=" on Windows.
180			// TODO(#49886): Should we consume only the first leading "=" as part
181			// of the key, or parse through arbitrarily many of them until a non-=,
182			// or try each possible key/value boundary until LookupEnv succeeds?
183			i = strings.Index(kv[1:], "=") + 1
184		}
185		if i < 0 {
186			t.Errorf("Environ entry missing '=': %q", kv)
187		}
188
189		k := kv[:i]
190		v := kv[i+1:]
191		v2, ok := LookupEnv(k)
192		if ok && v == v2 {
193			t.Logf("LookupEnv(%q) = %q, %t", k, v2, ok)
194		} else {
195			t.Errorf("Environ contains %q, but LookupEnv(%q) = %q, %t", kv, k, v2, ok)
196		}
197
198		// Since k=v is already present in the environment,
199		// setting it should be a no-op.
200		if err := Setenv(k, v); err == nil {
201			t.Logf("Setenv(%q, %q)", k, v)
202		} else {
203			t.Errorf("Environ contains %q, but SetEnv(%q, %q) = %q", kv, k, v, err)
204		}
205	}
206}
207