| /* |
| Copyright 2018 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // Package testfiles provides a wrapper around various optional ways |
| // of retrieving additional files needed during a test run: |
| // - builtin bindata |
| // - filesystem access |
| // |
| // Because it is a is self-contained package, it can be used by |
| // test/e2e/framework and test/e2e/manifest without creating |
| // a circular dependency. |
| package testfiles |
| |
| import ( |
| "errors" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "sort" |
| "strings" |
| ) |
| |
| var filesources []FileSource |
| |
| // AddFileSource registers another provider for files that may be |
| // needed at runtime. Should be called during initialization of a test |
| // binary. |
| func AddFileSource(filesource FileSource) { |
| filesources = append(filesources, filesource) |
| } |
| |
| // FileSource implements one way of retrieving test file content. For |
| // example, one file source could read from the original source code |
| // file tree, another from bindata compiled into a test executable. |
| type FileSource interface { |
| // ReadTestFile retrieves the content of a file that gets maintained |
| // alongside a test's source code. Files are identified by the |
| // relative path inside the repository containing the tests, for |
| // example "cluster/gce/upgrade.sh" inside kubernetes/kubernetes. |
| // |
| // When the file is not found, a nil slice is returned. An error is |
| // returned for all fatal errors. |
| ReadTestFile(filePath string) ([]byte, error) |
| |
| // DescribeFiles returns a multi-line description of which |
| // files are available via this source. It is meant to be |
| // used as part of the error message when a file cannot be |
| // found. |
| DescribeFiles() string |
| } |
| |
| // Fail is an error handler function with the same prototype and |
| // semantic as ginkgo.Fail. Typically ginkgo.Fail is what callers |
| // of ReadOrDie and Exists will pass. This way this package |
| // avoids depending on Ginkgo. |
| type Fail func(failure string, callerSkip ...int) |
| |
| // ReadOrDie tries to retrieve the desired file content from |
| // one of the registered file sources. In contrast to FileSource, it |
| // will either return a valid slice or abort the test by calling the fatal function, |
| // i.e. the caller doesn't have to implement error checking. |
| func ReadOrDie(filePath string, fail Fail) []byte { |
| data, err := Read(filePath) |
| if err != nil { |
| fail(err.Error(), 1) |
| } |
| return data |
| } |
| |
| // Read tries to retrieve the desired file content from |
| // one of the registered file sources. |
| func Read(filePath string) ([]byte, error) { |
| if len(filesources) == 0 { |
| return nil, fmt.Errorf("no file sources registered (yet?), cannot retrieve test file %s", filePath) |
| } |
| for _, filesource := range filesources { |
| data, err := filesource.ReadTestFile(filePath) |
| if err != nil { |
| return nil, fmt.Errorf("fatal error retrieving test file %s: %s", filePath, err) |
| } |
| if data != nil { |
| return data, nil |
| } |
| } |
| // Here we try to generate an error that points test authors |
| // or users in the right direction for resolving the problem. |
| error := fmt.Sprintf("Test file %q was not found.\n", filePath) |
| for _, filesource := range filesources { |
| error += filesource.DescribeFiles() |
| error += "\n" |
| } |
| return nil, errors.New(error) |
| } |
| |
| // Exists checks whether a file could be read. Unexpected errors |
| // are handled by calling the fail function, which then should |
| // abort the current test. |
| func Exists(filePath string, fail Fail) bool { |
| for _, filesource := range filesources { |
| data, err := filesource.ReadTestFile(filePath) |
| if err != nil { |
| fail(fmt.Sprintf("fatal error looking for test file %s: %s", filePath, err), 1) |
| } |
| if data != nil { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // RootFileSource looks for files relative to a root directory. |
| type RootFileSource struct { |
| Root string |
| } |
| |
| // ReadTestFile looks for the file relative to the configured |
| // root directory. |
| func (r RootFileSource) ReadTestFile(filePath string) ([]byte, error) { |
| fullPath := filepath.Join(r.Root, filePath) |
| data, err := ioutil.ReadFile(fullPath) |
| if os.IsNotExist(err) { |
| // Not an error (yet), some other provider may have the file. |
| return nil, nil |
| } |
| return data, err |
| } |
| |
| // DescribeFiles explains that it looks for files inside a certain |
| // root directory. |
| func (r RootFileSource) DescribeFiles() string { |
| description := fmt.Sprintf("Test files are expected in %q", r.Root) |
| if !path.IsAbs(r.Root) { |
| // The default in test_context.go is the relative path |
| // ../../, which doesn't really help locating the |
| // actual location. Therefore we add also the absolute |
| // path if necessary. |
| abs, err := filepath.Abs(r.Root) |
| if err == nil { |
| description += fmt.Sprintf(" = %q", abs) |
| } |
| } |
| description += "." |
| return description |
| } |
| |
| // BindataFileSource handles files stored in a package generated with bindata. |
| type BindataFileSource struct { |
| Asset func(string) ([]byte, error) |
| AssetNames func() []string |
| } |
| |
| // ReadTestFile looks for an asset with the given path. |
| func (b BindataFileSource) ReadTestFile(filePath string) ([]byte, error) { |
| fileBytes, err := b.Asset(filePath) |
| if err != nil { |
| // It would be nice to have a better way to detect |
| // "not found" errors :-/ |
| if strings.HasSuffix(err.Error(), "not found") { |
| return nil, nil |
| } |
| } |
| return fileBytes, nil |
| } |
| |
| // DescribeFiles explains about gobindata and then lists all available files. |
| func (b BindataFileSource) DescribeFiles() string { |
| var lines []string |
| lines = append(lines, "The following files are built into the test executable via gobindata. For questions on maintaining gobindata, contact the sig-testing group.") |
| assets := b.AssetNames() |
| sort.Strings(assets) |
| lines = append(lines, assets...) |
| description := strings.Join(lines, "\n ") |
| return description |
| } |