testfiles.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package testfiles provides a wrapper around various optional ways
  14. // of retrieving additional files needed during a test run:
  15. // - builtin bindata
  16. // - filesystem access
  17. //
  18. // Because it is a is self-contained package, it can be used by
  19. // test/e2e/framework and test/e2e/manifest without creating
  20. // a circular dependency.
  21. package testfiles
  22. import (
  23. "errors"
  24. "fmt"
  25. "io/ioutil"
  26. "os"
  27. "path"
  28. "path/filepath"
  29. "sort"
  30. "strings"
  31. "k8s.io/kubernetes/test/e2e/framework"
  32. )
  33. var filesources []FileSource
  34. // AddFileSource registers another provider for files that may be
  35. // needed at runtime. Should be called during initialization of a test
  36. // binary.
  37. func AddFileSource(filesource FileSource) {
  38. filesources = append(filesources, filesource)
  39. }
  40. // FileSource implements one way of retrieving test file content. For
  41. // example, one file source could read from the original source code
  42. // file tree, another from bindata compiled into a test executable.
  43. type FileSource interface {
  44. // ReadTestFile retrieves the content of a file that gets maintained
  45. // alongside a test's source code. Files are identified by the
  46. // relative path inside the repository containing the tests, for
  47. // example "cluster/gce/upgrade.sh" inside kubernetes/kubernetes.
  48. //
  49. // When the file is not found, a nil slice is returned. An error is
  50. // returned for all fatal errors.
  51. ReadTestFile(filePath string) ([]byte, error)
  52. // DescribeFiles returns a multi-line description of which
  53. // files are available via this source. It is meant to be
  54. // used as part of the error message when a file cannot be
  55. // found.
  56. DescribeFiles() string
  57. }
  58. // ReadOrDie tries to retrieve the desired file content from
  59. // one of the registered file sources. In contrast to FileSource, it
  60. // will either return a valid slice or abort the test by calling the fatal function,
  61. // i.e. the caller doesn't have to implement error checking.
  62. func ReadOrDie(filePath string) []byte {
  63. data, err := Read(filePath)
  64. if err != nil {
  65. framework.Fail(err.Error(), 1)
  66. }
  67. return data
  68. }
  69. // Read tries to retrieve the desired file content from
  70. // one of the registered file sources.
  71. func Read(filePath string) ([]byte, error) {
  72. if len(filesources) == 0 {
  73. return nil, fmt.Errorf("no file sources registered (yet?), cannot retrieve test file %s", filePath)
  74. }
  75. for _, filesource := range filesources {
  76. data, err := filesource.ReadTestFile(filePath)
  77. if err != nil {
  78. return nil, fmt.Errorf("fatal error retrieving test file %s: %s", filePath, err)
  79. }
  80. if data != nil {
  81. return data, nil
  82. }
  83. }
  84. // Here we try to generate an error that points test authors
  85. // or users in the right direction for resolving the problem.
  86. error := fmt.Sprintf("Test file %q was not found.\n", filePath)
  87. for _, filesource := range filesources {
  88. error += filesource.DescribeFiles()
  89. error += "\n"
  90. }
  91. return nil, errors.New(error)
  92. }
  93. // Exists checks whether a file could be read. Unexpected errors
  94. // are handled by calling the fail function, which then should
  95. // abort the current test.
  96. func Exists(filePath string) bool {
  97. for _, filesource := range filesources {
  98. data, err := filesource.ReadTestFile(filePath)
  99. if err != nil {
  100. framework.Fail(fmt.Sprintf("fatal error looking for test file %s: %s", filePath, err), 1)
  101. }
  102. if data != nil {
  103. return true
  104. }
  105. }
  106. return false
  107. }
  108. // RootFileSource looks for files relative to a root directory.
  109. type RootFileSource struct {
  110. Root string
  111. }
  112. // ReadTestFile looks for the file relative to the configured
  113. // root directory. If the path is already absolute, for example
  114. // in a test that has its own method of determining where
  115. // files are, then the path will be used directly.
  116. func (r RootFileSource) ReadTestFile(filePath string) ([]byte, error) {
  117. var fullPath string
  118. if path.IsAbs(filePath) {
  119. fullPath = filePath
  120. } else {
  121. fullPath = filepath.Join(r.Root, filePath)
  122. }
  123. data, err := ioutil.ReadFile(fullPath)
  124. if os.IsNotExist(err) {
  125. // Not an error (yet), some other provider may have the file.
  126. return nil, nil
  127. }
  128. return data, err
  129. }
  130. // DescribeFiles explains that it looks for files inside a certain
  131. // root directory.
  132. func (r RootFileSource) DescribeFiles() string {
  133. description := fmt.Sprintf("Test files are expected in %q", r.Root)
  134. if !path.IsAbs(r.Root) {
  135. // The default in test_context.go is the relative path
  136. // ../../, which doesn't really help locating the
  137. // actual location. Therefore we add also the absolute
  138. // path if necessary.
  139. abs, err := filepath.Abs(r.Root)
  140. if err == nil {
  141. description += fmt.Sprintf(" = %q", abs)
  142. }
  143. }
  144. description += "."
  145. return description
  146. }
  147. // BindataFileSource handles files stored in a package generated with bindata.
  148. type BindataFileSource struct {
  149. Asset func(string) ([]byte, error)
  150. AssetNames func() []string
  151. }
  152. // ReadTestFile looks for an asset with the given path.
  153. func (b BindataFileSource) ReadTestFile(filePath string) ([]byte, error) {
  154. fileBytes, err := b.Asset(filePath)
  155. if err != nil {
  156. // It would be nice to have a better way to detect
  157. // "not found" errors :-/
  158. if strings.HasSuffix(err.Error(), "not found") {
  159. return nil, nil
  160. }
  161. }
  162. return fileBytes, nil
  163. }
  164. // DescribeFiles explains about gobindata and then lists all available files.
  165. func (b BindataFileSource) DescribeFiles() string {
  166. var lines []string
  167. lines = append(lines, "The following files are built into the test executable via gobindata. For questions on maintaining gobindata, contact the sig-testing group.")
  168. assets := b.AssetNames()
  169. sort.Strings(assets)
  170. lines = append(lines, assets...)
  171. description := strings.Join(lines, "\n ")
  172. return description
  173. }