github.com/containerd/nerdctl@v1.7.7/pkg/flagutil/flagutil_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package flagutil
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"reflect"
    25  	"runtime"
    26  	"sort"
    27  	"strings"
    28  	"testing"
    29  
    30  	"gotest.tools/v3/assert"
    31  )
    32  
    33  // tmpFileWithContent will create a temp file with given content.
    34  func tmpFileWithContent(t *testing.T, content string) string {
    35  	t.Helper()
    36  	tmpFile, err := os.CreateTemp(t.TempDir(), "flagutil-test")
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  	defer tmpFile.Close()
    41  
    42  	_, err = tmpFile.WriteString(content)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	t.Cleanup(func() {
    47  		_ = os.Remove(tmpFile.Name())
    48  	})
    49  	return tmpFile.Name()
    50  }
    51  
    52  func TestReplaceOrAppendEnvValues(t *testing.T) {
    53  	tests := []struct {
    54  		defaults  []string
    55  		overrides []string
    56  		expected  []string
    57  	}{
    58  		// override defaults
    59  		{
    60  			defaults:  []string{"A=default", "B=default"},
    61  			overrides: []string{"A=override", "C=override"},
    62  			expected:  []string{"A=override", "B=default", "C=override"},
    63  		},
    64  		// empty defaults
    65  		{
    66  			defaults:  []string{"A=default", "B=default"},
    67  			overrides: []string{"A=override", "B="},
    68  			expected:  []string{"A=override", "B="},
    69  		},
    70  		// remove defaults
    71  		{
    72  			defaults:  []string{"A=default", "B=default"},
    73  			overrides: []string{"A=override", "B"},
    74  			expected:  []string{"A=override"},
    75  		},
    76  	}
    77  
    78  	comparator := func(s1, s2 []string) bool {
    79  		if len(s1) != len(s2) {
    80  			return false
    81  		}
    82  		sort.Slice(s1, func(i, j int) bool {
    83  			return s1[i] < s1[j]
    84  		})
    85  		sort.Slice(s2, func(i, j int) bool {
    86  			return s2[i] < s2[j]
    87  		})
    88  		for i, v := range s1 {
    89  			if v != s2[i] {
    90  				return false
    91  			}
    92  		}
    93  		return true
    94  	}
    95  	for _, tt := range tests {
    96  		actual := ReplaceOrAppendEnvValues(tt.defaults, tt.overrides)
    97  		assert.Assert(t, comparator(actual, tt.expected), fmt.Sprintf("expected: %s, actual: %s", tt.expected, actual))
    98  	}
    99  }
   100  
   101  // Test TestParseEnvFileGoodFile for a env file with a few well formatted lines.
   102  func TestParseEnvFileGoodFile(t *testing.T) {
   103  	content := `foo=bar
   104      baz=quux
   105  # comment
   106  
   107  _foobar=foobaz
   108  with.dots=working
   109  and_underscore=working too`
   110  	// Adding a newline + a line with pure whitespace.
   111  	// This is being done like this instead of the block above
   112  	// because it's common for editors to trim trailing whitespace
   113  	// from lines, which becomes annoying since that's the
   114  	// exact thing we need to test.
   115  	content += "\n    \t  "
   116  	tmpFile := tmpFileWithContent(t, content)
   117  
   118  	lines, err := parseEnvVars([]string{tmpFile})
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	expectedLines := []string{
   124  		"foo=bar",
   125  		"baz=quux",
   126  		"_foobar=foobaz",
   127  		"with.dots=working",
   128  		"and_underscore=working too",
   129  	}
   130  
   131  	if !reflect.DeepEqual(lines, expectedLines) {
   132  		t.Fatal("lines not equal to expectedLines")
   133  	}
   134  }
   135  
   136  // Test TestParseEnvFileEmptyFile for an empty file.
   137  func TestParseEnvFileEmptyFile(t *testing.T) {
   138  	tmpFile := tmpFileWithContent(t, "")
   139  
   140  	paths := []string{tmpFile}
   141  	lines, err := parseEnvVars(paths)
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  
   146  	if len(lines) != 0 {
   147  		t.Fatal("lines not empty; expected empty")
   148  	}
   149  }
   150  
   151  // Test TestParseEnvFileNonExistentFile for a non existent file.
   152  func TestParseEnvFileNonExistentFile(t *testing.T) {
   153  	_, err := parseEnvVars([]string{"foo_bar_baz"})
   154  	if err == nil {
   155  		t.Fatal("ParseEnvFile succeeded; expected failure")
   156  	}
   157  	if _, ok := errors.Unwrap(err).(*os.PathError); !ok {
   158  		t.Fatalf("Expected a PathError, got [%v]", err)
   159  	}
   160  }
   161  
   162  // Test TestValidateEnv for the validate function's correctness.
   163  func TestValidateEnv(t *testing.T) {
   164  	type testCase struct {
   165  		value    string
   166  		expected string
   167  		err      error
   168  	}
   169  	tests := []testCase{
   170  		{
   171  			value:    "a",
   172  			expected: "a",
   173  		},
   174  		{
   175  			value:    "something",
   176  			expected: "something",
   177  		},
   178  		{
   179  			value:    "_=a",
   180  			expected: "_=a",
   181  		},
   182  		{
   183  			value:    "env1=value1",
   184  			expected: "env1=value1",
   185  		},
   186  		{
   187  			value:    "_env1=value1",
   188  			expected: "_env1=value1",
   189  		},
   190  		{
   191  			value:    "env2=value2=value3",
   192  			expected: "env2=value2=value3",
   193  		},
   194  		{
   195  			value:    "env3=abc!qwe",
   196  			expected: "env3=abc!qwe",
   197  		},
   198  		{
   199  			value:    "env_4=value 4",
   200  			expected: "env_4=value 4",
   201  		},
   202  		{
   203  			value:    "PATH",
   204  			expected: fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
   205  		},
   206  		{
   207  			value: "=a",
   208  			err:   fmt.Errorf("invalid environment variable: =a"),
   209  		},
   210  		{
   211  			value:    "PATH=",
   212  			expected: "PATH=",
   213  		},
   214  		{
   215  			value:    "PATH=something",
   216  			expected: "PATH=something",
   217  		},
   218  		{
   219  			value:    "asd!qwe",
   220  			expected: "asd!qwe",
   221  		},
   222  		{
   223  			value:    "1asd",
   224  			expected: "1asd",
   225  		},
   226  		{
   227  			value:    "123",
   228  			expected: "123",
   229  		},
   230  		{
   231  			value:    "some space",
   232  			expected: "some space",
   233  		},
   234  		{
   235  			value:    "  some space before",
   236  			expected: "  some space before",
   237  		},
   238  		{
   239  			value:    "some space after  ",
   240  			expected: "some space after  ",
   241  		},
   242  		{
   243  			value: "=",
   244  			err:   fmt.Errorf("invalid environment variable: ="),
   245  		},
   246  	}
   247  
   248  	if runtime.GOOS == "windows" {
   249  		// Environment variables are case in-sensitive on Windows
   250  		tests = append(tests, testCase{
   251  			value:    "PaTh",
   252  			expected: fmt.Sprintf("PaTh=%v", os.Getenv("PATH")),
   253  			err:      nil,
   254  		})
   255  	}
   256  
   257  	for _, tc := range tests {
   258  		tc := tc
   259  		t.Run(tc.value, func(t *testing.T) {
   260  			actual, err := withOSEnv([]string{tc.value})
   261  			if tc.err == nil {
   262  				assert.NilError(t, err)
   263  			} else {
   264  				assert.Error(t, err, tc.err.Error())
   265  			}
   266  			if actual != nil {
   267  				v := actual[0]
   268  				assert.Equal(t, v, tc.expected)
   269  			}
   270  		})
   271  	}
   272  }
   273  
   274  // Test TestParseEnvFileLineTooLongFile for a file with a line exceeding bufio.MaxScanTokenSize.
   275  func TestParseEnvFileLineTooLongFile(t *testing.T) {
   276  	content := "foo=" + strings.Repeat("a", bufio.MaxScanTokenSize+42)
   277  	tmpFile := tmpFileWithContent(t, content)
   278  
   279  	_, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil)
   280  	if err == nil {
   281  		t.Fatal("ParseEnvFile succeeded; expected failure")
   282  	}
   283  }
   284  
   285  // Test TestParseEnvVariableWithNoNameFile for parsing env file with empty variable name.
   286  func TestParseEnvVariableWithNoNameFile(t *testing.T) {
   287  	content := `# comment=
   288  =blank variable names are an error case
   289  `
   290  	tmpFile := tmpFileWithContent(t, content)
   291  
   292  	_, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil)
   293  	if nil == err {
   294  		t.Fatal("if a variable has no name parsing an environment file must fail")
   295  	}
   296  }
   297  
   298  // Test TestMergeEnvFileAndOSEnv for merging variables from env-file and env.
   299  func TestMergeEnvFileAndOSEnv(t *testing.T) {
   300  	content := `HOME`
   301  	tmpFile := tmpFileWithContent(t, content)
   302  
   303  	variables, err := MergeEnvFileAndOSEnv([]string{tmpFile}, []string{"PATH"})
   304  	if nil != err {
   305  		t.Fatalf("unexpected error: %v", err)
   306  	}
   307  
   308  	if len(variables) != 2 {
   309  		t.Fatal("variables from env-file and flag env should be merged")
   310  	}
   311  
   312  	if "HOME="+os.Getenv("HOME") != variables[0] {
   313  		t.Fatal("the HOME variable is not properly imported as the first variable")
   314  	}
   315  
   316  	if "PATH="+os.Getenv("PATH") != variables[1] {
   317  		t.Fatal("the PATH variable is not properly imported as the second variable")
   318  	}
   319  }