github.com/olljanat/moby@v1.13.1/builder/dockerfile/evaluator_test.go (about)

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