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  }