github.com/mgoltzsche/ctnr@v0.7.1-alpha/image/builder/imagebuilder_test.go (about) 1 package builder 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/containers/image/types" 14 bstore "github.com/mgoltzsche/ctnr/bundle/store" 15 "github.com/mgoltzsche/ctnr/image" 16 "github.com/mgoltzsche/ctnr/image/builder/dockerfile" 17 istore "github.com/mgoltzsche/ctnr/image/store" 18 extlog "github.com/mgoltzsche/ctnr/pkg/log" 19 "github.com/mgoltzsche/ctnr/pkg/log/logrusadapt" 20 "github.com/mgoltzsche/ctnr/store" 21 "github.com/sirupsen/logrus" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "github.com/xeipuuv/gojsonpointer" 25 ) 26 27 // Integration test 28 func TestImageBuilder(t *testing.T) { 29 files, err := filepath.Glob("dockerfile/testfiles/*.test") 30 require.NoError(t, err) 31 tmpDir, err := ioutil.TempDir("", ".imagebuildertestdata-") 32 require.NoError(t, err) 33 defer os.RemoveAll(tmpDir) 34 srcDir := filepath.Join(tmpDir, "src") 35 err = os.Mkdir(srcDir, 0755) 36 require.NoError(t, err) 37 for _, f := range []string{"entrypoint.sh", "cfg-a.conf", "cfg-b.conf"} { 38 err = ioutil.WriteFile(filepath.Join(srcDir, f), []byte("x"), 0740) 39 require.NoError(t, err) 40 } 41 wd, err := os.Getwd() 42 require.NoError(t, err) 43 err = os.Chdir(tmpDir) 44 require.NoError(t, err) 45 defer os.Chdir(wd) 46 logger := logrus.New() 47 logger.Level = logrus.DebugLevel 48 logger.Out = os.Stdout 49 loggers := extlog.Loggers{ 50 Error: logrusadapt.NewErrorLogger(logger), 51 Warn: logrusadapt.NewWarnLogger(logger), 52 Info: logrusadapt.NewInfoLogger(logger), 53 Debug: logrusadapt.NewDebugLogger(logger), 54 } 55 56 var baseImg *image.Image 57 58 for _, file := range files { 59 if file == "dockerfile/testfiles/10-add.test" { 60 continue 61 } 62 loggers.Info.Println("\n\n TEST CASE " + file + "\n") 63 withNewTestee(t, tmpDir, loggers, func(testee *ImageBuilder) { 64 // Read input & assertion from file 65 b, err := ioutil.ReadFile(filepath.Join(wd, file)) 66 require.NoError(t, err, filepath.Base(file)) 67 68 // Run test 69 args := map[string]string{ 70 "argp": "pval", 71 } 72 testee.SetImageResolver(ResolveDockerImage) 73 startTime := time.Now() 74 df, err := dockerfile.LoadDockerfile(b, srcDir, args, loggers.Warn) 75 require.NoError(t, err, filepath.Base(file)) 76 err = df.Apply(testee) 77 require.NoError(t, err, filepath.Base(file)) 78 imageId := testee.Image() 79 assert.NotNil(t, imageId, "resulting image", filepath.Base(file)) 80 err = imageId.Validate() 81 require.NoError(t, err, "resulting image ID", filepath.Base(file)) 82 if baseImg == nil { 83 img, err := testee.images.ImageByName("docker://alpine:3.7") 84 require.NoError(t, err, "get common base image from store after build completed") 85 baseImg = &img 86 } 87 img, err := testee.images.Image(imageId) 88 require.NoError(t, err, filepath.Base(file)+" load resulting image") 89 elapsedTime1 := time.Now().Sub(startTime) 90 cfg := img.Config 91 92 // Assert 93 assertions := []string{} 94 for _, line := range strings.Split(string(b), "\n") { 95 if strings.HasPrefix(line, "# ASSERT ") { 96 assertions = append(assertions, line[9:]) 97 } 98 } 99 if len(assertions) == 0 { 100 t.Errorf("No assertion found in %s", filepath.Base(file)) 101 t.FailNow() 102 } 103 104 for _, assertionExpr := range assertions { 105 loggers.Info.Println("ASSERTION "+file+":", assertionExpr) 106 switch assertionExpr[:3] { 107 case "RUN": 108 // Assert by running command 109 cmd := assertionExpr[4:] 110 err = testee.Run([]string{"/bin/sh", "-c", cmd}, nil) 111 require.NoError(t, err, filepath.Base(file)+" assertion") 112 case "ERR": 113 // Assert failing command results in error 114 cmd := assertionExpr[4:] 115 err = testee.Run([]string{"/bin/sh", "-c", cmd}, nil) 116 require.Error(t, err, filepath.Base(file)+" - should fail") 117 case "CFG": 118 // Assert by JSON query 119 query := assertionExpr[4:] 120 spacePos := strings.Index(query, "=") 121 expected := query[spacePos+1:] 122 query = query[:spacePos] 123 assertPathEqual(t, &cfg, query, expected, filepath.Base(file)+" assertion query: "+query) 124 case "STG": 125 startTime = time.Now() 126 stage := strings.TrimSpace(assertionExpr[4:]) 127 df, err := dockerfile.LoadDockerfile(b, srcDir, args, loggers.Warn) 128 require.NoError(t, err, filepath.Base(file)+" assertion: stage load") 129 err = df.Target(stage) 130 require.NoError(t, err, filepath.Base(file)+" assertion: set target") 131 err = df.Apply(testee) 132 require.NoError(t, err, filepath.Base(file)+" assertion: apply stage") 133 // Test if the build was cached since it has been built previously 134 elapsedTime2 := time.Now().Sub(startTime) 135 if elapsedTime2 > elapsedTime1/2 { 136 t.Errorf(filepath.Base(file)+" assertion: stage %q execution took longer than half the full execution previously", stage) 137 t.FailNow() 138 } 139 default: 140 t.Errorf("Unsupported assertion in %s: %q", filepath.Base(file), assertionExpr) 141 t.FailNow() 142 } 143 } 144 145 // Test image size: image is too big it is likely that fsspec integration doesn't work 146 if img.Size() >= baseImg.Size()*2 { 147 t.Errorf("the whole base image seems to be copied into the next layer because new image size >= base image size * 2") 148 t.FailNow() 149 } 150 }) 151 } 152 } 153 154 func assertPathEqual(t *testing.T, o interface{}, query, expected, msg string) { 155 jp, err := gojsonpointer.NewJsonPointer(query) 156 require.NoError(t, err, msg) 157 jsonDoc := map[string]interface{}{} 158 b, err := json.Marshal(&o) 159 require.NoError(t, err, msg) 160 err = json.Unmarshal(b, &jsonDoc) 161 require.NoError(t, err, msg) 162 valueStr := "" 163 match, _, err := jp.Get(jsonDoc) 164 if expected != "" { 165 require.NoError(t, err, msg) 166 } 167 if match != nil { 168 valueStr = fmt.Sprintf("%s", match) 169 } 170 if !assert.Equal(t, expected, valueStr, msg) { 171 t.FailNow() 172 } 173 } 174 175 func withNewTestee(t *testing.T, tmpDir string, loggers extlog.Loggers, assertions func(*ImageBuilder)) { 176 ctx := &types.SystemContext{DockerInsecureSkipTLSVerify: true} 177 178 // Init image store 179 storero, err := store.NewStore(filepath.Join(tmpDir, "image-store"), true, ctx, istore.TrustPolicyInsecure(), loggers) 180 require.NoError(t, err) 181 lockedStore, err := storero.OpenLockedImageStore() 182 require.NoError(t, err) 183 defer func() { 184 if err := lockedStore.Close(); err != nil { 185 t.Error("failed to close locked store: ", err) 186 } 187 }() 188 189 // Init bundle store 190 bundleDir := filepath.Join(tmpDir, "bundle-store") 191 bundleStore := bstore.NewBundleStore(bundleDir, loggers.Info, loggers.Debug) 192 193 // Init testee 194 builderTmpDir := filepath.Join(tmpDir, "tmp") 195 testee := NewImageBuilder(ImageBuildConfig{ 196 Images: lockedStore, 197 Bundles: bundleStore, 198 Cache: NewImageBuildCache(filepath.Join(tmpDir, "cache"), loggers.Warn), 199 Tempfs: builderTmpDir, 200 RunRoot: filepath.Join(tmpDir, "run"), 201 Rootless: true, 202 PRoot: "", // TODO: also test using proot 203 RemoveSucceededBundles: true, 204 RemoveFailedBundle: true, 205 Loggers: loggers, 206 }) 207 defer func() { 208 if err := testee.Close(); err != nil { 209 t.Error("failed to close image builder: ", err) 210 } 211 }() 212 213 // Do tests 214 assertions(testee) 215 216 // Close builder 217 require.NoError(t, testee.Close(), "close image builder") 218 219 // Assert no temp files left 220 if _, err = os.Stat(builderTmpDir); err == nil { 221 files, err := ioutil.ReadDir(builderTmpDir) 222 require.NoError(t, err) 223 if !assert.True(t, len(files) == 0, "builder temp dir should contain no files after closed but found: %v", toFileNames(files)) { 224 t.FailNow() 225 } 226 } 227 228 // Assert no bundles left 229 if _, err = os.Stat(bundleDir); err == nil { 230 files, err := ioutil.ReadDir(bundleDir) 231 require.NoError(t, err) 232 if !assert.True(t, len(files) == 0, "bundle store dir should contain no files after closed but found: %v", toFileNames(files)) { 233 t.FailNow() 234 } 235 } 236 } 237 238 func toFileNames(files []os.FileInfo) []string { 239 s := make([]string, len(files)) 240 for i, e := range files { 241 s[i] = e.Name() 242 } 243 return s 244 }