github.com/YousefHaggyHeroku/pack@v1.5.5/internal/commands/build_test.go (about)

     1  package commands_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"testing"
    11  
    12  	pubcfg "github.com/YousefHaggyHeroku/pack/config"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	"github.com/heroku/color"
    16  	"github.com/pkg/errors"
    17  	"github.com/sclevine/spec"
    18  	"github.com/sclevine/spec/report"
    19  	"github.com/spf13/cobra"
    20  
    21  	"github.com/YousefHaggyHeroku/pack"
    22  	"github.com/YousefHaggyHeroku/pack/internal/commands"
    23  	"github.com/YousefHaggyHeroku/pack/internal/commands/testmocks"
    24  	"github.com/YousefHaggyHeroku/pack/internal/config"
    25  	ilogging "github.com/YousefHaggyHeroku/pack/internal/logging"
    26  	h "github.com/YousefHaggyHeroku/pack/testhelpers"
    27  )
    28  
    29  func TestBuildCommand(t *testing.T) {
    30  	color.Disable(true)
    31  	defer color.Disable(false)
    32  
    33  	spec.Run(t, "Commands", testBuildCommand, spec.Random(), spec.Report(report.Terminal{}))
    34  }
    35  
    36  func testBuildCommand(t *testing.T, when spec.G, it spec.S) {
    37  	var (
    38  		command        *cobra.Command
    39  		logger         *ilogging.LogWithWriters
    40  		outBuf         bytes.Buffer
    41  		mockController *gomock.Controller
    42  		mockClient     *testmocks.MockPackClient
    43  		cfg            config.Config
    44  	)
    45  
    46  	it.Before(func() {
    47  		logger = ilogging.NewLogWithWriters(&outBuf, &outBuf)
    48  		cfg = config.Config{}
    49  		mockController = gomock.NewController(t)
    50  		mockClient = testmocks.NewMockPackClient(mockController)
    51  
    52  		command = commands.Build(logger, cfg, mockClient)
    53  	})
    54  
    55  	when("#BuildCommand", func() {
    56  		when("no builder is specified", func() {
    57  			it("returns a soft error", func() {
    58  				mockClient.EXPECT().InspectBuilder(gomock.Any(), false).Return(&pack.BuilderInfo{
    59  					Description: "",
    60  				}, nil).AnyTimes()
    61  
    62  				command.SetArgs([]string{"image"})
    63  				err := command.Execute()
    64  				h.AssertError(t, err, pack.NewSoftError().Error())
    65  			})
    66  		})
    67  
    68  		when("a builder and image are set", func() {
    69  			it("builds an image with a builder", func() {
    70  				mockClient.EXPECT().
    71  					Build(gomock.Any(), EqBuildOptionsWithImage("my-builder", "image")).
    72  					Return(nil)
    73  
    74  				command.SetArgs([]string{"--builder", "my-builder", "image"})
    75  				h.AssertNil(t, command.Execute())
    76  			})
    77  
    78  			it("builds an image with a builder short command arg", func() {
    79  				mockClient.EXPECT().
    80  					Build(gomock.Any(), EqBuildOptionsWithImage("my-builder", "image")).
    81  					Return(nil)
    82  
    83  				logger.WantVerbose(true)
    84  				command.SetArgs([]string{"-B", "my-builder", "image"})
    85  				h.AssertNil(t, command.Execute())
    86  				h.AssertContains(t, outBuf.String(), "Builder 'my-builder' is untrusted")
    87  			})
    88  
    89  			when("the builder is trusted", func() {
    90  				it("sets the trust builder option", func() {
    91  					mockClient.EXPECT().
    92  						Build(gomock.Any(), EqBuildOptionsWithTrustedBuilder(true)).
    93  						Return(nil)
    94  
    95  					cfg := config.Config{TrustedBuilders: []config.TrustedBuilder{{Name: "my-builder"}}}
    96  					command := commands.Build(logger, cfg, mockClient)
    97  
    98  					logger.WantVerbose(true)
    99  					command.SetArgs([]string{"image", "--builder", "my-builder"})
   100  					h.AssertNil(t, command.Execute())
   101  					h.AssertContains(t, outBuf.String(), "Builder 'my-builder' is trusted")
   102  				})
   103  			})
   104  
   105  			when("the builder is suggested", func() {
   106  				it("sets the trust builder option", func() {
   107  					mockClient.EXPECT().
   108  						Build(gomock.Any(), EqBuildOptionsWithTrustedBuilder(true)).
   109  						Return(nil)
   110  
   111  					logger.WantVerbose(true)
   112  					command.SetArgs([]string{"image", "--builder", "heroku/buildpacks:18"})
   113  					h.AssertNil(t, command.Execute())
   114  					h.AssertContains(t, outBuf.String(), "Builder 'heroku/buildpacks:18' is trusted")
   115  				})
   116  			})
   117  		})
   118  
   119  		when("--buildpack-registry flag is specified but experimental isn't set in the config", func() {
   120  			it("errors with a descriptive message", func() {
   121  				command.SetArgs([]string{"image", "--builder", "my-builder", "--buildpack-registry", "some-registry"})
   122  				err := command.Execute()
   123  				h.AssertNotNil(t, err)
   124  				h.AssertError(t, err, "Support for buildpack registries is currently experimental.")
   125  			})
   126  		})
   127  
   128  		when("a network is given", func() {
   129  			it("forwards the network onto the client", func() {
   130  				mockClient.EXPECT().
   131  					Build(gomock.Any(), EqBuildOptionsWithNetwork("my-network")).
   132  					Return(nil)
   133  
   134  				command.SetArgs([]string{"image", "--builder", "my-builder", "--network", "my-network"})
   135  				h.AssertNil(t, command.Execute())
   136  			})
   137  		})
   138  
   139  		when("--pull-policy", func() {
   140  			it("sets pull-policy=never", func() {
   141  				mockClient.EXPECT().
   142  					Build(gomock.Any(), EqBuildOptionsWithPullPolicy(pubcfg.PullNever)).
   143  					Return(nil)
   144  
   145  				command.SetArgs([]string{"image", "--builder", "my-builder", "--pull-policy", "never"})
   146  				h.AssertNil(t, command.Execute())
   147  			})
   148  
   149  			it("returns error for unknown policy", func() {
   150  				command.SetArgs([]string{"image", "--builder", "my-builder", "--pull-policy", "unknown-policy"})
   151  				h.AssertError(t, command.Execute(), "parsing pull policy")
   152  			})
   153  		})
   154  
   155  		when("volume mounts are specified", func() {
   156  			it("mounts the volumes", func() {
   157  				mockClient.EXPECT().
   158  					Build(gomock.Any(), EqBuildOptionsWithVolumes([]string{"a:b", "c:d"})).
   159  					Return(nil)
   160  
   161  				command.SetArgs([]string{"image", "--builder", "my-builder", "--volume", "a:b", "--volume", "c:d"})
   162  				h.AssertNil(t, command.Execute())
   163  			})
   164  
   165  			it("warns when running with an untrusted builder", func() {
   166  				mockClient.EXPECT().
   167  					Build(gomock.Any(), EqBuildOptionsWithVolumes([]string{"a:b", "c:d"})).
   168  					Return(nil)
   169  
   170  				command.SetArgs([]string{"image", "--builder", "my-builder", "--volume", "a:b", "--volume", "c:d"})
   171  				h.AssertNil(t, command.Execute())
   172  				h.AssertContains(t, outBuf.String(), "Warning: Using untrusted builder with volume mounts")
   173  			})
   174  		})
   175  
   176  		when("a default process is specified", func() {
   177  			it("sets that process", func() {
   178  				mockClient.EXPECT().
   179  					Build(gomock.Any(), EqBuildOptionsDefaultProcess("my-proc")).
   180  					Return(nil)
   181  
   182  				command.SetArgs([]string{"image", "--builder", "my-builder", "--default-process", "my-proc"})
   183  				h.AssertNil(t, command.Execute())
   184  			})
   185  		})
   186  
   187  		when("env file", func() {
   188  			when("an env file is provided", func() {
   189  				var envPath string
   190  
   191  				it.Before(func() {
   192  					envfile, err := ioutil.TempFile("", "envfile")
   193  					h.AssertNil(t, err)
   194  					defer envfile.Close()
   195  
   196  					envfile.WriteString(`KEY=VALUE`)
   197  					envPath = envfile.Name()
   198  				})
   199  
   200  				it.After(func() {
   201  					h.AssertNil(t, os.RemoveAll(envPath))
   202  				})
   203  
   204  				it("builds an image env variables read from the env file", func() {
   205  					mockClient.EXPECT().
   206  						Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{
   207  							"KEY": "VALUE",
   208  						})).
   209  						Return(nil)
   210  
   211  					command.SetArgs([]string{"--builder", "my-builder", "image", "--env-file", envPath})
   212  					h.AssertNil(t, command.Execute())
   213  				})
   214  			})
   215  
   216  			when("a env file is provided but doesn't exist", func() {
   217  				it("fails to run", func() {
   218  					command.SetArgs([]string{"--builder", "my-builder", "image", "--env-file", ""})
   219  					err := command.Execute()
   220  					h.AssertError(t, err, "parse env file")
   221  				})
   222  			})
   223  
   224  			when("an empty env file is provided", func() {
   225  				var envPath string
   226  
   227  				it.Before(func() {
   228  					envfile, err := ioutil.TempFile("", "envfile")
   229  					h.AssertNil(t, err)
   230  					defer envfile.Close()
   231  
   232  					envfile.WriteString(``)
   233  					envPath = envfile.Name()
   234  				})
   235  
   236  				it.After(func() {
   237  					h.AssertNil(t, os.RemoveAll(envPath))
   238  				})
   239  
   240  				it("successfully builds", func() {
   241  					mockClient.EXPECT().
   242  						Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{})).
   243  						Return(nil)
   244  
   245  					command.SetArgs([]string{"--builder", "my-builder", "image", "--env-file", envPath})
   246  					h.AssertNil(t, command.Execute())
   247  				})
   248  			})
   249  
   250  			when("two env files are provided with conflicted keys", func() {
   251  				var envPath1 string
   252  				var envPath2 string
   253  
   254  				it.Before(func() {
   255  					envfile1, err := ioutil.TempFile("", "envfile")
   256  					h.AssertNil(t, err)
   257  					defer envfile1.Close()
   258  
   259  					envfile1.WriteString("KEY1=VALUE1\nKEY2=IGNORED")
   260  					envPath1 = envfile1.Name()
   261  
   262  					envfile2, err := ioutil.TempFile("", "envfile")
   263  					h.AssertNil(t, err)
   264  					defer envfile2.Close()
   265  
   266  					envfile2.WriteString("KEY2=VALUE2")
   267  					envPath2 = envfile2.Name()
   268  				})
   269  
   270  				it.After(func() {
   271  					h.AssertNil(t, os.RemoveAll(envPath1))
   272  					h.AssertNil(t, os.RemoveAll(envPath2))
   273  				})
   274  
   275  				it("builds an image with the last value of each env variable", func() {
   276  					mockClient.EXPECT().
   277  						Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{
   278  							"KEY1": "VALUE1",
   279  							"KEY2": "VALUE2",
   280  						})).
   281  						Return(nil)
   282  
   283  					command.SetArgs([]string{"--builder", "my-builder", "image", "--env-file", envPath1, "--env-file", envPath2})
   284  					h.AssertNil(t, command.Execute())
   285  				})
   286  			})
   287  		})
   288  
   289  		when("env vars are passed as flags", func() {
   290  			var (
   291  				tmpVar   = "tmpVar"
   292  				tmpValue = "tmpKey"
   293  			)
   294  
   295  			it.Before(func() {
   296  				h.AssertNil(t, os.Setenv(tmpVar, tmpValue))
   297  			})
   298  
   299  			it.After(func() {
   300  				h.AssertNil(t, os.Unsetenv(tmpVar))
   301  			})
   302  
   303  			it("sets flag variables", func() {
   304  				mockClient.EXPECT().
   305  					Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{
   306  						"KEY":  "VALUE",
   307  						tmpVar: tmpValue,
   308  					})).
   309  					Return(nil)
   310  
   311  				command.SetArgs([]string{"image", "--builder", "my-builder", "--env", "KEY=VALUE", "--env", tmpVar})
   312  				h.AssertNil(t, command.Execute())
   313  			})
   314  		})
   315  
   316  		when("build fails", func() {
   317  			it("should show an error", func() {
   318  				mockClient.EXPECT().
   319  					Build(gomock.Any(), gomock.Any()).
   320  					Return(errors.New(""))
   321  
   322  				command.SetArgs([]string{"--builder", "my-builder", "image"})
   323  				err := command.Execute()
   324  				h.AssertError(t, err, "failed to build")
   325  			})
   326  		})
   327  
   328  		when("user specifies an invalid project descriptor file", func() {
   329  			it("should show an error", func() {
   330  				projectTomlPath := "/incorrect/path/to/project.toml"
   331  				mockClient.EXPECT().
   332  					Build(gomock.Any(), EqBuildOptionsWithImage("my-builder", "image")).
   333  					Return(nil)
   334  
   335  				command.SetArgs([]string{"--builder", "my-builder", "--descriptor", projectTomlPath, "image"})
   336  				h.AssertNotNil(t, command.Execute())
   337  			})
   338  		})
   339  
   340  		when("parsing project descriptor", func() {
   341  			when("file is valid", func() {
   342  				var projectTomlPath string
   343  
   344  				it.Before(func() {
   345  					projectToml, err := ioutil.TempFile("", "project.toml")
   346  					h.AssertNil(t, err)
   347  					defer projectToml.Close()
   348  
   349  					projectToml.WriteString(`
   350  [project]
   351  name = "Sample"
   352  
   353  [[build.buildpacks]]
   354  id = "example/lua"
   355  version = "1.0"
   356  `)
   357  					projectTomlPath = projectToml.Name()
   358  				})
   359  
   360  				it.After(func() {
   361  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   362  				})
   363  
   364  				it("should build an image with configuration in descriptor", func() {
   365  					mockClient.EXPECT().
   366  						Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{
   367  							"example/lua@1.0",
   368  						})).
   369  						Return(nil)
   370  
   371  					command.SetArgs([]string{"--builder", "my-builder", "--descriptor", projectTomlPath, "image"})
   372  					h.AssertNil(t, command.Execute())
   373  				})
   374  			})
   375  
   376  			when("file is invalid", func() {
   377  				var projectTomlPath string
   378  
   379  				it.Before(func() {
   380  					projectToml, err := ioutil.TempFile("", "project.toml")
   381  					h.AssertNil(t, err)
   382  					defer projectToml.Close()
   383  
   384  					projectToml.WriteString("project]")
   385  					projectTomlPath = projectToml.Name()
   386  				})
   387  
   388  				it.After(func() {
   389  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   390  				})
   391  
   392  				it("should fail to build", func() {
   393  					mockClient.EXPECT().
   394  						Build(gomock.Any(), EqBuildOptionsWithImage("my-builder", "image")).
   395  						Return(nil)
   396  
   397  					command.SetArgs([]string{"--builder", "my-builder", "--descriptor", projectTomlPath, "image"})
   398  					h.AssertNotNil(t, command.Execute())
   399  				})
   400  			})
   401  
   402  			when("descriptor path is NOT specified", func() {
   403  				when("project.toml exists in source repo", func() {
   404  					it.Before(func() {
   405  						h.AssertNil(t, os.Chdir("testdata"))
   406  					})
   407  
   408  					it.After(func() {
   409  						h.AssertNil(t, os.Chdir(".."))
   410  					})
   411  
   412  					it("should use project.toml in source repo", func() {
   413  						mockClient.EXPECT().
   414  							Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{
   415  								"KEY1": "VALUE1",
   416  							})).
   417  							Return(nil)
   418  
   419  						command.SetArgs([]string{"--builder", "my-builder", "image"})
   420  						h.AssertNil(t, command.Execute())
   421  					})
   422  				})
   423  
   424  				when("project.toml does NOT exist in source repo", func() {
   425  					it("should use empty descriptor", func() {
   426  						mockClient.EXPECT().
   427  							Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{})).
   428  							Return(nil)
   429  
   430  						command.SetArgs([]string{"--builder", "my-builder", "image"})
   431  						h.AssertNil(t, command.Execute())
   432  					})
   433  				})
   434  			})
   435  
   436  			when("descriptor path is specified", func() {
   437  				when("descriptor file exists", func() {
   438  					var projectTomlPath string
   439  					it.Before(func() {
   440  						projectTomlPath = filepath.Join("testdata", "project.toml")
   441  					})
   442  
   443  					it("should use specified descriptor", func() {
   444  						mockClient.EXPECT().
   445  							Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{
   446  								"KEY1": "VALUE1",
   447  							})).
   448  							Return(nil)
   449  
   450  						command.SetArgs([]string{"--builder", "my-builder", "--descriptor", projectTomlPath, "image"})
   451  						h.AssertNil(t, command.Execute())
   452  					})
   453  				})
   454  
   455  				when("descriptor file does NOT exist in source repo", func() {
   456  					it("should fail with an error message", func() {
   457  						command.SetArgs([]string{"--builder", "my-builder", "--descriptor", "non-existent-path", "image"})
   458  						h.AssertError(t, command.Execute(), "stat project descriptor")
   459  					})
   460  				})
   461  			})
   462  
   463  			when("descriptor buildpack has uri", func() {
   464  				var projectTomlPath string
   465  
   466  				it.Before(func() {
   467  					projectToml, err := ioutil.TempFile("", "project.toml")
   468  					h.AssertNil(t, err)
   469  					defer projectToml.Close()
   470  
   471  					projectToml.WriteString(`
   472  [project]
   473  name = "Sample"
   474  
   475  [[build.buildpacks]]
   476  id = "example/lua"
   477  uri = "https://www.test.tgz"
   478  `)
   479  					projectTomlPath = projectToml.Name()
   480  				})
   481  
   482  				it.After(func() {
   483  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   484  				})
   485  
   486  				it("should build an image with configuration in descriptor", func() {
   487  					mockClient.EXPECT().
   488  						Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{
   489  							"https://www.test.tgz",
   490  						})).
   491  						Return(nil)
   492  
   493  					command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath})
   494  					h.AssertNil(t, command.Execute())
   495  				})
   496  			})
   497  
   498  			when("descriptor buildpack has malformed uri", func() {
   499  				var projectTomlPath string
   500  
   501  				it.Before(func() {
   502  					projectToml, err := ioutil.TempFile("", "project.toml")
   503  					h.AssertNil(t, err)
   504  					defer projectToml.Close()
   505  
   506  					projectToml.WriteString(`
   507  [project]
   508  name = "Sample"
   509  
   510  [[build.buildpacks]]
   511  id = "example/lua"
   512  uri = "://bad-uri"
   513  `)
   514  					projectTomlPath = projectToml.Name()
   515  				})
   516  
   517  				it.After(func() {
   518  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   519  				})
   520  
   521  				it("should build an image with configuration in descriptor", func() {
   522  					mockClient.EXPECT().
   523  						Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{
   524  							"https://www.test.tgz",
   525  						})).
   526  						Return(nil)
   527  
   528  					command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath})
   529  					err := command.Execute()
   530  					h.AssertError(t, err, "parse")
   531  				})
   532  			})
   533  
   534  			when("descriptor has exclude", func() {
   535  				var projectTomlPath string
   536  
   537  				it.Before(func() {
   538  					projectToml, err := ioutil.TempFile("", "project.toml")
   539  					h.AssertNil(t, err)
   540  					defer projectToml.Close()
   541  
   542  					projectToml.WriteString(`
   543  [project]
   544  name = "Sample"
   545  
   546  [build]
   547  exclude = [ "*.jar" ]
   548  `)
   549  					projectTomlPath = projectToml.Name()
   550  				})
   551  
   552  				it.After(func() {
   553  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   554  				})
   555  
   556  				it("should return appropriate fileFilter function", func() {
   557  					mockFilter := func(string) bool {
   558  						return false
   559  					}
   560  
   561  					mockClient.EXPECT().
   562  						Build(gomock.Any(), EqBuildOptionsWithFileFilter(mockFilter, "test.jar")).
   563  						Return(nil)
   564  
   565  					command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath})
   566  					h.AssertNil(t, command.Execute())
   567  				})
   568  			})
   569  
   570  			when("descriptor has include", func() {
   571  				var projectTomlPath string
   572  				it.Before(func() {
   573  					projectToml, err := ioutil.TempFile("", "project.toml")
   574  					h.AssertNil(t, err)
   575  					defer projectToml.Close()
   576  
   577  					projectToml.WriteString(`
   578  [project]
   579  name = "Sample"
   580  
   581  [build]
   582  include = [ "*.jar" ]
   583  `)
   584  					projectTomlPath = projectToml.Name()
   585  				})
   586  
   587  				it.After(func() {
   588  					h.AssertNil(t, os.RemoveAll(projectTomlPath))
   589  				})
   590  
   591  				it("should return appropriate fileFilter function", func() {
   592  					mockFilter := func(string) bool {
   593  						return true
   594  					}
   595  
   596  					mockClient.EXPECT().
   597  						Build(gomock.Any(), EqBuildOptionsWithFileFilter(mockFilter, "test.jar")).
   598  						Return(nil)
   599  
   600  					command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath})
   601  					h.AssertNil(t, command.Execute())
   602  				})
   603  			})
   604  		})
   605  	})
   606  }
   607  
   608  func EqBuildOptionsWithImage(builder, image string) gomock.Matcher {
   609  	return buildOptionsMatcher{
   610  		description: fmt.Sprintf("Builder=%s and Image=%s", builder, image),
   611  		equals: func(o pack.BuildOptions) bool {
   612  			return o.Builder == builder && o.Image == image
   613  		},
   614  	}
   615  }
   616  
   617  func EqBuildOptionsDefaultProcess(defaultProc string) gomock.Matcher {
   618  	return buildOptionsMatcher{
   619  		description: fmt.Sprintf("Default Process Type=%s", defaultProc),
   620  		equals: func(o pack.BuildOptions) bool {
   621  			return o.DefaultProcessType == defaultProc
   622  		},
   623  	}
   624  }
   625  
   626  func EqBuildOptionsWithPullPolicy(policy pubcfg.PullPolicy) gomock.Matcher {
   627  	return buildOptionsMatcher{
   628  		description: fmt.Sprintf("PullPolicy=%s", policy),
   629  		equals: func(o pack.BuildOptions) bool {
   630  			return o.PullPolicy == policy
   631  		},
   632  	}
   633  }
   634  
   635  func EqBuildOptionsWithNetwork(network string) gomock.Matcher {
   636  	return buildOptionsMatcher{
   637  		description: fmt.Sprintf("Network=%s", network),
   638  		equals: func(o pack.BuildOptions) bool {
   639  			return o.ContainerConfig.Network == network
   640  		},
   641  	}
   642  }
   643  
   644  func EqBuildOptionsWithTrustedBuilder(trustBuilder bool) gomock.Matcher {
   645  	return buildOptionsMatcher{
   646  		description: fmt.Sprintf("Trust Builder=%t", trustBuilder),
   647  		equals: func(o pack.BuildOptions) bool {
   648  			return o.TrustBuilder == trustBuilder
   649  		},
   650  	}
   651  }
   652  
   653  func EqBuildOptionsWithFileFilter(fileFilter func(string) bool, fileName string) gomock.Matcher {
   654  	return buildOptionsMatcher{
   655  		description: fmt.Sprintf("File Filter=%p", fileFilter),
   656  		equals: func(o pack.BuildOptions) bool {
   657  			return o.FileFilter(fileName) == fileFilter(fileName)
   658  		},
   659  	}
   660  }
   661  
   662  func EqBuildOptionsWithVolumes(volumes []string) gomock.Matcher {
   663  	return buildOptionsMatcher{
   664  		description: fmt.Sprintf("Volumes=%s", volumes),
   665  		equals: func(o pack.BuildOptions) bool {
   666  			return reflect.DeepEqual(o.ContainerConfig.Volumes, volumes)
   667  		},
   668  	}
   669  }
   670  
   671  func EqBuildOptionsWithEnv(env map[string]string) gomock.Matcher {
   672  	return buildOptionsMatcher{
   673  		description: fmt.Sprintf("Env=%+v", env),
   674  		equals: func(o pack.BuildOptions) bool {
   675  			for k, v := range o.Env {
   676  				if env[k] != v {
   677  					return false
   678  				}
   679  			}
   680  			for k, v := range env {
   681  				if o.Env[k] != v {
   682  					return false
   683  				}
   684  			}
   685  			return true
   686  		},
   687  	}
   688  }
   689  
   690  func EqBuildOptionsWithBuildpacks(buildpacks []string) gomock.Matcher {
   691  	return buildOptionsMatcher{
   692  		description: fmt.Sprintf("Buildpacks=%+v", buildpacks),
   693  		equals: func(o pack.BuildOptions) bool {
   694  			for _, bp := range o.Buildpacks {
   695  				if !contains(buildpacks, bp) {
   696  					return false
   697  				}
   698  			}
   699  			for _, bp := range buildpacks {
   700  				if !contains(o.Buildpacks, bp) {
   701  					return false
   702  				}
   703  			}
   704  			return true
   705  		},
   706  	}
   707  }
   708  
   709  type buildOptionsMatcher struct {
   710  	equals      func(pack.BuildOptions) bool
   711  	description string
   712  }
   713  
   714  func (m buildOptionsMatcher) Matches(x interface{}) bool {
   715  	if b, ok := x.(pack.BuildOptions); ok {
   716  		return m.equals(b)
   717  	}
   718  	return false
   719  }
   720  
   721  func (m buildOptionsMatcher) String() string {
   722  	return "is a BuildOptions with " + m.description
   723  }
   724  
   725  func contains(arr []string, str string) bool {
   726  	for _, a := range arr {
   727  		if a == str {
   728  			return true
   729  		}
   730  	}
   731  	return false
   732  }