changes_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. package archive
  2. import (
  3. "io/ioutil"
  4. "os"
  5. "os/exec"
  6. "path"
  7. "sort"
  8. "syscall"
  9. "testing"
  10. "time"
  11. )
  12. func max(x, y int) int {
  13. if x >= y {
  14. return x
  15. }
  16. return y
  17. }
  18. func copyDir(src, dst string) error {
  19. cmd := exec.Command("cp", "-a", src, dst)
  20. if err := cmd.Run(); err != nil {
  21. return err
  22. }
  23. return nil
  24. }
  25. type FileType uint32
  26. const (
  27. Regular FileType = iota
  28. Dir
  29. Symlink
  30. )
  31. type FileData struct {
  32. filetype FileType
  33. path string
  34. contents string
  35. permissions os.FileMode
  36. }
  37. func createSampleDir(t *testing.T, root string) {
  38. files := []FileData{
  39. {Regular, "file1", "file1\n", 0600},
  40. {Regular, "file2", "file2\n", 0666},
  41. {Regular, "file3", "file3\n", 0404},
  42. {Regular, "file4", "file4\n", 0600},
  43. {Regular, "file5", "file5\n", 0600},
  44. {Regular, "file6", "file6\n", 0600},
  45. {Regular, "file7", "file7\n", 0600},
  46. {Dir, "dir1", "", 0740},
  47. {Regular, "dir1/file1-1", "file1-1\n", 01444},
  48. {Regular, "dir1/file1-2", "file1-2\n", 0666},
  49. {Dir, "dir2", "", 0700},
  50. {Regular, "dir2/file2-1", "file2-1\n", 0666},
  51. {Regular, "dir2/file2-2", "file2-2\n", 0666},
  52. {Dir, "dir3", "", 0700},
  53. {Regular, "dir3/file3-1", "file3-1\n", 0666},
  54. {Regular, "dir3/file3-2", "file3-2\n", 0666},
  55. {Dir, "dir4", "", 0700},
  56. {Regular, "dir4/file3-1", "file4-1\n", 0666},
  57. {Regular, "dir4/file3-2", "file4-2\n", 0666},
  58. {Symlink, "symlink1", "target1", 0666},
  59. {Symlink, "symlink2", "target2", 0666},
  60. }
  61. now := time.Now()
  62. for _, info := range files {
  63. p := path.Join(root, info.path)
  64. if info.filetype == Dir {
  65. if err := os.MkdirAll(p, info.permissions); err != nil {
  66. t.Fatal(err)
  67. }
  68. } else if info.filetype == Regular {
  69. if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
  70. t.Fatal(err)
  71. }
  72. } else if info.filetype == Symlink {
  73. if err := os.Symlink(info.contents, p); err != nil {
  74. t.Fatal(err)
  75. }
  76. }
  77. if info.filetype != Symlink {
  78. // Set a consistent ctime, atime for all files and dirs
  79. if err := os.Chtimes(p, now, now); err != nil {
  80. t.Fatal(err)
  81. }
  82. }
  83. }
  84. }
  85. func TestChangeString(t *testing.T) {
  86. modifiyChange := Change{"change", ChangeModify}
  87. toString := modifiyChange.String()
  88. if toString != "C change" {
  89. t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString)
  90. }
  91. addChange := Change{"change", ChangeAdd}
  92. toString = addChange.String()
  93. if toString != "A change" {
  94. t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString)
  95. }
  96. deleteChange := Change{"change", ChangeDelete}
  97. toString = deleteChange.String()
  98. if toString != "D change" {
  99. t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString)
  100. }
  101. }
  102. func TestChangesWithNoChanges(t *testing.T) {
  103. rwLayer, err := ioutil.TempDir("", "docker-changes-test")
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. defer os.RemoveAll(rwLayer)
  108. layer, err := ioutil.TempDir("", "docker-changes-test-layer")
  109. if err != nil {
  110. t.Fatal(err)
  111. }
  112. defer os.RemoveAll(layer)
  113. createSampleDir(t, layer)
  114. changes, err := Changes([]string{layer}, rwLayer)
  115. if err != nil {
  116. t.Fatal(err)
  117. }
  118. if len(changes) != 0 {
  119. t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes))
  120. }
  121. }
  122. func TestChangesWithChanges(t *testing.T) {
  123. rwLayer, err := ioutil.TempDir("", "docker-changes-test")
  124. if err != nil {
  125. t.Fatal(err)
  126. }
  127. defer os.RemoveAll(rwLayer)
  128. // Create a folder
  129. dir1 := path.Join(rwLayer, "dir1")
  130. os.MkdirAll(dir1, 0740)
  131. deletedFile := path.Join(dir1, ".wh.file1-2")
  132. ioutil.WriteFile(deletedFile, []byte{}, 0600)
  133. modifiedFile := path.Join(dir1, "file1-1")
  134. ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444)
  135. // Let's add a subfolder for a newFile
  136. subfolder := path.Join(dir1, "subfolder")
  137. os.MkdirAll(subfolder, 0740)
  138. newFile := path.Join(subfolder, "newFile")
  139. ioutil.WriteFile(newFile, []byte{}, 0740)
  140. // Let's create folders that with have the role of layers with the same data
  141. layer, err := ioutil.TempDir("", "docker-changes-test-layer")
  142. if err != nil {
  143. t.Fatal(err)
  144. }
  145. defer os.RemoveAll(layer)
  146. createSampleDir(t, layer)
  147. os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740)
  148. // Let's modify modtime for dir1 to be sure it's the same for the two layer (to not having false positive)
  149. fi, err := os.Stat(dir1)
  150. if err != nil {
  151. return
  152. }
  153. mtime := fi.ModTime()
  154. stat := fi.Sys().(*syscall.Stat_t)
  155. atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
  156. layerDir1 := path.Join(layer, "dir1")
  157. os.Chtimes(layerDir1, atime, mtime)
  158. changes, err := Changes([]string{layer}, rwLayer)
  159. if err != nil {
  160. t.Fatal(err)
  161. }
  162. sort.Sort(changesByPath(changes))
  163. expectedChanges := []Change{
  164. {"/dir1/file1-1", ChangeModify},
  165. {"/dir1/file1-2", ChangeDelete},
  166. {"/dir1/subfolder", ChangeModify},
  167. {"/dir1/subfolder/newFile", ChangeAdd},
  168. }
  169. for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
  170. if i >= len(expectedChanges) {
  171. t.Fatalf("unexpected change %s\n", changes[i].String())
  172. }
  173. if i >= len(changes) {
  174. t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
  175. }
  176. if changes[i].Path == expectedChanges[i].Path {
  177. if changes[i] != expectedChanges[i] {
  178. t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
  179. }
  180. } else if changes[i].Path < expectedChanges[i].Path {
  181. t.Fatalf("unexpected change %s\n", changes[i].String())
  182. } else {
  183. t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
  184. }
  185. }
  186. }
  187. // Create an directory, copy it, make sure we report no changes between the two
  188. func TestChangesDirsEmpty(t *testing.T) {
  189. src, err := ioutil.TempDir("", "docker-changes-test")
  190. if err != nil {
  191. t.Fatal(err)
  192. }
  193. defer os.RemoveAll(src)
  194. createSampleDir(t, src)
  195. dst := src + "-copy"
  196. if err := copyDir(src, dst); err != nil {
  197. t.Fatal(err)
  198. }
  199. defer os.RemoveAll(dst)
  200. changes, err := ChangesDirs(dst, src)
  201. if err != nil {
  202. t.Fatal(err)
  203. }
  204. if len(changes) != 0 {
  205. t.Fatalf("Reported changes for identical dirs: %v", changes)
  206. }
  207. os.RemoveAll(src)
  208. os.RemoveAll(dst)
  209. }
  210. func mutateSampleDir(t *testing.T, root string) {
  211. // Remove a regular file
  212. if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
  213. t.Fatal(err)
  214. }
  215. // Remove a directory
  216. if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
  217. t.Fatal(err)
  218. }
  219. // Remove a symlink
  220. if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
  221. t.Fatal(err)
  222. }
  223. // Rewrite a file
  224. if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
  225. t.Fatal(err)
  226. }
  227. // Replace a file
  228. if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
  229. t.Fatal(err)
  230. }
  231. if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
  232. t.Fatal(err)
  233. }
  234. // Touch file
  235. if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
  236. t.Fatal(err)
  237. }
  238. // Replace file with dir
  239. if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
  240. t.Fatal(err)
  241. }
  242. if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
  243. t.Fatal(err)
  244. }
  245. // Create new file
  246. if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
  247. t.Fatal(err)
  248. }
  249. // Create new dir
  250. if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
  251. t.Fatal(err)
  252. }
  253. // Create a new symlink
  254. if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
  255. t.Fatal(err)
  256. }
  257. // Change a symlink
  258. if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
  259. t.Fatal(err)
  260. }
  261. if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
  262. t.Fatal(err)
  263. }
  264. // Replace dir with file
  265. if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
  266. t.Fatal(err)
  267. }
  268. if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
  269. t.Fatal(err)
  270. }
  271. // Touch dir
  272. if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
  273. t.Fatal(err)
  274. }
  275. }
  276. func TestChangesDirsMutated(t *testing.T) {
  277. src, err := ioutil.TempDir("", "docker-changes-test")
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. createSampleDir(t, src)
  282. dst := src + "-copy"
  283. if err := copyDir(src, dst); err != nil {
  284. t.Fatal(err)
  285. }
  286. defer os.RemoveAll(src)
  287. defer os.RemoveAll(dst)
  288. mutateSampleDir(t, dst)
  289. changes, err := ChangesDirs(dst, src)
  290. if err != nil {
  291. t.Fatal(err)
  292. }
  293. sort.Sort(changesByPath(changes))
  294. expectedChanges := []Change{
  295. {"/dir1", ChangeDelete},
  296. {"/dir2", ChangeModify},
  297. {"/dirnew", ChangeAdd},
  298. {"/file1", ChangeDelete},
  299. {"/file2", ChangeModify},
  300. {"/file3", ChangeModify},
  301. {"/file4", ChangeModify},
  302. {"/file5", ChangeModify},
  303. {"/filenew", ChangeAdd},
  304. {"/symlink1", ChangeDelete},
  305. {"/symlink2", ChangeModify},
  306. {"/symlinknew", ChangeAdd},
  307. }
  308. for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
  309. if i >= len(expectedChanges) {
  310. t.Fatalf("unexpected change %s\n", changes[i].String())
  311. }
  312. if i >= len(changes) {
  313. t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
  314. }
  315. if changes[i].Path == expectedChanges[i].Path {
  316. if changes[i] != expectedChanges[i] {
  317. t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
  318. }
  319. } else if changes[i].Path < expectedChanges[i].Path {
  320. t.Fatalf("unexpected change %s\n", changes[i].String())
  321. } else {
  322. t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
  323. }
  324. }
  325. }
  326. func TestApplyLayer(t *testing.T) {
  327. src, err := ioutil.TempDir("", "docker-changes-test")
  328. if err != nil {
  329. t.Fatal(err)
  330. }
  331. createSampleDir(t, src)
  332. defer os.RemoveAll(src)
  333. dst := src + "-copy"
  334. if err := copyDir(src, dst); err != nil {
  335. t.Fatal(err)
  336. }
  337. mutateSampleDir(t, dst)
  338. defer os.RemoveAll(dst)
  339. changes, err := ChangesDirs(dst, src)
  340. if err != nil {
  341. t.Fatal(err)
  342. }
  343. layer, err := ExportChanges(dst, changes)
  344. if err != nil {
  345. t.Fatal(err)
  346. }
  347. layerCopy, err := NewTempArchive(layer, "")
  348. if err != nil {
  349. t.Fatal(err)
  350. }
  351. if _, err := ApplyLayer(src, layerCopy); err != nil {
  352. t.Fatal(err)
  353. }
  354. changes2, err := ChangesDirs(src, dst)
  355. if err != nil {
  356. t.Fatal(err)
  357. }
  358. if len(changes2) != 0 {
  359. t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
  360. }
  361. }
  362. func TestChangesSizeWithNoChanges(t *testing.T) {
  363. size := ChangesSize("/tmp", nil)
  364. if size != 0 {
  365. t.Fatalf("ChangesSizes with no changes should be 0, was %d", size)
  366. }
  367. }
  368. func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) {
  369. changes := []Change{
  370. {Path: "deletedPath", Kind: ChangeDelete},
  371. }
  372. size := ChangesSize("/tmp", changes)
  373. if size != 0 {
  374. t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
  375. }
  376. }
  377. func TestChangesSize(t *testing.T) {
  378. parentPath, err := ioutil.TempDir("", "docker-changes-test")
  379. defer os.RemoveAll(parentPath)
  380. addition := path.Join(parentPath, "addition")
  381. if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
  382. t.Fatal(err)
  383. }
  384. modification := path.Join(parentPath, "modification")
  385. if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
  386. t.Fatal(err)
  387. }
  388. changes := []Change{
  389. {Path: "addition", Kind: ChangeAdd},
  390. {Path: "modification", Kind: ChangeModify},
  391. }
  392. size := ChangesSize(parentPath, changes)
  393. if size != 6 {
  394. t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
  395. }
  396. }