github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/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/buildpacks/lifecycle/api"
    10  	"github.com/heroku/color"
    11  	"github.com/sclevine/spec"
    12  	"github.com/sclevine/spec/report"
    13  
    14  	pubbldr "github.com/buildpacks/pack/builder"
    15  	"github.com/buildpacks/pack/internal/builder"
    16  	"github.com/buildpacks/pack/internal/builder/writer"
    17  	"github.com/buildpacks/pack/internal/config"
    18  	"github.com/buildpacks/pack/pkg/client"
    19  	"github.com/buildpacks/pack/pkg/dist"
    20  	"github.com/buildpacks/pack/pkg/logging"
    21  	h "github.com/buildpacks/pack/testhelpers"
    22  )
    23  
    24  func TestHumanReadable(t *testing.T) {
    25  	color.Disable(true)
    26  	defer color.Disable(false)
    27  	spec.Run(t, "Builder Writer", testHumanReadable, spec.Parallel(), spec.Report(report.Terminal{}))
    28  }
    29  
    30  func testHumanReadable(t *testing.T, when spec.G, it spec.S) {
    31  	var (
    32  		assert = h.NewAssertionManager(t)
    33  		outBuf bytes.Buffer
    34  
    35  		remoteInfo *client.BuilderInfo
    36  		localInfo  *client.BuilderInfo
    37  
    38  		expectedRemoteOutput = `
    39  REMOTE:
    40  
    41  Description: Some remote description
    42  
    43  Created By:
    44    Name: Pack CLI
    45    Version: 1.2.3
    46  
    47  Trusted: No
    48  
    49  Stack:
    50    ID: test.stack.id
    51  
    52  Lifecycle:
    53    Version: 6.7.8
    54    Buildpack APIs:
    55      Deprecated: (none)
    56      Supported: 1.2, 2.3
    57    Platform APIs:
    58      Deprecated: 0.1, 1.2
    59      Supported: 4.5
    60  
    61  Run Images:
    62    first/local     (user-configured)
    63    second/local    (user-configured)
    64    some/run-image
    65    first/default
    66    second/default
    67  
    68  Buildpacks:
    69    ID                     NAME        VERSION                        HOMEPAGE
    70    test.top.nested        -           test.top.nested.version        -
    71    test.nested            -                                          http://geocities.com/top-bp
    72    test.bp.one            -           test.bp.one.version            http://geocities.com/cool-bp
    73    test.bp.two            -           test.bp.two.version            -
    74    test.bp.three          -           test.bp.three.version          -
    75  
    76  Detection Order:
    77   ├ Group #1:
    78   │  ├ test.top.nested@test.top.nested.version
    79   │  │  └ Group #1:
    80   │  │     ├ test.nested
    81   │  │     │  └ Group #1:
    82   │  │     │     └ test.bp.one@test.bp.one.version      (optional)
    83   │  │     ├ test.bp.three@test.bp.three.version        (optional)
    84   │  │     └ test.nested.two@test.nested.two.version
    85   │  │        └ Group #2:
    86   │  │           └ test.bp.one@test.bp.one.version    (optional)[cyclic]
    87   │  └ test.bp.two@test.bp.two.version                (optional)
    88   └ test.bp.three@test.bp.three.version
    89  
    90  Extensions:
    91    ID                   NAME        VERSION                      HOMEPAGE
    92    test.bp.one          -           test.bp.one.version          http://geocities.com/cool-bp
    93    test.bp.two          -           test.bp.two.version          -
    94    test.bp.three        -           test.bp.three.version        -
    95  
    96  Detection Order (Extensions):
    97   ├ test.top.nested@test.top.nested.version
    98   ├ test.bp.one@test.bp.one.version            (optional)
    99   ├ test.bp.two@test.bp.two.version            (optional)
   100   └ test.bp.three@test.bp.three.version
   101  `
   102  		expectedRemoteOutputWithoutExtensions = `
   103  REMOTE:
   104  
   105  Description: Some remote description
   106  
   107  Created By:
   108    Name: Pack CLI
   109    Version: 1.2.3
   110  
   111  Trusted: No
   112  
   113  Stack:
   114    ID: test.stack.id
   115  
   116  Lifecycle:
   117    Version: 6.7.8
   118    Buildpack APIs:
   119      Deprecated: (none)
   120      Supported: 1.2, 2.3
   121    Platform APIs:
   122      Deprecated: 0.1, 1.2
   123      Supported: 4.5
   124  
   125  Run Images:
   126    first/local     (user-configured)
   127    second/local    (user-configured)
   128    some/run-image
   129    first/default
   130    second/default
   131  
   132  Buildpacks:
   133    ID                     NAME        VERSION                        HOMEPAGE
   134    test.top.nested        -           test.top.nested.version        -
   135    test.nested            -                                          http://geocities.com/top-bp
   136    test.bp.one            -           test.bp.one.version            http://geocities.com/cool-bp
   137    test.bp.two            -           test.bp.two.version            -
   138    test.bp.three          -           test.bp.three.version          -
   139  
   140  Detection Order:
   141   ├ Group #1:
   142   │  ├ test.top.nested@test.top.nested.version
   143   │  │  └ Group #1:
   144   │  │     ├ test.nested
   145   │  │     │  └ Group #1:
   146   │  │     │     └ test.bp.one@test.bp.one.version      (optional)
   147   │  │     ├ test.bp.three@test.bp.three.version        (optional)
   148   │  │     └ test.nested.two@test.nested.two.version
   149   │  │        └ Group #2:
   150   │  │           └ test.bp.one@test.bp.one.version    (optional)[cyclic]
   151   │  └ test.bp.two@test.bp.two.version                (optional)
   152   └ test.bp.three@test.bp.three.version
   153  `
   154  
   155  		expectedLocalOutput = `
   156  LOCAL:
   157  
   158  Description: Some local description
   159  
   160  Created By:
   161    Name: Pack CLI
   162    Version: 4.5.6
   163  
   164  Trusted: No
   165  
   166  Stack:
   167    ID: test.stack.id
   168  
   169  Lifecycle:
   170    Version: 4.5.6
   171    Buildpack APIs:
   172      Deprecated: 4.5, 6.7
   173      Supported: 8.9, 10.11
   174    Platform APIs:
   175      Deprecated: (none)
   176      Supported: 7.8
   177  
   178  Run Images:
   179    first/local     (user-configured)
   180    second/local    (user-configured)
   181    some/run-image
   182    first/local-default
   183    second/local-default
   184  
   185  Buildpacks:
   186    ID                     NAME        VERSION                        HOMEPAGE
   187    test.top.nested        -           test.top.nested.version        -
   188    test.nested            -                                          http://geocities.com/top-bp
   189    test.bp.one            -           test.bp.one.version            http://geocities.com/cool-bp
   190    test.bp.two            -           test.bp.two.version            -
   191    test.bp.three          -           test.bp.three.version          -
   192  
   193  Detection Order:
   194   ├ Group #1:
   195   │  ├ test.top.nested@test.top.nested.version
   196   │  │  └ Group #1:
   197   │  │     ├ test.nested
   198   │  │     │  └ Group #1:
   199   │  │     │     └ test.bp.one@test.bp.one.version      (optional)
   200   │  │     ├ test.bp.three@test.bp.three.version        (optional)
   201   │  │     └ test.nested.two@test.nested.two.version
   202   │  │        └ Group #2:
   203   │  │           └ test.bp.one@test.bp.one.version    (optional)[cyclic]
   204   │  └ test.bp.two@test.bp.two.version                (optional)
   205   └ test.bp.three@test.bp.three.version
   206  
   207  Extensions:
   208    ID                   NAME        VERSION                      HOMEPAGE
   209    test.bp.one          -           test.bp.one.version          http://geocities.com/cool-bp
   210    test.bp.two          -           test.bp.two.version          -
   211    test.bp.three        -           test.bp.three.version        -
   212  
   213  Detection Order (Extensions):
   214   ├ test.top.nested@test.top.nested.version
   215   ├ test.bp.one@test.bp.one.version            (optional)
   216   ├ test.bp.two@test.bp.two.version            (optional)
   217   └ test.bp.three@test.bp.three.version
   218  `
   219  
   220  		expectedLocalOutputWithoutExtensions = `
   221  LOCAL:
   222  
   223  Description: Some local description
   224  
   225  Created By:
   226    Name: Pack CLI
   227    Version: 4.5.6
   228  
   229  Trusted: No
   230  
   231  Stack:
   232    ID: test.stack.id
   233  
   234  Lifecycle:
   235    Version: 4.5.6
   236    Buildpack APIs:
   237      Deprecated: 4.5, 6.7
   238      Supported: 8.9, 10.11
   239    Platform APIs:
   240      Deprecated: (none)
   241      Supported: 7.8
   242  
   243  Run Images:
   244    first/local     (user-configured)
   245    second/local    (user-configured)
   246    some/run-image
   247    first/local-default
   248    second/local-default
   249  
   250  Buildpacks:
   251    ID                     NAME        VERSION                        HOMEPAGE
   252    test.top.nested        -           test.top.nested.version        -
   253    test.nested            -                                          http://geocities.com/top-bp
   254    test.bp.one            -           test.bp.one.version            http://geocities.com/cool-bp
   255    test.bp.two            -           test.bp.two.version            -
   256    test.bp.three          -           test.bp.three.version          -
   257  
   258  Detection Order:
   259   ├ Group #1:
   260   │  ├ test.top.nested@test.top.nested.version
   261   │  │  └ Group #1:
   262   │  │     ├ test.nested
   263   │  │     │  └ Group #1:
   264   │  │     │     └ test.bp.one@test.bp.one.version      (optional)
   265   │  │     ├ test.bp.three@test.bp.three.version        (optional)
   266   │  │     └ test.nested.two@test.nested.two.version
   267   │  │        └ Group #2:
   268   │  │           └ test.bp.one@test.bp.one.version    (optional)[cyclic]
   269   │  └ test.bp.two@test.bp.two.version                (optional)
   270   └ test.bp.three@test.bp.three.version
   271  `
   272  
   273  		expectedVerboseStack = `
   274  Stack:
   275    ID: test.stack.id
   276    Mixins:
   277      mixin1
   278      mixin2
   279      build:mixin3
   280      build:mixin4
   281  `
   282  		expectedNilLifecycleVersion = `
   283  Lifecycle:
   284    Version: (none)
   285  `
   286  		expectedEmptyRunImages = `
   287  Run Images:
   288    (none)
   289  `
   290  		expectedEmptyBuildpacks = `
   291  Buildpacks:
   292    (none)
   293  `
   294  		expectedEmptyOrder = `
   295  Detection Order:
   296    (none)
   297  `
   298  		expectedEmptyOrderExt = `
   299  Detection Order (Extensions):
   300    (none)
   301  `
   302  		expectedMissingLocalInfo = `
   303  LOCAL:
   304  (not present)
   305  `
   306  		expectedMissingRemoteInfo = `
   307  REMOTE:
   308  (not present)
   309  `
   310  	)
   311  
   312  	when("Print", func() {
   313  		it.Before(func() {
   314  			remoteInfo = &client.BuilderInfo{
   315  				Description:     "Some remote description",
   316  				Stack:           "test.stack.id",
   317  				Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
   318  				RunImages:       []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}},
   319  				Buildpacks:      buildpacks,
   320  				Order:           order,
   321  				Extensions:      extensions,
   322  				OrderExtensions: orderExtensions,
   323  				BuildpackLayers: dist.ModuleLayers{},
   324  				Lifecycle: builder.LifecycleDescriptor{
   325  					Info: builder.LifecycleInfo{
   326  						Version: &builder.Version{
   327  							Version: *semver.MustParse("6.7.8"),
   328  						},
   329  					},
   330  					APIs: builder.LifecycleAPIs{
   331  						Buildpack: builder.APIVersions{
   332  							Deprecated: nil,
   333  							Supported:  builder.APISet{api.MustParse("1.2"), api.MustParse("2.3")},
   334  						},
   335  						Platform: builder.APIVersions{
   336  							Deprecated: builder.APISet{api.MustParse("0.1"), api.MustParse("1.2")},
   337  							Supported:  builder.APISet{api.MustParse("4.5")},
   338  						},
   339  					},
   340  				},
   341  				CreatedBy: builder.CreatorMetadata{
   342  					Name:    "Pack CLI",
   343  					Version: "1.2.3",
   344  				},
   345  			}
   346  
   347  			localInfo = &client.BuilderInfo{
   348  				Description:     "Some local description",
   349  				Stack:           "test.stack.id",
   350  				Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
   351  				RunImages:       []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}},
   352  				Buildpacks:      buildpacks,
   353  				Order:           order,
   354  				Extensions:      extensions,
   355  				OrderExtensions: orderExtensions,
   356  				BuildpackLayers: dist.ModuleLayers{},
   357  				Lifecycle: builder.LifecycleDescriptor{
   358  					Info: builder.LifecycleInfo{
   359  						Version: &builder.Version{
   360  							Version: *semver.MustParse("4.5.6"),
   361  						},
   362  					},
   363  					APIs: builder.LifecycleAPIs{
   364  						Buildpack: builder.APIVersions{
   365  							Deprecated: builder.APISet{api.MustParse("4.5"), api.MustParse("6.7")},
   366  							Supported:  builder.APISet{api.MustParse("8.9"), api.MustParse("10.11")},
   367  						},
   368  						Platform: builder.APIVersions{
   369  							Deprecated: nil,
   370  							Supported:  builder.APISet{api.MustParse("7.8")},
   371  						},
   372  					},
   373  				},
   374  				CreatedBy: builder.CreatorMetadata{
   375  					Name:    "Pack CLI",
   376  					Version: "4.5.6",
   377  				},
   378  			}
   379  
   380  			outBuf = bytes.Buffer{}
   381  		})
   382  
   383  		it("prints both local and remote builders in a human readable format", func() {
   384  			humanReadableWriter := writer.NewHumanReadable()
   385  
   386  			logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   387  			err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   388  			assert.Nil(err)
   389  
   390  			assert.Contains(outBuf.String(), "Inspecting builder: 'test-builder'")
   391  			assert.Contains(outBuf.String(), expectedRemoteOutput)
   392  			assert.Contains(outBuf.String(), expectedLocalOutput)
   393  		})
   394  
   395  		when("builder is default", func() {
   396  			it("prints inspecting default builder", func() {
   397  				defaultSharedBuildInfo := sharedBuilderInfo
   398  				defaultSharedBuildInfo.IsDefault = true
   399  
   400  				humanReadableWriter := writer.NewHumanReadable()
   401  
   402  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   403  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, defaultSharedBuildInfo)
   404  				assert.Nil(err)
   405  
   406  				assert.Contains(outBuf.String(), "Inspecting default builder: 'test-builder'")
   407  			})
   408  		})
   409  
   410  		when("builder doesn't exist locally or remotely", func() {
   411  			it("returns an error", func() {
   412  				localInfo = nil
   413  				remoteInfo = nil
   414  
   415  				humanReadableWriter := writer.NewHumanReadable()
   416  
   417  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   418  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   419  				assert.ErrorWithMessage(err, "unable to find builder 'test-builder' locally or remotely")
   420  			})
   421  		})
   422  
   423  		when("builder doesn't exist locally", func() {
   424  			it("shows not present for local builder, and normal output for remote", func() {
   425  				localInfo = nil
   426  
   427  				humanReadableWriter := writer.NewHumanReadable()
   428  
   429  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   430  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   431  				assert.Nil(err)
   432  
   433  				assert.Contains(outBuf.String(), expectedMissingLocalInfo)
   434  				assert.Contains(outBuf.String(), expectedRemoteOutput)
   435  			})
   436  		})
   437  
   438  		when("builder doesn't exist remotely", func() {
   439  			it("shows not present for remote builder, and normal output for local", func() {
   440  				remoteInfo = nil
   441  
   442  				humanReadableWriter := writer.NewHumanReadable()
   443  
   444  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   445  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   446  				assert.Nil(err)
   447  
   448  				assert.Contains(outBuf.String(), expectedMissingRemoteInfo)
   449  				assert.Contains(outBuf.String(), expectedLocalOutput)
   450  			})
   451  		})
   452  
   453  		when("localErr is an error", func() {
   454  			it("error is logged, local info is not displayed, but remote info is", func() {
   455  				errorMessage := "failed to retrieve local info"
   456  
   457  				humanReadableWriter := writer.NewHumanReadable()
   458  
   459  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   460  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, errors.New(errorMessage), nil, sharedBuilderInfo)
   461  				assert.Nil(err)
   462  
   463  				assert.Contains(outBuf.String(), errorMessage)
   464  				assert.NotContains(outBuf.String(), expectedLocalOutput)
   465  				assert.Contains(outBuf.String(), expectedRemoteOutput)
   466  			})
   467  		})
   468  
   469  		when("remoteErr is an error", func() {
   470  			it("error is logged, remote info is not displayed, but local info is", func() {
   471  				errorMessage := "failed to retrieve remote info"
   472  
   473  				humanReadableWriter := writer.NewHumanReadable()
   474  
   475  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   476  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, errors.New(errorMessage), sharedBuilderInfo)
   477  				assert.Nil(err)
   478  
   479  				assert.Contains(outBuf.String(), errorMessage)
   480  				assert.NotContains(outBuf.String(), expectedRemoteOutput)
   481  				assert.Contains(outBuf.String(), expectedLocalOutput)
   482  			})
   483  		})
   484  
   485  		when("description is blank", func() {
   486  			it("doesn't print the description block", func() {
   487  				localInfo.Description = ""
   488  				remoteInfo.Description = ""
   489  
   490  				humanReadableWriter := writer.NewHumanReadable()
   491  
   492  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   493  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   494  				assert.Nil(err)
   495  
   496  				assert.NotContains(outBuf.String(), "Description:")
   497  			})
   498  		})
   499  
   500  		when("created by name is blank", func() {
   501  			it("doesn't print created by block", func() {
   502  				localInfo.CreatedBy.Name = ""
   503  				remoteInfo.CreatedBy.Name = ""
   504  
   505  				humanReadableWriter := writer.NewHumanReadable()
   506  
   507  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   508  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   509  				assert.Nil(err)
   510  
   511  				assert.NotContains(outBuf.String(), "Created By:")
   512  			})
   513  		})
   514  
   515  		when("logger is verbose", func() {
   516  			it("displays mixins associated with the stack", func() {
   517  				humanReadableWriter := writer.NewHumanReadable()
   518  
   519  				logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose())
   520  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   521  				assert.Nil(err)
   522  
   523  				assert.Contains(outBuf.String(), expectedVerboseStack)
   524  			})
   525  		})
   526  
   527  		when("lifecycle version is not set", func() {
   528  			it("displays lifecycle version as (none) and warns that version if not set", func() {
   529  				localInfo.Lifecycle.Info.Version = nil
   530  				remoteInfo.Lifecycle.Info.Version = nil
   531  
   532  				humanReadableWriter := writer.NewHumanReadable()
   533  
   534  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   535  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   536  				assert.Nil(err)
   537  
   538  				assert.Contains(outBuf.String(), expectedNilLifecycleVersion)
   539  				assert.Contains(outBuf.String(), "test-builder does not specify a Lifecycle version")
   540  			})
   541  		})
   542  
   543  		when("there are no supported buildpack APIs specified", func() {
   544  			it("prints a warning", func() {
   545  				localInfo.Lifecycle.APIs.Buildpack.Supported = builder.APISet{}
   546  				remoteInfo.Lifecycle.APIs.Buildpack.Supported = builder.APISet{}
   547  
   548  				humanReadableWriter := writer.NewHumanReadable()
   549  
   550  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   551  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   552  				assert.Nil(err)
   553  
   554  				assert.Contains(outBuf.String(), "test-builder does not specify supported Lifecycle Buildpack APIs")
   555  			})
   556  		})
   557  
   558  		when("there are no supported platform APIs specified", func() {
   559  			it("prints a warning", func() {
   560  				localInfo.Lifecycle.APIs.Platform.Supported = builder.APISet{}
   561  				remoteInfo.Lifecycle.APIs.Platform.Supported = builder.APISet{}
   562  
   563  				humanReadableWriter := writer.NewHumanReadable()
   564  
   565  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   566  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   567  				assert.Nil(err)
   568  
   569  				assert.Contains(outBuf.String(), "test-builder does not specify supported Lifecycle Platform APIs")
   570  			})
   571  		})
   572  
   573  		when("no run images are specified", func() {
   574  			it("displays run images as (none) and warns about unset run image", func() {
   575  				localInfo.RunImages = []pubbldr.RunImageConfig{}
   576  				remoteInfo.RunImages = []pubbldr.RunImageConfig{}
   577  				emptyLocalRunImages := []config.RunImage{}
   578  
   579  				humanReadableWriter := writer.NewHumanReadable()
   580  
   581  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   582  				err := humanReadableWriter.Print(logger, emptyLocalRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   583  				assert.Nil(err)
   584  
   585  				assert.Contains(outBuf.String(), expectedEmptyRunImages)
   586  				assert.Contains(outBuf.String(), "test-builder does not specify a run image")
   587  				assert.Contains(outBuf.String(), "Users must build with an explicitly specified run image")
   588  			})
   589  		})
   590  
   591  		when("no buildpacks are specified", func() {
   592  			it("displays buildpacks as (none) and prints warnings", func() {
   593  				localInfo.Buildpacks = []dist.ModuleInfo{}
   594  				remoteInfo.Buildpacks = []dist.ModuleInfo{}
   595  
   596  				humanReadableWriter := writer.NewHumanReadable()
   597  
   598  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   599  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   600  				assert.Nil(err)
   601  
   602  				assert.Contains(outBuf.String(), expectedEmptyBuildpacks)
   603  				assert.Contains(outBuf.String(), "test-builder has no buildpacks")
   604  				assert.Contains(outBuf.String(), "Users must supply buildpacks from the host machine")
   605  			})
   606  		})
   607  
   608  		when("no extensions are specified", func() {
   609  			it("displays no extensions as (none)", func() {
   610  				localInfo.Extensions = []dist.ModuleInfo{}
   611  				remoteInfo.Extensions = []dist.ModuleInfo{}
   612  
   613  				humanReadableWriter := writer.NewHumanReadable()
   614  
   615  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   616  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   617  				assert.Nil(err)
   618  
   619  				assert.Contains(outBuf.String(), "Inspecting builder: 'test-builder'")
   620  				assert.Contains(outBuf.String(), expectedRemoteOutputWithoutExtensions)
   621  				assert.Contains(outBuf.String(), expectedLocalOutputWithoutExtensions)
   622  			})
   623  		})
   624  
   625  		when("multiple top level groups", func() {
   626  			it("displays order correctly", func() {
   627  
   628  			})
   629  		})
   630  
   631  		when("no detection order is specified", func() {
   632  			it("displays detection order as (none) and prints warnings", func() {
   633  				localInfo.Order = pubbldr.DetectionOrder{}
   634  				remoteInfo.Order = pubbldr.DetectionOrder{}
   635  
   636  				humanReadableWriter := writer.NewHumanReadable()
   637  
   638  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   639  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   640  				assert.Nil(err)
   641  
   642  				assert.Contains(outBuf.String(), expectedEmptyOrder)
   643  				assert.Contains(outBuf.String(), "test-builder has no buildpacks")
   644  				assert.Contains(outBuf.String(), "Users must build with explicitly specified buildpacks")
   645  			})
   646  		})
   647  
   648  		when("no detection order for extension is specified", func() {
   649  			it("displays detection order for extensions as (none)", func() {
   650  				localInfo.OrderExtensions = pubbldr.DetectionOrder{}
   651  				remoteInfo.OrderExtensions = pubbldr.DetectionOrder{}
   652  
   653  				humanReadableWriter := writer.NewHumanReadable()
   654  
   655  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   656  				err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo)
   657  				assert.Nil(err)
   658  
   659  				assert.Contains(outBuf.String(), expectedEmptyOrderExt)
   660  			})
   661  		})
   662  	})
   663  }