github.com/mgoltzsche/ctnr@v0.7.1-alpha/image/builder/dockerfile/dockerfile_test.go (about) 1 package dockerfile 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "os" 8 "path/filepath" 9 "sort" 10 "strconv" 11 "strings" 12 "testing" 13 14 "github.com/mgoltzsche/ctnr/pkg/idutils" 15 "github.com/opencontainers/go-digest" 16 "github.com/pkg/errors" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestDockerfileApply(t *testing.T) { 22 files, err := filepath.Glob("testfiles/*.test") 23 require.NoError(t, err) 24 applyStageTested := false 25 Files: 26 for _, file := range files { 27 fmt.Println("CASE ", file) 28 efile, err := os.Open(file[0:len(file)-4] + "expected") 29 require.NoError(t, err) 30 defer efile.Close() 31 b, err := ioutil.ReadAll(efile) 32 require.NoError(t, err, file) 33 expected := strings.TrimSpace(string(b)) 34 expectedLinesBr := strings.Split(expected, "\n") 35 expectedLines := make([]string, 0, len(expectedLinesBr)) 36 for _, eline := range expectedLinesBr { 37 if eline != "" { 38 expectedLines = append(expectedLines, eline) 39 } 40 } 41 testee := newTestee(t, file) 42 require.NoError(t, err, file) 43 mock := mockBuilder{returnErr: -1} 44 err = testee.Apply(&mock) 45 require.NoError(t, err) 46 for i, eline := range expectedLines { 47 aline := "" 48 if len(mock.ops) > i { 49 aline = mock.ops[i] 50 } 51 if eline != aline { 52 t.Errorf("%s: line %d not equal:\n expected: %s\n received: %s", filepath.Base(file), i, eline, aline) 53 continue Files 54 } 55 } 56 if len(expectedLines) < len(mock.ops) { 57 t.Errorf("%s: testee did unexpected tailing operation: %s", filepath.Base(file), mock.ops[len(expectedLines)]) 58 } 59 60 // Test error handling 61 returnErr := 0 62 lastOpCount := 0 63 for { 64 testee = newTestee(t, file) 65 mock = mockBuilder{returnErr: returnErr} 66 err = testee.Apply(&mock) 67 if mock.returnCount == lastOpCount { 68 break 69 } 70 if mock.returnCount != lastOpCount+1 { 71 t.Errorf("%s: builder error not handled in %q", filepath.Base(file), mock.ops[len(mock.ops)-1]) 72 break 73 } 74 if err == nil { 75 t.Errorf("%s: builder error not returned in %q", filepath.Base(file), mock.ops[len(mock.ops)-1]) 76 break 77 } 78 lastOpCount = mock.returnCount 79 returnErr += 1 80 } 81 if lastOpCount < 2 { 82 t.Errorf("%s: test failed too early on builder error (or case contains <2 instructions)", file) 83 } 84 85 // Test single stage execution 86 if strings.Contains(file, "multistage") { 87 applyStageTested = true 88 expectedOps := mock.ops[mock.stage2OpOffset:mock.stage6OpOffset] 89 //panic(fmt.Sprintf("%d %s", mock.stage2OpOffset, strings.Join(expectedOps, "\n"))) 90 testee := newTestee(t, file) 91 require.NoError(t, err, file) 92 mock = mockBuilder{returnErr: -1, stageCount: 1} 93 err = testee.Target("slim") 94 require.NoError(t, err, file) 95 err = testee.Apply(&mock) 96 require.NoError(t, err, file) 97 if !assert.Equal(t, expectedOps, mock.ops, filepath.Base(file)+": apply slim stage") { 98 t.FailNow() 99 } 100 } 101 } 102 if !applyStageTested { 103 t.Errorf("ApplyStage() has not been tested") 104 } 105 } 106 107 func newTestee(t *testing.T, file string) *DockerfileBuilder { 108 args := map[string]string{ 109 "argp": "pval", 110 } 111 contents, err := ioutil.ReadFile(file) 112 require.NoError(t, err) 113 r, err := LoadDockerfile(contents, "./ctx", args, log.New(os.Stderr, "warn: "+file+":", 0)) 114 require.NoError(t, err) 115 return r 116 } 117 118 type mockBuilder struct { 119 ops []string 120 returnErr int 121 returnCount int 122 stageCount int 123 stage2OpOffset int 124 stage6OpOffset int 125 } 126 127 func (s *mockBuilder) err() (err error) { 128 if s.returnCount == s.returnErr { 129 err = errors.New("expected error") 130 } 131 s.returnCount++ 132 return 133 } 134 135 func (s *mockBuilder) add(op string) { 136 s.ops = append(s.ops, op) 137 } 138 139 func (s *mockBuilder) Image() digest.Digest { 140 return digest.Digest("stage" + strconv.Itoa(s.stageCount-1) + "-image") 141 } 142 143 func (s *mockBuilder) AddEnv(e map[string]string) error { 144 s.add("ENV " + mapToString(e)) 145 return s.err() 146 } 147 148 func (s *mockBuilder) AddExposedPorts(p []string) error { 149 s.add("EXPOSE " + strings.Join(p, " ")) 150 return s.err() 151 } 152 153 func (s *mockBuilder) AddLabels(l map[string]string) error { 154 s.add("LABEL " + mapToString(l)) 155 return s.err() 156 } 157 158 func (s *mockBuilder) AddVolumes(v []string) error { 159 s.add("VOLUME " + sliceToString(v)) 160 return s.err() 161 } 162 163 func (s *mockBuilder) AddFiles(srcDir string, srcPattern []string, dest string, user *idutils.User) error { 164 u := "nil" 165 if user != nil { 166 u = user.String() 167 } 168 s.add(fmt.Sprintf("ADD dir=%q %s %q %s", srcDir, sliceToString(srcPattern), dest, u)) 169 return s.err() 170 } 171 172 func (s *mockBuilder) CopyFiles(srcDir string, srcPattern []string, dest string, user *idutils.User) error { 173 u := "nil" 174 if user != nil { 175 u = user.String() 176 } 177 s.add(fmt.Sprintf("COPY dir=%q %s %q %s", srcDir, sliceToString(srcPattern), dest, u)) 178 return s.err() 179 } 180 181 func (s *mockBuilder) CopyFilesFromImage(srcImage string, srcPattern []string, dest string, user *idutils.User) error { 182 u := "nil" 183 if user != nil { 184 u = user.String() 185 } 186 s.add(fmt.Sprintf("COPY image=%q %s %q %s", srcImage, sliceToString(srcPattern), dest, u)) 187 return s.err() 188 } 189 190 func (s *mockBuilder) FromImage(name string) error { 191 s.add("FROM " + name) 192 s.stageCount++ 193 if s.stageCount == 2 { 194 s.stage2OpOffset = len(s.ops) - 1 195 } 196 if s.stageCount == 6 { 197 s.stage6OpOffset = len(s.ops) - 1 198 } 199 return s.err() 200 } 201 202 func (s *mockBuilder) Run(args []string, addEnv map[string]string) error { 203 s.add("RUN " + strings.TrimSpace(mapToString(addEnv)+" "+sliceToString(args))) 204 return s.err() 205 } 206 207 func (s *mockBuilder) SetAuthor(a string) error { 208 s.add("AUTHOR " + strconv.Quote(a)) 209 return s.err() 210 } 211 212 func (s *mockBuilder) SetCmd(c []string) error { 213 s.add("CMD " + sliceToString(c)) 214 return s.err() 215 } 216 217 func (s *mockBuilder) SetEntrypoint(e []string) error { 218 s.add("ENTRYPOINT " + sliceToString(e)) 219 return s.err() 220 } 221 222 func (s *mockBuilder) SetStopSignal(sig string) error { 223 s.add("STOPSIGNAL " + sig) 224 return s.err() 225 } 226 227 func (s *mockBuilder) SetUser(u string) error { 228 s.add("USER " + u) 229 return s.err() 230 } 231 232 func (s *mockBuilder) SetWorkingDir(w string) error { 233 s.add("WORKDIR " + w) 234 return s.err() 235 } 236 237 func mapToString(m map[string]string) string { 238 l := []string{} 239 for k, v := range m { 240 l = append(l, strconv.Quote(k)+"="+strconv.Quote(v)) 241 } 242 sort.Strings(l) 243 return strings.Join(l, " ") 244 } 245 246 func sliceToString(l []string) string { 247 r := []string{} 248 for _, e := range l { 249 r = append(r, strconv.Quote(e)) 250 } 251 return strings.Join(r, " ") 252 }