k8s.io/kubernetes@v1.29.3/test/e2e/framework/testfiles/testfiles.go (about) 1 /* 2 Copyright 2018 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 testfiles provides a wrapper around various optional ways 18 // of retrieving additional files needed during a test run: 19 // - builtin bindata 20 // - filesystem access 21 // 22 // Because it is a is self-contained package, it can be used by 23 // test/e2e/framework and test/e2e/manifest without creating 24 // a circular dependency. 25 package testfiles 26 27 import ( 28 "embed" 29 "errors" 30 "fmt" 31 "io/fs" 32 "os" 33 "path" 34 "path/filepath" 35 "strings" 36 ) 37 38 var filesources []FileSource 39 40 // AddFileSource registers another provider for files that may be 41 // needed at runtime. Should be called during initialization of a test 42 // binary. 43 func AddFileSource(filesource FileSource) { 44 filesources = append(filesources, filesource) 45 } 46 47 // FileSource implements one way of retrieving test file content. For 48 // example, one file source could read from the original source code 49 // file tree, another from bindata compiled into a test executable. 50 type FileSource interface { 51 // ReadTestFile retrieves the content of a file that gets maintained 52 // alongside a test's source code. Files are identified by the 53 // relative path inside the repository containing the tests, for 54 // example "cluster/gce/upgrade.sh" inside kubernetes/kubernetes. 55 // 56 // When the file is not found, a nil slice is returned. An error is 57 // returned for all fatal errors. 58 ReadTestFile(filePath string) ([]byte, error) 59 60 // DescribeFiles returns a multi-line description of which 61 // files are available via this source. It is meant to be 62 // used as part of the error message when a file cannot be 63 // found. 64 DescribeFiles() string 65 } 66 67 // Read tries to retrieve the desired file content from 68 // one of the registered file sources. 69 func Read(filePath string) ([]byte, error) { 70 if len(filesources) == 0 { 71 return nil, fmt.Errorf("no file sources registered (yet?), cannot retrieve test file %s", filePath) 72 } 73 for _, filesource := range filesources { 74 data, err := filesource.ReadTestFile(filePath) 75 if err != nil { 76 return nil, fmt.Errorf("fatal error retrieving test file %s: %w", filePath, err) 77 } 78 if data != nil { 79 return data, nil 80 } 81 } 82 // Here we try to generate an error that points test authors 83 // or users in the right direction for resolving the problem. 84 err := fmt.Sprintf("Test file %q was not found.\n", filePath) 85 for _, filesource := range filesources { 86 err += filesource.DescribeFiles() 87 err += "\n" 88 } 89 return nil, errors.New(err) 90 } 91 92 // Exists checks whether a file could be read. Unexpected errors 93 // are handled by calling the fail function, which then should 94 // abort the current test. 95 func Exists(filePath string) (bool, error) { 96 for _, filesource := range filesources { 97 data, err := filesource.ReadTestFile(filePath) 98 if err != nil { 99 return false, err 100 } 101 if data != nil { 102 return true, nil 103 } 104 } 105 return false, nil 106 } 107 108 // RootFileSource looks for files relative to a root directory. 109 type RootFileSource struct { 110 Root string 111 } 112 113 // ReadTestFile looks for the file relative to the configured 114 // root directory. If the path is already absolute, for example 115 // in a test that has its own method of determining where 116 // files are, then the path will be used directly. 117 func (r RootFileSource) ReadTestFile(filePath string) ([]byte, error) { 118 var fullPath string 119 if path.IsAbs(filePath) { 120 fullPath = filePath 121 } else { 122 fullPath = filepath.Join(r.Root, filePath) 123 } 124 data, err := os.ReadFile(fullPath) 125 if os.IsNotExist(err) { 126 // Not an error (yet), some other provider may have the file. 127 return nil, nil 128 } 129 return data, err 130 } 131 132 // DescribeFiles explains that it looks for files inside a certain 133 // root directory. 134 func (r RootFileSource) DescribeFiles() string { 135 description := fmt.Sprintf("Test files are expected in %q", r.Root) 136 if !path.IsAbs(r.Root) { 137 // The default in test_context.go is the relative path 138 // ../../, which doesn't really help locating the 139 // actual location. Therefore we add also the absolute 140 // path if necessary. 141 abs, err := filepath.Abs(r.Root) 142 if err == nil { 143 description += fmt.Sprintf(" = %q", abs) 144 } 145 } 146 description += "." 147 return description 148 } 149 150 // EmbeddedFileSource handles files stored in a package generated with bindata. 151 type EmbeddedFileSource struct { 152 EmbeddedFS embed.FS 153 Root string 154 fileList []string 155 } 156 157 // ReadTestFile looks for an embedded file with the given path. 158 func (e EmbeddedFileSource) ReadTestFile(filepath string) ([]byte, error) { 159 relativePath := strings.TrimPrefix(filepath, fmt.Sprintf("%s/", e.Root)) 160 161 b, err := e.EmbeddedFS.ReadFile(relativePath) 162 if err != nil { 163 if errors.Is(err, fs.ErrNotExist) { 164 return nil, nil 165 } 166 return nil, err 167 } 168 169 return b, nil 170 } 171 172 // DescribeFiles explains that it is looking inside an embedded filesystem 173 func (e EmbeddedFileSource) DescribeFiles() string { 174 var lines []string 175 lines = append(lines, "The following files are embedded into the test executable:") 176 177 if len(e.fileList) == 0 { 178 e.populateFileList() 179 } 180 lines = append(lines, e.fileList...) 181 182 return strings.Join(lines, "\n\t") 183 } 184 185 func (e *EmbeddedFileSource) populateFileList() { 186 fs.WalkDir(e.EmbeddedFS, ".", func(path string, d fs.DirEntry, err error) error { 187 if !d.IsDir() { 188 e.fileList = append(e.fileList, filepath.Join(e.Root, path)) 189 } 190 191 return nil 192 }) 193 }