github.com/kobeld/docker@v1.12.0-rc1/builder/dockerfile/evaluator_test.go (about) 1 package dockerfile 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/docker/docker/builder" 11 "github.com/docker/docker/builder/dockerfile/parser" 12 "github.com/docker/docker/pkg/archive" 13 "github.com/docker/docker/pkg/reexec" 14 "github.com/docker/engine-api/types" 15 "github.com/docker/engine-api/types/container" 16 ) 17 18 type dispatchTestCase struct { 19 name, dockerfile, expectedError string 20 files map[string]string 21 } 22 23 func init() { 24 reexec.Init() 25 } 26 27 func initDispatchTestCases() []dispatchTestCase { 28 dispatchTestCases := []dispatchTestCase{{ 29 name: "copyEmptyWhitespace", 30 dockerfile: `COPY 31 quux \ 32 bar`, 33 expectedError: "COPY requires at least one argument", 34 }, 35 { 36 name: "ONBUILD forbidden FROM", 37 dockerfile: "ONBUILD FROM scratch", 38 expectedError: "FROM isn't allowed as an ONBUILD trigger", 39 files: nil, 40 }, 41 { 42 name: "ONBUILD forbidden MAINTAINER", 43 dockerfile: "ONBUILD MAINTAINER docker.io", 44 expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger", 45 files: nil, 46 }, 47 { 48 name: "ARG two arguments", 49 dockerfile: "ARG foo bar", 50 expectedError: "ARG requires exactly one argument definition", 51 files: nil, 52 }, 53 { 54 name: "MAINTAINER unknown flag", 55 dockerfile: "MAINTAINER --boo joe@example.com", 56 expectedError: "Unknown flag: boo", 57 files: nil, 58 }, 59 { 60 name: "ADD multiple files to file", 61 dockerfile: "ADD file1.txt file2.txt test", 62 expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /", 63 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 64 }, 65 { 66 name: "JSON ADD multiple files to file", 67 dockerfile: `ADD ["file1.txt", "file2.txt", "test"]`, 68 expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /", 69 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 70 }, 71 { 72 name: "Wildcard ADD multiple files to file", 73 dockerfile: "ADD file*.txt test", 74 expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /", 75 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 76 }, 77 { 78 name: "Wildcard JSON ADD multiple files to file", 79 dockerfile: `ADD ["file*.txt", "test"]`, 80 expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /", 81 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 82 }, 83 { 84 name: "COPY multiple files to file", 85 dockerfile: "COPY file1.txt file2.txt test", 86 expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /", 87 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 88 }, 89 { 90 name: "JSON COPY multiple files to file", 91 dockerfile: `COPY ["file1.txt", "file2.txt", "test"]`, 92 expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /", 93 files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"}, 94 }, 95 { 96 name: "ADD multiple files to file with whitespace", 97 dockerfile: `ADD [ "test file1.txt", "test file2.txt", "test" ]`, 98 expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /", 99 files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"}, 100 }, 101 { 102 name: "COPY multiple files to file with whitespace", 103 dockerfile: `COPY [ "test file1.txt", "test file2.txt", "test" ]`, 104 expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /", 105 files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"}, 106 }, 107 { 108 name: "COPY wildcard no files", 109 dockerfile: `COPY file*.txt /tmp/`, 110 expectedError: "No source files were specified", 111 files: nil, 112 }, 113 { 114 name: "COPY url", 115 dockerfile: `COPY https://index.docker.io/robots.txt /`, 116 expectedError: "Source can't be a URL for COPY", 117 files: nil, 118 }, 119 { 120 name: "Chaining ONBUILD", 121 dockerfile: `ONBUILD ONBUILD RUN touch foobar`, 122 expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed", 123 files: nil, 124 }, 125 { 126 name: "Invalid instruction", 127 dockerfile: `foo bar`, 128 expectedError: "Unknown instruction: FOO", 129 files: nil, 130 }} 131 132 return dispatchTestCases 133 } 134 135 func TestDispatch(t *testing.T) { 136 testCases := initDispatchTestCases() 137 138 for _, testCase := range testCases { 139 executeTestCase(t, testCase) 140 } 141 } 142 143 func executeTestCase(t *testing.T, testCase dispatchTestCase) { 144 contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test") 145 defer cleanup() 146 147 for filename, content := range testCase.files { 148 createTestTempFile(t, contextDir, filename, content, 0777) 149 } 150 151 tarStream, err := archive.Tar(contextDir, archive.Uncompressed) 152 153 if err != nil { 154 t.Fatalf("Error when creating tar stream: %s", err) 155 } 156 157 defer func() { 158 if err = tarStream.Close(); err != nil { 159 t.Fatalf("Error when closing tar stream: %s", err) 160 } 161 }() 162 163 context, err := builder.MakeTarSumContext(tarStream) 164 165 if err != nil { 166 t.Fatalf("Error when creating tar context: %s", err) 167 } 168 169 defer func() { 170 if err = context.Close(); err != nil { 171 t.Fatalf("Error when closing tar context: %s", err) 172 } 173 }() 174 175 r := strings.NewReader(testCase.dockerfile) 176 n, err := parser.Parse(r) 177 178 if err != nil { 179 t.Fatalf("Error when parsing Dockerfile: %s", err) 180 } 181 182 config := &container.Config{} 183 options := &types.ImageBuildOptions{} 184 185 b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context} 186 187 err = b.dispatch(0, n.Children[0]) 188 189 if err == nil { 190 t.Fatalf("No error when executing test %s", testCase.name) 191 } 192 193 if !strings.Contains(err.Error(), testCase.expectedError) { 194 t.Fatalf("Wrong error message. Should be \"%s\". Got \"%s\"", testCase.expectedError, err.Error()) 195 } 196 197 } 198 199 // createTestTempDir creates a temporary directory for testing. 200 // It returns the created path and a cleanup function which is meant to be used as deferred call. 201 // When an error occurs, it terminates the test. 202 func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) { 203 path, err := ioutil.TempDir(dir, prefix) 204 205 if err != nil { 206 t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err) 207 } 208 209 return path, func() { 210 err = os.RemoveAll(path) 211 212 if err != nil { 213 t.Fatalf("Error when removing directory %s: %s", path, err) 214 } 215 } 216 } 217 218 // createTestTempFile creates a temporary file within dir with specific contents and permissions. 219 // When an error occurs, it terminates the test 220 func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string { 221 filePath := filepath.Join(dir, filename) 222 err := ioutil.WriteFile(filePath, []byte(contents), perm) 223 224 if err != nil { 225 t.Fatalf("Error when creating %s file: %s", filename, err) 226 } 227 228 return filePath 229 }