github.com/paketo-buildpacks/packit@v1.3.2-0.20211206231111-86b75c657449/build_test.go (about)

     1  package packit_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  	"testing/iotest"
    11  
    12  	"github.com/paketo-buildpacks/packit"
    13  	"github.com/paketo-buildpacks/packit/fakes"
    14  	"github.com/sclevine/spec"
    15  
    16  	. "github.com/onsi/gomega"
    17  	. "github.com/paketo-buildpacks/packit/matchers"
    18  )
    19  
    20  func testBuild(t *testing.T, context spec.G, it spec.S) {
    21  	var (
    22  		Expect = NewWithT(t).Expect
    23  
    24  		workingDir  string
    25  		platformDir string
    26  		tmpDir      string
    27  		layersDir   string
    28  		planPath    string
    29  		cnbDir      string
    30  		envCnbDir   string
    31  		binaryPath  string
    32  		exitHandler *fakes.ExitHandler
    33  	)
    34  
    35  	it.Before(func() {
    36  		var err error
    37  		workingDir, err = os.Getwd()
    38  		Expect(err).NotTo(HaveOccurred())
    39  
    40  		tmpDir, err = os.MkdirTemp("", "working-dir")
    41  		Expect(err).NotTo(HaveOccurred())
    42  
    43  		tmpDir, err = filepath.EvalSymlinks(tmpDir)
    44  		Expect(err).NotTo(HaveOccurred())
    45  
    46  		Expect(os.Chdir(tmpDir)).To(Succeed())
    47  
    48  		layersDir, err = os.MkdirTemp("", "layers")
    49  		Expect(err).NotTo(HaveOccurred())
    50  
    51  		platformDir, err = os.MkdirTemp("", "platform")
    52  		Expect(err).NotTo(HaveOccurred())
    53  
    54  		file, err := os.CreateTemp("", "plan.toml")
    55  		Expect(err).NotTo(HaveOccurred())
    56  		defer file.Close()
    57  
    58  		_, err = file.WriteString(`
    59  [[entries]]
    60    name = "some-entry"
    61  
    62  [entries.metadata]
    63    version = "some-version"
    64    some-key = "some-value"
    65  `)
    66  		Expect(err).NotTo(HaveOccurred())
    67  
    68  		planPath = file.Name()
    69  
    70  		cnbDir, err = os.MkdirTemp("", "cnb")
    71  		Expect(err).NotTo(HaveOccurred())
    72  
    73  		envCnbDir, err = os.MkdirTemp("", "envCnb")
    74  		Expect(err).NotTo(HaveOccurred())
    75  
    76  		bpTOML := []byte(`
    77  api = "0.7"
    78  [buildpack]
    79    id = "some-id"
    80    name = "some-name"
    81    version = "some-version"
    82  	homepage = "some-homepage"
    83  	description = "some-description"
    84  	keywords = ["some-keyword"]
    85  	sbom-formats = ["some-sbom-format", "some-other-sbom-format"]
    86    clear-env = false
    87  
    88  	[[buildpack.licenses]]
    89  		type = "some-license-type"
    90  		uri = "some-license-uri"
    91  `)
    92  		Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
    93  		Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
    94  
    95  		binaryPath = filepath.Join(cnbDir, "bin", "build")
    96  
    97  		Expect(os.Setenv("CNB_STACK_ID", "some-stack")).To(Succeed())
    98  
    99  		exitHandler = &fakes.ExitHandler{}
   100  	})
   101  
   102  	it.After(func() {
   103  		Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
   104  
   105  		Expect(os.Chdir(workingDir)).To(Succeed())
   106  		Expect(os.RemoveAll(tmpDir)).To(Succeed())
   107  		Expect(os.RemoveAll(layersDir)).To(Succeed())
   108  		Expect(os.RemoveAll(platformDir)).To(Succeed())
   109  	})
   110  
   111  	it("provides the build context to the given BuildFunc", func() {
   112  		var context packit.BuildContext
   113  
   114  		packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   115  			context = ctx
   116  
   117  			return packit.BuildResult{}, nil
   118  		}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   119  
   120  		Expect(context).To(Equal(packit.BuildContext{
   121  			CNBPath: cnbDir,
   122  			Stack:   "some-stack",
   123  			Platform: packit.Platform{
   124  				Path: platformDir,
   125  			},
   126  			WorkingDir: tmpDir,
   127  			Plan: packit.BuildpackPlan{
   128  				Entries: []packit.BuildpackPlanEntry{
   129  					{
   130  						Name: "some-entry",
   131  						Metadata: map[string]interface{}{
   132  							"version":  "some-version",
   133  							"some-key": "some-value",
   134  						},
   135  					},
   136  				},
   137  			},
   138  			Layers: packit.Layers{
   139  				Path: layersDir,
   140  			},
   141  			BuildpackInfo: packit.BuildpackInfo{
   142  				ID:          "some-id",
   143  				Name:        "some-name",
   144  				Version:     "some-version",
   145  				Homepage:    "some-homepage",
   146  				Description: "some-description",
   147  				Keywords:    []string{"some-keyword"},
   148  				SBOMFormats: []string{"some-sbom-format", "some-other-sbom-format"},
   149  				Licenses: []packit.BuildpackInfoLicense{
   150  					{
   151  						Type: "some-license-type",
   152  						URI:  "some-license-uri",
   153  					},
   154  				},
   155  			},
   156  		}))
   157  	})
   158  
   159  	context("when there are updates to the build plan", func() {
   160  		context("when the api version is less than 0.5", func() {
   161  			it.Before(func() {
   162  				bpTOML := []byte(`
   163  api = "0.4"
   164  [buildpack]
   165    id = "some-id"
   166    name = "some-name"
   167    version = "some-version"
   168    clear-env = false
   169  `)
   170  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   171  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   172  
   173  			})
   174  
   175  			it("updates the buildpack plan.toml with any changes", func() {
   176  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   177  					ctx.Plan.Entries[0].Metadata["other-key"] = "other-value"
   178  
   179  					return packit.BuildResult{
   180  						Plan: ctx.Plan,
   181  					}, nil
   182  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   183  
   184  				contents, err := os.ReadFile(planPath)
   185  				Expect(err).NotTo(HaveOccurred())
   186  
   187  				Expect(string(contents)).To(MatchTOML(`
   188  [[entries]]
   189    name = "some-entry"
   190  
   191  [entries.metadata]
   192    version = "some-version"
   193    some-key = "some-value"
   194    other-key = "other-value"
   195  `))
   196  			})
   197  		})
   198  
   199  		context("when the api version is greater or equal to 0.5", func() {
   200  			it("throws an error", func() {
   201  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   202  					return packit.BuildResult{
   203  						Plan: ctx.Plan,
   204  					}, nil
   205  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   206  
   207  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("buildpack plan is read only")))
   208  			})
   209  		})
   210  	})
   211  
   212  	it("persists layer metadata", func() {
   213  		packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   214  			layerPath := filepath.Join(ctx.Layers.Path, "some-layer")
   215  			Expect(os.MkdirAll(layerPath, os.ModePerm)).To(Succeed())
   216  
   217  			return packit.BuildResult{
   218  				Layers: []packit.Layer{
   219  					packit.Layer{
   220  						Path:   layerPath,
   221  						Name:   "some-layer",
   222  						Build:  true,
   223  						Launch: true,
   224  						Cache:  true,
   225  						Metadata: map[string]interface{}{
   226  							"some-key": "some-value",
   227  						},
   228  					},
   229  				},
   230  			}, nil
   231  		}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   232  
   233  		contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer.toml"))
   234  		Expect(err).NotTo(HaveOccurred())
   235  
   236  		Expect(string(contents)).To(MatchTOML(`
   237  [types]
   238    launch = true
   239    build = true
   240    cache = true
   241  
   242  [metadata]
   243    some-key = "some-value"
   244  `))
   245  	})
   246  
   247  	context("when the buildpack api version is less than 0.6", func() {
   248  		it.Before(func() {
   249  			bpTOML := []byte(`
   250  api = "0.5"
   251  [buildpack]
   252    id = "some-id"
   253    name = "some-name"
   254    version = "some-version"
   255  `)
   256  			Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   257  		})
   258  
   259  		it("persists layer metadata", func() {
   260  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   261  				layerPath := filepath.Join(ctx.Layers.Path, "some-layer")
   262  				Expect(os.MkdirAll(layerPath, os.ModePerm)).To(Succeed())
   263  
   264  				return packit.BuildResult{
   265  					Layers: []packit.Layer{
   266  						packit.Layer{
   267  							Path:   layerPath,
   268  							Name:   "some-layer",
   269  							Build:  true,
   270  							Launch: true,
   271  							Cache:  true,
   272  							Metadata: map[string]interface{}{
   273  								"some-key": "some-value",
   274  							},
   275  						},
   276  					},
   277  				}, nil
   278  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   279  
   280  			contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer.toml"))
   281  			Expect(err).NotTo(HaveOccurred())
   282  
   283  			Expect(string(contents)).To(MatchTOML(`
   284  launch = true
   285  build = true
   286  cache = true
   287  
   288  [metadata]
   289    some-key = "some-value"
   290  `))
   291  		})
   292  	})
   293  
   294  	context("when there are sbom entries in layer metadata", func() {
   295  		it("writes them to their specified locations", func() {
   296  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   297  				layerPath := filepath.Join(ctx.Layers.Path, "some-layer")
   298  				Expect(os.MkdirAll(layerPath, os.ModePerm)).To(Succeed())
   299  
   300  				return packit.BuildResult{
   301  					Layers: []packit.Layer{
   302  						packit.Layer{
   303  							Path: layerPath,
   304  							Name: "some-layer",
   305  							SBOM: packit.SBOMFormats{
   306  								{
   307  									Extension: "some.json",
   308  									Content:   strings.NewReader(`{"some-key": "some-value"}`),
   309  								},
   310  								{
   311  									Extension: "other.yml",
   312  									Content:   strings.NewReader(`other-key: other-value`),
   313  								},
   314  							},
   315  						},
   316  					},
   317  				}, nil
   318  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   319  
   320  			contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer.sbom.some.json"))
   321  			Expect(err).NotTo(HaveOccurred())
   322  			Expect(string(contents)).To(MatchJSON(`{"some-key": "some-value"}`))
   323  
   324  			contents, err = os.ReadFile(filepath.Join(layersDir, "some-layer.sbom.other.yml"))
   325  			Expect(err).NotTo(HaveOccurred())
   326  			Expect(string(contents)).To(MatchYAML(`other-key: other-value`))
   327  		})
   328  
   329  		context("when the api version is less than 0.7", func() {
   330  			it.Before(func() {
   331  				bpTOML := []byte(`
   332  api = "0.6"
   333  [buildpack]
   334    id = "some-id"
   335    name = "some-name"
   336    version = "some-version"
   337    clear-env = false
   338  `)
   339  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   340  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   341  			})
   342  
   343  			it("throws an error", func() {
   344  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   345  					layerPath := filepath.Join(ctx.Layers.Path, "some-layer")
   346  					Expect(os.MkdirAll(layerPath, os.ModePerm)).To(Succeed())
   347  
   348  					return packit.BuildResult{
   349  						Layers: []packit.Layer{
   350  							packit.Layer{
   351  								Path: layerPath,
   352  								Name: "some-layer",
   353  								SBOM: packit.SBOMFormats{
   354  									{
   355  										Extension: "some.json",
   356  										Content:   strings.NewReader(`{"some-key": "some-value"}`),
   357  									},
   358  								},
   359  							},
   360  						},
   361  					}, nil
   362  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   363  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("some-layer.sbom.* output is only supported with Buildpack API v0.7 or higher")))
   364  			})
   365  		})
   366  	})
   367  
   368  	context("when there are existing layer.toml files", func() {
   369  		context("when the layer.toml's will not be re-written", func() {
   370  			var obsoleteLayerPath string
   371  
   372  			it.Before(func() {
   373  				obsoleteLayerPath = filepath.Join(layersDir, "obsolete-layer")
   374  				Expect(os.MkdirAll(obsoleteLayerPath, os.ModePerm)).To(Succeed())
   375  				Expect(os.WriteFile(obsoleteLayerPath+".toml", []byte{}, 0600)).To(Succeed())
   376  
   377  				Expect(os.WriteFile(filepath.Join(layersDir, "launch.toml"), []byte{}, 0600)).To(Succeed())
   378  				Expect(os.WriteFile(filepath.Join(layersDir, "store.toml"), []byte{}, 0600)).To(Succeed())
   379  			})
   380  
   381  			context("when the buildpack api version is less than 0.6", func() {
   382  				it.Before(func() {
   383  					bpTOML := []byte(`
   384  api = "0.5"
   385  [buildpack]
   386    id = "some-id"
   387    name = "some-name"
   388    version = "some-version"
   389  `)
   390  					Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   391  				})
   392  
   393  				it("removes them", func() {
   394  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   395  						return packit.BuildResult{
   396  							Layers: []packit.Layer{},
   397  						}, nil
   398  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   399  					Expect(obsoleteLayerPath).NotTo(BeARegularFile())
   400  					Expect(obsoleteLayerPath + ".toml").NotTo(BeARegularFile())
   401  
   402  					Expect(filepath.Join(layersDir, "launch.toml")).To(BeARegularFile())
   403  					Expect(filepath.Join(layersDir, "store.toml")).To(BeARegularFile())
   404  				})
   405  
   406  				context("failures", func() {
   407  					context("when getting the layer toml list", func() {
   408  						var unremovableTOMLPath string
   409  
   410  						it.Before(func() {
   411  							unremovableTOMLPath = filepath.Join(layersDir, "unremovable.toml")
   412  							Expect(os.MkdirAll(filepath.Join(layersDir, "unremovable"), os.ModePerm)).To(Succeed())
   413  							Expect(os.WriteFile(unremovableTOMLPath, []byte{}, os.ModePerm)).To(Succeed())
   414  							Expect(os.Chmod(layersDir, 0666)).To(Succeed())
   415  						})
   416  
   417  						it.After(func() {
   418  							Expect(os.Chmod(layersDir, os.ModePerm)).To(Succeed())
   419  						})
   420  
   421  						it("returns an error", func() {
   422  							packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   423  								return packit.BuildResult{
   424  									Layers: []packit.Layer{},
   425  								}, nil
   426  							}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   427  							Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("failed to remove layer toml:")))
   428  						})
   429  					})
   430  				})
   431  			})
   432  
   433  			context("when the buildpack api version is greater than or equal to 0.6", func() {
   434  				it.Before(func() {
   435  					bpTOML := []byte(`
   436  api = "0.6"
   437  [buildpack]
   438    id = "some-id"
   439    name = "some-name"
   440    version = "some-version"
   441  `)
   442  					Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   443  				})
   444  
   445  				it("leaves them in place", func() {
   446  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   447  						return packit.BuildResult{
   448  							Layers: []packit.Layer{},
   449  						}, nil
   450  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   451  
   452  					Expect(obsoleteLayerPath).To(BeADirectory())
   453  					Expect(obsoleteLayerPath + ".toml").To(BeARegularFile())
   454  
   455  					Expect(filepath.Join(layersDir, "launch.toml")).To(BeARegularFile())
   456  					Expect(filepath.Join(layersDir, "store.toml")).To(BeARegularFile())
   457  				})
   458  			})
   459  		})
   460  	})
   461  
   462  	context("when the CNB_BUILDPACK_DIR environment variable is set", func() {
   463  		it.Before(func() {
   464  			os.Setenv("CNB_BUILDPACK_DIR", envCnbDir)
   465  		})
   466  
   467  		it.After(func() {
   468  			os.Unsetenv("CNB_BUILDPACK_DIR")
   469  		})
   470  
   471  		it("sets the correct value for CNBdir in the Build context", func() {
   472  			var context packit.BuildContext
   473  
   474  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   475  				context = ctx
   476  
   477  				return packit.BuildResult{}, nil
   478  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   479  
   480  			Expect(context).To(Equal(packit.BuildContext{
   481  				CNBPath: envCnbDir,
   482  				Platform: packit.Platform{
   483  					Path: platformDir,
   484  				},
   485  				Stack:      "some-stack",
   486  				WorkingDir: tmpDir,
   487  				Plan: packit.BuildpackPlan{
   488  					Entries: []packit.BuildpackPlanEntry{
   489  						{
   490  							Name: "some-entry",
   491  							Metadata: map[string]interface{}{
   492  								"version":  "some-version",
   493  								"some-key": "some-value",
   494  							},
   495  						},
   496  					},
   497  				},
   498  				Layers: packit.Layers{
   499  					Path: layersDir,
   500  				},
   501  				BuildpackInfo: packit.BuildpackInfo{
   502  					ID:          "some-id",
   503  					Name:        "some-name",
   504  					Version:     "some-version",
   505  					Homepage:    "some-homepage",
   506  					Description: "some-description",
   507  					Keywords:    []string{"some-keyword"},
   508  					SBOMFormats: []string{"some-sbom-format", "some-other-sbom-format"},
   509  					Licenses: []packit.BuildpackInfoLicense{
   510  						{
   511  							Type: "some-license-type",
   512  							URI:  "some-license-uri",
   513  						},
   514  					},
   515  				},
   516  			}))
   517  		})
   518  	})
   519  
   520  	context("when there are sbom entries in the build metadata", func() {
   521  		it("writes them to their specified locations", func() {
   522  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   523  				return packit.BuildResult{
   524  					Build: packit.BuildMetadata{
   525  						SBOM: packit.SBOMFormats{
   526  							{
   527  								Extension: "some.json",
   528  								Content:   strings.NewReader(`{"some-key": "some-value"}`),
   529  							},
   530  							{
   531  								Extension: "other.yml",
   532  								Content:   strings.NewReader(`other-key: other-value`),
   533  							},
   534  						},
   535  					},
   536  				}, nil
   537  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   538  
   539  			contents, err := os.ReadFile(filepath.Join(layersDir, "build.sbom.some.json"))
   540  			Expect(err).NotTo(HaveOccurred())
   541  			Expect(string(contents)).To(MatchJSON(`{"some-key": "some-value"}`))
   542  
   543  			contents, err = os.ReadFile(filepath.Join(layersDir, "build.sbom.other.yml"))
   544  			Expect(err).NotTo(HaveOccurred())
   545  			Expect(string(contents)).To(MatchYAML(`other-key: other-value`))
   546  		})
   547  
   548  		context("when the api version is less than 0.7", func() {
   549  			it.Before(func() {
   550  				bpTOML := []byte(`
   551  api = "0.6"
   552  [buildpack]
   553    id = "some-id"
   554    name = "some-name"
   555    version = "some-version"
   556    clear-env = false
   557  `)
   558  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   559  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   560  			})
   561  
   562  			it("throws an error", func() {
   563  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   564  					return packit.BuildResult{
   565  						Build: packit.BuildMetadata{
   566  							SBOM: packit.SBOMFormats{
   567  								{
   568  									Extension: "some.json",
   569  									Content:   strings.NewReader(`{"some-key": "some-value"}`),
   570  								},
   571  							},
   572  						},
   573  					}, nil
   574  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   575  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("build.sbom.* output is only supported with Buildpack API v0.7 or higher")))
   576  			})
   577  		})
   578  	})
   579  
   580  	context("when there are bom entries in the build metadata", func() {
   581  		it("persists a build.toml", func() {
   582  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   583  				return packit.BuildResult{
   584  					Build: packit.BuildMetadata{
   585  						BOM: []packit.BOMEntry{
   586  							{
   587  								Name: "example",
   588  							},
   589  							{
   590  								Name: "another-example",
   591  								Metadata: map[string]string{
   592  									"some-key": "some-value",
   593  								},
   594  							},
   595  						},
   596  					},
   597  				}, nil
   598  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   599  
   600  			contents, err := os.ReadFile(filepath.Join(layersDir, "build.toml"))
   601  			Expect(err).NotTo(HaveOccurred())
   602  
   603  			Expect(string(contents)).To(MatchTOML(`
   604  				[[bom]]
   605  					name = "example"
   606  				[[bom]]
   607  					name = "another-example"
   608  				[bom.metadata]
   609  					some-key = "some-value"
   610  			`))
   611  		})
   612  
   613  		context("when the api version is less than 0.5", func() {
   614  			it.Before(func() {
   615  				bpTOML := []byte(`
   616  api = "0.4"
   617  [buildpack]
   618    id = "some-id"
   619    name = "some-name"
   620    version = "some-version"
   621    clear-env = false
   622  `)
   623  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   624  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   625  			})
   626  
   627  			it("throws an error", func() {
   628  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   629  					return packit.BuildResult{
   630  						Build: packit.BuildMetadata{
   631  							BOM: []packit.BOMEntry{
   632  								{
   633  									Name: "example",
   634  								},
   635  								{
   636  									Name: "another-example",
   637  									Metadata: map[string]string{
   638  										"some-key": "some-value",
   639  									},
   640  								},
   641  							},
   642  						},
   643  					}, nil
   644  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   645  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("build.toml is only supported with Buildpack API v0.5 or higher")))
   646  			})
   647  		})
   648  	})
   649  
   650  	context("when there are unmet entries in the build metadata", func() {
   651  		it("persists a build.toml", func() {
   652  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   653  				return packit.BuildResult{
   654  					Build: packit.BuildMetadata{
   655  						Unmet: []packit.UnmetEntry{
   656  							{
   657  								Name: "example",
   658  							},
   659  							{
   660  								Name: "another-example",
   661  							},
   662  						},
   663  					},
   664  				}, nil
   665  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   666  
   667  			contents, err := os.ReadFile(filepath.Join(layersDir, "build.toml"))
   668  			Expect(err).NotTo(HaveOccurred())
   669  
   670  			Expect(string(contents)).To(MatchTOML(`
   671  				[[unmet]]
   672  					name = "example"
   673  				[[unmet]]
   674  					name = "another-example"
   675  			`))
   676  		})
   677  		context("when the api version is less than 0.5", func() {
   678  			it.Before(func() {
   679  				bpTOML := []byte(`
   680  api = "0.4"
   681  [buildpack]
   682    id = "some-id"
   683    name = "some-name"
   684    version = "some-version"
   685    clear-env = false
   686  `)
   687  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   688  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   689  
   690  			})
   691  
   692  			it("throws an error", func() {
   693  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   694  					return packit.BuildResult{
   695  						Build: packit.BuildMetadata{
   696  							Unmet: []packit.UnmetEntry{
   697  								{
   698  									Name: "example",
   699  								},
   700  								{
   701  									Name: "another-example",
   702  								},
   703  							},
   704  						},
   705  					}, nil
   706  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   707  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("build.toml is only supported with Buildpack API v0.5 or higher")))
   708  
   709  			})
   710  		})
   711  	})
   712  
   713  	context("when there are sbom entries in the launch metadata", func() {
   714  		it("writes them to their specified locations", func() {
   715  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   716  				return packit.BuildResult{
   717  					Launch: packit.LaunchMetadata{
   718  						SBOM: packit.SBOMFormats{
   719  							{
   720  								Extension: "some.json",
   721  								Content:   strings.NewReader(`{"some-key": "some-value"}`),
   722  							},
   723  							{
   724  								Extension: "other.yml",
   725  								Content:   strings.NewReader(`other-key: other-value`),
   726  							},
   727  						},
   728  					},
   729  				}, nil
   730  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   731  
   732  			contents, err := os.ReadFile(filepath.Join(layersDir, "launch.sbom.some.json"))
   733  			Expect(err).NotTo(HaveOccurred())
   734  			Expect(string(contents)).To(MatchJSON(`{"some-key": "some-value"}`))
   735  
   736  			contents, err = os.ReadFile(filepath.Join(layersDir, "launch.sbom.other.yml"))
   737  			Expect(err).NotTo(HaveOccurred())
   738  			Expect(string(contents)).To(MatchYAML(`other-key: other-value`))
   739  		})
   740  
   741  		context("when the api version is less than 0.7", func() {
   742  			it.Before(func() {
   743  				bpTOML := []byte(`
   744  api = "0.6"
   745  [buildpack]
   746    id = "some-id"
   747    name = "some-name"
   748    version = "some-version"
   749    clear-env = false
   750  `)
   751  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   752  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
   753  			})
   754  
   755  			it("throws an error", func() {
   756  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   757  					return packit.BuildResult{
   758  						Launch: packit.LaunchMetadata{
   759  							SBOM: packit.SBOMFormats{
   760  								{
   761  									Extension: "some.json",
   762  									Content:   strings.NewReader(`{"some-key": "some-value"}`),
   763  								},
   764  							},
   765  						},
   766  					}, nil
   767  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   768  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("launch.sbom.* output is only supported with Buildpack API v0.7 or higher")))
   769  			})
   770  		})
   771  	})
   772  
   773  	context("when there are bom entries in the launch metadata", func() {
   774  		it("persists a launch.toml", func() {
   775  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   776  				return packit.BuildResult{
   777  					Launch: packit.LaunchMetadata{
   778  						BOM: []packit.BOMEntry{
   779  							{
   780  								Name: "example",
   781  							},
   782  							{
   783  								Name: "another-example",
   784  								Metadata: map[string]string{
   785  									"some-key": "some-value",
   786  								},
   787  							},
   788  						},
   789  					},
   790  				}, nil
   791  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   792  
   793  			contents, err := os.ReadFile(filepath.Join(layersDir, "launch.toml"))
   794  			Expect(err).NotTo(HaveOccurred())
   795  
   796  			Expect(string(contents)).To(MatchTOML(`
   797  				[[bom]]
   798  					name = "example"
   799  				[[bom]]
   800  					name = "another-example"
   801  				[bom.metadata]
   802  					some-key = "some-value"
   803  			`))
   804  		})
   805  	})
   806  
   807  	context("when there are processes in the result", func() {
   808  		it("persists a launch.toml", func() {
   809  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   810  				return packit.BuildResult{
   811  					Launch: packit.LaunchMetadata{
   812  						Processes: []packit.Process{
   813  							{
   814  								Type:    "some-type",
   815  								Command: "some-command",
   816  								Args:    []string{"some-arg"},
   817  								Direct:  true,
   818  							},
   819  						},
   820  					},
   821  				}, nil
   822  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   823  
   824  			contents, err := os.ReadFile(filepath.Join(layersDir, "launch.toml"))
   825  			Expect(err).NotTo(HaveOccurred())
   826  
   827  			Expect(string(contents)).To(MatchTOML(`
   828  				[[processes]]
   829  					type = "some-type"
   830  					command = "some-command"
   831  					args = ["some-arg"]
   832  					direct = true
   833  			`))
   834  		})
   835  
   836  		context("when the process is the default", func() {
   837  			it("persists a launch.toml", func() {
   838  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   839  					return packit.BuildResult{
   840  						Launch: packit.LaunchMetadata{
   841  							Processes: []packit.Process{
   842  								{
   843  									Type:    "some-type",
   844  									Command: "some-command",
   845  									Args:    []string{"some-arg"},
   846  									Direct:  true,
   847  									Default: true,
   848  								},
   849  							},
   850  						},
   851  					}, nil
   852  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   853  
   854  				contents, err := os.ReadFile(filepath.Join(layersDir, "launch.toml"))
   855  				Expect(err).NotTo(HaveOccurred())
   856  
   857  				Expect(string(contents)).To(MatchTOML(`
   858  					[[processes]]
   859  						type = "some-type"
   860  						command = "some-command"
   861  						args = ["some-arg"]
   862  						direct = true
   863  						default = true
   864  				`))
   865  			})
   866  		})
   867  
   868  		context("when the api version is less than 0.6", func() {
   869  			it.Before(func() {
   870  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), []byte(`
   871  api = "0.5"
   872  [buildpack]
   873    id = "some-id"
   874    name = "some-name"
   875    version = "some-version"
   876    clear-env = false
   877  `), 0600)).To(Succeed())
   878  			})
   879  
   880  			it("errors", func() {
   881  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   882  					return packit.BuildResult{
   883  						Launch: packit.LaunchMetadata{
   884  							Processes: []packit.Process{
   885  								{
   886  									Type:    "some-type",
   887  									Command: "some-command",
   888  									Args:    []string{"some-arg"},
   889  									Direct:  true,
   890  									Default: true,
   891  								},
   892  							},
   893  						},
   894  					}, nil
   895  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
   896  
   897  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("processes can only be marked as default with Buildpack API v0.6 or higher")))
   898  			})
   899  		})
   900  	})
   901  
   902  	context("when there are slices in the result", func() {
   903  		it("persists a launch.toml", func() {
   904  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   905  				return packit.BuildResult{
   906  					Launch: packit.LaunchMetadata{
   907  						Slices: []packit.Slice{
   908  							{
   909  								Paths: []string{"some-slice"},
   910  							},
   911  						},
   912  					},
   913  				}, nil
   914  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   915  
   916  			contents, err := os.ReadFile(filepath.Join(layersDir, "launch.toml"))
   917  			Expect(err).NotTo(HaveOccurred())
   918  
   919  			Expect(string(contents)).To(MatchTOML(`
   920  				[[slices]]
   921  					paths = ["some-slice"]
   922  			`))
   923  		})
   924  	})
   925  
   926  	context("when there are labels in the result", func() {
   927  		it("persists a launch.toml", func() {
   928  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   929  				return packit.BuildResult{
   930  					Launch: packit.LaunchMetadata{
   931  						Labels: map[string]string{
   932  							"some key":       "some value",
   933  							"some-other-key": "some-other-value",
   934  						},
   935  					},
   936  				}, nil
   937  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   938  
   939  			contents, err := os.ReadFile(filepath.Join(layersDir, "launch.toml"))
   940  			Expect(err).NotTo(HaveOccurred())
   941  
   942  			Expect(string(contents)).To(MatchTOML(`
   943  				[[labels]]
   944  					key = "some key"
   945  					value = "some value"
   946  
   947  				[[labels]]
   948  					key = "some-other-key"
   949  					value = "some-other-value"
   950  			`))
   951  		})
   952  	})
   953  
   954  	context("when there are no processes, slices, bom or labels in the result", func() {
   955  		it("does not persist a launch.toml", func() {
   956  			packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   957  				return packit.BuildResult{}, nil
   958  			}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   959  
   960  			Expect(filepath.Join(layersDir, "launch.toml")).NotTo(BeARegularFile())
   961  		})
   962  	})
   963  
   964  	context("persists env vars", func() {
   965  		context("writes to shared env folder", func() {
   966  			it("writes env vars into env directory", func() {
   967  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   968  					return packit.BuildResult{
   969  						Layers: []packit.Layer{
   970  							{
   971  								Path: filepath.Join(ctx.Layers.Path, "some-layer"),
   972  								SharedEnv: packit.Environment{
   973  									"SOME_VAR.append":   "append-value",
   974  									"SOME_VAR.default":  "default-value",
   975  									"SOME_VAR.delim":    "delim-value",
   976  									"SOME_VAR.prepend":  "prepend-value",
   977  									"SOME_VAR.override": "override-value",
   978  								},
   979  							},
   980  						},
   981  					}, nil
   982  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
   983  
   984  				for _, modifier := range []string{"append", "default", "delim", "prepend", "override"} {
   985  					contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer", "env", fmt.Sprintf("SOME_VAR.%s", modifier)))
   986  					Expect(err).NotTo(HaveOccurred())
   987  					Expect(string(contents)).To(Equal(fmt.Sprintf("%s-value", modifier)))
   988  				}
   989  			})
   990  		})
   991  
   992  		context("writes to launch folder", func() {
   993  			it("writes env vars into env.launch directory", func() {
   994  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
   995  					return packit.BuildResult{
   996  						Layers: []packit.Layer{
   997  							{
   998  								Path: filepath.Join(ctx.Layers.Path, "some-layer"),
   999  								LaunchEnv: packit.Environment{
  1000  									"SOME_VAR.append":   "append-value",
  1001  									"SOME_VAR.default":  "default-value",
  1002  									"SOME_VAR.delim":    "delim-value",
  1003  									"SOME_VAR.prepend":  "prepend-value",
  1004  									"SOME_VAR.override": "override-value",
  1005  								},
  1006  							},
  1007  						},
  1008  					}, nil
  1009  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
  1010  
  1011  				for _, modifier := range []string{"append", "default", "delim", "prepend", "override"} {
  1012  					contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer", "env.launch", fmt.Sprintf("SOME_VAR.%s", modifier)))
  1013  					Expect(err).NotTo(HaveOccurred())
  1014  					Expect(string(contents)).To(Equal(fmt.Sprintf("%s-value", modifier)))
  1015  				}
  1016  			})
  1017  			it("writes env vars into env.launch/<process> directory", func() {
  1018  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1019  					return packit.BuildResult{
  1020  						Layers: []packit.Layer{
  1021  							{
  1022  								Path: filepath.Join(ctx.Layers.Path, "some-layer"),
  1023  								ProcessLaunchEnv: map[string]packit.Environment{
  1024  									"process-name": {
  1025  										"SOME_VAR.append":   "append-value",
  1026  										"SOME_VAR.default":  "default-value",
  1027  										"SOME_VAR.delim":    "delim-value",
  1028  										"SOME_VAR.prepend":  "prepend-value",
  1029  										"SOME_VAR.override": "override-value",
  1030  									},
  1031  									"another-process-name": {
  1032  										"SOME_VAR.append":   "append-value",
  1033  										"SOME_VAR.default":  "default-value",
  1034  										"SOME_VAR.delim":    "delim-value",
  1035  										"SOME_VAR.prepend":  "prepend-value",
  1036  										"SOME_VAR.override": "override-value",
  1037  									},
  1038  								},
  1039  							},
  1040  						},
  1041  					}, nil
  1042  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
  1043  
  1044  				for _, process := range []string{"process-name", "another-process-name"} {
  1045  					for _, modifier := range []string{"append", "default", "delim", "prepend", "override"} {
  1046  						contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer", "env.launch", process, fmt.Sprintf("SOME_VAR.%s", modifier)))
  1047  						Expect(err).NotTo(HaveOccurred())
  1048  						Expect(string(contents)).To(Equal(fmt.Sprintf("%s-value", modifier)))
  1049  					}
  1050  				}
  1051  			})
  1052  		})
  1053  
  1054  		context("writes to build folder", func() {
  1055  			it("writes env vars into env.build directory", func() {
  1056  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1057  					return packit.BuildResult{
  1058  						Layers: []packit.Layer{
  1059  							{
  1060  								Path: filepath.Join(ctx.Layers.Path, "some-layer"),
  1061  								BuildEnv: packit.Environment{
  1062  									"SOME_VAR.append":   "append-value",
  1063  									"SOME_VAR.default":  "default-value",
  1064  									"SOME_VAR.delim":    "delim-value",
  1065  									"SOME_VAR.prepend":  "prepend-value",
  1066  									"SOME_VAR.override": "override-value",
  1067  								},
  1068  							},
  1069  						},
  1070  					}, nil
  1071  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}))
  1072  
  1073  				for _, modifier := range []string{"append", "default", "delim", "prepend", "override"} {
  1074  					contents, err := os.ReadFile(filepath.Join(layersDir, "some-layer", "env.build", fmt.Sprintf("SOME_VAR.%s", modifier)))
  1075  					Expect(err).NotTo(HaveOccurred())
  1076  					Expect(string(contents)).To(Equal(fmt.Sprintf("%s-value", modifier)))
  1077  				}
  1078  			})
  1079  		})
  1080  	})
  1081  
  1082  	context("failure cases", func() {
  1083  		context("when the buildpack plan.toml is malformed", func() {
  1084  			it.Before(func() {
  1085  				err := os.WriteFile(planPath, []byte("%%%"), 0600)
  1086  				Expect(err).NotTo(HaveOccurred())
  1087  			})
  1088  
  1089  			it("calls the exit handler", func() {
  1090  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1091  					return packit.BuildResult{}, nil
  1092  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1093  
  1094  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("expected '.' or '=', but got '%' instead")))
  1095  			})
  1096  		})
  1097  
  1098  		context("when the build func returns an error", func() {
  1099  			it("calls the exit handler", func() {
  1100  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1101  					return packit.BuildResult{}, errors.New("build failed")
  1102  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1103  
  1104  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError("build failed"))
  1105  			})
  1106  		})
  1107  
  1108  		context("when the buildpack.toml is malformed", func() {
  1109  			it.Before(func() {
  1110  				err := os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), []byte("%%%"), 0600)
  1111  				Expect(err).NotTo(HaveOccurred())
  1112  			})
  1113  
  1114  			it("calls the exit handler", func() {
  1115  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1116  					return packit.BuildResult{}, nil
  1117  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1118  
  1119  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("expected '.' or '=', but got '%' instead")))
  1120  			})
  1121  		})
  1122  
  1123  		context("when the buildpack plan.toml cannot be written", func() {
  1124  			it.Before(func() {
  1125  				bpTOML := []byte(`
  1126  api = "0.4"
  1127  [buildpack]
  1128    id = "some-id"
  1129    name = "some-name"
  1130    version = "some-version"
  1131    clear-env = false
  1132  				`)
  1133  				Expect(os.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
  1134  				Expect(os.WriteFile(filepath.Join(envCnbDir, "buildpack.toml"), bpTOML, 0600)).To(Succeed())
  1135  				Expect(os.Chmod(planPath, 0444)).To(Succeed())
  1136  			})
  1137  
  1138  			it("calls the exit handler", func() {
  1139  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1140  					return packit.BuildResult{Plan: ctx.Plan}, nil
  1141  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1142  
  1143  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1144  			})
  1145  		})
  1146  
  1147  		context("when the layer.toml file cannot be written", func() {
  1148  			it.Before(func() {
  1149  				Expect(os.Chmod(layersDir, 0000)).To(Succeed())
  1150  			})
  1151  
  1152  			it.After(func() {
  1153  				Expect(os.Chmod(layersDir, os.ModePerm)).To(Succeed())
  1154  			})
  1155  
  1156  			it("calls the exit handler", func() {
  1157  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1158  					return packit.BuildResult{
  1159  						Layers: []packit.Layer{
  1160  							packit.Layer{
  1161  								Path: filepath.Join(layersDir, "some-layer"),
  1162  								Name: "some-layer",
  1163  							},
  1164  						},
  1165  					}, nil
  1166  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1167  
  1168  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1169  			})
  1170  		})
  1171  
  1172  		context("when the layer sbom cannot be written", func() {
  1173  			it("calls the exit handler", func() {
  1174  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1175  					return packit.BuildResult{
  1176  						Layers: []packit.Layer{
  1177  							packit.Layer{
  1178  								Path: filepath.Join(layersDir, "some-layer"),
  1179  								Name: "some-layer",
  1180  								SBOM: packit.SBOMFormats{
  1181  									{
  1182  										Extension: "some.json",
  1183  										Content:   iotest.ErrReader(errors.New("failed to format layer sbom")),
  1184  									},
  1185  								},
  1186  							},
  1187  						},
  1188  					}, nil
  1189  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1190  
  1191  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("failed to format layer sbom")))
  1192  			})
  1193  		})
  1194  
  1195  		context("when the launch.toml file cannot be written", func() {
  1196  			it.Before(func() {
  1197  				_, err := os.OpenFile(filepath.Join(layersDir, "launch.toml"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0000)
  1198  				Expect(err).NotTo(HaveOccurred())
  1199  			})
  1200  
  1201  			it.After(func() {
  1202  				Expect(os.Chmod(filepath.Join(layersDir, "launch.toml"), os.ModePerm)).To(Succeed())
  1203  			})
  1204  
  1205  			it("calls the exit handler", func() {
  1206  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1207  					return packit.BuildResult{
  1208  						Launch: packit.LaunchMetadata{
  1209  							Processes: []packit.Process{{}},
  1210  						},
  1211  					}, nil
  1212  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1213  
  1214  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1215  			})
  1216  		})
  1217  
  1218  		context("when the env dir cannot be created", func() {
  1219  			var envDir string
  1220  			it.Before(func() {
  1221  				var err error
  1222  				envDir, err = os.MkdirTemp("", "environment")
  1223  				Expect(err).NotTo(HaveOccurred())
  1224  
  1225  				Expect(os.Chmod(envDir, 0000)).To(Succeed())
  1226  			})
  1227  
  1228  			it.After(func() {
  1229  				Expect(os.Chmod(envDir, os.ModePerm)).To(Succeed())
  1230  			})
  1231  
  1232  			context("SharedEnv", func() {
  1233  				it("calls the exit handler", func() {
  1234  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1235  						return packit.BuildResult{
  1236  							Layers: []packit.Layer{
  1237  								{
  1238  									Path: envDir,
  1239  									SharedEnv: packit.Environment{
  1240  										"SOME_VAR.override": "some-value",
  1241  									},
  1242  								},
  1243  							},
  1244  						}, nil
  1245  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1246  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1247  				})
  1248  			})
  1249  
  1250  			context("BuildEnv", func() {
  1251  				it("calls the exit handler", func() {
  1252  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1253  						return packit.BuildResult{
  1254  							Layers: []packit.Layer{
  1255  								{
  1256  									Path: envDir,
  1257  									BuildEnv: packit.Environment{
  1258  										"SOME_VAR.override": "some-value",
  1259  									},
  1260  								},
  1261  							},
  1262  						}, nil
  1263  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1264  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1265  				})
  1266  			})
  1267  
  1268  			context("LaunchEnv", func() {
  1269  				it("calls the exit handler", func() {
  1270  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1271  						return packit.BuildResult{
  1272  							Layers: []packit.Layer{
  1273  								{
  1274  									Path: envDir,
  1275  									LaunchEnv: packit.Environment{
  1276  										"SOME_VAR.override": "some-value",
  1277  									},
  1278  								},
  1279  							},
  1280  						}, nil
  1281  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1282  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1283  				})
  1284  			})
  1285  		})
  1286  
  1287  		context("when the env file cannot be created", func() {
  1288  			context("SharedEnv", func() {
  1289  				var envDir string
  1290  				it.Before(func() {
  1291  					envDir = filepath.Join(layersDir, "some-layer", "env")
  1292  					Expect(os.MkdirAll(envDir, os.ModePerm)).To(Succeed())
  1293  					Expect(os.Chmod(envDir, 0000)).To(Succeed())
  1294  				})
  1295  
  1296  				it.After(func() {
  1297  					Expect(os.Chmod(envDir, os.ModePerm)).To(Succeed())
  1298  				})
  1299  
  1300  				it("calls the exit handler", func() {
  1301  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1302  						return packit.BuildResult{
  1303  							Layers: []packit.Layer{{
  1304  								Path: filepath.Join(layersDir, "some-layer"),
  1305  								SharedEnv: packit.Environment{
  1306  									"SOME_VAR.override": "some-value",
  1307  								},
  1308  							}},
  1309  						}, nil
  1310  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1311  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1312  				})
  1313  			})
  1314  
  1315  			context("BuildEnv", func() {
  1316  				var envDir string
  1317  				it.Before(func() {
  1318  					envDir = filepath.Join(layersDir, "some-layer", "env.build")
  1319  					Expect(os.MkdirAll(envDir, os.ModePerm)).To(Succeed())
  1320  					Expect(os.Chmod(envDir, 0000)).To(Succeed())
  1321  				})
  1322  
  1323  				it.After(func() {
  1324  					Expect(os.Chmod(envDir, os.ModePerm)).To(Succeed())
  1325  				})
  1326  
  1327  				it("calls the exit handler", func() {
  1328  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1329  						return packit.BuildResult{
  1330  							Layers: []packit.Layer{{
  1331  								Path: filepath.Join(layersDir, "some-layer"),
  1332  								BuildEnv: packit.Environment{
  1333  									"SOME_VAR.override": "some-value",
  1334  								},
  1335  							}},
  1336  						}, nil
  1337  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1338  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1339  				})
  1340  			})
  1341  
  1342  			context("LaunchEnv", func() {
  1343  				var envDir string
  1344  				it.Before(func() {
  1345  					envDir = filepath.Join(layersDir, "some-layer", "env.launch")
  1346  					Expect(os.MkdirAll(envDir, os.ModePerm)).To(Succeed())
  1347  					Expect(os.Chmod(envDir, 0000)).To(Succeed())
  1348  				})
  1349  
  1350  				it.After(func() {
  1351  					Expect(os.Chmod(envDir, os.ModePerm)).To(Succeed())
  1352  				})
  1353  
  1354  				it("calls the exit handler", func() {
  1355  					packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1356  						return packit.BuildResult{
  1357  							Layers: []packit.Layer{{
  1358  								Path: filepath.Join(layersDir, "some-layer"),
  1359  								LaunchEnv: packit.Environment{
  1360  									"SOME_VAR.override": "some-value",
  1361  								},
  1362  							}},
  1363  						}, nil
  1364  					}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1365  					Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("permission denied")))
  1366  				})
  1367  			})
  1368  		})
  1369  
  1370  		context("when the launch sbom cannot be written", func() {
  1371  			it("calls the exit handler", func() {
  1372  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1373  					return packit.BuildResult{
  1374  						Launch: packit.LaunchMetadata{
  1375  							SBOM: packit.SBOMFormats{
  1376  								{
  1377  									Extension: "some.json",
  1378  									Content:   iotest.ErrReader(errors.New("failed to format launch sbom")),
  1379  								},
  1380  							},
  1381  						},
  1382  					}, nil
  1383  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1384  
  1385  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("failed to format launch sbom")))
  1386  			})
  1387  		})
  1388  
  1389  		context("when the build sbom cannot be written", func() {
  1390  			it("calls the exit handler", func() {
  1391  				packit.Build(func(ctx packit.BuildContext) (packit.BuildResult, error) {
  1392  					return packit.BuildResult{
  1393  						Build: packit.BuildMetadata{
  1394  							SBOM: packit.SBOMFormats{
  1395  								{
  1396  									Extension: "some.json",
  1397  									Content:   iotest.ErrReader(errors.New("failed to format build sbom")),
  1398  								},
  1399  							},
  1400  						},
  1401  					}, nil
  1402  				}, packit.WithArgs([]string{binaryPath, layersDir, platformDir, planPath}), packit.WithExitHandler(exitHandler))
  1403  
  1404  				Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError(ContainSubstring("failed to format build sbom")))
  1405  			})
  1406  		})
  1407  	})
  1408  }