Skip to content

Commit

Permalink
Add unit tests for stagebuilder build and optimize
Browse files Browse the repository at this point in the history
* refactor stagebuilder to allow for more dependency injection
* add basic unit tests for build and optimize
* add fake types for use in unit tests
  • Loading branch information
cvgw committed Nov 26, 2019
1 parent e51cfd5 commit 69c8794
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 1 deletion.
11 changes: 10 additions & 1 deletion pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,27 @@ import (
// This is the size of an empty tar in Go
const emptyTarSize = 1024

type cachePusher func(*config.KanikoOptions, string, string, string) error
type snapShotter interface {
Init() error
TakeSnapshotFS() (string, error)
TakeSnapshot([]string) (string, error)
}

// stageBuilder contains all fields necessary to build one stage of a Dockerfile
type stageBuilder struct {
stage config.KanikoStage
image v1.Image
cf *v1.ConfigFile
snapshotter *snapshot.Snapshotter
snapshotter snapShotter
baseImageDigest string
opts *config.KanikoOptions
cmds []commands.DockerCommand
args *dockerfile.BuildArgs
crossStageDeps map[int][]string
digestMap map[string]v1.Hash
layerCache cache.LayerCache
pushCache cachePusher
}

// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
Expand Down
107 changes: 107 additions & 0 deletions pkg/executor/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ limitations under the License.
package executor

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"

"github.com/GoogleContainerTools/kaniko/pkg/commands"
"github.com/GoogleContainerTools/kaniko/pkg/config"
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
"github.com/GoogleContainerTools/kaniko/testutil"
Expand Down Expand Up @@ -462,3 +464,108 @@ func TestInitializeConfig(t *testing.T) {
testutil.CheckDeepEqual(t, tt.expected, actual.Config)
}
}

func Test_stageBuilder_optimize(t *testing.T) {
testCases := []struct {
opts *config.KanikoOptions
err bool
retrieve bool
}{
{
opts: &config.KanikoOptions{Cache: true},
err: true,
},
{
opts: &config.KanikoOptions{Cache: true},
retrieve: true,
},
{
opts: &config.KanikoOptions{Cache: false},
},
{
opts: &config.KanikoOptions{Cache: false},
retrieve: true,
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("Case %d", i), func(t *testing.T) {
cf := &v1.ConfigFile{}
snap := fakeSnapShotter{}
lc := fakeLayerCache{retrieve: tc.retrieve}
sb := &stageBuilder{opts: tc.opts, cf: cf, snapshotter: snap, layerCache: lc}
ck := CompositeCache{}
file, err := ioutil.TempFile("", "foo")
if err != nil {
t.Error(err)
}
command := MockDockerCommand{
contextFiles: []string{file.Name()},
cacheCommand: MockCachedDockerCommand{},
}
sb.cmds = []commands.DockerCommand{command}
err = sb.optimize(ck, cf.Config)
if err == nil {
t.Errorf("Expected error to be nil but was %v", err)
}
})
}
}

func Test_stageBuilder_build(t *testing.T) {
testCases := []struct {
opts *config.KanikoOptions
err bool
retrieve bool
}{
{
opts: &config.KanikoOptions{Cache: true},
err: true,
},
{
opts: &config.KanikoOptions{Cache: true},
retrieve: true,
},
{
opts: &config.KanikoOptions{Cache: false},
},
{
opts: &config.KanikoOptions{Cache: false},
retrieve: true,
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("Case %d", i), func(t *testing.T) {
file, err := ioutil.TempFile("", "foo")
if err != nil {
t.Error(err)
}

cf := &v1.ConfigFile{}
snap := fakeSnapShotter{file: file.Name()}
lc := fakeLayerCache{retrieve: tc.retrieve}
sb := &stageBuilder{opts: tc.opts, cf: cf, snapshotter: snap, layerCache: lc, pushCache: fakeCachePush}

command := MockDockerCommand{
contextFiles: []string{file.Name()},
cacheCommand: MockCachedDockerCommand{
contextFiles: []string{file.Name()},
},
}
sb.cmds = []commands.DockerCommand{command}
err = sb.build()
if err == nil {
if tc.err {
t.Error("Expected err but was nil")
}
} else {
if !tc.err {
if reflect.TypeOf(tc.err) != reflect.TypeOf(err) {
t.Errorf("Expected stopCacheErr but was %v", reflect.TypeOf(err))
}
} else {
t.Errorf("Expected error to be nil but was %v", err)
}
}
})
}
}
111 changes: 111 additions & 0 deletions pkg/executor/fakes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// for use in tests
package executor

import (
"errors"

"github.com/GoogleContainerTools/kaniko/pkg/commands"
"github.com/GoogleContainerTools/kaniko/pkg/config"
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
v1 "github.com/google/go-containerregistry/pkg/v1"
)

func fakeCachePush(_ *config.KanikoOptions, _, _, _ string) error {
return nil
}

type fakeSnapShotter struct {
file string
}

func (f fakeSnapShotter) Init() error { return nil }
func (f fakeSnapShotter) TakeSnapshotFS() (string, error) {
return f.file, nil
}
func (f fakeSnapShotter) TakeSnapshot(_ []string) (string, error) {
return f.file, nil
}

type MockDockerCommand struct {
contextFiles []string
cacheCommand commands.DockerCommand
}

func (m MockDockerCommand) ExecuteCommand(c *v1.Config, args *dockerfile.BuildArgs) error { return nil }
func (m MockDockerCommand) String() string {
return "meow"
}
func (m MockDockerCommand) FilesToSnapshot() []string {
return []string{"meow-snapshot-no-cache"}
}
func (m MockDockerCommand) CacheCommand(image v1.Image) commands.DockerCommand {
return m.cacheCommand
}
func (m MockDockerCommand) FilesUsedFromContext(c *v1.Config, args *dockerfile.BuildArgs) ([]string, error) {
return m.contextFiles, nil
}
func (m MockDockerCommand) MetadataOnly() bool {
return false
}
func (m MockDockerCommand) RequiresUnpackedFS() bool {
return false
}
func (m MockDockerCommand) ShouldCacheOutput() bool {
return true
}

type MockCachedDockerCommand struct {
contextFiles []string
}

func (m MockCachedDockerCommand) ExecuteCommand(c *v1.Config, args *dockerfile.BuildArgs) error {
return nil
}
func (m MockCachedDockerCommand) String() string {
return "meow"
}
func (m MockCachedDockerCommand) FilesToSnapshot() []string {
return []string{"meow-snapshot"}
}
func (m MockCachedDockerCommand) CacheCommand(image v1.Image) commands.DockerCommand {
return nil
}
func (m MockCachedDockerCommand) FilesUsedFromContext(c *v1.Config, args *dockerfile.BuildArgs) ([]string, error) {
return m.contextFiles, nil
}
func (m MockCachedDockerCommand) MetadataOnly() bool {
return false
}
func (m MockCachedDockerCommand) RequiresUnpackedFS() bool {
return false
}
func (m MockCachedDockerCommand) ShouldCacheOutput() bool {
return true
}

type fakeLayerCache struct {
retrieve bool
}

func (f fakeLayerCache) RetrieveLayer(_ string) (v1.Image, error) {
if !f.retrieve {
return nil, errors.New("could not find layer")
}
return nil, nil
}

0 comments on commit 69c8794

Please sign in to comment.