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  }