...

Source file src/k8s.io/cli-runtime/pkg/resource/visitor_test.go

Documentation: k8s.io/cli-runtime/pkg/resource

     1  /*
     2  Copyright 2016 The Kubernetes 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 resource
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"io/fs"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"k8s.io/apimachinery/pkg/util/dump"
    33  )
    34  
    35  func TestVisitorHttpGet(t *testing.T) {
    36  	type httpArgs struct {
    37  		duration time.Duration
    38  		u        string
    39  		attempts int
    40  	}
    41  
    42  	i := 0
    43  	tests := []struct {
    44  		name        string
    45  		httpRetries httpget
    46  		args        httpArgs
    47  		expectedErr error
    48  		actualBytes io.ReadCloser
    49  		actualErr   error
    50  		count       int
    51  		isNotNil    bool
    52  	}{
    53  		{
    54  			name: "Test retries on errors",
    55  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
    56  				assert.Equal(t, "hello", url)
    57  				i++
    58  				if i > 2 {
    59  					return 0, "", nil, fmt.Errorf("Failed to get http")
    60  				}
    61  				return 0, "", nil, fmt.Errorf("Unexpected error")
    62  
    63  			},
    64  			expectedErr: fmt.Errorf("Failed to get http"),
    65  			args: httpArgs{
    66  				duration: 0,
    67  				u:        "hello",
    68  				attempts: 3,
    69  			},
    70  			count: 3,
    71  		},
    72  		{
    73  			name: "Test that 500s are retried",
    74  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
    75  				assert.Equal(t, "hello", url)
    76  				i++
    77  				return 501, "Status", io.NopCloser(new(bytes.Buffer)), nil
    78  			},
    79  			args: httpArgs{
    80  				duration: 0,
    81  				u:        "hello",
    82  				attempts: 3,
    83  			},
    84  			count: 3,
    85  		},
    86  		{
    87  			name: "Test that 300s are not retried",
    88  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
    89  				assert.Equal(t, "hello", url)
    90  				i++
    91  				return 300, "Status", io.NopCloser(new(bytes.Buffer)), nil
    92  
    93  			},
    94  			args: httpArgs{
    95  				duration: 0,
    96  				u:        "hello",
    97  				attempts: 3,
    98  			},
    99  			count: 1,
   100  		},
   101  		{
   102  			name: "Test attempt count is respected",
   103  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
   104  				assert.Equal(t, "hello", url)
   105  				i++
   106  				return 501, "Status", io.NopCloser(new(bytes.Buffer)), nil
   107  
   108  			},
   109  			args: httpArgs{
   110  				duration: 0,
   111  				u:        "hello",
   112  				attempts: 1,
   113  			},
   114  			count: 1,
   115  		},
   116  		{
   117  			name: "Test attempts less than 1 results in an error",
   118  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
   119  				return 200, "Status", io.NopCloser(new(bytes.Buffer)), nil
   120  
   121  			},
   122  			args: httpArgs{
   123  				duration: 0,
   124  				u:        "hello",
   125  				attempts: 0,
   126  			},
   127  			count: 0,
   128  		},
   129  		{
   130  			name: "Test Success",
   131  			httpRetries: func(url string) (int, string, io.ReadCloser, error) {
   132  				assert.Equal(t, "hello", url)
   133  				i++
   134  				if i > 1 {
   135  					return 200, "Status", io.NopCloser(new(bytes.Buffer)), nil
   136  				}
   137  				return 501, "Status", io.NopCloser(new(bytes.Buffer)), nil
   138  
   139  			},
   140  			args: httpArgs{
   141  				duration: 0,
   142  				u:        "hello",
   143  				attempts: 3,
   144  			},
   145  			count:    2,
   146  			isNotNil: true,
   147  		},
   148  	}
   149  
   150  	for _, tt := range tests {
   151  		t.Run(tt.name, func(t *testing.T) {
   152  			i = 0
   153  			actualBytes, actualErr := readHttpWithRetries(tt.httpRetries, tt.args.duration, tt.args.u, tt.args.attempts)
   154  
   155  			if tt.isNotNil {
   156  				assert.Nil(t, actualErr)
   157  				assert.NotNil(t, actualBytes)
   158  			} else {
   159  				if tt.expectedErr != nil {
   160  					assert.Equal(t, tt.expectedErr, actualErr)
   161  				} else {
   162  					assert.Error(t, actualErr)
   163  				}
   164  				assert.Nil(t, actualBytes)
   165  			}
   166  
   167  			assert.Equal(t, tt.count, i)
   168  		})
   169  	}
   170  }
   171  
   172  func TestFlattenListVisitor(t *testing.T) {
   173  	b := newDefaultBuilder().
   174  		FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/deeply-nested.yaml"}}).
   175  		Flatten()
   176  
   177  	test := &testVisitor{}
   178  
   179  	err := b.Do().Visit(test.Handle)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if len(test.Infos) != 6 {
   184  		t.Fatal(dump.Pretty(test.Infos))
   185  	}
   186  }
   187  
   188  func TestFlattenListVisitorWithVisitorError(t *testing.T) {
   189  	b := newDefaultBuilder().
   190  		FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/deeply-nested.yaml"}}).
   191  		Flatten()
   192  
   193  	test := &testVisitor{InjectErr: errors.New("visitor error")}
   194  	err := b.Do().Visit(test.Handle)
   195  	if err == nil || !strings.Contains(err.Error(), "visitor error") {
   196  		t.Fatal(err)
   197  	}
   198  	if len(test.Infos) != 6 {
   199  		t.Fatal(dump.Pretty(test.Infos))
   200  	}
   201  }
   202  
   203  func TestExpandPathsToFileVisitors(t *testing.T) {
   204  	// Define a directory structure that will be used for testing and create empty files
   205  	testDir := t.TempDir()
   206  	filePaths := []string{
   207  		filepath.Join(testDir, "0", "10.yaml"),
   208  		filepath.Join(testDir, "0", "a", "10.yaml"),
   209  		filepath.Join(testDir, "02.yaml"),
   210  		filepath.Join(testDir, "10.yaml"),
   211  		filepath.Join(testDir, "2.yaml"),
   212  		filepath.Join(testDir, "AB.yaml"),
   213  		filepath.Join(testDir, "a", "a.yaml"),
   214  		filepath.Join(testDir, "a", "b.json"),
   215  		filepath.Join(testDir, "a.yaml"),
   216  		filepath.Join(testDir, "aa.yaml"),
   217  		filepath.Join(testDir, "b.yml"),
   218  	}
   219  	for _, fp := range filePaths {
   220  		if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil {
   221  			t.Fatal(err)
   222  		}
   223  		func() {
   224  			f, err := os.Create(fp)
   225  			if err != nil {
   226  				t.Fatal(err)
   227  			}
   228  			defer f.Close()
   229  		}()
   230  	}
   231  
   232  	// Define and execute test cases
   233  	tests := []struct {
   234  		name            string
   235  		path            string
   236  		recursive       bool
   237  		fileExtensions  []string
   238  		expectedPaths   []string
   239  		expectPathError bool
   240  	}{
   241  		{
   242  			name:           "Recursive with default file extensions",
   243  			path:           testDir,
   244  			recursive:      true,
   245  			fileExtensions: FileExtensions,
   246  			expectedPaths: []string{
   247  				filepath.Join(testDir, "0", "10.yaml"),
   248  				filepath.Join(testDir, "0", "a", "10.yaml"),
   249  				filepath.Join(testDir, "02.yaml"),
   250  				filepath.Join(testDir, "10.yaml"),
   251  				filepath.Join(testDir, "2.yaml"),
   252  				filepath.Join(testDir, "AB.yaml"),
   253  				filepath.Join(testDir, "a", "a.yaml"),
   254  				filepath.Join(testDir, "a", "b.json"),
   255  				filepath.Join(testDir, "a.yaml"),
   256  				filepath.Join(testDir, "aa.yaml"),
   257  				filepath.Join(testDir, "b.yml"),
   258  			},
   259  		},
   260  		{
   261  			name:           "Non-recursive with default file extensions",
   262  			path:           testDir,
   263  			fileExtensions: FileExtensions,
   264  			expectedPaths: []string{
   265  				filepath.Join(testDir, "02.yaml"),
   266  				filepath.Join(testDir, "10.yaml"),
   267  				filepath.Join(testDir, "2.yaml"),
   268  				filepath.Join(testDir, "AB.yaml"),
   269  				filepath.Join(testDir, "a.yaml"),
   270  				filepath.Join(testDir, "aa.yaml"),
   271  				filepath.Join(testDir, "b.yml"),
   272  			},
   273  		},
   274  		{
   275  			name:           "Recursive with yaml file extension",
   276  			path:           testDir,
   277  			recursive:      true,
   278  			fileExtensions: []string{".yaml"},
   279  			expectedPaths: []string{
   280  				filepath.Join(testDir, "0", "10.yaml"),
   281  				filepath.Join(testDir, "0", "a", "10.yaml"),
   282  				filepath.Join(testDir, "02.yaml"),
   283  				filepath.Join(testDir, "10.yaml"),
   284  				filepath.Join(testDir, "2.yaml"),
   285  				filepath.Join(testDir, "AB.yaml"),
   286  				filepath.Join(testDir, "a", "a.yaml"),
   287  				filepath.Join(testDir, "a.yaml"),
   288  				filepath.Join(testDir, "aa.yaml"),
   289  			},
   290  		},
   291  		{
   292  			name:           "Recursive with json and yml file extensions",
   293  			path:           testDir,
   294  			recursive:      true,
   295  			fileExtensions: []string{".json", ".yml"},
   296  			expectedPaths: []string{
   297  				filepath.Join(testDir, "a", "b.json"),
   298  				filepath.Join(testDir, "b.yml"),
   299  			},
   300  		},
   301  		{
   302  			name:           "Non-recursive with json and yml file extensions",
   303  			path:           testDir,
   304  			fileExtensions: []string{".json", ".yml"},
   305  			expectedPaths: []string{
   306  				filepath.Join(testDir, "b.yml"),
   307  			},
   308  		},
   309  		{
   310  			name:           "Non-existent file extensions should return nothing",
   311  			path:           testDir,
   312  			recursive:      true,
   313  			fileExtensions: []string{".foo"},
   314  			expectedPaths:  []string{},
   315  		},
   316  		{
   317  			name:            "Non-existent path should return file not found error",
   318  			path:            filepath.Join(testDir, "does", "not", "exist"),
   319  			recursive:       true,
   320  			fileExtensions:  []string{".foo"},
   321  			expectedPaths:   []string{},
   322  			expectPathError: true,
   323  		},
   324  		{
   325  			name:           "Visitor for single file is returned even if extension does not match",
   326  			path:           filepath.Join(testDir, "a.yaml"),
   327  			recursive:      true,
   328  			fileExtensions: []string{"foo"},
   329  			expectedPaths: []string{
   330  				filepath.Join(testDir, "a.yaml"),
   331  			},
   332  		},
   333  	}
   334  
   335  	for _, tt := range tests {
   336  		t.Run(tt.name, func(t *testing.T) {
   337  			visitors, err := ExpandPathsToFileVisitors(nil, tt.path, tt.recursive, tt.fileExtensions, nil)
   338  			if err != nil {
   339  				switch e := err.(type) {
   340  				case *fs.PathError:
   341  					if tt.expectPathError {
   342  						// The other details of PathError are os-specific, so only assert that the error has the path
   343  						assert.Equal(t, tt.path, e.Path)
   344  						return
   345  					}
   346  				}
   347  				t.Fatal(err)
   348  			}
   349  
   350  			actualPaths := []string{}
   351  			for _, v := range visitors {
   352  				actualPaths = append(actualPaths, v.(*FileVisitor).Path)
   353  			}
   354  			assert.Equal(t, tt.expectedPaths, actualPaths)
   355  		})
   356  	}
   357  }
   358  

View as plain text