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  }