github.com/YousefHaggyHeroku/pack@v1.5.5/internal/builder/writer/toml_test.go (about)

     1  package writer_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/pelletier/go-toml"
    10  
    11  	"github.com/Masterminds/semver"
    12  	"github.com/heroku/color"
    13  	"github.com/sclevine/spec"
    14  	"github.com/sclevine/spec/report"
    15  
    16  	"github.com/buildpacks/lifecycle/api"
    17  
    18  	"github.com/YousefHaggyHeroku/pack"
    19  	pubbldr "github.com/YousefHaggyHeroku/pack/builder"
    20  	"github.com/YousefHaggyHeroku/pack/internal/builder"
    21  	"github.com/YousefHaggyHeroku/pack/internal/builder/writer"
    22  	"github.com/YousefHaggyHeroku/pack/internal/config"
    23  	"github.com/YousefHaggyHeroku/pack/internal/dist"
    24  	ilogging "github.com/YousefHaggyHeroku/pack/internal/logging"
    25  	h "github.com/YousefHaggyHeroku/pack/testhelpers"
    26  )
    27  
    28  func TestTOML(t *testing.T) {
    29  	color.Disable(true)
    30  	defer color.Disable(false)
    31  	spec.Run(t, "Builder Writer", testTOML, spec.Parallel(), spec.Report(report.Terminal{}))
    32  }
    33  
    34  func testTOML(t *testing.T, when spec.G, it spec.S) {
    35  	const (
    36  		expectedRemoteRunImages = `  [[remote_info.run_images]]
    37      name = "first/local"
    38      user_configured = true
    39  
    40    [[remote_info.run_images]]
    41      name = "second/local"
    42      user_configured = true
    43  
    44    [[remote_info.run_images]]
    45      name = "some/run-image"
    46  
    47    [[remote_info.run_images]]
    48      name = "first/default"
    49  
    50    [[remote_info.run_images]]
    51      name = "second/default"`
    52  
    53  		expectedLocalRunImages = `  [[local_info.run_images]]
    54      name = "first/local"
    55      user_configured = true
    56  
    57    [[local_info.run_images]]
    58      name = "second/local"
    59      user_configured = true
    60  
    61    [[local_info.run_images]]
    62      name = "some/run-image"
    63  
    64    [[local_info.run_images]]
    65      name = "first/local-default"
    66  
    67    [[local_info.run_images]]
    68      name = "second/local-default"`
    69  
    70  		expectedLocalBuildpacks = `  [[local_info.buildpacks]]
    71      id = "test.top.nested"
    72      version = "test.top.nested.version"
    73  
    74    [[local_info.buildpacks]]
    75      id = "test.nested"
    76      homepage = "http://geocities.com/top-bp"
    77  
    78    [[local_info.buildpacks]]
    79      id = "test.bp.one"
    80      version = "test.bp.one.version"
    81      homepage = "http://geocities.com/cool-bp"
    82  
    83    [[local_info.buildpacks]]
    84      id = "test.bp.two"
    85      version = "test.bp.two.version"
    86  
    87    [[local_info.buildpacks]]
    88      id = "test.bp.three"
    89      version = "test.bp.three.version"`
    90  
    91  		expectedRemoteBuildpacks = `  [[remote_info.buildpacks]]
    92      id = "test.top.nested"
    93      version = "test.top.nested.version"
    94  
    95    [[remote_info.buildpacks]]
    96      id = "test.nested"
    97      homepage = "http://geocities.com/top-bp"
    98  
    99    [[remote_info.buildpacks]]
   100      id = "test.bp.one"
   101      version = "test.bp.one.version"
   102      homepage = "http://geocities.com/cool-bp"
   103  
   104    [[remote_info.buildpacks]]
   105      id = "test.bp.two"
   106      version = "test.bp.two.version"
   107  
   108    [[remote_info.buildpacks]]
   109      id = "test.bp.three"
   110      version = "test.bp.three.version"`
   111  
   112  		expectedLocalDetectionOrder = `  [[local_info.detection_order]]
   113  
   114      [[local_info.detection_order.buildpacks]]
   115        id = "test.top.nested"
   116        version = "test.top.nested.version"
   117  
   118        [[local_info.detection_order.buildpacks.buildpacks]]
   119          id = "test.nested"
   120          homepage = "http://geocities.com/top-bp"
   121  
   122          [[local_info.detection_order.buildpacks.buildpacks.buildpacks]]
   123            id = "test.bp.one"
   124            version = "test.bp.one.version"
   125            homepage = "http://geocities.com/cool-bp"
   126            optional = true
   127  
   128        [[local_info.detection_order.buildpacks.buildpacks]]
   129          id = "test.bp.three"
   130          version = "test.bp.three.version"
   131          optional = true
   132  
   133        [[local_info.detection_order.buildpacks.buildpacks]]
   134          id = "test.nested.two"
   135          version = "test.nested.two.version"
   136  
   137          [[local_info.detection_order.buildpacks.buildpacks.buildpacks]]
   138            id = "test.bp.one"
   139            version = "test.bp.one.version"
   140            homepage = "http://geocities.com/cool-bp"
   141            optional = true
   142            cyclic = true
   143  
   144      [[local_info.detection_order.buildpacks]]
   145        id = "test.bp.two"
   146        version = "test.bp.two.version"
   147        optional = true
   148  
   149    [[local_info.detection_order]]
   150      id = "test.bp.three"
   151      version = "test.bp.three.version"`
   152  
   153  		expectedRemoteDetectionOrder = `  [[remote_info.detection_order]]
   154  
   155      [[remote_info.detection_order.buildpacks]]
   156        id = "test.top.nested"
   157        version = "test.top.nested.version"
   158  
   159        [[remote_info.detection_order.buildpacks.buildpacks]]
   160          id = "test.nested"
   161          homepage = "http://geocities.com/top-bp"
   162  
   163          [[remote_info.detection_order.buildpacks.buildpacks.buildpacks]]
   164            id = "test.bp.one"
   165            version = "test.bp.one.version"
   166            homepage = "http://geocities.com/cool-bp"
   167            optional = true
   168  
   169        [[remote_info.detection_order.buildpacks.buildpacks]]
   170          id = "test.bp.three"
   171          version = "test.bp.three.version"
   172          optional = true
   173  
   174        [[remote_info.detection_order.buildpacks.buildpacks]]
   175          id = "test.nested.two"
   176          version = "test.nested.two.version"
   177  
   178          [[remote_info.detection_order.buildpacks.buildpacks.buildpacks]]
   179            id = "test.bp.one"
   180            version = "test.bp.one.version"
   181            homepage = "http://geocities.com/cool-bp"
   182            optional = true
   183            cyclic = true
   184  
   185      [[remote_info.detection_order.buildpacks]]
   186        id = "test.bp.two"
   187        version = "test.bp.two.version"
   188        optional = true
   189  
   190    [[remote_info.detection_order]]
   191      id = "test.bp.three"
   192      version = "test.bp.three.version"`
   193  
   194  		stackWithMixins = `  [stack]
   195      id = "test.stack.id"
   196      mixins = ["mixin1", "mixin2", "build:mixin3", "build:mixin4"]`
   197  	)
   198  
   199  	var (
   200  		assert = h.NewAssertionManager(t)
   201  		outBuf bytes.Buffer
   202  
   203  		remoteInfo *pack.BuilderInfo
   204  		localInfo  *pack.BuilderInfo
   205  
   206  		expectedRemoteInfo = fmt.Sprintf(`[remote_info]
   207    description = "Some remote description"
   208  
   209    [remote_info.created_by]
   210      Name = "Pack CLI"
   211      Version = "1.2.3"
   212  
   213    [remote_info.stack]
   214      id = "test.stack.id"
   215  
   216    [remote_info.lifecycle]
   217      version = "6.7.8"
   218  
   219      [remote_info.lifecycle.buildpack_apis]
   220        deprecated = []
   221        supported = ["1.2", "2.3"]
   222  
   223      [remote_info.lifecycle.platform_apis]
   224        deprecated = ["0.1", "1.2"]
   225        supported = ["4.5"]
   226  
   227  %s
   228  
   229  %s
   230  
   231  %s`, expectedRemoteRunImages, expectedRemoteBuildpacks, expectedRemoteDetectionOrder)
   232  
   233  		expectedLocalInfo = fmt.Sprintf(`[local_info]
   234    description = "Some local description"
   235  
   236    [local_info.created_by]
   237      Name = "Pack CLI"
   238      Version = "4.5.6"
   239  
   240    [local_info.stack]
   241      id = "test.stack.id"
   242  
   243    [local_info.lifecycle]
   244      version = "4.5.6"
   245  
   246      [local_info.lifecycle.buildpack_apis]
   247        deprecated = ["4.5", "6.7"]
   248        supported = ["8.9", "10.11"]
   249  
   250      [local_info.lifecycle.platform_apis]
   251        deprecated = []
   252        supported = ["7.8"]
   253  
   254  %s
   255  
   256  %s
   257  
   258  %s`, expectedLocalRunImages, expectedLocalBuildpacks, expectedLocalDetectionOrder)
   259  
   260  		expectedPrettifiedTOML = fmt.Sprintf(`builder_name = "test-builder"
   261  trusted = false
   262  default = false
   263  
   264  %s
   265  
   266  %s`, expectedRemoteInfo, expectedLocalInfo)
   267  	)
   268  
   269  	when("Print", func() {
   270  		it.Before(func() {
   271  			remoteInfo = &pack.BuilderInfo{
   272  				Description:     "Some remote description",
   273  				Stack:           "test.stack.id",
   274  				Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
   275  				RunImage:        "some/run-image",
   276  				RunImageMirrors: []string{"first/default", "second/default"},
   277  				Buildpacks:      buildpacks,
   278  				Order:           order,
   279  				BuildpackLayers: dist.BuildpackLayers{},
   280  				Lifecycle: builder.LifecycleDescriptor{
   281  					Info: builder.LifecycleInfo{
   282  						Version: &builder.Version{
   283  							Version: *semver.MustParse("6.7.8"),
   284  						},
   285  					},
   286  					APIs: builder.LifecycleAPIs{
   287  						Buildpack: builder.APIVersions{
   288  							Deprecated: nil,
   289  							Supported:  builder.APISet{api.MustParse("1.2"), api.MustParse("2.3")},
   290  						},
   291  						Platform: builder.APIVersions{
   292  							Deprecated: builder.APISet{api.MustParse("0.1"), api.MustParse("1.2")},
   293  							Supported:  builder.APISet{api.MustParse("4.5")},
   294  						},
   295  					},
   296  				},
   297  				CreatedBy: builder.CreatorMetadata{
   298  					Name:    "Pack CLI",
   299  					Version: "1.2.3",
   300  				},
   301  			}
   302  
   303  			localInfo = &pack.BuilderInfo{
   304  				Description:     "Some local description",
   305  				Stack:           "test.stack.id",
   306  				Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
   307  				RunImage:        "some/run-image",
   308  				RunImageMirrors: []string{"first/local-default", "second/local-default"},
   309  				Buildpacks:      buildpacks,
   310  				Order:           order,
   311  				BuildpackLayers: dist.BuildpackLayers{},
   312  				Lifecycle: builder.LifecycleDescriptor{
   313  					Info: builder.LifecycleInfo{
   314  						Version: &builder.Version{
   315  							Version: *semver.MustParse("4.5.6"),
   316  						},
   317  					},
   318  					APIs: builder.LifecycleAPIs{
   319  						Buildpack: builder.APIVersions{
   320  							Deprecated: builder.APISet{api.MustParse("4.5"), api.MustParse("6.7")},
   321  							Supported:  builder.APISet{api.MustParse("8.9"), api.MustParse("10.11")},
   322  						},
   323  						Platform: builder.APIVersions{
   324  							Deprecated: nil,
   325  							Supported:  builder.APISet{api.MustParse("7.8")},
   326  						},
   327  					},
   328  				},
   329  				CreatedBy: builder.CreatorMetadata{
   330  					Name:    "Pack CLI",
   331  					Version: "4.5.6",
   332  				},
   333  			}
   334  		})
   335  
   336  		it("prints both local remote builders as valid TOML", func() {
   337  			tomlWriter := writer.NewTOML()
   338  
   339  			logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   340  			err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   341  			assert.Nil(err)
   342  
   343  			assert.Succeeds(validTOMLOutput(outBuf))
   344  			assert.Nil(err)
   345  
   346  			assert.ContainsTOML(outBuf.String(), expectedPrettifiedTOML)
   347  		})
   348  
   349  		when("builder doesn't exist locally or remotely", func() {
   350  			it("returns an error", func() {
   351  				tomlWriter := writer.NewTOML()
   352  
   353  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   354  				err := tomlWriter.Print(logger, localRunImages, nil, nil, nil, nil, sharedBuilderInfo)
   355  				assert.ErrorWithMessage(err, "unable to find builder 'test-builder' locally or remotely")
   356  			})
   357  		})
   358  
   359  		when("builder doesn't exist locally", func() {
   360  			it("shows null for local builder, and normal output for remote", func() {
   361  				tomlWriter := writer.NewTOML()
   362  
   363  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   364  				err := tomlWriter.Print(logger, localRunImages, nil, remoteInfo, nil, nil, sharedBuilderInfo)
   365  				assert.Nil(err)
   366  
   367  				assert.Succeeds(validTOMLOutput(outBuf))
   368  				assert.Nil(err)
   369  
   370  				assert.NotContains(outBuf.String(), "local_info")
   371  				assert.ContainsTOML(outBuf.String(), expectedRemoteInfo)
   372  			})
   373  		})
   374  
   375  		when("builder doesn't exist remotely", func() {
   376  			it("shows null for remote builder, and normal output for local", func() {
   377  				tomlWriter := writer.NewTOML()
   378  
   379  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   380  				err := tomlWriter.Print(logger, localRunImages, localInfo, nil, nil, nil, sharedBuilderInfo)
   381  				assert.Nil(err)
   382  
   383  				assert.Succeeds(validTOMLOutput(outBuf))
   384  				assert.Nil(err)
   385  
   386  				assert.NotContains(outBuf.String(), "remote_info")
   387  				assert.ContainsTOML(outBuf.String(), expectedLocalInfo)
   388  			})
   389  		})
   390  
   391  		when("localErr is an error", func() {
   392  			it("returns the error, and doesn't write any toml output", func() {
   393  				expectedErr := errors.New("failed to retrieve local info")
   394  
   395  				tomlWriter := writer.NewTOML()
   396  
   397  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   398  				err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, expectedErr, nil, sharedBuilderInfo)
   399  				assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve local info")
   400  
   401  				assert.Equal(outBuf.String(), "")
   402  			})
   403  		})
   404  
   405  		when("remoteErr is an error", func() {
   406  			it("returns the error, and doesn't write any toml output", func() {
   407  				expectedErr := errors.New("failed to retrieve remote info")
   408  
   409  				tomlWriter := writer.NewTOML()
   410  
   411  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf)
   412  				err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, expectedErr, sharedBuilderInfo)
   413  				assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve remote info")
   414  
   415  				assert.Equal(outBuf.String(), "")
   416  			})
   417  		})
   418  
   419  		when("logger is verbose", func() {
   420  			it("displays mixins associated with the stack", func() {
   421  				tomlWriter := writer.NewTOML()
   422  
   423  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose())
   424  				err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   425  				assert.Nil(err)
   426  
   427  				assert.Succeeds(validTOMLOutput(outBuf))
   428  				assert.ContainsTOML(outBuf.String(), stackWithMixins)
   429  			})
   430  		})
   431  
   432  		when("no run images are specified", func() {
   433  			it("omits run images from output", func() {
   434  				localInfo.RunImage = ""
   435  				localInfo.RunImageMirrors = []string{}
   436  				remoteInfo.RunImage = ""
   437  				remoteInfo.RunImageMirrors = []string{}
   438  				emptyLocalRunImages := []config.RunImage{}
   439  
   440  				tomlWriter := writer.NewTOML()
   441  
   442  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose())
   443  				err := tomlWriter.Print(logger, emptyLocalRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   444  				assert.Nil(err)
   445  
   446  				assert.Succeeds(validTOMLOutput(outBuf))
   447  
   448  				assert.NotContains(outBuf.String(), "run_images")
   449  			})
   450  		})
   451  
   452  		when("no buildpacks are specified", func() {
   453  			it("omits buildpacks from output", func() {
   454  				localInfo.Buildpacks = []dist.BuildpackInfo{}
   455  				remoteInfo.Buildpacks = []dist.BuildpackInfo{}
   456  
   457  				tomlWriter := writer.NewTOML()
   458  
   459  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose())
   460  				err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   461  				assert.Nil(err)
   462  
   463  				assert.Succeeds(validTOMLOutput(outBuf))
   464  
   465  				assert.NotContains(outBuf.String(), "local_info.buildpacks")
   466  				assert.NotContains(outBuf.String(), "remote_info.buildpacks")
   467  			})
   468  		})
   469  
   470  		when("no detection order is specified", func() {
   471  			it("omits dection order in output", func() {
   472  				localInfo.Order = pubbldr.DetectionOrder{}
   473  				remoteInfo.Order = pubbldr.DetectionOrder{}
   474  
   475  				tomlWriter := writer.NewTOML()
   476  
   477  				logger := ilogging.NewLogWithWriters(&outBuf, &outBuf, ilogging.WithVerbose())
   478  				err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   479  				assert.Nil(err)
   480  
   481  				assert.Succeeds(validTOMLOutput(outBuf))
   482  				assert.NotContains(outBuf.String(), "detection_order")
   483  			})
   484  		})
   485  	})
   486  }
   487  
   488  func validTOMLOutput(source bytes.Buffer) error {
   489  	err := toml.NewDecoder(&source).Decode(&struct{}{})
   490  	if err != nil {
   491  		return fmt.Errorf("failed to unmarshal to toml: %w", err)
   492  	}
   493  	return nil
   494  }