files_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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 files
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "testing"
  19. utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
  20. utilfs "k8s.io/kubernetes/pkg/util/filesystem"
  21. )
  22. const (
  23. prefix = "test-util-files"
  24. )
  25. type file struct {
  26. name string
  27. // mode distinguishes file type,
  28. // we only check for regular vs. directory in these tests,
  29. // specify regular as 0, directory as os.ModeDir
  30. mode os.FileMode
  31. data string // ignored if mode == os.ModeDir
  32. }
  33. func (f *file) write(fs utilfs.Filesystem, dir string) error {
  34. path := filepath.Join(dir, f.name)
  35. if f.mode.IsDir() {
  36. if err := fs.MkdirAll(path, defaultPerm); err != nil {
  37. return err
  38. }
  39. } else if f.mode.IsRegular() {
  40. // create parent directories, if necessary
  41. parents := filepath.Dir(path)
  42. if err := fs.MkdirAll(parents, defaultPerm); err != nil {
  43. return err
  44. }
  45. // create the file
  46. handle, err := fs.Create(path)
  47. if err != nil {
  48. return err
  49. }
  50. _, err = handle.Write([]byte(f.data))
  51. if err != nil {
  52. if cerr := handle.Close(); cerr != nil {
  53. return fmt.Errorf("error %v closing file after error: %v", cerr, err)
  54. }
  55. return err
  56. }
  57. } else {
  58. return fmt.Errorf("mode not implemented for testing %s", f.mode.String())
  59. }
  60. return nil
  61. }
  62. func (f *file) expect(fs utilfs.Filesystem, dir string) error {
  63. path := filepath.Join(dir, f.name)
  64. if f.mode.IsDir() {
  65. info, err := fs.Stat(path)
  66. if err != nil {
  67. return err
  68. }
  69. if !info.IsDir() {
  70. return fmt.Errorf("expected directory, got mode %s", info.Mode().String())
  71. }
  72. } else if f.mode.IsRegular() {
  73. info, err := fs.Stat(path)
  74. if err != nil {
  75. return err
  76. }
  77. if !info.Mode().IsRegular() {
  78. return fmt.Errorf("expected regular file, got mode %s", info.Mode().String())
  79. }
  80. data, err := fs.ReadFile(path)
  81. if err != nil {
  82. return err
  83. }
  84. if f.data != string(data) {
  85. return fmt.Errorf("expected file data %q, got %q", f.data, string(data))
  86. }
  87. } else {
  88. return fmt.Errorf("mode not implemented for testing %s", f.mode.String())
  89. }
  90. return nil
  91. }
  92. // write files, perform some function, then attempt to read files back
  93. // if err is non-empty, expects an error from the function performed in the test
  94. // and skips reading back the expected files
  95. type test struct {
  96. desc string
  97. writes []file
  98. expects []file
  99. fn func(fs utilfs.Filesystem, dir string, c *test) []error
  100. err string
  101. }
  102. func (c *test) write(t *testing.T, fs utilfs.Filesystem, dir string) {
  103. for _, f := range c.writes {
  104. if err := f.write(fs, dir); err != nil {
  105. t.Fatalf("error pre-writing file: %v", err)
  106. }
  107. }
  108. }
  109. // you can optionally skip calling t.Errorf by passing a nil t, and process the
  110. // returned errors instead
  111. func (c *test) expect(t *testing.T, fs utilfs.Filesystem, dir string) []error {
  112. errs := []error{}
  113. for _, f := range c.expects {
  114. if err := f.expect(fs, dir); err != nil {
  115. msg := fmt.Errorf("expect %#v, got error: %v", f, err)
  116. errs = append(errs, msg)
  117. if t != nil {
  118. t.Errorf("%s", msg)
  119. }
  120. }
  121. }
  122. return errs
  123. }
  124. // run a test case, with an arbitrary function to execute between write and expect
  125. // if c.fn is nil, errors from c.expect are checked against c.err, instead of errors
  126. // from fn being checked against c.err
  127. func (c *test) run(t *testing.T, fs utilfs.Filesystem) {
  128. // isolate each test case in a new temporary directory
  129. dir, err := fs.TempDir("", prefix)
  130. if err != nil {
  131. t.Fatalf("error creating temporary directory for test: %v", err)
  132. }
  133. c.write(t, fs, dir)
  134. // if fn exists, check errors from fn, then check expected files
  135. if c.fn != nil {
  136. errs := c.fn(fs, dir, c)
  137. if len(errs) > 0 {
  138. for _, err := range errs {
  139. utiltest.ExpectError(t, err, c.err)
  140. }
  141. // skip checking expected files if we expected errors
  142. // (usually means we didn't create file)
  143. return
  144. }
  145. c.expect(t, fs, dir)
  146. return
  147. }
  148. // just check expected files, and compare errors from c.expect to c.err
  149. // (this lets us test the helper functions above)
  150. errs := c.expect(nil, fs, dir)
  151. for _, err := range errs {
  152. utiltest.ExpectError(t, err, c.err)
  153. }
  154. }
  155. // simple test of the above helper functions
  156. func TestHelpers(t *testing.T) {
  157. // omitting the test.fn means test.err is compared to errors from test.expect
  158. cases := []test{
  159. {
  160. desc: "regular file",
  161. writes: []file{{name: "foo", data: "bar"}},
  162. expects: []file{{name: "foo", data: "bar"}},
  163. },
  164. {
  165. desc: "directory",
  166. writes: []file{{name: "foo", mode: os.ModeDir}},
  167. expects: []file{{name: "foo", mode: os.ModeDir}},
  168. },
  169. {
  170. desc: "deep regular file",
  171. writes: []file{{name: "foo/bar", data: "baz"}},
  172. expects: []file{{name: "foo/bar", data: "baz"}},
  173. },
  174. {
  175. desc: "deep directory",
  176. writes: []file{{name: "foo/bar", mode: os.ModeDir}},
  177. expects: []file{{name: "foo/bar", mode: os.ModeDir}},
  178. },
  179. {
  180. desc: "missing file",
  181. expects: []file{{name: "foo", data: "bar"}},
  182. err: "no such file or directory",
  183. },
  184. {
  185. desc: "missing directory",
  186. expects: []file{{name: "foo/bar", mode: os.ModeDir}},
  187. err: "no such file or directory",
  188. },
  189. }
  190. for _, c := range cases {
  191. t.Run(c.desc, func(t *testing.T) {
  192. c.run(t, utilfs.DefaultFs{})
  193. })
  194. }
  195. }
  196. func TestFileExists(t *testing.T) {
  197. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  198. ok, err := FileExists(fs, filepath.Join(dir, "foo"))
  199. if err != nil {
  200. return []error{err}
  201. }
  202. if !ok {
  203. return []error{fmt.Errorf("does not exist (test)")}
  204. }
  205. return nil
  206. }
  207. cases := []test{
  208. {
  209. fn: fn,
  210. desc: "file exists",
  211. writes: []file{{name: "foo"}},
  212. },
  213. {
  214. fn: fn,
  215. desc: "file does not exist",
  216. err: "does not exist (test)",
  217. },
  218. {
  219. fn: fn,
  220. desc: "object has non-file mode",
  221. writes: []file{{name: "foo", mode: os.ModeDir}},
  222. err: "expected regular file",
  223. },
  224. }
  225. for _, c := range cases {
  226. t.Run(c.desc, func(t *testing.T) {
  227. c.run(t, utilfs.DefaultFs{})
  228. })
  229. }
  230. }
  231. func TestEnsureFile(t *testing.T) {
  232. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  233. var errs []error
  234. for _, f := range c.expects {
  235. if err := EnsureFile(fs, filepath.Join(dir, f.name)); err != nil {
  236. errs = append(errs, err)
  237. }
  238. }
  239. return errs
  240. }
  241. cases := []test{
  242. {
  243. fn: fn,
  244. desc: "file exists",
  245. writes: []file{{name: "foo"}},
  246. expects: []file{{name: "foo"}},
  247. },
  248. {
  249. fn: fn,
  250. desc: "file does not exist",
  251. expects: []file{{name: "bar"}},
  252. },
  253. {
  254. fn: fn,
  255. desc: "neither parent nor file exists",
  256. expects: []file{{name: "baz/quux"}},
  257. },
  258. }
  259. for _, c := range cases {
  260. t.Run(c.desc, func(t *testing.T) {
  261. c.run(t, utilfs.DefaultFs{})
  262. })
  263. }
  264. }
  265. // Note: This transitively tests WriteTmpFile
  266. func TestReplaceFile(t *testing.T) {
  267. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  268. var errs []error
  269. for _, f := range c.expects {
  270. if err := ReplaceFile(fs, filepath.Join(dir, f.name), []byte(f.data)); err != nil {
  271. errs = append(errs, err)
  272. }
  273. }
  274. return errs
  275. }
  276. cases := []test{
  277. {
  278. fn: fn,
  279. desc: "file exists",
  280. writes: []file{{name: "foo"}},
  281. expects: []file{{name: "foo", data: "bar"}},
  282. },
  283. {
  284. fn: fn,
  285. desc: "file does not exist",
  286. expects: []file{{name: "foo", data: "bar"}},
  287. },
  288. {
  289. fn: func(fs utilfs.Filesystem, dir string, c *test) []error {
  290. if err := ReplaceFile(fs, filepath.Join(dir, "foo/bar"), []byte("")); err != nil {
  291. return []error{err}
  292. }
  293. return nil
  294. },
  295. desc: "neither parent nor file exists",
  296. err: "no such file or directory",
  297. },
  298. }
  299. for _, c := range cases {
  300. t.Run(c.desc, func(t *testing.T) {
  301. c.run(t, utilfs.DefaultFs{})
  302. })
  303. }
  304. }
  305. func TestDirExists(t *testing.T) {
  306. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  307. ok, err := DirExists(fs, filepath.Join(dir, "foo"))
  308. if err != nil {
  309. return []error{err}
  310. }
  311. if !ok {
  312. return []error{fmt.Errorf("does not exist (test)")}
  313. }
  314. return nil
  315. }
  316. cases := []test{
  317. {
  318. fn: fn,
  319. desc: "dir exists",
  320. writes: []file{{name: "foo", mode: os.ModeDir}},
  321. },
  322. {
  323. fn: fn,
  324. desc: "dir does not exist",
  325. err: "does not exist (test)",
  326. },
  327. {
  328. fn: fn,
  329. desc: "object has non-dir mode",
  330. writes: []file{{name: "foo"}},
  331. err: "expected dir",
  332. },
  333. }
  334. for _, c := range cases {
  335. t.Run(c.desc, func(t *testing.T) {
  336. c.run(t, utilfs.DefaultFs{})
  337. })
  338. }
  339. }
  340. func TestEnsureDir(t *testing.T) {
  341. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  342. var errs []error
  343. for _, f := range c.expects {
  344. if err := EnsureDir(fs, filepath.Join(dir, f.name)); err != nil {
  345. errs = append(errs, err)
  346. }
  347. }
  348. return errs
  349. }
  350. cases := []test{
  351. {
  352. fn: fn,
  353. desc: "dir exists",
  354. writes: []file{{name: "foo", mode: os.ModeDir}},
  355. expects: []file{{name: "foo", mode: os.ModeDir}},
  356. },
  357. {
  358. fn: fn,
  359. desc: "dir does not exist",
  360. expects: []file{{name: "bar", mode: os.ModeDir}},
  361. },
  362. {
  363. fn: fn,
  364. desc: "neither parent nor dir exists",
  365. expects: []file{{name: "baz/quux", mode: os.ModeDir}},
  366. },
  367. }
  368. for _, c := range cases {
  369. t.Run(c.desc, func(t *testing.T) {
  370. c.run(t, utilfs.DefaultFs{})
  371. })
  372. }
  373. }
  374. func TestWriteTempDir(t *testing.T) {
  375. // writing a tmp dir is covered by TestReplaceDir, but we additionally test filename validation here
  376. c := test{
  377. desc: "invalid file key",
  378. err: "invalid file key",
  379. fn: func(fs utilfs.Filesystem, dir string, c *test) []error {
  380. if _, err := WriteTempDir(fs, filepath.Join(dir, "tmpdir"), map[string]string{"foo/bar": ""}); err != nil {
  381. return []error{err}
  382. }
  383. return nil
  384. },
  385. }
  386. c.run(t, utilfs.DefaultFs{})
  387. }
  388. func TestReplaceDir(t *testing.T) {
  389. fn := func(fs utilfs.Filesystem, dir string, c *test) []error {
  390. errs := []error{}
  391. // compute filesets from expected files and call ReplaceDir for each
  392. // we don't nest dirs in test cases, order of ReplaceDir call is not guaranteed
  393. dirs := map[string]map[string]string{}
  394. // allocate dirs
  395. for _, f := range c.expects {
  396. if f.mode.IsDir() {
  397. path := filepath.Join(dir, f.name)
  398. if _, ok := dirs[path]; !ok {
  399. dirs[path] = map[string]string{}
  400. }
  401. } else if f.mode.IsRegular() {
  402. path := filepath.Join(dir, filepath.Dir(f.name))
  403. if _, ok := dirs[path]; !ok {
  404. // require an expectation for the parent directory if there is an expectation for the file
  405. errs = append(errs, fmt.Errorf("no prior parent directory in c.expects for file %s", f.name))
  406. continue
  407. }
  408. dirs[path][filepath.Base(f.name)] = f.data
  409. }
  410. }
  411. // short-circuit test case validation errors
  412. if len(errs) > 0 {
  413. return errs
  414. }
  415. // call ReplaceDir for each desired dir
  416. for path, files := range dirs {
  417. if err := ReplaceDir(fs, path, files); err != nil {
  418. errs = append(errs, err)
  419. }
  420. }
  421. return errs
  422. }
  423. cases := []test{
  424. {
  425. fn: fn,
  426. desc: "fn catches invalid test case",
  427. expects: []file{{name: "foo/bar"}},
  428. err: "no prior parent directory",
  429. },
  430. {
  431. fn: fn,
  432. desc: "empty dir",
  433. expects: []file{{name: "foo", mode: os.ModeDir}},
  434. },
  435. {
  436. fn: fn,
  437. desc: "dir with files",
  438. expects: []file{
  439. {name: "foo", mode: os.ModeDir},
  440. {name: "foo/bar", data: "baz"},
  441. {name: "foo/baz", data: "bar"},
  442. },
  443. },
  444. }
  445. for _, c := range cases {
  446. t.Run(c.desc, func(t *testing.T) {
  447. c.run(t, utilfs.DefaultFs{})
  448. })
  449. }
  450. }