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 }