go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/system/environ/environment_test.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package environ 16 17 import ( 18 "context" 19 "reflect" 20 "strings" 21 "testing" 22 23 . "github.com/smartystreets/goconvey/convey" 24 ) 25 26 // Note: all tests here are NOT marked with t.Parallel() because they mutate 27 // global 'normalizeKeyCase'. 28 29 func pretendWindows() { 30 normalizeKeyCase = strings.ToUpper 31 } 32 33 func pretendLinux() { 34 normalizeKeyCase = func(k string) string { return k } 35 } 36 37 func TestEnvironmentConversion(t *testing.T) { 38 Convey(`Source environment slice translates correctly to/from an Env.`, t, func() { 39 Convey(`Case insensitive (e.g., Windows)`, func() { 40 pretendWindows() 41 42 env := New([]string{ 43 "", 44 "FOO", 45 "BAR=BAZ", 46 "bar=baz", 47 "qux=quux=quuuuuuux", 48 }) 49 So(env, ShouldResemble, Env{ 50 env: map[string]string{ 51 "FOO": "FOO=", 52 "BAR": "bar=baz", 53 "QUX": "qux=quux=quuuuuuux", 54 }, 55 }) 56 57 So(env.Sorted(), ShouldResemble, []string{ 58 "FOO=", 59 "bar=baz", 60 "qux=quux=quuuuuuux", 61 }) 62 63 So(env.Get(""), ShouldEqual, "") 64 So(env.Get("FOO"), ShouldEqual, "") 65 So(env.Get("BAR"), ShouldEqual, "baz") 66 So(env.Get("bar"), ShouldEqual, "baz") 67 So(env.Get("qux"), ShouldEqual, "quux=quuuuuuux") 68 So(env.Get("QuX"), ShouldEqual, "quux=quuuuuuux") 69 }) 70 71 Convey(`Case sensitive (e.g., POSIX)`, func() { 72 pretendLinux() 73 74 env := New([]string{ 75 "", 76 "FOO", 77 "BAR=BAZ", 78 "bar=baz", 79 "qux=quux=quuuuuuux", 80 }) 81 So(env, ShouldResemble, Env{ 82 env: map[string]string{ 83 "FOO": "FOO=", 84 "BAR": "BAR=BAZ", 85 "bar": "bar=baz", 86 "qux": "qux=quux=quuuuuuux", 87 }, 88 }) 89 90 So(env.Sorted(), ShouldResemble, []string{ 91 "BAR=BAZ", 92 "FOO=", 93 "bar=baz", 94 "qux=quux=quuuuuuux", 95 }) 96 97 So(env.Get(""), ShouldEqual, "") 98 So(env.Get("FOO"), ShouldEqual, "") 99 So(env.Get("BAR"), ShouldEqual, "BAZ") 100 So(env.Get("bar"), ShouldEqual, "baz") 101 So(env.Get("qux"), ShouldEqual, "quux=quuuuuuux") 102 So(env.Get("QuX"), ShouldEqual, "") 103 }) 104 }) 105 } 106 107 func TestEnvironmentManipulation(t *testing.T) { 108 Convey(`A zero-valued Env`, t, func() { 109 pretendLinux() 110 111 var env Env 112 So(env.Len(), ShouldEqual, 0) 113 114 Convey(`Can be sorted.`, func() { 115 So(env.Sorted(), ShouldBeNil) 116 }) 117 118 Convey(`Can call Get`, func() { 119 v, ok := env.Lookup("foo") 120 So(ok, ShouldBeFalse) 121 So(v, ShouldEqual, "") 122 }) 123 124 Convey(`Can be cloned`, func() { 125 So(env.Clone(), ShouldResemble, New(nil)) 126 }) 127 128 Convey(`Set panics`, func() { 129 So(func() { env.Set("foo", "bar") }, ShouldPanic) 130 }) 131 }) 132 133 Convey(`An empty Env`, t, func() { 134 pretendLinux() 135 136 env := New(nil) 137 So(env.Len(), ShouldEqual, 0) 138 139 Convey(`Can be sorted.`, func() { 140 So(env.Sorted(), ShouldBeNil) 141 }) 142 143 Convey(`Can call Get`, func() { 144 v, ok := env.Lookup("foo") 145 So(ok, ShouldBeFalse) 146 So(v, ShouldEqual, "") 147 }) 148 149 Convey(`Can call Set`, func() { 150 env.Set("foo", "bar") 151 So(env.Len(), ShouldEqual, 1) 152 So(env.Sorted(), ShouldResemble, []string{"foo=bar"}) 153 }) 154 155 Convey(`Can be cloned`, func() { 156 So(env.Clone(), ShouldResemble, New(nil)) 157 }) 158 }) 159 160 Convey(`A testing Env`, t, func() { 161 pretendWindows() 162 163 env := New([]string{ 164 "PYTHONPATH=/foo:/bar:/baz", 165 "http_proxy=wiped-out-by-next", 166 "http_proxy=http://example.com", 167 "novalue", 168 }) 169 So(env.Len(), ShouldEqual, 3) 170 171 Convey(`Can Get values.`, func() { 172 v, ok := env.Lookup("PYTHONPATH") 173 So(ok, ShouldBeTrue) 174 So(v, ShouldEqual, "/foo:/bar:/baz") 175 176 v, ok = env.Lookup("http_proxy") 177 So(ok, ShouldBeTrue) 178 So(v, ShouldEqual, "http://example.com") 179 180 v, ok = env.Lookup("novalue") 181 So(ok, ShouldBeTrue) 182 So(v, ShouldEqual, "") 183 }) 184 185 Convey(`Will note missing values.`, func() { 186 _, ok := env.Lookup("missing") 187 So(ok, ShouldBeFalse) 188 189 _, ok = env.Lookup("") 190 So(ok, ShouldBeFalse) 191 }) 192 193 Convey(`Can be converted into a map and enumerated`, func() { 194 So(env.Map(), ShouldResemble, map[string]string{ 195 "PYTHONPATH": "/foo:/bar:/baz", 196 "http_proxy": "http://example.com", 197 "novalue": "", 198 }) 199 200 Convey(`Can perform iteration`, func() { 201 buildMap := make(map[string]string) 202 So(env.Iter(func(k, v string) error { 203 buildMap[k] = v 204 return nil 205 }), ShouldBeNil) 206 So(env.Map(), ShouldResemble, buildMap) 207 }) 208 209 Convey(`Can have elements removed through iteration`, func() { 210 env.RemoveMatch(func(k, v string) bool { 211 switch k { 212 case "PYTHONPATH", "novalue": 213 return true 214 default: 215 return false 216 } 217 }) 218 So(env.Map(), ShouldResemble, map[string]string{ 219 "http_proxy": "http://example.com", 220 }) 221 }) 222 }) 223 224 Convey(`Can update its values.`, func() { 225 orig := env.Clone() 226 227 // Update PYTHONPATH, confirm that it updated correctly. 228 v, _ := env.Lookup("PYTHONPATH") 229 env.Set("PYTHONPATH", "/override:"+v) 230 So(env.Sorted(), ShouldResemble, []string{ 231 "PYTHONPATH=/override:/foo:/bar:/baz", 232 "http_proxy=http://example.com", 233 "novalue=", 234 }) 235 236 // Use a different-case key, and confirm that it still updated correctly. 237 Convey(`When case insensitive, will update common keys.`, func() { 238 env.Set("pYtHoNpAtH", "/override:"+v) 239 So(env.Sorted(), ShouldResemble, []string{ 240 "http_proxy=http://example.com", 241 "novalue=", 242 "pYtHoNpAtH=/override:/foo:/bar:/baz", 243 }) 244 So(env.Get("PYTHONPATH"), ShouldEqual, "/override:/foo:/bar:/baz") 245 246 So(env.Remove("HTTP_PROXY"), ShouldBeTrue) 247 So(env.Remove("nonexistent"), ShouldBeFalse) 248 So(env.Sorted(), ShouldResemble, []string{ 249 "novalue=", 250 "pYtHoNpAtH=/override:/foo:/bar:/baz", 251 }) 252 253 // Test that the clone didn't change. 254 So(orig.Sorted(), ShouldResemble, []string{ 255 "PYTHONPATH=/foo:/bar:/baz", 256 "http_proxy=http://example.com", 257 "novalue=", 258 }) 259 260 orig.Update(New([]string{ 261 "http_PROXY=foo", 262 "HTTP_PROXY=FOO", 263 "newkey=value", 264 })) 265 So(orig.Sorted(), ShouldResemble, []string{ 266 "HTTP_PROXY=FOO", 267 "PYTHONPATH=/foo:/bar:/baz", 268 "newkey=value", 269 "novalue=", 270 }) 271 }) 272 }) 273 }) 274 } 275 276 func TestEnvironmentConstruction(t *testing.T) { 277 pretendLinux() 278 279 Convey(`Can load an initial set of values from a map`, t, func() { 280 env := New(nil) 281 env.Load(map[string]string{ 282 "FOO": "BAR", 283 "foo": "bar", 284 }) 285 So(env, ShouldResemble, Env{ 286 env: map[string]string{ 287 "FOO": "FOO=BAR", 288 "foo": "foo=bar", 289 }, 290 }) 291 }) 292 } 293 294 func TestEnvironmentContext(t *testing.T) { 295 pretendLinux() 296 297 Convey(`Can set and retrieve env from context`, t, func() { 298 ctx := context.Background() 299 300 Convey(`Default is system`, func() { 301 So(FromCtx(ctx), ShouldResemble, System()) 302 }) 303 304 Convey(`Setting nil works`, func() { 305 ctx = (Env{}).SetInCtx(ctx) 306 env := FromCtx(ctx) 307 // We specifically want FromCtx to always return a mutable Env, even if 308 // the one in context is nil. 309 So(env, ShouldNotBeNil) 310 So(env, ShouldResemble, Env{env: map[string]string{}}) 311 }) 312 313 Convey(`Can set in context`, func() { 314 env := New(nil) 315 env.Load(map[string]string{ 316 "FOO": "BAR", 317 "COOL": "Stuff", 318 }) 319 320 ctx = env.SetInCtx(ctx) 321 322 Convey(`And get a copy back`, func() { 323 ptr := func(e Env) uintptr { 324 return reflect.ValueOf(e.env).Pointer() 325 } 326 327 env2 := FromCtx(ctx) 328 So(ptr(env2), ShouldNotEqual, ptr(env)) 329 So(env2, ShouldResemble, env) 330 331 So(ptr(FromCtx(ctx)), ShouldNotEqual, ptr(env)) 332 So(ptr(FromCtx(ctx)), ShouldNotEqual, ptr(env2)) 333 }) 334 335 Convey(`Mutating after installation has no effect`, func() { 336 env.Set("COOL", "Nope") 337 338 env2 := FromCtx(ctx) 339 So(env2, ShouldNotEqual, env) 340 So(env2, ShouldNotResemble, env) 341 342 env2.Set("COOL", "Nope") 343 So(env2, ShouldResemble, env) 344 }) 345 }) 346 }) 347 }