teststale_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*
  2. Copyright 2016 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 main
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "math/rand"
  18. "os"
  19. "path"
  20. "path/filepath"
  21. "testing"
  22. "time"
  23. )
  24. const (
  25. // seed for rand.Source to generate data for files
  26. seed int64 = 42
  27. // 1K binary file
  28. binLen = 1024
  29. // Directory of the test package relative to $GOPATH
  30. testImportDir = "example.com/proj/pkg"
  31. )
  32. var (
  33. pastHour = time.Now().Add(-1 * time.Hour)
  34. // The test package we are testing against
  35. testPkg = path.Join(testImportDir, "test")
  36. )
  37. // fakegolist implements the `golist` interface providing fake package information for testing.
  38. type fakegolist struct {
  39. dir string
  40. importMap map[string]pkg
  41. testFiles []string
  42. binfile string
  43. }
  44. func newFakegolist() (*fakegolist, error) {
  45. dir, err := ioutil.TempDir("", "teststale")
  46. if err != nil {
  47. // test can't proceed without a temp directory.
  48. return nil, fmt.Errorf("failed to create a temp directory for testing: %v", err)
  49. }
  50. // Set the temp directory as the $GOPATH
  51. if err := os.Setenv("GOPATH", dir); err != nil {
  52. // can't proceed without pointing the $GOPATH to the temp directory.
  53. return nil, fmt.Errorf("failed to set \"$GOPATH\" pointing to %q: %v", dir, err)
  54. }
  55. // Setup $GOPATH directory layout.
  56. // Yeah! I am bored of repeatedly writing "if err != nil {}"!
  57. if os.MkdirAll(filepath.Join(dir, "bin"), 0750) != nil ||
  58. os.MkdirAll(filepath.Join(dir, "pkg", "linux_amd64"), 0750) != nil ||
  59. os.MkdirAll(filepath.Join(dir, "src"), 0750) != nil {
  60. return nil, fmt.Errorf("failed to setup the $GOPATH directory structure")
  61. }
  62. // Create a temp file to represent the test binary.
  63. binfile, err := ioutil.TempFile("", "testbin")
  64. if err != nil {
  65. return nil, fmt.Errorf("failed to create the temp file to represent the test binary: %v", err)
  66. }
  67. // Could have used crypto/rand instead, but it doesn't matter.
  68. rr := rand.New(rand.NewSource(42))
  69. bin := make([]byte, binLen)
  70. if _, err = rr.Read(bin); err != nil {
  71. return nil, fmt.Errorf("couldn't read from the random source: %v", err)
  72. }
  73. if _, err := binfile.Write(bin); err != nil {
  74. return nil, fmt.Errorf("couldn't write to the binary file %q: %v", binfile.Name(), err)
  75. }
  76. if err := binfile.Close(); err != nil {
  77. // It is arguable whether this should be fatal.
  78. return nil, fmt.Errorf("failed to close the binary file %q: %v", binfile.Name(), err)
  79. }
  80. if err := os.Chtimes(binfile.Name(), time.Now(), time.Now()); err != nil {
  81. return nil, fmt.Errorf("failed to modify the mtime of the binary file %q: %v", binfile.Name(), err)
  82. }
  83. // Create test source files directory.
  84. testdir := filepath.Join(dir, "src", testPkg)
  85. if err := os.MkdirAll(testdir, 0750); err != nil {
  86. return nil, fmt.Errorf("failed to create test source directory %q: %v", testdir, err)
  87. }
  88. fgl := &fakegolist{
  89. dir: dir,
  90. importMap: map[string]pkg{
  91. "example.com/proj/pkg/test": {
  92. Dir: path.Join(dir, "src", testPkg),
  93. ImportPath: testPkg,
  94. Target: path.Join(dir, "pkg", "linux_amd64", testImportDir, "test.a"),
  95. Stale: false,
  96. TestGoFiles: []string{
  97. "foo_test.go",
  98. "bar_test.go",
  99. },
  100. TestImports: []string{
  101. "example.com/proj/pkg/p1",
  102. "example.com/proj/pkg/p1/c11",
  103. "example.com/proj/pkg/p2",
  104. "example.com/proj/cmd/p3/c12/c23",
  105. "strings",
  106. "testing",
  107. },
  108. XTestGoFiles: []string{
  109. "xfoo_test.go",
  110. "xbar_test.go",
  111. "xbaz_test.go",
  112. },
  113. XTestImports: []string{
  114. "example.com/proj/pkg/test",
  115. "example.com/proj/pkg/p1",
  116. "example.com/proj/cmd/p3/c12/c23",
  117. "os",
  118. "testing",
  119. },
  120. },
  121. "example.com/proj/pkg/p1": {Stale: false},
  122. "example.com/proj/pkg/p1/c11": {Stale: false},
  123. "example.com/proj/pkg/p2": {Stale: false},
  124. "example.com/proj/cmd/p3/c12/c23": {Stale: false},
  125. "strings": {Stale: false},
  126. "testing": {Stale: false},
  127. "os": {Stale: false},
  128. },
  129. testFiles: []string{
  130. "foo_test.go",
  131. "bar_test.go",
  132. "xfoo_test.go",
  133. "xbar_test.go",
  134. "xbaz_test.go",
  135. },
  136. binfile: binfile.Name(),
  137. }
  138. // Create test source files.
  139. for _, fn := range fgl.testFiles {
  140. fp := filepath.Join(testdir, fn)
  141. if _, err := os.Create(fp); err != nil {
  142. return nil, fmt.Errorf("failed to create the test file %q: %v", fp, err)
  143. }
  144. if err := os.Chtimes(fp, time.Now(), pastHour); err != nil {
  145. return nil, fmt.Errorf("failed to modify the mtime of the test file %q: %v", binfile.Name(), err)
  146. }
  147. }
  148. return fgl, nil
  149. }
  150. func (fgl *fakegolist) pkgInfo(pkgPaths []string) ([]pkg, error) {
  151. var pkgs []pkg
  152. for _, path := range pkgPaths {
  153. p, ok := fgl.importMap[path]
  154. if !ok {
  155. return nil, fmt.Errorf("package %q not found", path)
  156. }
  157. pkgs = append(pkgs, p)
  158. }
  159. return pkgs, nil
  160. }
  161. func (fgl *fakegolist) chMtime(filename string, mtime time.Time) error {
  162. for _, fn := range fgl.testFiles {
  163. if fn == filename {
  164. fp := filepath.Join(fgl.dir, "src", testPkg, fn)
  165. if err := os.Chtimes(fp, time.Now(), mtime); err != nil {
  166. return fmt.Errorf("failed to modify the mtime of %q: %v", filename, err)
  167. }
  168. return nil
  169. }
  170. }
  171. return fmt.Errorf("file %q not found", filename)
  172. }
  173. func (fgl *fakegolist) chStale(pkg string, stale bool) error {
  174. if p, ok := fgl.importMap[pkg]; ok {
  175. p.Stale = stale
  176. fgl.importMap[pkg] = p
  177. return nil
  178. }
  179. return fmt.Errorf("package %q not found", pkg)
  180. }
  181. func (fgl *fakegolist) cleanup() {
  182. os.RemoveAll(fgl.dir)
  183. os.Remove(fgl.binfile)
  184. }
  185. func TestIsTestStale(t *testing.T) {
  186. cases := []struct {
  187. fileMtime map[string]time.Time
  188. pkgStaleness map[string]bool
  189. result bool
  190. }{
  191. // Basic test: binary is fresh, all modifications were before the binary was built.
  192. {
  193. result: false,
  194. },
  195. // A local test file is new, hence binary must be stale.
  196. {
  197. fileMtime: map[string]time.Time{
  198. "foo_test.go": time.Now().Add(1 * time.Hour),
  199. },
  200. result: true,
  201. },
  202. // Test package is new, so binary must be stale.
  203. {
  204. pkgStaleness: map[string]bool{
  205. "example.com/proj/pkg/test": true,
  206. },
  207. result: true,
  208. },
  209. // Test package dependencies are new, so binary must be stale.
  210. {
  211. pkgStaleness: map[string]bool{
  212. "example.com/proj/cmd/p3/c12/c23": true,
  213. "strings": true,
  214. },
  215. result: true,
  216. },
  217. // External test files are new, hence binary must be stale.
  218. {
  219. fileMtime: map[string]time.Time{
  220. "xfoo_test.go": time.Now().Add(1 * time.Hour),
  221. "xbar_test.go": time.Now().Add(2 * time.Hour),
  222. },
  223. result: true,
  224. },
  225. // External test dependency is new, so binary must be stale.
  226. {
  227. pkgStaleness: map[string]bool{
  228. "os": true,
  229. },
  230. result: true,
  231. },
  232. // Multiple source files and dependencies are new, so binary must be stale.
  233. {
  234. fileMtime: map[string]time.Time{
  235. "foo_test.go": time.Now().Add(1 * time.Hour),
  236. "xfoo_test.go": time.Now().Add(2 * time.Hour),
  237. "xbar_test.go": time.Now().Add(3 * time.Hour),
  238. },
  239. pkgStaleness: map[string]bool{
  240. "example.com/proj/pkg/p1": true,
  241. "example.com/proj/pkg/p1/c11": true,
  242. "example.com/proj/pkg/p2": true,
  243. "example.com/proj/cmd/p3/c12/c23": true,
  244. "strings": true,
  245. "os": true,
  246. },
  247. result: true,
  248. },
  249. // Everything is new, so binary must be stale.
  250. {
  251. fileMtime: map[string]time.Time{
  252. "foo_test.go": time.Now().Add(3 * time.Hour),
  253. "bar_test.go": time.Now().Add(1 * time.Hour),
  254. "xfoo_test.go": time.Now().Add(2 * time.Hour),
  255. "xbar_test.go": time.Now().Add(1 * time.Hour),
  256. "xbaz_test.go": time.Now().Add(2 * time.Hour),
  257. },
  258. pkgStaleness: map[string]bool{
  259. "example.com/proj/pkg/p1": true,
  260. "example.com/proj/pkg/p1/c11": true,
  261. "example.com/proj/pkg/p2": true,
  262. "example.com/proj/cmd/p3/c12/c23": true,
  263. "example.com/proj/pkg/test": true,
  264. "strings": true,
  265. "testing": true,
  266. "os": true,
  267. },
  268. result: true,
  269. },
  270. }
  271. for _, tc := range cases {
  272. fgl, err := newFakegolist()
  273. if err != nil {
  274. t.Fatalf("failed to setup the test: %v", err)
  275. }
  276. defer fgl.cleanup()
  277. for fn, mtime := range tc.fileMtime {
  278. if err := fgl.chMtime(fn, mtime); err != nil {
  279. t.Fatalf("failed to change the mtime of %q: %v", fn, err)
  280. }
  281. }
  282. for pkg, stale := range tc.pkgStaleness {
  283. if err := fgl.chStale(pkg, stale); err != nil {
  284. t.Fatalf("failed to change the staleness of %q: %v", pkg, err)
  285. }
  286. }
  287. if tc.result != isTestStale(fgl, fgl.binfile, testPkg) {
  288. if tc.result {
  289. t.Errorf("Expected test package %q to be stale", testPkg)
  290. } else {
  291. t.Errorf("Expected test package %q to be not stale", testPkg)
  292. }
  293. }
  294. }
  295. }