evaluator_test.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package dockerfile
  2. import (
  3. "io/ioutil"
  4. "strings"
  5. "testing"
  6. "github.com/docker/docker/builder"
  7. "github.com/docker/docker/builder/dockerfile/parser"
  8. "github.com/docker/docker/pkg/archive"
  9. "github.com/docker/docker/pkg/reexec"
  10. "github.com/docker/engine-api/types"
  11. "github.com/docker/engine-api/types/container"
  12. )
  13. type dispatchTestCase struct {
  14. name, dockerfile, expectedError string
  15. files map[string]string
  16. }
  17. func init() {
  18. reexec.Init()
  19. }
  20. func initDispatchTestCases() []dispatchTestCase {
  21. dispatchTestCases := []dispatchTestCase{{
  22. name: "copyEmptyWhitespace",
  23. dockerfile: `COPY
  24. quux \
  25. bar`,
  26. expectedError: "COPY requires at least two arguments",
  27. },
  28. {
  29. name: "ONBUILD forbidden FROM",
  30. dockerfile: "ONBUILD FROM scratch",
  31. expectedError: "FROM isn't allowed as an ONBUILD trigger",
  32. files: nil,
  33. },
  34. {
  35. name: "ONBUILD forbidden MAINTAINER",
  36. dockerfile: "ONBUILD MAINTAINER docker.io",
  37. expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger",
  38. files: nil,
  39. },
  40. {
  41. name: "ARG two arguments",
  42. dockerfile: "ARG foo bar",
  43. expectedError: "ARG requires exactly one argument",
  44. files: nil,
  45. },
  46. {
  47. name: "MAINTAINER unknown flag",
  48. dockerfile: "MAINTAINER --boo joe@example.com",
  49. expectedError: "Unknown flag: boo",
  50. files: nil,
  51. },
  52. {
  53. name: "ADD multiple files to file",
  54. dockerfile: "ADD file1.txt file2.txt test",
  55. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  56. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  57. },
  58. {
  59. name: "JSON ADD multiple files to file",
  60. dockerfile: `ADD ["file1.txt", "file2.txt", "test"]`,
  61. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  62. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  63. },
  64. {
  65. name: "Wildcard ADD multiple files to file",
  66. dockerfile: "ADD file*.txt test",
  67. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  68. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  69. },
  70. {
  71. name: "Wildcard JSON ADD multiple files to file",
  72. dockerfile: `ADD ["file*.txt", "test"]`,
  73. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  74. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  75. },
  76. {
  77. name: "COPY multiple files to file",
  78. dockerfile: "COPY file1.txt file2.txt test",
  79. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  80. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  81. },
  82. {
  83. name: "JSON COPY multiple files to file",
  84. dockerfile: `COPY ["file1.txt", "file2.txt", "test"]`,
  85. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  86. files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
  87. },
  88. {
  89. name: "ADD multiple files to file with whitespace",
  90. dockerfile: `ADD [ "test file1.txt", "test file2.txt", "test" ]`,
  91. expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
  92. files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
  93. },
  94. {
  95. name: "COPY multiple files to file with whitespace",
  96. dockerfile: `COPY [ "test file1.txt", "test file2.txt", "test" ]`,
  97. expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
  98. files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
  99. },
  100. {
  101. name: "COPY wildcard no files",
  102. dockerfile: `COPY file*.txt /tmp/`,
  103. expectedError: "No source files were specified",
  104. files: nil,
  105. },
  106. {
  107. name: "COPY url",
  108. dockerfile: `COPY https://index.docker.io/robots.txt /`,
  109. expectedError: "Source can't be a URL for COPY",
  110. files: nil,
  111. },
  112. {
  113. name: "Chaining ONBUILD",
  114. dockerfile: `ONBUILD ONBUILD RUN touch foobar`,
  115. expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
  116. files: nil,
  117. },
  118. {
  119. name: "Invalid instruction",
  120. dockerfile: `foo bar`,
  121. expectedError: "Unknown instruction: FOO",
  122. files: nil,
  123. }}
  124. return dispatchTestCases
  125. }
  126. func TestDispatch(t *testing.T) {
  127. testCases := initDispatchTestCases()
  128. for _, testCase := range testCases {
  129. executeTestCase(t, testCase)
  130. }
  131. }
  132. func executeTestCase(t *testing.T, testCase dispatchTestCase) {
  133. contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
  134. defer cleanup()
  135. for filename, content := range testCase.files {
  136. createTestTempFile(t, contextDir, filename, content, 0777)
  137. }
  138. tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
  139. if err != nil {
  140. t.Fatalf("Error when creating tar stream: %s", err)
  141. }
  142. defer func() {
  143. if err = tarStream.Close(); err != nil {
  144. t.Fatalf("Error when closing tar stream: %s", err)
  145. }
  146. }()
  147. context, err := builder.MakeTarSumContext(tarStream)
  148. if err != nil {
  149. t.Fatalf("Error when creating tar context: %s", err)
  150. }
  151. defer func() {
  152. if err = context.Close(); err != nil {
  153. t.Fatalf("Error when closing tar context: %s", err)
  154. }
  155. }()
  156. r := strings.NewReader(testCase.dockerfile)
  157. d := parser.Directive{}
  158. parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
  159. n, err := parser.Parse(r, &d)
  160. if err != nil {
  161. t.Fatalf("Error when parsing Dockerfile: %s", err)
  162. }
  163. config := &container.Config{}
  164. options := &types.ImageBuildOptions{}
  165. b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context}
  166. err = b.dispatch(0, len(n.Children), n.Children[0])
  167. if err == nil {
  168. t.Fatalf("No error when executing test %s", testCase.name)
  169. }
  170. if !strings.Contains(err.Error(), testCase.expectedError) {
  171. t.Fatalf("Wrong error message. Should be \"%s\". Got \"%s\"", testCase.expectedError, err.Error())
  172. }
  173. }