github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/scheduler_test.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package build
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/google/go-cmp/cmp"
    31  
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    36  	"github.com/GoogleContainerTools/skaffold/testutil"
    37  	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
    38  )
    39  
    40  func TestGetBuild(t *testing.T) {
    41  	tests := []struct {
    42  		description   string
    43  		buildArtifact ArtifactBuilder
    44  		tags          tag.ImageTags
    45  		expectedTag   string
    46  		expectedOut   string
    47  		shouldErr     bool
    48  	}{
    49  		{
    50  			description: "build succeeds",
    51  			buildArtifact: func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
    52  				out.Write([]byte("build succeeds"))
    53  				return fmt.Sprintf("%s@sha256:abac", tag), nil
    54  			},
    55  			tags: tag.ImageTags{
    56  				"skaffold/image1": "skaffold/image1:v0.0.1",
    57  				"skaffold/image2": "skaffold/image2:v0.0.2",
    58  			},
    59  			expectedTag: "skaffold/image1:v0.0.1@sha256:abac",
    60  			expectedOut: "build succeeds",
    61  		},
    62  		{
    63  			description: "tag with ko scheme prefix and Go import path with uppercase characters is sanitized",
    64  			buildArtifact: func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
    65  				out.Write([]byte("build succeeds"))
    66  				return fmt.Sprintf("%s@sha256:abac", tag), nil
    67  			},
    68  			tags: tag.ImageTags{
    69  				"skaffold/image1": "ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold:v0.0.1",
    70  			},
    71  			expectedTag: "github.com/googlecontainertools/skaffold/cmd/skaffold:v0.0.1@sha256:abac",
    72  			expectedOut: "build succeeds",
    73  		},
    74  		{
    75  			description: "build fails",
    76  			buildArtifact: func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
    77  				return "", fmt.Errorf("build fails")
    78  			},
    79  			tags: tag.ImageTags{
    80  				"skaffold/image1": "",
    81  			},
    82  			expectedOut: "",
    83  			shouldErr:   true,
    84  		},
    85  		{
    86  			description: "tag not found",
    87  			tags:        tag.ImageTags{},
    88  			expectedOut: "",
    89  			shouldErr:   true,
    90  		},
    91  	}
    92  	for _, test := range tests {
    93  		testutil.Run(t, test.description, func(t *testutil.T) {
    94  			out := new(bytes.Buffer)
    95  
    96  			artifact := &latest.Artifact{ImageName: "skaffold/image1"}
    97  			got, err := performBuild(context.Background(), out, test.tags, platform.Resolver{}, artifact, test.buildArtifact)
    98  
    99  			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedTag, got)
   100  			t.CheckDeepEqual(test.expectedOut, out.String())
   101  		})
   102  	}
   103  }
   104  
   105  func TestFormatResults(t *testing.T) {
   106  	tests := []struct {
   107  		description string
   108  		artifacts   []*latest.Artifact
   109  		expected    []graph.Artifact
   110  		results     map[string]interface{}
   111  		shouldErr   bool
   112  	}{
   113  		{
   114  			description: "all builds completely successfully",
   115  			artifacts: []*latest.Artifact{
   116  				{ImageName: "skaffold/image1"},
   117  				{ImageName: "skaffold/image2"},
   118  			},
   119  			expected: []graph.Artifact{
   120  				{ImageName: "skaffold/image1", Tag: "skaffold/image1:v0.0.1@sha256:abac"},
   121  				{ImageName: "skaffold/image2", Tag: "skaffold/image2:v0.0.2@sha256:abac"},
   122  			},
   123  			results: map[string]interface{}{
   124  				"skaffold/image1": "skaffold/image1:v0.0.1@sha256:abac",
   125  				"skaffold/image2": "skaffold/image2:v0.0.2@sha256:abac",
   126  			},
   127  		},
   128  		{
   129  			description: "no build result produced for a build",
   130  			artifacts: []*latest.Artifact{
   131  				{ImageName: "skaffold/image1"},
   132  				{ImageName: "skaffold/image2"},
   133  			},
   134  			expected: nil,
   135  			results: map[string]interface{}{
   136  				"skaffold/image1": "skaffold/image1:v0.0.1@sha256:abac",
   137  			},
   138  			shouldErr: true,
   139  		},
   140  	}
   141  	for _, test := range tests {
   142  		testutil.Run(t, test.description, func(t *testutil.T) {
   143  			m := new(sync.Map)
   144  			for k, v := range test.results {
   145  				m.Store(k, v)
   146  			}
   147  			results := &artifactStoreImpl{m: m}
   148  			got, err := results.GetArtifacts(test.artifacts)
   149  
   150  			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, got)
   151  		})
   152  	}
   153  }
   154  
   155  func TestInOrder(t *testing.T) {
   156  	tests := []struct {
   157  		description string
   158  		buildFunc   ArtifactBuilder
   159  		expected    string
   160  	}{
   161  		{
   162  			description: "short and nice build log",
   163  			expected:    "Building 2 artifacts in parallel\nBuilding [skaffold/image1]...\nshort\nBuild [skaffold/image1] succeeded\nBuilding [skaffold/image2]...\nshort\nBuild [skaffold/image2] succeeded\n",
   164  			buildFunc: func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   165  				out.Write([]byte("short\n"))
   166  				return fmt.Sprintf("%s:tag", artifact.ImageName), nil
   167  			},
   168  		},
   169  		{
   170  			description: "long build log gets printed correctly",
   171  			expected: `Building 2 artifacts in parallel
   172  Building [skaffold/image1]...
   173  This is a long string more than 10 bytes.
   174  And new lines
   175  Build [skaffold/image1] succeeded
   176  Building [skaffold/image2]...
   177  This is a long string more than 10 bytes.
   178  And new lines
   179  Build [skaffold/image2] succeeded
   180  `,
   181  			buildFunc: func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   182  				out.Write([]byte("This is a long string more than 10 bytes.\nAnd new lines\n"))
   183  				return fmt.Sprintf("%s:tag", artifact.ImageName), nil
   184  			},
   185  		},
   186  	}
   187  	for _, test := range tests {
   188  		testutil.Run(t, test.description, func(t *testutil.T) {
   189  			out := new(bytes.Buffer)
   190  			artifacts := []*latest.Artifact{
   191  				{ImageName: "skaffold/image1"},
   192  				{ImageName: "skaffold/image2", Dependencies: []*latest.ArtifactDependency{{ImageName: "skaffold/image1"}}},
   193  			}
   194  			tags := tag.ImageTags{
   195  				"skaffold/image1": "skaffold/image1:v0.0.1",
   196  				"skaffold/image2": "skaffold/image2:v0.0.2",
   197  			}
   198  			initializeEvents()
   199  
   200  			InOrder(context.Background(), out, tags, platform.Resolver{}, artifacts, test.buildFunc, 0, NewArtifactStore())
   201  
   202  			t.CheckDeepEqual(test.expected, out.String())
   203  		})
   204  	}
   205  }
   206  
   207  func TestInOrderConcurrency(t *testing.T) {
   208  	tests := []struct {
   209  		artifacts      int
   210  		limit          int
   211  		maxConcurrency int
   212  	}{
   213  		{
   214  			artifacts:      10,
   215  			limit:          0, // default - no limit
   216  			maxConcurrency: 10,
   217  		},
   218  		{
   219  			artifacts:      50,
   220  			limit:          1,
   221  			maxConcurrency: 1,
   222  		},
   223  		{
   224  			artifacts:      50,
   225  			limit:          10,
   226  			maxConcurrency: 10,
   227  		},
   228  	}
   229  	for _, test := range tests {
   230  		testutil.Run(t, fmt.Sprintf("%d artifacts, max concurrency=%d", test.artifacts, test.limit), func(t *testutil.T) {
   231  			var artifacts []*latest.Artifact
   232  			tags := tag.ImageTags{}
   233  
   234  			for i := 0; i < test.artifacts; i++ {
   235  				imageName := fmt.Sprintf("skaffold/image%d", i)
   236  				tag := fmt.Sprintf("skaffold/image%d:tag", i)
   237  
   238  				artifacts = append(artifacts, &latest.Artifact{ImageName: imageName})
   239  				tags[imageName] = tag
   240  			}
   241  
   242  			var actualConcurrency int32
   243  
   244  			builder := func(_ context.Context, _ io.Writer, _ *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   245  				if atomic.AddInt32(&actualConcurrency, 1) > int32(test.maxConcurrency) {
   246  					return "", fmt.Errorf("only %d build can run at a time", test.maxConcurrency)
   247  				}
   248  				time.Sleep(5 * time.Millisecond)
   249  				atomic.AddInt32(&actualConcurrency, -1)
   250  
   251  				return tag, nil
   252  			}
   253  
   254  			initializeEvents()
   255  			results, err := InOrder(context.Background(), ioutil.Discard, tags, platform.Resolver{}, artifacts, builder, test.limit, NewArtifactStore())
   256  
   257  			t.CheckNoError(err)
   258  			t.CheckDeepEqual(test.artifacts, len(results))
   259  		})
   260  	}
   261  }
   262  
   263  func TestInOrderForArgs(t *testing.T) {
   264  	tests := []struct {
   265  		description   string
   266  		buildArtifact ArtifactBuilder
   267  		artifactLen   int
   268  		concurrency   int
   269  		dependency    map[int][]int
   270  		expected      []graph.Artifact
   271  		err           error
   272  	}{
   273  		{
   274  			description: "runs in parallel for 2 artifacts with no dependency",
   275  			buildArtifact: func(_ context.Context, _ io.Writer, _ *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   276  				return tag, nil
   277  			},
   278  			artifactLen: 2,
   279  			expected: []graph.Artifact{
   280  				{ImageName: "artifact1", Tag: "artifact1@tag1"},
   281  				{ImageName: "artifact2", Tag: "artifact2@tag2"},
   282  			},
   283  		},
   284  		{
   285  			description: "runs in parallel for 5 artifacts with dependencies",
   286  			buildArtifact: func(_ context.Context, _ io.Writer, _ *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   287  				return tag, nil
   288  			},
   289  			dependency: map[int][]int{
   290  				0: {2, 3},
   291  				1: {3},
   292  				2: {1},
   293  				3: {4},
   294  			},
   295  			artifactLen: 5,
   296  			expected: []graph.Artifact{
   297  				{ImageName: "artifact1", Tag: "artifact1@tag1"},
   298  				{ImageName: "artifact2", Tag: "artifact2@tag2"},
   299  				{ImageName: "artifact3", Tag: "artifact3@tag3"},
   300  				{ImageName: "artifact4", Tag: "artifact4@tag4"},
   301  				{ImageName: "artifact5", Tag: "artifact5@tag5"},
   302  			},
   303  		},
   304  		{
   305  			description: "runs with max concurrency of 2 for 5 artifacts with dependencies",
   306  			buildArtifact: func(_ context.Context, _ io.Writer, _ *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   307  				return tag, nil
   308  			},
   309  			dependency: map[int][]int{
   310  				0: {2, 3},
   311  				1: {3},
   312  				2: {1},
   313  				3: {4},
   314  			},
   315  			artifactLen: 5,
   316  			concurrency: 2,
   317  			expected: []graph.Artifact{
   318  				{ImageName: "artifact1", Tag: "artifact1@tag1"},
   319  				{ImageName: "artifact2", Tag: "artifact2@tag2"},
   320  				{ImageName: "artifact3", Tag: "artifact3@tag3"},
   321  				{ImageName: "artifact4", Tag: "artifact4@tag4"},
   322  				{ImageName: "artifact5", Tag: "artifact5@tag5"},
   323  			},
   324  		},
   325  		{
   326  			description: "runs in parallel should return for 0 artifacts",
   327  			artifactLen: 0,
   328  			expected:    nil,
   329  		},
   330  		{
   331  			description: "build fails for artifacts without dependencies",
   332  			buildArtifact: func(c context.Context, _ io.Writer, a *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   333  				if a.ImageName == "artifact2" {
   334  					return "", fmt.Errorf(`some error occurred while building "artifact2"`)
   335  				}
   336  				select {
   337  				case <-c.Done():
   338  					return "", c.Err()
   339  				case <-time.After(5 * time.Second):
   340  					return tag, nil
   341  				}
   342  			},
   343  			artifactLen: 5,
   344  			expected:    nil,
   345  			err:         fmt.Errorf(`build [artifact2] failed: %w`, fmt.Errorf(`some error occurred while building "artifact2"`)),
   346  		},
   347  		{
   348  			description: "build fails for artifacts with dependencies",
   349  			buildArtifact: func(_ context.Context, _ io.Writer, a *latest.Artifact, tag string, _ platform.Matcher) (string, error) {
   350  				if a.ImageName == "artifact2" {
   351  					return "", fmt.Errorf(`some error occurred while building "artifact2"`)
   352  				}
   353  				return tag, nil
   354  			},
   355  			dependency: map[int][]int{
   356  				0: {1},
   357  				1: {2},
   358  				2: {3},
   359  				3: {4},
   360  			},
   361  			artifactLen: 5,
   362  			expected:    nil,
   363  			err:         fmt.Errorf(`build [artifact2] failed: %w`, fmt.Errorf(`some error occurred while building "artifact2"`)),
   364  		},
   365  	}
   366  	for _, test := range tests {
   367  		testutil.Run(t, test.description, func(t *testutil.T) {
   368  			artifacts := make([]*latest.Artifact, test.artifactLen)
   369  			tags := tag.ImageTags{}
   370  			for i := 0; i < test.artifactLen; i++ {
   371  				a := fmt.Sprintf("artifact%d", i+1)
   372  				artifacts[i] = &latest.Artifact{ImageName: a}
   373  				tags[a] = fmt.Sprintf("%s@tag%d", a, i+1)
   374  			}
   375  
   376  			setDependencies(artifacts, test.dependency)
   377  			initializeEvents()
   378  			actual, err := InOrder(context.Background(), ioutil.Discard, tags, platform.Resolver{}, artifacts, test.buildArtifact, test.concurrency, NewArtifactStore())
   379  
   380  			t.CheckDeepEqual(test.expected, actual)
   381  			t.CheckDeepEqual(test.err, err, cmp.Comparer(errorsComparer))
   382  		})
   383  	}
   384  }
   385  
   386  // setDependencies constructs a graph of artifact dependencies using the map as an adjacency list representation of indices in the artifacts array.
   387  // For example:
   388  // m = {
   389  //    0 : {1, 2},
   390  //    2 : {3},
   391  //}
   392  // implies that a[0] artifact depends on a[1] and a[2]; and a[2] depends on a[3].
   393  
   394  func setDependencies(a []*latest.Artifact, d map[int][]int) {
   395  	for k, dep := range d {
   396  		for i := range dep {
   397  			a[k].Dependencies = append(a[k].Dependencies, &latest.ArtifactDependency{
   398  				ImageName: a[dep[i]].ImageName,
   399  			})
   400  		}
   401  	}
   402  }
   403  
   404  func initializeEvents() {
   405  	pipes := []latest.Pipeline{{
   406  		Deploy: latest.DeployConfig{},
   407  		Build: latest.BuildConfig{
   408  			BuildType: latest.BuildType{
   409  				LocalBuild: &latest.LocalBuild{},
   410  			},
   411  		},
   412  	}}
   413  	testEvent.InitializeState(pipes)
   414  }
   415  
   416  func errorsComparer(a, b error) bool {
   417  	if a == nil && b == nil {
   418  		return true
   419  	}
   420  	if a == nil || b == nil {
   421  		return false
   422  	}
   423  	return a.Error() == b.Error()
   424  }