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

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