github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/osutil/env_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil_test 21 22 import ( 23 "fmt" 24 "math" 25 "os" 26 "strings" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/osutil" 31 ) 32 33 type envSuite struct{} 34 35 var _ = Suite(&envSuite{}) 36 37 func (s *envSuite) TestGetenvBoolTrue(c *C) { 38 key := "__XYZZY__" 39 os.Unsetenv(key) 40 41 for _, s := range []string{ 42 "1", "t", "TRUE", 43 } { 44 os.Setenv(key, s) 45 c.Assert(os.Getenv(key), Equals, s) 46 c.Check(osutil.GetenvBool(key), Equals, true, Commentf(s)) 47 c.Check(osutil.GetenvBool(key, false), Equals, true, Commentf(s)) 48 c.Check(osutil.GetenvBool(key, true), Equals, true, Commentf(s)) 49 } 50 } 51 52 func (s *envSuite) TestGetenvBoolFalse(c *C) { 53 key := "__XYZZY__" 54 os.Unsetenv(key) 55 c.Assert(osutil.GetenvBool(key), Equals, false) 56 57 for _, s := range []string{ 58 "", "0", "f", "FALSE", "potato", 59 } { 60 os.Setenv(key, s) 61 c.Assert(os.Getenv(key), Equals, s) 62 c.Check(osutil.GetenvBool(key), Equals, false, Commentf(s)) 63 c.Check(osutil.GetenvBool(key, false), Equals, false, Commentf(s)) 64 } 65 } 66 67 func (s *envSuite) TestGetenvBoolFalseDefaultTrue(c *C) { 68 key := "__XYZZY__" 69 os.Unsetenv(key) 70 c.Assert(osutil.GetenvBool(key), Equals, false) 71 72 for _, s := range []string{ 73 "0", "f", "FALSE", 74 } { 75 os.Setenv(key, s) 76 c.Assert(os.Getenv(key), Equals, s) 77 c.Check(osutil.GetenvBool(key, true), Equals, false, Commentf(s)) 78 } 79 80 for _, s := range []string{ 81 "", "potato", // etc 82 } { 83 os.Setenv(key, s) 84 c.Assert(os.Getenv(key), Equals, s) 85 c.Check(osutil.GetenvBool(key, true), Equals, true, Commentf(s)) 86 } 87 } 88 89 func (s *envSuite) TestGetenvInt64(c *C) { 90 key := "__XYZZY__" 91 os.Unsetenv(key) 92 93 c.Check(osutil.GetenvInt64(key), Equals, int64(0)) 94 c.Check(osutil.GetenvInt64(key, -1), Equals, int64(-1)) 95 c.Check(osutil.GetenvInt64(key, math.MaxInt64), Equals, int64(math.MaxInt64)) 96 c.Check(osutil.GetenvInt64(key, math.MinInt64), Equals, int64(math.MinInt64)) 97 98 for _, n := range []int64{ 99 0, -1, math.MinInt64, math.MaxInt64, 100 } { 101 for _, tpl := range []string{"%d", " %d ", "%#x", "%#X", "%#o"} { 102 v := fmt.Sprintf(tpl, n) 103 os.Setenv(key, v) 104 c.Assert(os.Getenv(key), Equals, v) 105 c.Check(osutil.GetenvInt64(key), Equals, n, Commentf(v)) 106 } 107 } 108 } 109 110 func (s *envSuite) TestParseRawEnvironmentHappy(c *C) { 111 for _, t := range []struct { 112 env []string 113 expected map[string]string 114 }{ 115 { 116 []string{"K=V"}, 117 map[string]string{"K": "V"}, 118 }, 119 { 120 []string{"K=V=V=V"}, 121 map[string]string{"K": "V=V=V"}, 122 }, 123 { 124 []string{"K1=V1", "K2=V2"}, 125 map[string]string{"K1": "V1", "K2": "V2"}, 126 }, 127 } { 128 env, err := osutil.ParseRawEnvironment(t.env) 129 c.Assert(err, IsNil) 130 c.Check(env, DeepEquals, osutil.Environment(t.expected), Commentf("invalid result for %q, got %q expected %q", t.env, env, t.expected)) 131 } 132 } 133 134 func (s *envSuite) TestParseRawEnvironmentNotKeyValue(c *C) { 135 env, err := osutil.ParseRawEnvironment([]string{"KEY"}) 136 c.Assert(err, ErrorMatches, `cannot parse environment entry: "KEY"`) 137 c.Assert(env, IsNil) 138 } 139 140 func (s *envSuite) TestParseRawEnvironmentEmptyKey(c *C) { 141 env, err := osutil.ParseRawEnvironment([]string{"=VALUE"}) 142 c.Assert(err, ErrorMatches, `environment variable name cannot be empty: "=VALUE"`) 143 c.Assert(env, IsNil) 144 } 145 146 func (s *envSuite) TestParseRawEnvironmentDuplicateKey(c *C) { 147 env, err := osutil.ParseRawEnvironment([]string{"K=1", "K=2"}) 148 c.Assert(err, ErrorMatches, `cannot overwrite earlier value of "K"`) 149 c.Assert(env, IsNil) 150 } 151 152 func (s *envSuite) TestOSEnvironment(c *C) { 153 env, err := osutil.OSEnvironment() 154 c.Assert(err, IsNil) 155 c.Check(len(os.Environ()), Equals, len(env.ForExec())) 156 c.Check(os.Getenv("PATH"), Equals, env["PATH"]) 157 } 158 159 func (s *envSuite) TestOSEnvironmentUnescapeUnsafe(c *C) { 160 os.Setenv("SNAPD_UNSAFE_PREFIX_A", "a") 161 defer os.Unsetenv("SNAPD_UNSAFE_PREFIX_A") 162 os.Setenv("SNAPDEXTRA", "2") 163 defer os.Unsetenv("SNAPDEXTRA") 164 os.Setenv("SNAPD_UNSAFE_PREFIX_SNAPDEXTRA", "1") 165 defer os.Unsetenv("SNAPD_UNSAFE_PREFIX_SNAPDEXTRA") 166 167 env, err := osutil.OSEnvironmentUnescapeUnsafe("SNAPD_UNSAFE_PREFIX_") 168 c.Assert(err, IsNil) 169 // -1 because only the unescaped SNAPDEXTRA is kept 170 c.Check(len(os.Environ())-1, Equals, len(env.ForExec())) 171 c.Check(os.Getenv("PATH"), Equals, env["PATH"]) 172 c.Check("a", Equals, env["A"]) 173 c.Check("2", Equals, env["SNAPDEXTRA"]) 174 } 175 176 func (s *envSuite) TestGet(c *C) { 177 env := osutil.Environment{"K": "V"} 178 c.Assert(env["K"], Equals, "V") 179 c.Assert(env["missing"], Equals, "") 180 } 181 182 func (s *envSuite) TestDel(c *C) { 183 env := osutil.Environment{"K": "V"} 184 delete(env, "K") 185 c.Assert(env["K"], Equals, "") 186 delete(env, "missing") 187 c.Assert(env["missing"], Equals, "") 188 } 189 190 func (s *envSuite) TestForExec(c *C) { 191 env := osutil.Environment{"K1": "V1", "K2": "V2"} 192 c.Check(env.ForExec(), DeepEquals, []string{"K1=V1", "K2=V2"}) 193 } 194 func (s *envSuite) TestNewExpandableEnv(c *C) { 195 eenv := osutil.NewExpandableEnv("K1", "V1", "K2", "$K1") 196 c.Check(eenv.Get("K1"), Equals, "V1") 197 c.Check(eenv.Get("K2"), Equals, "$K1") 198 } 199 200 func (s *envSuite) TestParseRawExpandableEnvHappy(c *C) { 201 eenv, err := osutil.ParseRawExpandableEnv([]string{"K1=V1", "K2=$K1"}) 202 c.Assert(err, IsNil) 203 c.Check(eenv.Get("K1"), Equals, "V1") 204 c.Check(eenv.Get("K2"), Equals, "$K1") 205 } 206 207 func (s *envSuite) TestParseRawExpandableEnvNotKeyValue(c *C) { 208 eenv, err := osutil.ParseRawExpandableEnv([]string{"KEY"}) 209 c.Assert(err, ErrorMatches, `cannot parse environment entry: "KEY"`) 210 c.Assert(eenv, DeepEquals, osutil.ExpandableEnv{}) 211 } 212 213 func (s *envSuite) TestParseRawExpandableEnvEmptyKey(c *C) { 214 eenv, err := osutil.ParseRawExpandableEnv([]string{"=VALUE"}) 215 c.Assert(err, ErrorMatches, `environment variable name cannot be empty: "=VALUE"`) 216 c.Assert(eenv, DeepEquals, osutil.ExpandableEnv{}) 217 } 218 219 func (s *envSuite) TestParseRawExpandableEnvDuplicateKey(c *C) { 220 eenv, err := osutil.ParseRawExpandableEnv([]string{"K=1", "K=2"}) 221 c.Assert(err, ErrorMatches, `cannot overwrite earlier value of "K"`) 222 c.Assert(eenv, DeepEquals, osutil.ExpandableEnv{}) 223 } 224 225 func (s *envSuite) TestExtendWithExpanded(c *C) { 226 env := osutil.Environment{"A": "a"} 227 env.ExtendWithExpanded(osutil.NewExpandableEnv( 228 "B", "$C", // $C is undefined so it expands to "" 229 "C", "$A", // $A is defined in the environment so it expands to "a" 230 "D", "$D", // $D is undefined so it expands to "" 231 )) 232 c.Check(env, DeepEquals, osutil.Environment{"A": "a", "B": "", "C": "a", "D": ""}) 233 } 234 235 func (s *envSuite) TestExtendWithExpandedOfNil(c *C) { 236 var env osutil.Environment 237 env.ExtendWithExpanded(osutil.NewExpandableEnv( 238 "A", "a", 239 "B", "$C", // $C is undefined so it expands to "" 240 "C", "$A", // $A is defined in the environment so it expands to "a" 241 "D", "$D", // $D is undefined so it expands to "" 242 )) 243 c.Check(env, DeepEquals, osutil.Environment{"A": "a", "B": "", "C": "a", "D": ""}) 244 } 245 246 func (s *envSuite) TestExtendWithExpandedForEnvOverride(c *C) { 247 env := osutil.Environment{"PATH": "system-value"} 248 env.ExtendWithExpanded(osutil.NewExpandableEnv("PATH", "snap-level-override")) 249 env.ExtendWithExpanded(osutil.NewExpandableEnv("PATH", "app-level-override")) 250 c.Check(env, DeepEquals, osutil.Environment{"PATH": "app-level-override"}) 251 } 252 253 func (s *envSuite) TestExtendWithExpandedForEnvExpansion(c *C) { 254 env := osutil.Environment{"PATH": "system-value"} 255 env.ExtendWithExpanded(osutil.NewExpandableEnv("PATH", "snap-ext:$PATH")) 256 env.ExtendWithExpanded(osutil.NewExpandableEnv("PATH", "app-ext:$PATH")) 257 c.Check(env, DeepEquals, osutil.Environment{"PATH": "app-ext:snap-ext:system-value"}) 258 } 259 260 func (s *envSuite) TestExtendWithExpandedVarious(c *C) { 261 for _, t := range []struct { 262 env string 263 expected string 264 }{ 265 // trivial 266 {"K1=V1,K2=V2", "K1=V1,K2=V2"}, 267 // simple (order is preserved) 268 {"K=V,K2=$K", "K=V,K2=V"}, 269 // simple from environment 270 {"K=$PATH", fmt.Sprintf("K=%s", os.Getenv("PATH"))}, 271 // append to substitution from environment 272 {"K=${PATH}:/foo", fmt.Sprintf("K=%s", os.Getenv("PATH")+":/foo")}, 273 // multi-level 274 {"A=1,B=$A/2,C=$B/3,D=$C/4", "A=1,B=1/2,C=1/2/3,D=1/2/3/4"}, 275 // parsing is top down 276 {"A=$A", "A="}, 277 {"A=$B,B=$A", "A=,B="}, 278 {"A=$B,B=$C,C=$A", "A=,B=,C="}, 279 } { 280 eenv, err := osutil.ParseRawExpandableEnv(strings.Split(t.env, ",")) 281 c.Assert(err, IsNil) 282 env := osutil.Environment{} 283 if strings.Contains(t.env, "PATH") { 284 env["PATH"] = os.Getenv("PATH") 285 } 286 env.ExtendWithExpanded(eenv) 287 delete(env, "PATH") 288 c.Check(strings.Join(env.ForExec(), ","), DeepEquals, t.expected, Commentf("invalid result for %q, got %q expected %q", t.env, env, t.expected)) 289 } 290 } 291 292 func (s *envSuite) TestForExecEscapeUnsafe(c *C) { 293 env := osutil.Environment{ 294 "FOO": "foo", 295 "LD_PRELOAD": "/opt/lib/libfunky.so", 296 "SNAP_DATA": "snap-data", 297 "SNAP_SAVED_WHAT": "what", // will be dropped 298 "SNAP_SAVED": "snap-saved", 299 "SNAP_S": "snap-s", 300 "XDG_STUFF": "xdg-stuff", // will be prefixed 301 "TMPDIR": "/var/tmp", // will be prefixed 302 } 303 raw := env.ForExecEscapeUnsafe("SNAP_SAVED_") 304 c.Check(raw, DeepEquals, []string{ 305 "FOO=foo", 306 "SNAP_DATA=snap-data", 307 "SNAP_S=snap-s", 308 "SNAP_SAVED=snap-saved", 309 "SNAP_SAVED_LD_PRELOAD=/opt/lib/libfunky.so", 310 "SNAP_SAVED_TMPDIR=/var/tmp", 311 "XDG_STUFF=xdg-stuff", 312 }) 313 } 314 315 func (s *envSuite) TestForExecEscapeUnsafeNothingToEscape(c *C) { 316 env := osutil.Environment{ 317 "FOO": "foo", 318 "SNAP_DATA": "snap-data", 319 "SNAP_SAVED_WHAT": "what", 320 "SNAP_SAVED": "snap-saved", 321 "SNAP_S": "snap-s", 322 "XDG_STUFF": "xdg-stuff", 323 } 324 raw := env.ForExecEscapeUnsafe("SNAP_SAVED_") 325 c.Check(raw, DeepEquals, []string{ 326 "FOO=foo", 327 "SNAP_DATA=snap-data", 328 "SNAP_S=snap-s", 329 "SNAP_SAVED=snap-saved", 330 "XDG_STUFF=xdg-stuff", 331 }) 332 }