Repository: getporter/porter
Pull request #1370 (2020-11-18 15:02:58)

Support running tests in parallel by iosolating process state 
I will rebase before merging, I know there are a lot of commits... 😅 

# What does this change
* Updated integration tests to run in parallel because I hate waiting. This brought the CI time from 31 -> 24 minutes. We run our CI on a small linux machine, it would go much faster on our Windows agent which is a better spec (more cores = GOMAXPROCS is higher and more tests can run at the same time). I am working on that right now,  a lot of our tests assume they are not on Windows but it will come in a follow-up.
* The jsonschema library used by cnab isn't threadsafe, so I am using a fork that helps us.
* Store process level values such as environment values and current working directory in our context struct.
* Resolve env vars and other process dependent values against our custom context.
* I fixed cnab-go so that our command driver doesn't rely on global process state.

Note that to run a test matrix in parallel you need to avoid accidentally using go closures wrong:

```
t.Run(tc.name, func(t *testing.T) {
  t.Parallel()
  tc := tc // Ensure that the testcase is in the closure
```

# What issue does it fix
This allow us to run our tests in parallel.

# Notes for the reviewer
* Let me know if this seems maintainable, or a mess
* I am going to try to put additional changes in a follow-up, e.g. marking more tests with t.Parallel, running tests on a different agent, etc because this is getting big.
* An alternative to this would be change all the integration tests to e2e tests, which run in their own process space because they always start from the porter command line. We wouldn't be able to run unit tests in parallel, and would need to change all of those tests to work from the command-line. I am not sure if this would be better or not (it may be harder to test from the outside, we would have to always use real implementations, ever mocked ones that can simulate failures) so your thoughts are appreciated.

# Checklist
- [x] Unit Tests
- [x] Documentation
- [ ] Schema (porter.yaml)



Patch:
diff --git a/Makefile b/Makefile
index 950bdabb..8e523a5b 100644
--- a/Makefile
+++ b/Makefile
@@ -84,7 +84,7 @@ test-unit:
 	$(GO) test ./...
 
 test-integration: clean-last-testrun start-local-docker-registry
-	$(GO) build -o $(PORTER_HOME)/testplugin ./cmd/testplugin
+	$(GO) build -o bin/testplugin ./cmd/testplugin
 	PROJECT_ROOT=$(shell pwd) $(GO) test -timeout 30m -tags=integration ./...
 
 teste2e:
diff --git a/cmd/exec/main.go b/cmd/exec/main.go
index 6ddc18a6..16c53040 100644
--- a/cmd/exec/main.go
+++ b/cmd/exec/main.go
@@ -17,12 +17,13 @@ func main() {
 
 func buildRootCommand(in io.Reader) *cobra.Command {
 	m := exec.New()
-	m.In = in
+
 	cmd := &cobra.Command{
 		Use:  "exec",
 		Long: "exec is a porter 👩🏽‍✈️ mixin that you can you can use to execute arbitrary commands",
 		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			// Enable swapping out stdout/stderr for testing
+			// Enable swapping out stdout/stderr/stdin for testing
+			m.In = in
 			m.Out = cmd.OutOrStdout()
 			m.Err = cmd.OutOrStderr()
 		},
diff --git a/go.mod b/go.mod
index db5cb695..6e2902dd 100644
--- a/go.mod
+++ b/go.mod
@@ -3,17 +3,26 @@ module get.porter.sh/porter
 go 1.13
 
 replace (
+	// jsonschema lock
+	github.com/cnabio/cnab-go => github.com/carolynvs/cnab-go v0.13.4-0.20201230032116-229dd4b057af
+
 	// See https://github.com/containerd/containerd/issues/3031
 	// When I try to just use the require, go is shortening it to v2.7.1+incompatible which then fails to build...
 	github.com/docker/distribution => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
 	github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
 
-	github.com/hashicorp/go-plugin => github.com/carolynvs/go-plugin v1.0.1-acceptstdin
+	// See https://github.com/hashicorp/go-plugin/pull/127 and
+	// https://github.com/hashicorp/go-plugin/pull/163
+	// Tagged from v1.4.0, the improved-configuration branch
+	github.com/hashicorp/go-plugin => github.com/getporter/go-plugin v1.4.0-improved-configuration
 
 	// Fork (fluent branch) that adds fluent syntax and supports running a
 	// command in a directory without using chdir
 	github.com/magefile/mage => github.com/carolynvs/mage v1.10.1-0.20201116013517-68243214dee0
 
+	// jsonschema lock
+	github.com/qri-io/jsonschema => github.com/carolynvs/jsonschema v0.2.1-0.20201229145510-cc593f443fdb
+
 	golang.org/x/sys => golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69
 )
 
@@ -21,6 +30,7 @@ require (
 	github.com/Masterminds/semver v1.5.0
 	github.com/PaesslerAG/jsonpath v0.1.1
 	github.com/PuerkitoBio/goquery v1.5.0 // indirect
+	github.com/carolynvs/aferox v0.2.1
 	github.com/carolynvs/datetime-printer v0.2.0
 	github.com/carolynvs/magex v0.2.1-0.20201116013928-d6af240c802c
 	github.com/cbroglie/mustache v1.0.1
@@ -42,9 +52,9 @@ require (
 	github.com/gogo/googleapis v1.3.2 // indirect
 	github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3
 	github.com/gorilla/mux v1.7.4 // indirect
-	github.com/hashicorp/go-hclog v0.9.2
+	github.com/hashicorp/go-hclog v0.14.1
 	github.com/hashicorp/go-multierror v1.1.0
-	github.com/hashicorp/go-plugin v0.0.0-00010101000000-000000000000
+	github.com/hashicorp/go-plugin v1.4.0
 	github.com/hashicorp/go-version v1.2.0 // indirect
 	github.com/magefile/mage v1.10.0
 	github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59
@@ -53,7 +63,7 @@ require (
 	github.com/olekukonko/tablewriter v0.0.4
 	github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c
 	github.com/pkg/errors v0.9.1
-	github.com/spf13/afero v1.2.2
+	github.com/spf13/afero v1.4.1
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.4.0
diff --git a/go.sum b/go.sum
index 7f83ca85..05008f86 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,7 @@ cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISt
 github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-autorest v10.15.5+incompatible h1:vdxx6wM1rVkKt/3niByPVjguoLWkWImOcJNvEykgBzY=
 github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest v12.2.0+incompatible h1:2Fxszbg492oAJrcvJlgyVaTqnQYRkxmEK6VPCLLVpBI=
 github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
@@ -80,10 +81,14 @@ github.com/bugsnag/bugsnag-go v1.5.0 h1:tP8hiPv1pGGW3LA6LKy5lW6WG+y9J2xWUdPd3WC4
 github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
 github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
 github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/carolynvs/aferox v0.2.1 h1:TE9vIOI2ElsN0G53Th5PU2ygoJUD9Cw0FOjwItwjKTE=
+github.com/carolynvs/aferox v0.2.1/go.mod h1:5H8FDMsDCbKrVfZTid78zbfp1jVdYDRItuyMK2G1mVM=
+github.com/carolynvs/cnab-go v0.13.4-0.20201230032116-229dd4b057af h1:qDvnCQWrSsK/6kHV5t9CBch7ULxfm8pQ9ayF5YG3vsY=
+github.com/carolynvs/cnab-go v0.13.4-0.20201230032116-229dd4b057af/go.mod h1:vW7N9iN+Jd5P37cQNYS/q1IQLCfakqIFg08BQ8MHZdw=
 github.com/carolynvs/datetime-printer v0.2.0 h1:Td3FU4YGzx0OogCMhCmLBTUTDPQcq0xlgCeMhAKZmMc=
 github.com/carolynvs/datetime-printer v0.2.0/go.mod h1:p9W8ZUhmQUOVD5kiDuGXwRG65/nTkZWlLylY7s+Qw2k=
-github.com/carolynvs/go-plugin v1.0.1-acceptstdin h1:8JccOWqcZoqCILz191C0D6RTnz/DKfNcY8+T6F3/G9g=
-github.com/carolynvs/go-plugin v1.0.1-acceptstdin/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
+github.com/carolynvs/jsonschema v0.2.1-0.20201229145510-cc593f443fdb h1:c7GTaHPGaDjZ7z0GYHcpml41Xy72pSkaw4xFMPbz3Aw=
+github.com/carolynvs/jsonschema v0.2.1-0.20201229145510-cc593f443fdb/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
 github.com/carolynvs/mage v1.10.1-0.20201116013517-68243214dee0 h1:5W+JLpLwd/6dZGllMKWGlEGuUzI2o27CZXzKXWdLUnI=
 github.com/carolynvs/mage v1.10.1-0.20201116013517-68243214dee0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
 github.com/carolynvs/magex v0.2.1-0.20201116013928-d6af240c802c h1:X+V0HFxdUNGSxAX4ej9gmpGLPcOqqnhdRTs4RsEXypA=
@@ -92,6 +97,7 @@ github.com/cbroglie/mustache v1.0.1 h1:ivMg8MguXq/rrz2eu3tw6g3b16+PQhoTn6EZAhst2
 github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g=
 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
@@ -102,9 +108,6 @@ github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiK
 github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
 github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
 github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
-github.com/cnabio/cnab-go v0.10.0-beta1/go.mod h1:5c4uOP6ZppR4nUGtCMAElscRiYEUi44vNQwtSAvISXk=
-github.com/cnabio/cnab-go v0.15.0 h1:2jjlB8btV5x14empYZv03CzRI99erUEAFJdpi0giGpM=
-github.com/cnabio/cnab-go v0.15.0/go.mod h1:MlIz5HFApIBvFxa5swvb/IxO/DJ4+viUMGb0Dt5tdEg=
 github.com/cnabio/cnab-to-oci v0.3.1-beta1 h1:qAuLRt+2J7U7wIB5YG+COtS630NQCf4G1h1p0Yk6llo=
 github.com/cnabio/cnab-to-oci v0.3.1-beta1/go.mod h1:8BomA5Vye+3V/Kd2NSFblCBmp1rJV5NfXBYKbIGT5Rw=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=
@@ -178,15 +181,20 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/getporter/go-plugin v1.4.0-improved-configuration h1:UedA6wc5lRjLOjG/g/AH3QplFLZt1JvCfyzGkp2koyQ=
+github.com/getporter/go-plugin v1.4.0-improved-configuration/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
 github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -243,6 +251,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -285,9 +295,8 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc
 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
-github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
-github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
+github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
 github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
 github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@@ -308,6 +317,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
+github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
 github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
 github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -340,6 +351,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -369,6 +381,7 @@ github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3
 github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@@ -439,6 +452,7 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
 github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ=
 github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
@@ -454,6 +468,7 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -486,8 +501,8 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/qri-io/jsonpointer v0.1.0 h1:OcTtTmorodUCRc2CZhj/ZwOET8zVj6uo0ArEmzoThZI=
 github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
-github.com/qri-io/jsonschema v0.1.1 h1:t//Doa/gvMqJ0bDhG7PGIKfaWGGxRVaffp+bcvBGGEk=
-github.com/qri-io/jsonschema v0.1.1/go.mod h1:QpzJ6gBQ0GYgGmh7mDQ1YsvvhSgE4rYj0k8t5MBOmUY=
+github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
+github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -505,14 +520,16 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
+github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -537,7 +554,6 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -591,8 +607,8 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
 golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -606,6 +622,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -632,7 +649,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -641,8 +657,9 @@ golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
@@ -674,6 +691,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -681,12 +699,17 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs=
 google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM=
 google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 gopkg.in/AlecAivazis/survey.v1 v1.8.7 h1:oBJqtgsyBLg9K5FK9twNUbcPnbCPoh+R9a+7nag3qJM=
 gopkg.in/AlecAivazis/survey.v1 v1.8.7/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
diff --git a/mixin.mk b/mixin.mk
index 8a571740..bff7cb66 100644
--- a/mixin.mk
+++ b/mixin.mk
@@ -10,7 +10,7 @@ PERMALINK ?= $(shell git describe --tags --exact-match &> /dev/null && echo late
 
 LDFLAGS = -w -X $(PKG)/pkg.Version=$(VERSION) -X $(PKG)/pkg.Commit=$(COMMIT)
 GO = GO111MODULE=on go
-XBUILD = CGO_ENABLED=0 GO111MODULE=on $(GO) build -a -tags netgo -ldflags '$(LDFLAGS)'
+XBUILD = CGO_ENABLED=0 GO111MODULE=on $(GO) build -ldflags '$(LDFLAGS)'
 BINDIR ?= bin/mixins/$(MIXIN)
 
 CLIENT_PLATFORM ?= $(shell go env GOOS)
diff --git a/pkg/build/build.go b/pkg/build/build.go
index a663f0fd..4f176a54 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -5,6 +5,9 @@ import (
 )
 
 var (
+	// DOCKER_FILE is the file generated before running a docker build.
+	DOCKER_FILE = "Dockerfile"
+
 	// LOCAL_CNAB is the generated directory where porter stages the /cnab directory.
 	LOCAL_CNAB = ".cnab"
 
@@ -14,13 +17,13 @@ var (
 	// LOCAL_BUNDLE is the generated bundle.json file.
 	LOCAL_BUNDLE = filepath.Join(LOCAL_CNAB, "bundle.json")
 
+	// LOCAL_RUN is the path to the generated CNAB entrypoint script, located at /cnab/app/run.
+	LOCAL_RUN = filepath.Join(LOCAL_APP, "run")
+
 	// LOCAL_MANIFEST is the canonical Porter manifest generated from the
 	// user-provided manifest and any dynamic overrides
 	LOCAL_MANIFEST = filepath.Join(LOCAL_APP, "porter.yaml")
 
-	// LOCAL_RUN is the path to the generated CNAB entrypoint script, located at /cnab/app/run.
-	LOCAL_RUN = filepath.Join(LOCAL_APP, "run")
-
 	// BUNDLE_DIR is the directory where the bundle is located in the CNAB execution environment.
 	BUNDLE_DIR = "/cnab/app"
 
diff --git a/pkg/build/provider/docker.go b/pkg/build/provider/docker.go
index 36ff3d7f..3012937c 100644
--- a/pkg/build/provider/docker.go
+++ b/pkg/build/provider/docker.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 	"io/ioutil"
-	"os"
 
 	"get.porter.sh/porter/pkg/build"
 	portercontext "get.porter.sh/porter/pkg/context"
@@ -30,29 +29,25 @@ func NewDockerBuilder(cxt *portercontext.Context) *DockerBuilder {
 }
 
 func (b *DockerBuilder) BuildInvocationImage(manifest *manifest.Manifest) error {
-	fmt.Fprintf(b.Out, "\nStarting Invocation Image Build =======> \n")
-	path, err := os.Getwd()
-	if err != nil {
-		return errors.Wrap(err, "could not get current working directory")
-	}
+	fmt.Fprintf(b.Out, "\nStarting Invocation Image Build (%s) =======> \n", manifest.Image)
 	buildOptions := types.ImageBuildOptions{
 		SuppressOutput: false,
 		PullParent:     false,
 		Remove:         true,
 		Tags:           []string{manifest.Image},
-		Dockerfile:     "Dockerfile",
+		Dockerfile:     build.DOCKER_FILE,
 		BuildArgs: map[string]*string{
 			"BUNDLE_DIR": &build.BUNDLE_DIR,
 		},
 	}
 
-	excludes, err := clibuild.ReadDockerignore(path)
+	excludes, err := clibuild.ReadDockerignore(b.Getwd())
 	if err != nil {
 		return err
 	}
 	excludes = clibuild.TrimBuildFilesFromExcludes(excludes, buildOptions.Dockerfile, false)
 
-	tar, err := archive.TarWithOptions(path, &archive.TarOptions{ExcludePatterns: excludes})
+	tar, err := archive.TarWithOptions(b.Getwd(), &archive.TarOptions{ExcludePatterns: excludes})
 	if err != nil {
 		return err
 	}
diff --git a/pkg/cache/cached_bundle.go b/pkg/cache/cached_bundle.go
index 2bab1759..ff4df5fb 100644
--- a/pkg/cache/cached_bundle.go
+++ b/pkg/cache/cached_bundle.go
@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"path/filepath"
 
+	"get.porter.sh/porter/pkg/cnab"
 	"get.porter.sh/porter/pkg/config"
 	"get.porter.sh/porter/pkg/context"
 	"get.porter.sh/porter/pkg/manifest"
@@ -104,20 +105,14 @@ func (cb *CachedBundle) Load(cxt *context.Context) (bool, error) {
 		cb.ManifestPath = manifestPath
 	}
 
-	// Read the files
-	data, err := cxt.FileSystem.ReadFile(cb.BundlePath)
-	if err != nil {
-		return true, errors.Wrapf(err, "unable to read cached bundle file at %s", cb.BundlePath)
-	}
-
-	bun, err := bundle.Unmarshal(data)
+	bun, err := cnab.LoadBundle(cxt, cb.BundlePath)
 	if err != nil {
 		return true, errors.Wrapf(err, "unable to parse cached bundle file at %s", cb.BundlePath)
 	}
-	cb.Bundle = *bun
+	cb.Bundle = bun
 
 	if cb.RelocationFilePath != "" {
-		data, err = cxt.FileSystem.ReadFile(cb.RelocationFilePath)
+		data, err := cxt.FileSystem.ReadFile(cb.RelocationFilePath)
 		if err != nil {
 			return true, errors.Wrapf(err, "unable to read cached relocation file at %s", cb.RelocationFilePath)
 		}
diff --git a/pkg/cnab/bundle.go b/pkg/cnab/bundle.go
index 6b1cbeaa..39fa90aa 100644
--- a/pkg/cnab/bundle.go
+++ b/pkg/cnab/bundle.go
@@ -4,7 +4,6 @@ import (
 	"get.porter.sh/porter/pkg/context"
 	"github.com/cnabio/cnab-go/bundle"
 	"github.com/cnabio/cnab-go/bundle/definition"
-	"github.com/cnabio/cnab-go/bundle/loader"
 	"github.com/pkg/errors"
 )
 
@@ -13,14 +12,12 @@ func IsFileType(s *definition.Schema) bool {
 }
 
 func LoadBundle(c *context.Context, bundleFile string) (bundle.Bundle, error) {
-	l := loader.New()
-
 	bunD, err := c.FileSystem.ReadFile(bundleFile)
 	if err != nil {
 		return bundle.Bundle{}, errors.Wrapf(err, "cannot read bundle at %s", bundleFile)
 	}
 
-	bun, err := l.LoadData(bunD)
+	bun, err := bundle.Unmarshal(bunD)
 	if err != nil {
 		return bundle.Bundle{}, errors.Wrapf(err, "cannot load bundle from\n%s at %s", string(bunD), bundleFile)
 	}
diff --git a/pkg/cnab/config-adapter/stamp.go b/pkg/cnab/config-adapter/stamp.go
index f15b9c3b..7bebd0ec 100644
--- a/pkg/cnab/config-adapter/stamp.go
+++ b/pkg/cnab/config-adapter/stamp.go
@@ -102,9 +102,8 @@ func (c *ManifestConverter) DigestManifest() (string, error) {
 	v := pkg.Version
 	data = append(data, v...)
 
-	// Porter may update the invocation image name on-the-fly
 	// (for instance, during publish), so add this to the data
-	data = append(data, []byte(c.Manifest.Image)...)
+	data = append(data, c.Manifest.Image...)
 
 	for _, m := range c.Mixins {
 		data = append(append(data, m.Name...), m.Version...)
diff --git a/pkg/cnab/drivers/drivers.go b/pkg/cnab/drivers/drivers.go
new file mode 100644
index 00000000..8e2d4382
--- /dev/null
+++ b/pkg/cnab/drivers/drivers.go
@@ -0,0 +1,35 @@
+package drivers
+
+import (
+	"get.porter.sh/porter/pkg/context"
+	"github.com/cnabio/cnab-go/driver"
+	"github.com/cnabio/cnab-go/driver/command"
+	"github.com/cnabio/cnab-go/driver/debug"
+	"github.com/cnabio/cnab-go/driver/docker"
+	"github.com/cnabio/cnab-go/driver/kubernetes"
+	"github.com/pkg/errors"
+)
+
+// LookupDriver creates a driver by name.
+//
+// This replaces cnab-go's lookup function because cnab-go uses global process
+// values, such as $PATH, instead of our context.
+func LookupDriver(cxt *context.Context, name string) (driver.Driver, error) {
+	switch name {
+	case "docker":
+		return &docker.Driver{}, nil
+	case "kubernetes", "k8s":
+		return &kubernetes.Driver{}, nil
+	case "debug":
+		return &debug.Driver{}, nil
+	default:
+		// Drivers must be named "cnab-NAME" and be on the PATH
+		if driverPath, ok := cxt.LookPath("cnab-" + name); ok {
+			d := &command.Driver{Name: name}
+			d.Path = driverPath
+			return d, nil
+		}
+
+		return nil, errors.Errorf("unsupported driver or driver not found in PATH: %s", name)
+	}
+}
diff --git a/pkg/cnab/provider/driver.go b/pkg/cnab/provider/driver.go
index e3ab51f1..dba0f7d5 100644
--- a/pkg/cnab/provider/driver.go
+++ b/pkg/cnab/provider/driver.go
@@ -1,12 +1,10 @@
 package cnabprovider
 
 import (
-	"os"
-
+	"get.porter.sh/porter/pkg/cnab/drivers"
 	"get.porter.sh/porter/pkg/cnab/extensions"
 	"github.com/cnabio/cnab-go/driver"
 	"github.com/cnabio/cnab-go/driver/docker"
-	"github.com/cnabio/cnab-go/driver/lookup"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/mount"
 	"github.com/pkg/errors"
@@ -41,7 +39,7 @@ func (r *Runtime) newDriver(driverName string, claimName string, args ActionArgu
 			return nil, errors.Errorf("extension %q is required but allow-docker-host-access was not enabled",
 				extensions.DockerExtensionKey)
 		}
-		driverImpl, err = lookup.Lookup(driverName)
+		driverImpl, err = drivers.LookupDriver(r.Context, driverName)
 	}
 	if err != nil {
 		return nil, err
@@ -51,7 +49,7 @@ func (r *Runtime) newDriver(driverName string, claimName string, args ActionArgu
 		driverCfg := make(map[string]string)
 		// Load any driver-specific config out of the environment
 		for env := range configurable.Config() {
-			if val, ok := os.LookupEnv(env); ok {
+			if val, ok := r.LookupEnv(env); ok {
 				driverCfg[env] = val
 			}
 		}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 29ab4da0..e30b7187 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -95,7 +95,7 @@ func (c *Config) GetHomeDir() (string, error) {
 		return c.porterHome, nil
 	}
 
-	home := os.Getenv(EnvHOME)
+	home := c.Getenv(EnvHOME)
 	if home == "" {
 		userHome, err := os.UserHomeDir()
 		if err != nil {
@@ -107,12 +107,8 @@ func (c *Config) GetHomeDir() (string, error) {
 	// As a relative path may be supplied via EnvHOME,
 	// we want to return the absolute path for programmatic usage elsewhere,
 	// for instance, in setting up volume mounts for outputs
-	absoluteHome, err := filepath.Abs(home)
-	if err != nil {
-		return "", errors.Wrap(err, "could not get the absolute path for the porter home directory")
-	}
+	c.SetHomeDir(c.FileSystem.Abs(home))
 
-	c.porterHome = absoluteHome
 	return c.porterHome, nil
 }
 
@@ -120,6 +116,10 @@ func (c *Config) GetHomeDir() (string, error) {
 // Porter home directory.
 func (c *Config) SetHomeDir(home string) {
 	c.porterHome = home
+
+	// Set this as an environment variable so that when we spawn new processes
+	// such as a mixin or plugin, that they can find PORTER_HOME too
+	c.Setenv(EnvHOME, home)
 }
 
 // SetPorterPath is a test function that allows tests to use an alternate
diff --git a/pkg/config/helpers.go b/pkg/config/helpers.go
index 0b05fc86..a6a5ad1e 100644
--- a/pkg/config/helpers.go
+++ b/pkg/config/helpers.go
@@ -14,17 +14,19 @@ type TestConfig struct {
 
 // NewTestConfig initializes a configuration suitable for testing, with the output buffered, and an in-memory file system.
 func NewTestConfig(t *testing.T) *TestConfig {
-	tc := context.NewTestContext(t)
+	cxt := context.NewTestContext(t)
 	cfg := New()
-	cfg.Context = tc.Context
-	return &TestConfig{
+	cfg.Context = cxt.Context
+	tc := &TestConfig{
 		Config:      cfg,
-		TestContext: tc,
+		TestContext: cxt,
 	}
+	tc.SetupUnitTest()
+	return tc
 }
 
-// InitializePorterHome initializes the test filesystem with the supporting files in the PORTER_HOME directory.
-func (c *TestConfig) SetupPorterHome() {
+// SetupUnitTest initializes the unit test filesystem with the supporting files in the PORTER_HOME directory.
+func (c *TestConfig) SetupUnitTest() {
 	// Set up the test porter home directory
 	home := "/root/.porter"
 	c.SetHomeDir(home)
@@ -40,16 +42,28 @@ func (c *TestConfig) SetupPorterHome() {
 	c.FileSystem.Create(filepath.Join(mixinsDir, "helm/runtimes/helm-runtime"))
 }
 
-// InitializePorterHome initializes the filesystem with the supporting files in the PORTER_HOME directory.
-func (c *TestConfig) SetupIntegrationTest(home string) {
-	c.SetHomeDir(home)
+// SetupIntegrationTest initializes the filesystem with the supporting files in
+// a temp PORTER_HOME directory.
+func (c *TestConfig) SetupIntegrationTest() (testDir string, homeDir string) {
+	testDir, homeDir = c.TestContext.UseFilesystem()
+	c.SetHomeDir(homeDir)
 
 	// Use the compiled porter binary in the test home directory,
 	// and not the go test binary that is generated when we run integration tests.
 	// This way when Porter calls back to itself, e.g. for internal plugins,
 	// it is calling the normal porter binary.
-	c.SetPorterPath(filepath.Join(home, "porter"))
+	c.SetPorterPath(filepath.Join(homeDir, "porter"))
 
 	// Copy bin dir contents to the home directory
-	c.TestContext.AddTestDirectory(c.TestContext.FindBinDir(), home)
+	c.TestContext.AddTestDirectory(c.TestContext.FindBinDir(), homeDir)
+
+	// Remove any rando stuff copied from the dev bin, you won't find this in CI but a local dev run may have it
+	// Not checking for an error, since the files won't be there on CI
+	c.FileSystem.RemoveAll(filepath.Join(homeDir, "installations"))
+	c.FileSystem.RemoveAll(filepath.Join(homeDir, "claims"))
+	c.FileSystem.RemoveAll(filepath.Join(homeDir, "results"))
+	c.FileSystem.RemoveAll(filepath.Join(homeDir, "outputs"))
+	c.FileSystem.Remove(filepath.Join(homeDir, "schema.json"))
+
+	return testDir, homeDir
 }
diff --git a/pkg/context/context.go b/pkg/context/context.go
index 6ee5becf..a9362fd6 100644
--- a/pkg/context/context.go
+++ b/pkg/context/context.go
@@ -2,13 +2,14 @@ package context
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"strconv"
 	"strings"
 
+	"github.com/carolynvs/aferox"
 	"github.com/pkg/errors"
 	"github.com/spf13/afero"
 )
@@ -24,7 +25,8 @@ type Context struct {
 	Debug              bool
 	DebugPlugins       bool
 	verbose            bool
-	FileSystem         *afero.Afero
+	environ            map[string]string
+	FileSystem         aferox.Aferox
 	In                 io.Reader
 	Out                io.Writer
 	Err                io.Writer
@@ -32,6 +34,61 @@ type Context struct {
 	PlugInDebugContext *PluginDebugContext
 }
 
+// New creates a new context in the specified directory.
+func New() *Context {
+	// Ignore any error getting the working directory and report errors
+	// when we attempt to access files in the current directory. This
+	// allows us to use the current directory as a default, and allow
+	// tests to override it.
+	pwd, _ := os.Getwd()
+
+	c := &Context{
+		environ:    getEnviron(),
+		FileSystem: aferox.NewAferox(pwd, afero.NewOsFs()),
+		In:         os.Stdin,
+		Out:        NewCensoredWriter(os.Stdout),
+		Err:        NewCensoredWriter(os.Stderr),
+	}
+	c.defaultNewCommand()
+	c.PlugInDebugContext = NewPluginDebugContext(c)
+	return c
+}
+
+func (c *Context) defaultNewCommand() {
+	c.NewCommand = func(name string, arg ...string) *exec.Cmd {
+		return c.Command(name, arg...)
+	}
+}
+
+// Command creates a new exec.Cmd using the context's current directory.
+func (c *Context) Command(name string, arg ...string) *exec.Cmd {
+	cmd := &exec.Cmd{
+		Dir:  c.Getwd(),
+		Path: name,
+		Args: append([]string{name}, arg...),
+	}
+	if filepath.Base(name) == name {
+		if lp, ok := c.LookPath(name); ok {
+			cmd.Path = lp
+		}
+	}
+	return cmd
+}
+
+func getEnviron() map[string]string {
+	environ := map[string]string{}
+	for _, env := range os.Environ() {
+		envParts := strings.SplitN(env, "=", 2)
+		key := envParts[0]
+		value := ""
+		if len(envParts) > 1 {
+			value = envParts[1]
+		}
+		environ[key] = value
+	}
+	return environ
+}
+
 func (c *Context) SetVerbose(value bool) {
 	c.verbose = value
 }
@@ -40,6 +97,76 @@ func (c *Context) IsVerbose() bool {
 	return c.Debug || c.verbose
 }
 
+// Environ returns a copy of strings representing the environment,
+// in the form "key=value".
+func (c *Context) Environ() []string {
+	e := make([]string, 0, len(c.environ))
+	for k, v := range c.environ {
+		e = append(e, fmt.Sprintf("%s=%s", k, v))
+	}
+	return e
+}
+
+// ExpandEnv replaces ${var} or $var in the string according to the values
+// of the current environment variables. References to undefined
+// variables are replaced by the empty string.
+func (c *Context) ExpandEnv(s string) string {
+	return os.Expand(s, func(key string) string { return c.Getenv(key) })
+}
+
+// Getenv retrieves the value of the environment variable named by the key.
+// It returns the value, which will be empty if the variable is not present.
+// To distinguish between an empty value and an unset value, use LookupEnv.
+func (c *Context) Getenv(key string) string {
+	return c.environ[key]
+}
+
+// This is a simplified exec.LookPath that checks if command is accessible given
+// a PATH environment variable.
+func (c *Context) LookPath(file string) (string, bool) {
+	return c.FileSystem.LookPath(file, c.Getenv("PATH"), c.Getenv("PATHEXT"))
+}
+
+// LookupEnv retrieves the value of the environment variable named
+// by the key. If the variable is present in the environment the
+// value (which may be empty) is returned and the boolean is true.
+// Otherwise the returned value will be empty and the boolean will
+// be false.
+func (c *Context) LookupEnv(key string) (string, bool) {
+	value, ok := c.environ[key]
+	return value, ok
+}
+
+// Setenv sets the value of the environment variable named by the key.
+// It returns an error, if any.
+func (c *Context) Setenv(key string, value string) {
+	if c.environ == nil {
+		c.environ = make(map[string]string, 1)
+	}
+
+	c.environ[key] = value
+}
+
+// Unsetenv unsets a single environment variable.
+func (c *Context) Unsetenv(key string) {
+	delete(c.environ, key)
+}
+
+// Clearenv deletes all environment variables.
+func (c *Context) Clearenv() {
+	c.environ = make(map[string]string, 0)
+}
+
+// Getwd returns a rooted path name corresponding to the current directory.
+func (c *Context) Getwd() string {
+	return c.FileSystem.Getwd()
+}
+
+// Chdir changes the current working directory to the named directory.
+func (c *Context) Chdir(dir string) {
+	c.FileSystem.Chdir(dir)
+}
+
 // CensoredWriter is a writer wrapping the provided io.Writer with logic to censor certain values
 type CensoredWriter struct {
 	writer          io.Writer
@@ -69,21 +196,6 @@ func (cw *CensoredWriter) Write(b []byte) (int, error) {
 	return len(b), err
 }
 
-func New() *Context {
-	// Default to respecting the PORTER_DEBUG env variable, the cli will override if --debug is set otherwise
-	debug, _ := strconv.ParseBool(os.Getenv("PORTER_DEBUG"))
-
-	return &Context{
-		Debug:              debug,
-		FileSystem:         &afero.Afero{Fs: afero.NewOsFs()},
-		In:                 os.Stdin,
-		Out:                NewCensoredWriter(os.Stdout),
-		Err:                NewCensoredWriter(os.Stderr),
-		NewCommand:         exec.Command,
-		PlugInDebugContext: NewPluginDebugContext(),
-	}
-}
-
 func (c *Context) CopyDirectory(srcDir, destDir string, includeBaseDir bool) error {
 	var stripPrefix string
 	if includeBaseDir {
diff --git a/pkg/context/helpers.go b/pkg/context/helpers.go
index 202e39eb..120a989d 100644
--- a/pkg/context/helpers.go
+++ b/pkg/context/helpers.go
@@ -8,10 +8,13 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"testing"
 
 	"get.porter.sh/porter/pkg/test"
+	"github.com/carolynvs/aferox"
+	"github.com/pkg/errors"
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/require"
 )
@@ -25,7 +28,9 @@ type TestContext struct {
 	T           *testing.T
 }
 
-// NewTestContext initializes a configuration suitable for testing, with the output buffered, and an in-memory file system.
+// NewTestContext initializes a configuration suitable for testing, with the
+// output buffered, and an in-memory file system, using the specified
+// environment variables.
 func NewTestContext(t *testing.T) *TestContext {
 	// Provide a way for tests to provide and capture stdin and stdout
 	// Copy output to the test log simultaneously, use go test -v to see the output
@@ -37,11 +42,11 @@ func NewTestContext(t *testing.T) *TestContext {
 	c := &TestContext{
 		Context: &Context{
 			Debug:      true,
-			FileSystem: &afero.Afero{Fs: afero.NewMemMapFs()},
+			environ:    getEnviron(),
+			FileSystem: aferox.NewAferox("/", afero.NewMemMapFs()),
 			In:         &bytes.Buffer{},
 			Out:        aggOut,
 			Err:        aggErr,
-			NewCommand: NewTestCommand(),
 			PlugInDebugContext: &PluginDebugContext{
 				DebuggerPort:           "2735",
 				RunPlugInInDebugger:    "",
@@ -53,33 +58,51 @@ func NewTestContext(t *testing.T) *TestContext {
 		T:           t,
 	}
 
+	c.NewCommand = NewTestCommand(c.Context)
+
 	return c
 }
 
-func NewTestCommand() CommandBuilder {
+func NewTestCommand(c *Context) CommandBuilder {
 	return func(command string, args ...string) *exec.Cmd {
 		testArgs := append([]string{command}, args...)
 		cmd := exec.Command(os.Args[0], testArgs...)
-
+		cmd.Dir = c.Getwd()
 		cmd.Env = []string{
 			fmt.Sprintf("%s=true", test.MockedCommandEnv),
-			fmt.Sprintf("%s=%s", test.ExpectedCommandEnv, os.Getenv(test.ExpectedCommandEnv)),
+			fmt.Sprintf("%s=%s", test.ExpectedCommandEnv, c.Getenv(test.ExpectedCommandEnv)),
 		}
 
 		return cmd
 	}
 }
 
-// UseFilesystem has porter's context use the OS filesystem instead of an in-memory filesystem
-// Returns the temp porter home directory created for the test
-func (c *TestContext) UseFilesystem() string {
-	c.FileSystem = &afero.Afero{Fs: afero.NewOsFs()}
+func (c *TestContext) GetTestDefinitionDirectory() string {
+	for i := 0; true; i++ {
+		_, filename, _, ok := runtime.Caller(i)
+		if !ok {
+			c.T.Fatal("could not determine calling test directory")
+		}
+		filename = strings.ToLower(filename)
+		if strings.HasSuffix(filename, "_test.go") {
+			return filepath.Dir(filename)
+		}
+	}
+	return ""
+}
 
-	testDir, err := ioutil.TempDir("", "porter-test")
+// UseFilesystem has porter's context use the OS filesystem instead of an in-memory filesystem
+// Returns the test directory, and the temp porter home directory.
+func (c *TestContext) UseFilesystem() (testDir string, homeDir string) {
+	homeDir, err := ioutil.TempDir("", "porter-test")
 	require.NoError(c.T, err)
-	c.cleanupDirs = append(c.cleanupDirs, testDir)
+	c.cleanupDirs = append(c.cleanupDirs, homeDir)
+
+	testDir = c.GetTestDefinitionDirectory()
+	c.FileSystem = aferox.NewAferox(testDir, afero.NewOsFs())
+	c.defaultNewCommand()
 
-	return testDir
+	return testDir, homeDir
 }
 
 func (c *TestContext) AddCleanupDir(dir string) {
@@ -95,12 +118,12 @@ func (c *TestContext) Cleanup() {
 func (c *TestContext) AddTestFile(src, dest string) []byte {
 	data, err := ioutil.ReadFile(src)
 	if err != nil {
-		c.T.Fatal(err)
+		c.T.Fatal(errors.Wrapf(err, "error reading file %s from host filesystem", src))
 	}
 
 	err = c.FileSystem.WriteFile(dest, data, os.ModePerm)
 	if err != nil {
-		c.T.Fatal(err)
+		c.T.Fatal(errors.Wrapf(err, "error writing file %s to test filesystem", dest))
 	}
 
 	return data
@@ -171,10 +194,10 @@ func (c *TestContext) AddTestDriver(src, name string) string {
 		c.T.Fatal(err)
 	}
 
-	path := os.Getenv("PATH")
+	path := c.Getenv("PATH")
 	pathlist := []string{dirname, path}
 	newpath := strings.Join(pathlist, string(os.PathListSeparator))
-	os.Setenv("PATH", newpath)
+	c.Setenv("PATH", newpath)
 
 	return dirname
 }
@@ -196,10 +219,7 @@ func (c *TestContext) ClearOutputs() {
 
 func (c *TestContext) FindBinDir() string {
 	var binDir string
-	d, err := os.Getwd()
-	if err != nil {
-		c.T.Fatal(err)
-	}
+	d := c.GetTestDefinitionDirectory()
 	for {
 		binDir = c.getBinDir(d)
 		if binDir != "" {
diff --git a/pkg/context/plugindebugcontext.go b/pkg/context/plugindebugcontext.go
index 57233581..9c3a7966 100644
--- a/pkg/context/plugindebugcontext.go
+++ b/pkg/context/plugindebugcontext.go
@@ -1,7 +1,6 @@
 package context
 
 import (
-	"os"
 	"strconv"
 )
 
@@ -11,11 +10,11 @@ type PluginDebugContext struct {
 	DebuggerPort           string
 }
 
-func NewPluginDebugContext() *PluginDebugContext {
+func NewPluginDebugContext(c *Context) *PluginDebugContext {
 
-	runPlugInInDebugger := os.Getenv("PORTER_RUN_PLUGIN_IN_DEBUGGER")
-	debugWorkingDirectory := os.Getenv("PORTER_PLUGIN_WORKING_DIRECTORY")
-	debuggerPort := os.Getenv("PORTER_DEBUGGER_PORT")
+	runPlugInInDebugger := c.Getenv("PORTER_RUN_PLUGIN_IN_DEBUGGER")
+	debugWorkingDirectory := c.Getenv("PORTER_PLUGIN_WORKING_DIRECTORY")
+	debuggerPort := c.Getenv("PORTER_DEBUGGER_PORT")
 
 	port := "2345"
 	if _, err := strconv.ParseInt(debuggerPort, 10, 16); err != nil {
diff --git a/pkg/editor/editor.go b/pkg/editor/editor.go
index 9d487793..affe5ded 100644
--- a/pkg/editor/editor.go
+++ b/pkg/editor/editor.go
@@ -32,14 +32,14 @@ func New(context *context.Context, tempFilename string, contents []byte) *Editor
 	}
 }
 
-func editorArgs(filename string) []string {
-	shell := defaultShell
-	if os.Getenv("SHELL") != "" {
-		shell = os.Getenv("SHELL")
+func (e *Editor) editorArgs(filename string) []string {
+	shell := e.Getenv("SHELL")
+	if shell == "" {
+		shell = defaultShell
 	}
-	editor := defaultEditor
-	if os.Getenv("EDITOR") != "" {
-		editor = os.Getenv("EDITOR")
+	editor := e.Getenv("EDITOR")
+	if editor == "" {
+		editor = defaultEditor
 	}
 
 	// Example of what will be run:
@@ -69,7 +69,7 @@ func (e *Editor) Run() ([]byte, error) {
 	// close here without defer so cmd can grab the file
 	tempFile.Close()
 
-	args := editorArgs(tempFile.Name())
+	args := e.editorArgs(tempFile.Name())
 	cmd := e.NewCommand(args[0], args[1:]...)
 	cmd.Stdout = e.Out
 	cmd.Stderr = e.Err
diff --git a/pkg/exec/builder/execute.go b/pkg/exec/builder/execute.go
index c4476b13..6b15f328 100644
--- a/pkg/exec/builder/execute.go
+++ b/pkg/exec/builder/execute.go
@@ -113,7 +113,7 @@ func ExecuteStep(cxt *context.Context, step ExecutableStep) (string, error) {
 	args = append(args, suffixArgs...)
 
 	cmd := cxt.NewCommand(step.GetCommand(), args...)
-	prettyCmd := fmt.Sprintf("%s%s", cmd.Dir, strings.Join(cmd.Args, " "))
+	prettyCmd := fmt.Sprintf("%s %s", cmd.Dir, strings.Join(cmd.Args, " "))
 
 	// Setup output streams for command
 	// If Step suppresses output, update streams accordingly
diff --git a/pkg/exec/helpers.go b/pkg/exec/helpers.go
index fd78fbf8..61b74b4d 100644
--- a/pkg/exec/helpers.go
+++ b/pkg/exec/helpers.go
@@ -11,7 +11,7 @@ type TestMixin struct {
 	TestContext *context.TestContext
 }
 
-// NewTestMixin initializes a helm mixin, with the output buffered, and an in-memory file system.
+// NewTestMixin initializes an exec mixin, with the output buffered, and an in-memory file system.
 func NewTestMixin(t *testing.T) *TestMixin {
 	tc := context.NewTestContext(t)
 	m := New()
diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go
index 99953d5b..1e70d60f 100644
--- a/pkg/manifest/manifest.go
+++ b/pkg/manifest/manifest.go
@@ -17,7 +17,7 @@ import (
 	"github.com/cnabio/cnab-go/bundle/definition"
 	"github.com/cnabio/cnab-go/claim"
 	"github.com/docker/distribution/reference"
-	"github.com/hashicorp/go-multierror"
+	multierror "github.com/hashicorp/go-multierror"
 	"github.com/pkg/errors"
 	"gopkg.in/yaml.v2"
 )
diff --git a/pkg/manifest/sync.go b/pkg/manifest/sync.go
new file mode 100644
index 00000000..6c9ea9ba
--- /dev/null
+++ b/pkg/manifest/sync.go
@@ -0,0 +1,98 @@
+package manifest
+
+import (
+	"bytes"
+	"io/ioutil"
+	"sync"
+
+	"get.porter.sh/porter/pkg/config"
+	"get.porter.sh/porter/pkg/context"
+	"github.com/mikefarah/yq/v3/pkg/yqlib"
+	"github.com/pkg/errors"
+	"gopkg.in/op/go-logging.v1"
+	"gopkg.in/yaml.v3"
+)
+
+var (
+	// Only initialize the yq logging backend a single time, in a thread-safe way
+	yqLogInit sync.Once
+)
+
+// Editor can modify the yaml in a Porter manifest.
+type Editor struct {
+	context  *context.Context
+	manifest *Manifest
+	yq       yqlib.YqLib
+	node     *yaml.Node
+}
+
+func NewEditor(cxt *context.Context) *Editor {
+	e := &Editor{
+		context: cxt,
+	}
+	yqLogInit.Do(e.suppressYqLogging)
+	return e
+}
+
+// Hide yq log statements
+func (e *Editor) suppressYqLogging() {
+	// TODO: We could improve the logging and yq libs to be parallel friendly
+	// This turns off all logging from yq
+	// yq doesn't return errors from its api, and logs them instead (awkward)
+	// The yq logger is global, unless we seriously edit yq, we can't separate
+	// logging for once instance of a yq run from any parallel runs. It just
+	// seemed better to turn it off.
+	// yq has moved to v4 which is a very large api change, so it would be
+	// a lot of work though.
+
+	// The yq lib that we use makes frequent calls to a logger that are by default
+	// printed directly to stderr
+	var backend = logging.AddModuleLevel(logging.NewLogBackend(ioutil.Discard, "", 0))
+	backend.SetLevel(logging.ERROR, "yq")
+	logging.SetBackend(backend)
+}
+
+func (e *Editor) Read(data []byte) (n int, err error) {
+	e.yq = yqlib.NewYqLib()
+	e.node = &yaml.Node{}
+
+	var decoder = yaml.NewDecoder(bytes.NewReader(data))
+	err = decoder.Decode(e.node)
+	if err != nil {
+		return len(data), errors.Wrapf(err, "could not parse manifest:\n%s", string(data))
+	}
+
+	return len(data), nil
+}
+
+func (e *Editor) ReadFile(src string) error {
+	contents, err := e.context.FileSystem.ReadFile(src)
+	if err != nil {
+		return errors.Wrapf(err, "could not read the manifest at %q", src)
+	}
+	_, err = e.Read(contents)
+	return errors.Wrapf(err, "could not parse the manifest at %q", src)
+}
+
+func (e *Editor) WriteFile(dest string) error {
+	destFile, err := e.context.FileSystem.Create(dest)
+	if err != nil {
+		return errors.Wrapf(err, "could not open destination manifest location %s", config.Name)
+	}
+	defer destFile.Close()
+
+	// Encode the updated manifest to the proper location
+	// yqlib.NewYamlEncoder takes: dest (io.Writer), indent spaces (int), colorized output (bool)
+	var encoder = yqlib.NewYamlEncoder(destFile, 2, false)
+	return errors.Wrapf(encoder.Encode(e.node), "unable to write the manifest to %s", dest)
+}
+
+func (e *Editor) SetValue(path string, value string) error {
+	var valueParser = yqlib.NewValueParser()
+	// valueParser.Parse takes: argument (string), custom tag (string),
+	// custom style (string), anchor name (string), create alias (bool)
+	var parsedValue = valueParser.Parse(value, "", "", "", false)
+	cmd := yqlib.UpdateCommand{Command: "update", Path: path, Value: parsedValue, Overwrite: true}
+	err := e.yq.Update(e.node, cmd, true)
+	return errors.Wrapf(err, "could not update manifest path %q with value %q", path, value)
+}
diff --git a/pkg/pkgmgmt/client/filesystem.go b/pkg/pkgmgmt/client/filesystem.go
index 3055107d..f3941e0a 100644
--- a/pkg/pkgmgmt/client/filesystem.go
+++ b/pkg/pkgmgmt/client/filesystem.go
@@ -133,7 +133,7 @@ func (fs *FileSystem) GetPackageDir(name string) (string, error) {
 		return "", errors.Wrapf(err, "%s %s not accessible at %s", fs.PackageType, name, pkgDir)
 	}
 	if !dirExists {
-		return "", fmt.Errorf("%s %s not installed", fs.PackageType, name)
+		return "", fmt.Errorf("%s %s not installed in %s", fs.PackageType, name, pkgDir)
 	}
 
 	return pkgDir, nil
diff --git a/pkg/pkgmgmt/feed/generate.go b/pkg/pkgmgmt/feed/generate.go
index 4e5c681d..0ed72a44 100644
--- a/pkg/pkgmgmt/feed/generate.go
+++ b/pkg/pkgmgmt/feed/generate.go
@@ -29,12 +29,7 @@ func (o *GenerateOptions) Validate(c *context.Context) error {
 
 func (o *GenerateOptions) ValidateSearchDirectory(cxt *context.Context) error {
 	if o.SearchDirectory == "" {
-		wd, err := os.Getwd()
-		if err != nil {
-			return errors.Wrap(err, "could not get current working directory")
-		}
-
-		o.SearchDirectory = wd
+		o.SearchDirectory = cxt.Getwd()
 	}
 
 	if _, err := cxt.FileSystem.Stat(o.SearchDirectory); err != nil {
diff --git a/pkg/plugins/pluggable/loader.go b/pkg/plugins/pluggable/loader.go
index 2aa27e9a..dd4bd3fd 100644
--- a/pkg/plugins/pluggable/loader.go
+++ b/pkg/plugins/pluggable/loader.go
@@ -14,8 +14,8 @@ import (
 
 	"get.porter.sh/porter/pkg/config"
 	"get.porter.sh/porter/pkg/plugins"
-	"github.com/hashicorp/go-hclog"
-	"github.com/hashicorp/go-plugin"
+	hclog "github.com/hashicorp/go-hclog"
+	plugin "github.com/hashicorp/go-plugin"
 	"github.com/pkg/errors"
 )
 
@@ -68,14 +68,7 @@ func (l *PluginLoader) Load(pluginType PluginTypeConfig) (interface{}, func(), e
 	pluginCommand.Stdin = configReader
 
 	// Explicitly set PORTER_HOME for the plugin
-	pluginCommand.Env = os.Environ()
-	if _, homeSet := os.LookupEnv(config.EnvHOME); !homeSet {
-		home, err := l.GetHomeDir()
-		if err != nil {
-			return nil, nil, err
-		}
-		pluginCommand.Env = append(pluginCommand.Env, fmt.Sprintf("PORTER_HOME=%s", home))
-	}
+	pluginCommand.Env = l.Environ()
 
 	if l.DebugPlugins {
 		fmt.Fprintf(l.Err, "Resolved %s plugin to %s\n", pluginType.Interface, l.SelectedPluginKey)
diff --git a/pkg/porter/cnab.go b/pkg/porter/cnab.go
index a0b59c62..11707e80 100644
--- a/pkg/porter/cnab.go
+++ b/pkg/porter/cnab.go
@@ -1,16 +1,14 @@
 package porter
 
 import (
-	"fmt"
-	"os"
 	"path/filepath"
 
 	"get.porter.sh/porter/pkg/build"
+	"get.porter.sh/porter/pkg/cnab/drivers"
 	cnabprovider "get.porter.sh/porter/pkg/cnab/provider"
 	"get.porter.sh/porter/pkg/config"
 	"get.porter.sh/porter/pkg/context"
 	"get.porter.sh/porter/pkg/parameters"
-	"github.com/cnabio/cnab-go/driver/command"
 	"github.com/pkg/errors"
 )
 
@@ -111,7 +109,7 @@ func (o *sharedOptions) Validate(args []string, p *Porter) error {
 	}
 
 	o.defaultDriver()
-	err = o.validateDriver()
+	err = o.validateDriver(p.Context)
 	if err != nil {
 		return err
 	}
@@ -138,12 +136,7 @@ func (o *bundleFileOptions) defaultBundleFiles(cxt *context.Context) error {
 	} else if o.CNABFile != "" { // --cnab-file
 		// Nothing to default
 	} else { // no flags passed (--tag is handled elsewhere)
-		pwd, err := os.Getwd()
-		if err != nil {
-			return errors.Wrap(err, "could not get current working directory")
-		}
-
-		manifestExists, err := cxt.FileSystem.Exists(filepath.Join(pwd, config.Name))
+		manifestExists, err := cxt.FileSystem.Exists(config.Name)
 		if err != nil {
 			return errors.Wrap(err, "could not check if porter manifest exists in current directory")
 		}
@@ -197,18 +190,7 @@ func (o *bundleFileOptions) validateCNABFile(cxt *context.Context) error {
 	originalPath := o.CNABFile
 	if !filepath.IsAbs(o.CNABFile) {
 		// Convert to an absolute filepath because runtime needs it that way
-		pwd, err := os.Getwd()
-		if err != nil {
-			return errors.Wrap(err, "could not get current working directory")
-		}
-
-		f := filepath.Join(pwd, o.CNABFile)
-		f, err = filepath.Abs(f)
-		if err != nil {
-			return errors.Wrapf(err, "could not get absolute path for --cnab-file %s", o.CNABFile)
-		}
-
-		o.CNABFile = f
+		o.CNABFile = filepath.Join(cxt.Getwd(), o.CNABFile)
 	}
 
 	// Verify the file can be accessed
@@ -293,16 +275,7 @@ func (o *sharedOptions) defaultDriver() {
 }
 
 // validateDriver validates that the provided driver is supported by Porter
-func (o *sharedOptions) validateDriver() error {
-	switch o.Driver {
-	case DockerDriver, DebugDriver:
-		return nil
-	default:
-		cmddriver := &command.Driver{Name: o.Driver}
-		if cmddriver.CheckDriverExists() {
-			return nil
-		}
-
-		return fmt.Errorf("unsupported driver or driver not found in PATH: %s", o.Driver)
-	}
+func (o *sharedOptions) validateDriver(cxt *context.Context) error {
+	_, err := drivers.LookupDriver(cxt, o.Driver)
+	return err
 }
diff --git a/pkg/porter/generateManifest.go b/pkg/porter/generateManifest.go
index 361b12b3..dda9b0e1 100644
--- a/pkg/porter/generateManifest.go
+++ b/pkg/porter/generateManifest.go
@@ -1,14 +1,10 @@
 package porter
 
 import (
-	"os"
-
 	"get.porter.sh/porter/pkg/build"
 	"get.porter.sh/porter/pkg/config"
-	"github.com/mikefarah/yq/v3/pkg/yqlib"
+	"get.porter.sh/porter/pkg/manifest"
 	"github.com/pkg/errors"
-	"gopkg.in/op/go-logging.v1"
-	"gopkg.in/yaml.v3"
 )
 
 // metadataOpts contain manifest fields eligible for dynamic
@@ -23,72 +19,29 @@ type metadataOpts struct {
 // the provided generateInternalManifestOpts, saving the updated manifest to the path
 // designated by build.LOCAL_MANIFEST
 func (p *Porter) generateInternalManifest(opts metadataOpts) error {
-	p.initYqLogger()
-	var lib = yqlib.NewYqLib()
-
-	// Decode the user's manifest file into a yaml.Node
-	var node yaml.Node
-	input, err := p.FileSystem.Open(config.Name)
+	// Create the local app dir if it does not already exist
+	err := p.FileSystem.MkdirAll(build.LOCAL_APP, 0755)
 	if err != nil {
-		return errors.Wrapf(err, "error opening %s", config.Name)
+		return errors.Wrapf(err, "unable to create directory %s", build.LOCAL_APP)
 	}
-	defer input.Close()
 
-	var decoder = yaml.NewDecoder(input)
-	err = decoder.Decode(&node)
+	e := manifest.NewEditor(p.Context)
+	err = e.ReadFile(config.Name)
 	if err != nil {
-		return errors.Wrap(err, "unable to decode manifest")
+		return err
 	}
 
-	// Assemble update commands
-	var updateCommands []yqlib.UpdateCommand
 	if opts.Name != "" {
-		updateCommands = append(updateCommands, createUpdateCommand("name", opts.Name))
-	}
-	if opts.Version != "" {
-		updateCommands = append(updateCommands, createUpdateCommand("version", opts.Version))
-	}
-
-	// Make updates
-	for _, updateCommand := range updateCommands {
-		err = lib.Update(&node, updateCommand, true)
-		if err != nil {
-			return errors.Wrapf(err, "could not update manifest path %q with value %q", updateCommand.Path, updateCommand.Value.Value)
+		if err = e.SetValue("name", opts.Name); err != nil {
+			return err
 		}
 	}
 
-	// Create the local app dir if it does not already exist
-	err = p.FileSystem.MkdirAll(build.LOCAL_APP, 0755)
-	if err != nil {
-		return errors.Wrapf(err, "unable to create directory %s", build.LOCAL_APP)
-	}
-
-	output, err := p.FileSystem.OpenFile(build.LOCAL_MANIFEST, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
-	if err != nil {
-		return errors.Wrapf(err, "error creating %s", build.LOCAL_MANIFEST)
+	if opts.Version != "" {
+		if err = e.SetValue("version", opts.Version); err != nil {
+			return err
+		}
 	}
-	defer output.Close()
-
-	// Encode the updated manifest to the proper location
-	// yqlib.NewYamlEncoder takes: dest (io.Writer), indent spaces (int), colorised output (bool)
-	var encoder = yqlib.NewYamlEncoder(output, 2, false)
-	return errors.Wrapf(encoder.Encode(&node), "unable to encode the manifest at %s", build.LOCAL_MANIFEST)
-}
-
-// createUpdateCommand creates a yqlib.UpdateCommand using the supplied YAML
-// path and replacement value
-func createUpdateCommand(path, value string) yqlib.UpdateCommand {
-	var valueParser = yqlib.NewValueParser()
-	// valueParser.Parse takes: argument (string), custom tag (string),
-	// custom style (string), anchor name (string), create alias (bool)
-	var parsedValue = valueParser.Parse(value, "", "", "", false)
-	return yqlib.UpdateCommand{Command: "update", Path: path, Value: parsedValue, Overwrite: true}
-}
 
-func (p *Porter) initYqLogger() {
-	// The yq lib that we use makes frequent calls to a logger
-	// Here we set up to log to Porter's Err stream at log level ERROR
-	var backend = logging.AddModuleLevel(logging.NewLogBackend(p.Err, "", 0))
-	backend.SetLevel(logging.ERROR, "yq")
-	logging.SetBackend(backend)
+	return e.WriteFile(build.LOCAL_MANIFEST)
 }
diff --git a/pkg/porter/helpers.go b/pkg/porter/helpers.go
index e74a4fc8..121cccd1 100644
--- a/pkg/porter/helpers.go
+++ b/pkg/porter/helpers.go
@@ -1,12 +1,13 @@
 package porter
 
 import (
+	"fmt"
 	"io/ioutil"
-	"os"
-	"os/exec"
+	"math/rand"
 	"path/filepath"
 	"strings"
 	"testing"
+	"time"
 
 	"get.porter.sh/porter/pkg/cache"
 	"get.porter.sh/porter/pkg/claims"
@@ -77,7 +78,6 @@ func (p *TestPorter) SetupIntegrationTest() {
 
 	// Undo changes above to make a unit test friendly Porter, so we hit the host
 	p.Porter = NewWithConfig(p.Config)
-	p.NewCommand = exec.Command
 
 	/*
 		// Update test providers to use the instances we just reset above
@@ -89,19 +89,15 @@ func (p *TestPorter) SetupIntegrationTest() {
 	*/
 
 	// Run the test in a temp directory
-	homeDir := p.TestConfig.TestContext.UseFilesystem()
-	p.TestConfig.SetupIntegrationTest(homeDir)
-	bundleDir := p.CreateBundleDir()
-
-	p.TestDir, _ = os.Getwd()
-	err := os.Chdir(bundleDir)
-	require.NoError(t, err)
+	testDir, homeDir := p.TestConfig.SetupIntegrationTest()
+	p.TestDir = testDir
+	p.CreateBundleDir()
 
 	// Copy test credentials into porter home, with KUBECONFIG replaced properly
 	p.AddTestFile("../build/testdata/schema.json", filepath.Join(homeDir, "schema.json"))
-	kubeconfig := os.Getenv("KUBECONFIG")
+	kubeconfig := p.Getenv("KUBECONFIG")
 	if kubeconfig == "" {
-		home := os.Getenv("HOME")
+		home := p.Getenv("HOME")
 		kubeconfig = filepath.Join(home, ".kube/config")
 	}
 	ciCredsPath := filepath.Join(p.TestDir, "../build/testdata/credentials/ci.json")
@@ -142,6 +138,7 @@ func (p *TestPorter) CreateBundleDir() string {
 	require.NoError(p.T(), err)
 
 	p.BundleDir = bundleDir
+	p.Chdir(bundleDir)
 	p.TestConfig.TestContext.AddCleanupDir(p.BundleDir)
 
 	return bundleDir
@@ -152,11 +149,7 @@ func (p *TestPorter) T() *testing.T {
 }
 
 func (p *TestPorter) CleanupIntegrationTest() {
-	os.Unsetenv(config.EnvHOME)
-
 	p.TestConfig.TestContext.Cleanup()
-
-	os.Chdir(p.TestDir)
 }
 
 func (p *TestPorter) ReadBundle(path string) bundle.Bundle {
@@ -169,7 +162,45 @@ func (p *TestPorter) ReadBundle(path string) bundle.Bundle {
 	return *bun
 }
 
-type TestBuildProvider struct{}
+func (p *TestPorter) RandomString(len int) string {
+	rand.Seed(time.Now().UnixNano())
+	bytes := make([]byte, len)
+	for i := 0; i < len; i++ {
+		//A=97 and Z = 97+25
+		bytes[i] = byte(97 + rand.Intn(25))
+	}
+	return string(bytes)
+}
+
+// AddTestBundleDir into the test bundle directory and give it a unique name
+// to avoid collisions with other tests running in parallel.
+func (p *TestPorter) AddTestBundleDir(bundleDir string, generateUniqueName bool) string {
+	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, bundleDir), p.BundleDir)
+
+	testManifest := filepath.Join(p.BundleDir, config.Name)
+	m, err := manifest.LoadManifestFrom(p.Context, testManifest)
+	require.NoError(p.T(), err)
+
+	if !generateUniqueName {
+		return m.Name
+	}
+
+	e := manifest.NewEditor(p.Context)
+	err = e.ReadFile(testManifest)
+	require.NoError(p.T(), err)
+
+	uniqueName := fmt.Sprintf("%s-%s", m.Name, p.RandomString(5))
+	err = e.SetValue("name", uniqueName)
+	require.NoError(p.T(), err)
+
+	err = e.WriteFile(testManifest)
+	require.NoError(p.T(), err)
+
+	return uniqueName
+}
+
+type TestBuildProvider struct {
+}
 
 func NewTestBuildProvider() *TestBuildProvider {
 	return &TestBuildProvider{}
diff --git a/pkg/porter/lint.go b/pkg/porter/lint.go
index 95fe6f5b..e33643f6 100644
--- a/pkg/porter/lint.go
+++ b/pkg/porter/lint.go
@@ -2,8 +2,6 @@ package porter
 
 import (
 	"fmt"
-	"os"
-	"path/filepath"
 
 	"get.porter.sh/porter/pkg/config"
 	"get.porter.sh/porter/pkg/context"
@@ -33,12 +31,7 @@ func (o *LintOptions) Validate(cxt *context.Context) error {
 
 func (o *LintOptions) validateFile(cxt *context.Context) error {
 	if o.File == "" {
-		pwd, err := os.Getwd()
-		if err != nil {
-			return errors.Wrap(err, "could not get current working directory")
-		}
-
-		manifestExists, err := cxt.FileSystem.Exists(filepath.Join(pwd, config.Name))
+		manifestExists, err := cxt.FileSystem.Exists(config.Name)
 		if err != nil {
 			return errors.Wrap(err, "could not check if porter manifest exists in current directory")
 		}
diff --git a/pkg/porter/publish.go b/pkg/porter/publish.go
index 10206872..64a7ac08 100644
--- a/pkg/porter/publish.go
+++ b/pkg/porter/publish.go
@@ -32,11 +32,7 @@ type PublishOptions struct {
 func (o *PublishOptions) Validate(cxt *portercontext.Context) error {
 	if o.ArchiveFile != "" {
 		// Verify the archive file can be accessed
-		path, err := filepath.Abs(o.ArchiveFile)
-		if err != nil {
-			return errors.Wrapf(err, "unable to determine absolute path for --archive %s", o.ArchiveFile)
-		}
-		if _, err := cxt.FileSystem.Stat(path); err != nil {
+		if _, err := cxt.FileSystem.Stat(o.ArchiveFile); err != nil {
 			return errors.Wrapf(err, "unable to access --archive %s", o.ArchiveFile)
 		}
 
@@ -149,11 +145,7 @@ func (p *Porter) publishFromFile(opts PublishOptions) error {
 // this approach will need to be refactored, via preserving the original bundle and employing
 // a relocation mapping approach to associate the bundle's (old) images with the newly copied images.
 func (p *Porter) publishFromArchive(opts PublishOptions) error {
-	source, err := filepath.Abs(opts.ArchiveFile)
-	if err != nil {
-		return errors.Wrapf(err, "could not determine absolute path to archive file %s", opts.ArchiveFile)
-	}
-
+	source := p.FileSystem.Abs(opts.ArchiveFile)
 	tmpDir, err := p.FileSystem.TempDir("", "porter")
 	if err != nil {
 		return errors.Wrap(err, "error creating temp directory for archive extraction")
diff --git a/pkg/porter/run.go b/pkg/porter/run.go
index 1672f23b..1a13c809 100644
--- a/pkg/porter/run.go
+++ b/pkg/porter/run.go
@@ -2,7 +2,6 @@ package porter
 
 import (
 	"fmt"
-	"os"
 	"strconv"
 
 	"get.porter.sh/porter/pkg/config"
@@ -39,7 +38,7 @@ func (o *RunOptions) Validate() error {
 
 func (o *RunOptions) validateAction() error {
 	if o.Action == "" {
-		o.Action = os.Getenv(config.EnvACTION)
+		o.Action = o.config.Getenv(config.EnvACTION)
 		if o.config.Debug {
 			fmt.Fprintf(o.config.Err, "DEBUG: defaulting action to %s (%s)\n", config.EnvACTION, o.Action)
 		}
@@ -54,7 +53,7 @@ func (o *RunOptions) defaultDebug() error {
 		return nil
 	}
 
-	rawDebug, set := os.LookupEnv(config.EnvDEBUG)
+	rawDebug, set := o.config.LookupEnv(config.EnvDEBUG)
 	if !set {
 		return nil
 	}
diff --git a/pkg/porter/stamp.go b/pkg/porter/stamp.go
index 994b7bbf..3063b611 100644
--- a/pkg/porter/stamp.go
+++ b/pkg/porter/stamp.go
@@ -3,8 +3,8 @@ package porter
 import (
 	"fmt"
 
+	"get.porter.sh/porter/pkg/cnab"
 	configadapter "get.porter.sh/porter/pkg/cnab/config-adapter"
-	"github.com/cnabio/cnab-go/bundle"
 	"github.com/pkg/errors"
 )
 
@@ -30,17 +30,12 @@ func (p *Porter) ensureLocalBundleIsUpToDate(opts bundleFileOptions) error {
 // IsBundleUpToDate checks the hash of the manifest against the hash in cnab/bundle.json.
 func (p *Porter) IsBundleUpToDate(opts bundleFileOptions) (bool, error) {
 	if exists, _ := p.FileSystem.Exists(opts.CNABFile); exists {
-		bunData, err := p.FileSystem.ReadFile(opts.CNABFile)
-		if err != nil {
-			return false, errors.Wrapf(err, "could not read data from %s", opts.CNABFile)
-		}
-
-		bun, err := bundle.Unmarshal(bunData)
+		bun, err := cnab.LoadBundle(p.Context, opts.CNABFile)
 		if err != nil {
 			return false, errors.Wrapf(err, "could not marshal data from %s", opts.CNABFile)
 		}
 
-		oldStamp, err := configadapter.LoadStamp(*bun)
+		oldStamp, err := configadapter.LoadStamp(bun)
 		if err != nil {
 			return false, errors.Wrapf(err, "could not load stamp from %s", opts.CNABFile)
 		}
diff --git a/pkg/runtime/outputs.go b/pkg/runtime/outputs.go
index 37d65dda..610e3af1 100644
--- a/pkg/runtime/outputs.go
+++ b/pkg/runtime/outputs.go
@@ -1,8 +1,6 @@
 package runtime
 
 import (
-	"os"
-
 	"get.porter.sh/porter/pkg/manifest"
 	"github.com/pkg/errors"
 )
@@ -12,7 +10,7 @@ import (
 func (m *RuntimeManifest) ReadDependencyOutputValue(ref manifest.DependencyOutputReference) (string, error) {
 	ps := manifest.GetParameterSourceForDependency(ref)
 	psEnvVar := manifest.ParamToEnvVar(ps)
-	output, ok := os.LookupEnv(psEnvVar)
+	output, ok := m.LookupEnv(psEnvVar)
 	if !ok {
 		err := errors.Errorf("bundle dependency %s output %s was not passed into the runtime", ref.Dependency, ref.Output)
 		return "", err
diff --git a/pkg/runtime/runtime-manifest.go b/pkg/runtime/runtime-manifest.go
index bd4e4d08..b9c3830e 100644
--- a/pkg/runtime/runtime-manifest.go
+++ b/pkg/runtime/runtime-manifest.go
@@ -81,7 +81,7 @@ func (m *RuntimeManifest) loadBundle() error {
 }
 
 func (m *RuntimeManifest) GetInstallationName() string {
-	return os.Getenv(config.EnvInstallationName)
+	return m.Getenv(config.EnvInstallationName)
 }
 
 func (m *RuntimeManifest) loadDependencyDefinitions() error {
@@ -103,20 +103,20 @@ func (m *RuntimeManifest) loadDependencyDefinitions() error {
 	return nil
 }
 
-func resolveParameter(pd manifest.ParameterDefinition) string {
+func (m *RuntimeManifest) resolveParameter(pd manifest.ParameterDefinition) string {
 	if pd.Destination.EnvironmentVariable != "" {
-		return os.Getenv(pd.Destination.EnvironmentVariable)
+		return m.Getenv(pd.Destination.EnvironmentVariable)
 	}
 	if pd.Destination.Path != "" {
 		return pd.Destination.Path
 	}
 	envVar := manifest.ParamToEnvVar(pd.Name)
-	return os.Getenv(envVar)
+	return m.Getenv(envVar)
 }
 
-func resolveCredential(cd manifest.CredentialDefinition) (string, error) {
+func (m *RuntimeManifest) resolveCredential(cd manifest.CredentialDefinition) (string, error) {
 	if cd.EnvironmentVariable != "" {
-		return os.Getenv(cd.EnvironmentVariable), nil
+		return m.Getenv(cd.EnvironmentVariable), nil
 	} else if cd.Path != "" {
 		return cd.Path, nil
 	} else {
@@ -128,7 +128,7 @@ func (m *RuntimeManifest) resolveBundleOutput(outputName string) (string, error)
 	// Get the output's value from the injected parameter source
 	ps := manifest.GetParameterSourceForOutput(outputName)
 	psParamEnv := manifest.ParamToEnvVar(ps)
-	outputValue, ok := os.LookupEnv(psParamEnv)
+	outputValue, ok := m.LookupEnv(psParamEnv)
 	if !ok {
 		return "", errors.Errorf("No parameter source was injected for output %s", outputName)
 	}
@@ -237,7 +237,7 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) {
 		}
 
 		pe := param.Name
-		val := resolveParameter(param)
+		val := m.resolveParameter(param)
 		if param.Sensitive {
 			m.setSensitiveValue(val)
 		}
@@ -248,7 +248,7 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) {
 	bun["credentials"] = creds
 	for _, cred := range m.Credentials {
 		pe := cred.Name
-		val, err := resolveCredential(cred)
+		val, err := m.resolveCredential(cred)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go
index a36936c8..eb9de7a3 100644
--- a/pkg/runtime/runtime.go
+++ b/pkg/runtime/runtime.go
@@ -3,7 +3,6 @@ package runtime
 import (
 	"encoding/json"
 	"fmt"
-	"os"
 	"path/filepath"
 
 	"get.porter.sh/porter/pkg/config"
@@ -34,8 +33,8 @@ func NewPorterRuntime(cxt *context.Context, mixins pkgmgmt.PackageManager) *Port
 func (r *PorterRuntime) Execute(rm *RuntimeManifest) error {
 	r.RuntimeManifest = rm
 
-	installationName := os.Getenv(config.EnvInstallationName)
-	bundleName := os.Getenv(config.EnvBundleName)
+	installationName := r.Getenv(config.EnvInstallationName)
+	bundleName := r.Getenv(config.EnvBundleName)
 	fmt.Fprintf(r.Out, "executing %s action from %s (installation: %s)\n", r.RuntimeManifest.Action, bundleName, installationName)
 
 	err := r.RuntimeManifest.Validate()


Test patch:
diff --git a/cmd/porter/run_test.go b/cmd/porter/run_test.go
index 9e30616c..a79cc1b8 100644
--- a/cmd/porter/run_test.go
+++ b/cmd/porter/run_test.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/config"
@@ -11,32 +10,28 @@ import (
 )
 
 func TestRun_Validate(t *testing.T) {
-	defer os.Unsetenv(config.EnvACTION)
-
 	p := porter.NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	configTpl, err := p.Templates.GetManifest()
 	require.NoError(t, err)
 	p.TestConfig.TestContext.AddTestFileContents(configTpl, config.Name)
 	cmd := buildRunCommand(p.Porter)
 
-	os.Setenv(config.EnvACTION, string(claim.ActionInstall))
+	p.Setenv(config.EnvACTION, claim.ActionInstall)
 
 	err = cmd.PreRunE(cmd, []string{})
 	require.Nil(t, err)
 }
 
 func TestRun_ValidateCustomAction(t *testing.T) {
-	defer os.Unsetenv(config.EnvACTION)
-
 	p := porter.NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	configTpl, err := p.Templates.GetManifest()
 	require.NoError(t, err)
 	p.TestConfig.TestContext.AddTestFileContents(configTpl, config.Name)
 	cmd := buildRunCommand(p.Porter)
 
-	os.Setenv(config.EnvACTION, "status")
+	p.Setenv(config.EnvACTION, "status")
 
 	err = cmd.PreRunE(cmd, []string{})
 	require.Nil(t, err)
diff --git a/docs/content/mixin-dev-guide/test.md b/docs/content/mixin-dev-guide/test.md
index 3619192c..bb4d26bb 100644
--- a/docs/content/mixin-dev-guide/test.md
+++ b/docs/content/mixin-dev-guide/test.md
@@ -31,14 +31,13 @@ m := NewTestMixin(t)
 This sets up your test binary to assert that expected command(s) were called. You tell it what command to expect with
 
 ```go
-os.Setenv(test.ExpectedCommandEnv, "helm install")
-defer os.Unsetenv(test.ExpectedCommandEnv)
+m.Setenv(test.ExpectedCommandEnv, "helm install")
 ```
 
 If your mixin action executes multiple commands, separate them with a newline `\n` like so
 
 ```go
-os.Setenv(test.ExpectedCommandEnv, "helm install\nhelm upgrade")
+m.Setenv(test.ExpectedCommandEnv, "helm install\nhelm upgrade")
 ```
 
 Now execute your mixin action:
diff --git a/pkg/build/dockerfile-generator_test.go b/pkg/build/dockerfile-generator_test.go
index 045f7060..0fb2eb55 100644
--- a/pkg/build/dockerfile-generator_test.go
+++ b/pkg/build/dockerfile-generator_test.go
@@ -13,6 +13,8 @@ import (
 )
 
 func TestPorter_buildDockerfile(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
@@ -49,16 +51,19 @@ func TestPorter_buildDockerfile(t *testing.T) {
 }
 
 func TestPorter_buildCustomDockerfile(t *testing.T) {
-	c := config.NewTestConfig(t)
-	tmpl := templates.NewTemplates()
-	configTpl, err := tmpl.GetManifest()
-	require.Nil(t, err)
-	c.TestContext.AddTestFileContents(configTpl, config.Name)
-
-	m, err := manifest.LoadManifestFrom(c.Context, config.Name)
-	require.NoError(t, err, "could not load manifest")
+	t.Parallel()
 
 	t.Run("build from custom docker without supplying ARG BUNDLE_DIR", func(t *testing.T) {
+		t.Parallel()
+
+		c := config.NewTestConfig(t)
+		tmpl := templates.NewTemplates()
+		configTpl, err := tmpl.GetManifest()
+		require.Nil(t, err)
+		c.TestContext.AddTestFileContents(configTpl, config.Name)
+
+		m, err := manifest.LoadManifestFrom(c.Context, config.Name)
+		require.NoError(t, err, "could not load manifest")
 
 		// Use a custom dockerfile template
 		m.Dockerfile = "Dockerfile.template"
@@ -83,6 +88,16 @@ COPY mybin /cnab/app/
 	})
 
 	t.Run("build from custom docker with ARG BUNDLE_DIR supplied", func(t *testing.T) {
+		t.Parallel()
+
+		c := config.NewTestConfig(t)
+		tmpl := templates.NewTemplates()
+		configTpl, err := tmpl.GetManifest()
+		require.Nil(t, err)
+		c.TestContext.AddTestFileContents(configTpl, config.Name)
+
+		m, err := manifest.LoadManifestFrom(c.Context, config.Name)
+		require.NoError(t, err, "could not load manifest")
 
 		// Use a custom dockerfile template
 		m.Dockerfile = "Dockerfile.template"
@@ -118,6 +133,8 @@ COPY mybin /cnab/app/
 }
 
 func TestPorter_buildDockerfile_output(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
@@ -155,6 +172,8 @@ CMD ["/cnab/app/run"]
 }
 
 func TestPorter_generateDockerfile(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
@@ -183,8 +202,9 @@ func TestPorter_generateDockerfile(t *testing.T) {
 }
 
 func TestPorter_prepareDockerFilesystem(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
-	c.SetupPorterHome()
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
 	require.Nil(t, err)
@@ -215,6 +235,8 @@ func TestPorter_prepareDockerFilesystem(t *testing.T) {
 }
 
 func TestPorter_replacePorterMixinTokenWithBuildInstructions(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
@@ -257,6 +279,8 @@ COPY mybin /cnab/app/
 }
 
 func TestPorter_appendBuildInstructionsIfMixinTokenIsNotPresent(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
@@ -299,6 +323,8 @@ COPY mybin /cnab/app/
 }
 
 func TestPorter_buildMixinsSection_mixinErr(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	tmpl := templates.NewTemplates()
 	configTpl, err := tmpl.GetManifest()
diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go
index 5c0697d4..aa4bdff2 100644
--- a/pkg/cache/cache_test.go
+++ b/pkg/cache/cache_test.go
@@ -3,6 +3,7 @@ package cache
 import (
 	"bytes"
 	"fmt"
+	"path"
 	"path/filepath"
 	"testing"
 
@@ -21,6 +22,8 @@ const (
 )
 
 func TestFindBundleCacheExists(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	home, err := cfg.Config.GetHomeDir()
 	require.NoError(t, err, "should have had a porter home dir")
@@ -34,6 +37,8 @@ func TestFindBundleCacheExists(t *testing.T) {
 }
 
 func TestFindBundleCacheDoesNotExist(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	home, err := cfg.Config.GetHomeDir()
 	require.NoError(t, err, "should have had a porter home dir")
@@ -47,6 +52,8 @@ func TestFindBundleCacheDoesNotExist(t *testing.T) {
 }
 
 func TestFindBundleBundleCached(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	home, err := cfg.Config.GetHomeDir()
 	require.NoError(t, err, "should have had a porter home dir")
@@ -65,6 +72,8 @@ func TestFindBundleBundleCached(t *testing.T) {
 }
 
 func TestFindBundleBundleNotCached(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	c := New(cfg.Config)
 	cb, ok, err := c.FindBundle(kahnlatest)
@@ -74,6 +83,8 @@ func TestFindBundleBundleNotCached(t *testing.T) {
 }
 
 func TestCacheWriteNoCacheDir(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	cfg.TestContext.AddTestFile("testdata/cnab/bundle.json", "/cnab/bundle.json")
 	b, err := cfg.FileSystem.ReadFile("/cnab/bundle.json")
@@ -95,6 +106,8 @@ func TestCacheWriteNoCacheDir(t *testing.T) {
 }
 
 func TestCacheWriteCacheDirExists(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	home, err := cfg.Config.GetHomeDir()
 	require.NoError(t, err, "should have had a porter home dir")
@@ -118,11 +131,6 @@ func TestCacheWriteCacheDirExists(t *testing.T) {
 }
 
 func TestStoreRelocationMapping(t *testing.T) {
-
-	cfg := config.NewTestConfig(t)
-	home, _ := cfg.Config.GetHomeDir()
-	cacheDir := filepath.Join(home, "cache")
-
 	tests := []struct {
 		name              string
 		relocationMapping *relocation.ImageRelocationMap
@@ -138,7 +146,7 @@ func TestStoreRelocationMapping(t *testing.T) {
 			relocationMapping: &relocation.ImageRelocationMap{
 				"asd": "asdf",
 			},
-			wantedReloPath: filepath.Join(cacheDir, kahn1dot0Hash, "cnab", "relocation-mapping.json"),
+			wantedReloPath: path.Join("/.porter/cache", kahn1dot0Hash, "cnab", "relocation-mapping.json"),
 		},
 		{
 			name:           "no relocation file gets no path",
@@ -148,15 +156,20 @@ func TestStoreRelocationMapping(t *testing.T) {
 		},
 	}
 
-	c := New(cfg.Config)
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			cb, err := c.StoreBundle(test.tag, test.bundle, test.relocationMapping)
-			assert.NoError(t, err, fmt.Sprintf("didn't expect storage error for test %s", test.name))
-			assert.Equal(t, test.wantedReloPath, cb.RelocationFilePath, "didn't get expected path for store")
-
-			cb, _, err = c.FindBundle(test.tag)
-			assert.Equal(t, test.wantedReloPath, cb.RelocationFilePath, "didn't get expected path for load")
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
+			cfg := config.NewTestConfig(t)
+			c := New(cfg.Config)
+
+			cb, err := c.StoreBundle(tc.tag, tc.bundle, tc.relocationMapping)
+			assert.NoError(t, err, fmt.Sprintf("didn't expect storage error for test %s", tc.name))
+			assert.Equal(t, tc.wantedReloPath, cb.RelocationFilePath, "didn't get expected path for store")
+
+			cb, _, err = c.FindBundle(tc.tag)
+			assert.Equal(t, tc.wantedReloPath, cb.RelocationFilePath, "didn't get expected path for load")
 		})
 	}
 }
@@ -200,19 +213,22 @@ func TestStoreManifest(t *testing.T) {
 		},
 	}
 
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
 			cfg := config.NewTestConfig(t)
 			home, _ := cfg.Config.GetHomeDir()
 			cacheDir := filepath.Join(home, "cache")
 			cfg.TestContext.AddTestDirectory("testdata", cacheDir)
 			c := New(cfg.Config)
 
-			cb, err := c.StoreBundle(test.tag, test.bundle, nil)
+			cb, err := c.StoreBundle(tc.tag, tc.bundle, nil)
 			require.NoError(t, err, "StoreBundle failed")
 
 			cachedManifestExists, _ := cfg.FileSystem.Exists(cb.BuildManifestPath())
-			if test.shouldCacheManifest {
+			if tc.shouldCacheManifest {
 				assert.Equal(t, cb.BuildManifestPath(), cb.ManifestPath, "CachedBundle.ManifestPath was not set")
 				assert.True(t, cachedManifestExists, "Expected the porter.yaml manifest to be cached but it wasn't")
 			} else {
@@ -224,6 +240,8 @@ func TestStoreManifest(t *testing.T) {
 }
 
 func TestCache_StoreBundle_Overwrite(t *testing.T) {
+	t.Parallel()
+
 	cfg := config.NewTestConfig(t)
 	home, _ := cfg.Config.GetHomeDir()
 	cacheDir := filepath.Join(home, "cache")
diff --git a/pkg/cache/cached_bundle_test.go b/pkg/cache/cached_bundle_test.go
index 3e236ec5..d29f8fd7 100644
--- a/pkg/cache/cached_bundle_test.go
+++ b/pkg/cache/cached_bundle_test.go
@@ -8,6 +8,8 @@ import (
 )
 
 func TestCachedBundle_GetBundleID(t *testing.T) {
+	t.Parallel()
+
 	cb := CachedBundle{}
 
 	cb.Tag = kahn1dot01
diff --git a/pkg/claims/claimStorage_test.go b/pkg/claims/claimStorage_test.go
index 6996964c..a76b9a48 100644
--- a/pkg/claims/claimStorage_test.go
+++ b/pkg/claims/claimStorage_test.go
@@ -8,14 +8,16 @@ import (
 	"get.porter.sh/porter/pkg/storage"
 	"get.porter.sh/porter/pkg/storage/filesystem"
 	"github.com/cnabio/cnab-go/claim"
-	"github.com/hashicorp/go-hclog"
+	hclog "github.com/hashicorp/go-hclog"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestClaimStorage_HaltOnMigrationRequired(t *testing.T) {
+	t.Parallel()
+
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -44,8 +46,10 @@ func TestClaimStorage_HaltOnMigrationRequired(t *testing.T) {
 }
 
 func TestClaimStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T) {
+	t.Parallel()
+
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -62,8 +66,10 @@ func TestClaimStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T) {
 }
 
 func TestClaimStorage_NoMigrationRequiredForEmptyHome(t *testing.T) {
+	t.Parallel()
+
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
diff --git a/pkg/cnab/config-adapter/adapter_test.go b/pkg/cnab/config-adapter/adapter_test.go
index 5eb21cb5..2bfe42f0 100644
--- a/pkg/cnab/config-adapter/adapter_test.go
+++ b/pkg/cnab/config-adapter/adapter_test.go
@@ -18,6 +18,8 @@ import (
 )
 
 func TestManifestConverter_ToBundle(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter.yaml", config.Name)
 
@@ -48,17 +50,6 @@ func TestManifestConverter_ToBundle(t *testing.T) {
 }
 
 func TestManifestConverter_generateBundleParametersSchema(t *testing.T) {
-	c := config.NewTestConfig(t)
-	c.TestContext.AddTestFile("testdata/porter-with-parameters.yaml", config.Name)
-
-	m, err := manifest.LoadManifestFrom(c.Context, config.Name)
-	require.NoError(t, err, "could not load manifest")
-
-	a := NewManifestConverter(c.Context, m, nil, nil)
-
-	defs := make(definition.Definitions, len(m.Parameters))
-	params := a.generateBundleParameters(&defs)
-
 	testcases := []struct {
 		propname  string
 		wantParam bundle.Parameter
@@ -195,6 +186,20 @@ func TestManifestConverter_generateBundleParametersSchema(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.propname, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
+			c := config.NewTestConfig(t)
+			c.TestContext.AddTestFile("testdata/porter-with-parameters.yaml", config.Name)
+
+			m, err := manifest.LoadManifestFrom(c.Context, config.Name)
+			require.NoError(t, err, "could not load manifest")
+
+			a := NewManifestConverter(c.Context, m, nil, nil)
+
+			defs := make(definition.Definitions, len(m.Parameters))
+			params := a.generateBundleParameters(&defs)
+
 			param, ok := params[tc.propname]
 			require.True(t, ok, "parameter definition was not generated")
 
@@ -208,6 +213,8 @@ func TestManifestConverter_generateBundleParametersSchema(t *testing.T) {
 }
 
 func TestManifestConverter_buildDefaultPorterParameters(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
 
@@ -231,6 +238,8 @@ func TestManifestConverter_buildDefaultPorterParameters(t *testing.T) {
 }
 
 func TestManifestConverter_generateImages(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
 
@@ -270,6 +279,8 @@ func TestManifestConverter_generateImages(t *testing.T) {
 }
 
 func TestManifestConverter_generateBundleImages_EmptyLabels(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
 
@@ -297,6 +308,8 @@ func TestManifestConverter_generateBundleImages_EmptyLabels(t *testing.T) {
 }
 
 func TestManifestConverter_generateBundleOutputs(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
 
@@ -386,17 +399,7 @@ func TestManifestConverter_generateBundleOutputs(t *testing.T) {
 }
 
 func TestManifestConverter_generateDependencies(t *testing.T) {
-	c := config.NewTestConfig(t)
-	c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name)
-
-	m, err := manifest.LoadManifestFrom(c.Context, config.Name)
-	require.NoError(t, err, "could not load manifest")
-
-	a := NewManifestConverter(c.Context, m, nil, nil)
-
-	deps := a.generateDependencies()
-	require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated")
-	require.Equal(t, []string{"mysql", "ad", "storage"}, deps.Sequence, "incorrect sequence was generated")
+	t.Parallel()
 
 	testcases := []struct {
 		name    string
@@ -427,6 +430,21 @@ func TestManifestConverter_generateDependencies(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
+			c := config.NewTestConfig(t)
+			c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name)
+
+			m, err := manifest.LoadManifestFrom(c.Context, config.Name)
+			require.NoError(t, err, "could not load manifest")
+
+			a := NewManifestConverter(c.Context, m, nil, nil)
+
+			deps := a.generateDependencies()
+			require.Len(t, deps.Requires, 3, "incorrect number of dependencies were generated")
+			require.Equal(t, []string{"mysql", "ad", "storage"}, deps.Sequence, "incorrect sequence was generated")
+
 			var dep *extensions.Dependency
 			for _, d := range deps.Requires {
 				if d.Bundle == tc.wantDep.Bundle {
@@ -442,6 +460,8 @@ func TestManifestConverter_generateDependencies(t *testing.T) {
 }
 
 func TestManifestConverter_generateRequiredExtensions_Dependencies(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-deps.yaml", config.Name)
 
@@ -456,6 +476,8 @@ func TestManifestConverter_generateRequiredExtensions_Dependencies(t *testing.T)
 }
 
 func TestManifestConverter_generateParameterSources(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
 
@@ -479,6 +501,8 @@ func TestManifestConverter_generateParameterSources(t *testing.T) {
 }
 
 func TestNewManifestConverter_generateOutputWiringParameter(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
 
@@ -505,6 +529,8 @@ func TestNewManifestConverter_generateOutputWiringParameter(t *testing.T) {
 	}
 
 	t.Run("generate parameter", func(t *testing.T) {
+		t.Parallel()
+
 		name, param, paramDef := a.generateOutputWiringParameter(b, "msg")
 
 		assert.Equal(t, "porter-msg-output", name, "unexpected parameter name")
@@ -519,6 +545,8 @@ func TestNewManifestConverter_generateOutputWiringParameter(t *testing.T) {
 	})
 
 	t.Run("param with hyphen", func(t *testing.T) {
+		t.Parallel()
+
 		name, param, _ := a.generateOutputWiringParameter(b, "some-thing")
 
 		assert.Equal(t, "porter-some-thing-output", name, "unexpected parameter name")
@@ -528,6 +556,8 @@ func TestNewManifestConverter_generateOutputWiringParameter(t *testing.T) {
 }
 
 func TestNewManifestConverter_generateDependencyOutputWiringParameter(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
 
@@ -536,22 +566,22 @@ func TestNewManifestConverter_generateDependencyOutputWiringParameter(t *testing
 
 	a := NewManifestConverter(c.Context, m, nil, nil)
 
-	t.Run("generate parameter", func(t *testing.T) {
-		ref := manifest.DependencyOutputReference{Dependency: "mysql", Output: "mysql-password"}
-		name, param, paramDef := a.generateDependencyOutputWiringParameter(ref)
+	ref := manifest.DependencyOutputReference{Dependency: "mysql", Output: "mysql-password"}
+	name, param, paramDef := a.generateDependencyOutputWiringParameter(ref)
 
-		assert.Equal(t, "porter-mysql-mysql-password-dep-output", name, "unexpected parameter name")
-		assert.False(t, param.Required, "wiring parameters should NOT be required")
-		require.NotNil(t, param.Destination, "wiring parameters should have a destination set")
-		assert.Equal(t, "PORTER_MYSQL_MYSQL_PASSWORD_DEP_OUTPUT", param.Destination.EnvironmentVariable, "unexpected destination environment variable set")
+	assert.Equal(t, "porter-mysql-mysql-password-dep-output", name, "unexpected parameter name")
+	assert.False(t, param.Required, "wiring parameters should NOT be required")
+	require.NotNil(t, param.Destination, "wiring parameters should have a destination set")
+	assert.Equal(t, "PORTER_MYSQL_MYSQL_PASSWORD_DEP_OUTPUT", param.Destination.EnvironmentVariable, "unexpected destination environment variable set")
 
-		assert.Equal(t, "https://porter.sh/generated-bundle/#porter-parameter-source-definition", paramDef.ID, "wiring parameter should have a schema id set")
-		assert.Equal(t, parameters.PorterInternal, paramDef.Comment, "wiring parameter should be flagged as internal")
-		assert.Empty(t, paramDef.Type, "dependency output types are of unknown types and should not be defined")
-	})
+	assert.Equal(t, "https://porter.sh/generated-bundle/#porter-parameter-source-definition", paramDef.ID, "wiring parameter should have a schema id set")
+	assert.Equal(t, parameters.PorterInternal, paramDef.Comment, "wiring parameter should be flagged as internal")
+	assert.Empty(t, paramDef.Type, "dependency output types are of unknown types and should not be defined")
 }
 
 func TestManifestConverter_generateRequiredExtensions_ParameterSources(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-templating.yaml", config.Name)
 
@@ -566,6 +596,8 @@ func TestManifestConverter_generateRequiredExtensions_ParameterSources(t *testin
 }
 
 func TestManifestConverter_generateRequiredExtensions(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-required-extensions.yaml", config.Name)
 
@@ -582,6 +614,8 @@ func TestManifestConverter_generateRequiredExtensions(t *testing.T) {
 }
 
 func TestManifestConverter_generateCustomExtensions_withRequired(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-required-extensions.yaml", config.Name)
 
@@ -598,6 +632,8 @@ func TestManifestConverter_generateCustomExtensions_withRequired(t *testing.T) {
 }
 
 func TestManifestConverter_GenerateCustomActionDefinitions(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("testdata/porter-with-custom-action.yaml", config.Name)
 
@@ -623,7 +659,7 @@ func TestManifestConverter_GenerateCustomActionDefinitions(t *testing.T) {
 }
 
 func TestManifestConverter_generateDefaultAction(t *testing.T) {
-	a := ManifestConverter{}
+	t.Parallel()
 
 	testcases := []struct {
 		action     string
@@ -659,6 +695,10 @@ func TestManifestConverter_generateDefaultAction(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.action, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
+			a := ManifestConverter{}
 			gotAction := a.generateDefaultAction(tc.action)
 			assert.Equal(t, tc.wantAction, gotAction)
 		})
@@ -666,6 +706,8 @@ func TestManifestConverter_generateDefaultAction(t *testing.T) {
 }
 
 func TestManifestConverter_generateCustomMetadata(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("./testdata/porter-with-custom-metadata.yaml", config.Name)
 
@@ -690,5 +732,4 @@ func TestManifestConverter_generateCustomMetadata(t *testing.T) {
 
 	assert.NoError(t, err, "Failed to read bundle file")
 	assert.Truef(t, strings.Contains(string(bundleData), expectedCustomMetaData), "Created bundle should be equal to expected bundle ")
-
 }
diff --git a/pkg/cnab/config-adapter/stamp_test.go b/pkg/cnab/config-adapter/stamp_test.go
index 4c57c06c..7803d714 100644
--- a/pkg/cnab/config-adapter/stamp_test.go
+++ b/pkg/cnab/config-adapter/stamp_test.go
@@ -15,6 +15,8 @@ import (
 var simpleManifestDigest = "b3be65771034c64a0d49d2c8a4ac3103a1ec12d6e41015ef57861fd913f72ecf"
 
 func TestConfig_GenerateStamp(t *testing.T) {
+	t.Parallel()
+
 	c := config.NewTestConfig(t)
 	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
 
@@ -35,6 +37,8 @@ func TestConfig_GenerateStamp(t *testing.T) {
 }
 
 func TestConfig_LoadStamp(t *testing.T) {
+	t.Parallel()
+
 	bun := &bundle.Bundle{
 		Custom: map[string]interface{}{
 			config.CustomPorterKey: map[string]interface{}{
@@ -55,6 +59,8 @@ func TestConfig_LoadStamp(t *testing.T) {
 }
 
 func TestConfig_LoadStamp_Invalid(t *testing.T) {
+	t.Parallel()
+
 	bun := &bundle.Bundle{
 		Custom: map[string]interface{}{
 			config.CustomPorterKey: []string{
@@ -70,9 +76,12 @@ func TestConfig_LoadStamp_Invalid(t *testing.T) {
 }
 
 func TestStamp_DecodeManifest(t *testing.T) {
-	c := config.NewTestConfig(t)
+	t.Parallel()
 
 	t.Run("manifest populated", func(t *testing.T) {
+		t.Parallel()
+
+		c := config.NewTestConfig(t)
 		s := Stamp{
 			EncodedManifest: "bmFtZTogaGVsbG8=", // name: hello
 		}
@@ -88,6 +97,8 @@ func TestStamp_DecodeManifest(t *testing.T) {
 	})
 
 	t.Run("manifest empty", func(t *testing.T) {
+		t.Parallel()
+
 		s := Stamp{}
 
 		data, err := s.DecodeManifest()
@@ -97,6 +108,8 @@ func TestStamp_DecodeManifest(t *testing.T) {
 	})
 
 	t.Run("manifest invalid", func(t *testing.T) {
+		t.Parallel()
+
 		s := Stamp{
 			EncodedManifest: "name: hello", // this should be base64 encoded
 		}
@@ -111,10 +124,14 @@ func TestStamp_DecodeManifest(t *testing.T) {
 }
 
 func TestConfig_DigestManifest(t *testing.T) {
-	c := config.NewTestConfig(t)
-	c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
+	t.Parallel()
 
 	t.Run("updated invocation image", func(t *testing.T) {
+		t.Parallel()
+
+		c := config.NewTestConfig(t)
+		c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
+
 		m, err := manifest.LoadManifestFrom(c.Context, config.Name)
 		require.NoError(t, err, "could not load manifest")
 
@@ -129,6 +146,11 @@ func TestConfig_DigestManifest(t *testing.T) {
 	})
 
 	t.Run("updated version", func(t *testing.T) {
+		t.Parallel()
+
+		c := config.NewTestConfig(t)
+		c.TestContext.AddTestFile("../../manifest/testdata/simple.porter.yaml", config.Name)
+
 		m, err := manifest.LoadManifestFrom(c.Context, config.Name)
 		require.NoError(t, err, "could not load manifest")
 
diff --git a/pkg/cnab/extensions/dependencies_test.go b/pkg/cnab/extensions/dependencies_test.go
index a8f2dafc..67693954 100644
--- a/pkg/cnab/extensions/dependencies_test.go
+++ b/pkg/cnab/extensions/dependencies_test.go
@@ -10,6 +10,8 @@ import (
 )
 
 func TestReadDependencyProperties(t *testing.T) {
+	t.Parallel()
+
 	data, err := ioutil.ReadFile("testdata/bundle.json")
 	require.NoError(t, err, "cannot read bundle file")
 
@@ -36,6 +38,8 @@ func TestReadDependencyProperties(t *testing.T) {
 }
 
 func TestDependencies_ListBySequence(t *testing.T) {
+	t.Parallel()
+
 	sequenceMock := []string{"nginx", "storage", "mysql"}
 
 	bun := bundle.Bundle{
diff --git a/pkg/cnab/extensions/docker_test.go b/pkg/cnab/extensions/docker_test.go
index cb4790d7..e9f42b07 100644
--- a/pkg/cnab/extensions/docker_test.go
+++ b/pkg/cnab/extensions/docker_test.go
@@ -8,7 +8,11 @@ import (
 )
 
 func TestProcessedExtensions_GetDockerExtension(t *testing.T) {
+	t.Parallel()
+
 	t.Run("extension present", func(t *testing.T) {
+		t.Parallel()
+
 		ext := ProcessedExtensions{
 			DockerExtensionKey: Docker{
 				Privileged: true,
@@ -22,6 +26,8 @@ func TestProcessedExtensions_GetDockerExtension(t *testing.T) {
 	})
 
 	t.Run("extension missing", func(t *testing.T) {
+		t.Parallel()
+
 		ext := ProcessedExtensions{}
 
 		dockerExt, dockerRequired, err := ext.GetDocker()
@@ -31,6 +37,8 @@ func TestProcessedExtensions_GetDockerExtension(t *testing.T) {
 	})
 
 	t.Run("extension invalid", func(t *testing.T) {
+		t.Parallel()
+
 		ext := ProcessedExtensions{
 			DockerExtensionKey: map[string]string{"ponies": "are great"},
 		}
diff --git a/pkg/cnab/extensions/paramter_sources_test.go b/pkg/cnab/extensions/paramter_sources_test.go
index 2ad8269d..6577e7d5 100644
--- a/pkg/cnab/extensions/paramter_sources_test.go
+++ b/pkg/cnab/extensions/paramter_sources_test.go
@@ -10,7 +10,11 @@ import (
 )
 
 func TestProcessedExtensions_GetParameterSourcesExtension(t *testing.T) {
+	t.Parallel()
+
 	t.Run("extension present", func(t *testing.T) {
+		t.Parallel()
+
 		var ps ParameterSources
 		ps.SetParameterFromOutput("tfstate", "tfstate")
 		processed := ProcessedExtensions{
@@ -24,6 +28,8 @@ func TestProcessedExtensions_GetParameterSourcesExtension(t *testing.T) {
 	})
 
 	t.Run("extension missing", func(t *testing.T) {
+		t.Parallel()
+
 		processed := ProcessedExtensions{}
 
 		ext, required, err := processed.GetParameterSources()
@@ -33,6 +39,8 @@ func TestProcessedExtensions_GetParameterSourcesExtension(t *testing.T) {
 	})
 
 	t.Run("extension invalid", func(t *testing.T) {
+		t.Parallel()
+
 		processed := ProcessedExtensions{
 			ParameterSourcesKey: map[string]string{"ponies": "are great"},
 		}
@@ -45,6 +53,8 @@ func TestProcessedExtensions_GetParameterSourcesExtension(t *testing.T) {
 }
 
 func TestReadParameterSourcesProperties(t *testing.T) {
+	t.Parallel()
+
 	data, err := ioutil.ReadFile("testdata/bundle.json")
 	require.NoError(t, err, "cannot read bundle file")
 
@@ -61,6 +71,8 @@ func TestReadParameterSourcesProperties(t *testing.T) {
 }
 
 func TestParameterSource_ListSourcesByPriority(t *testing.T) {
+	t.Parallel()
+
 	ps := ParameterSources{}
 	ps.SetParameterFromOutput("tfstate", "tfstate")
 	got := ps["tfstate"].ListSourcesByPriority()
diff --git a/pkg/cnab/extensions/required_test.go b/pkg/cnab/extensions/required_test.go
index 4828136f..c9a9ff9e 100644
--- a/pkg/cnab/extensions/required_test.go
+++ b/pkg/cnab/extensions/required_test.go
@@ -9,7 +9,11 @@ import (
 )
 
 func TestProcessRequiredExtensions(t *testing.T) {
+	t.Parallel()
+
 	t.Run("supported", func(t *testing.T) {
+		t.Parallel()
+
 		bun := cnab.ReadTestBundle(t, "testdata/bundle.json")
 		exts, err := ProcessRequiredExtensions(bun)
 		require.NoError(t, err, "could not process required extensions")
@@ -51,12 +55,16 @@ func TestProcessRequiredExtensions(t *testing.T) {
 	})
 
 	t.Run("supported unprocessable", func(t *testing.T) {
+		t.Parallel()
+
 		bun := cnab.ReadTestBundle(t, "testdata/bundle-supported-unprocessable.json")
 		_, err := ProcessRequiredExtensions(bun)
 		require.EqualError(t, err, "unable to process extension: io.cnab.docker: no custom extension configuration found")
 	})
 
 	t.Run("unsupported", func(t *testing.T) {
+		t.Parallel()
+
 		bun := cnab.ReadTestBundle(t, "testdata/bundle-unsupported-required.json")
 		_, err := ProcessRequiredExtensions(bun)
 		require.EqualError(t, err, "unsupported required extension: donuts")
@@ -64,14 +72,20 @@ func TestProcessRequiredExtensions(t *testing.T) {
 }
 
 func TestGetSupportedExtension(t *testing.T) {
+	t.Parallel()
+
 	for _, supported := range SupportedExtensions {
 		t.Run(fmt.Sprintf("%s - shorthand", supported.Shorthand), func(t *testing.T) {
+			t.Parallel()
+
 			ext, err := GetSupportedExtension(supported.Shorthand)
 			require.NoError(t, err)
 			require.Equal(t, supported.Key, ext.Key)
 		})
 
 		t.Run(fmt.Sprintf("%s - key", supported.Key), func(t *testing.T) {
+			t.Parallel()
+
 			ext, err := GetSupportedExtension(supported.Key)
 			require.NoError(t, err)
 			require.Equal(t, supported.Key, ext.Key)
@@ -79,6 +93,8 @@ func TestGetSupportedExtension(t *testing.T) {
 	}
 
 	t.Run("unsupported", func(t *testing.T) {
+		t.Parallel()
+
 		_, err := GetSupportedExtension("donuts")
 		require.EqualError(t, err, "unsupported required extension: donuts")
 	})
diff --git a/pkg/cnab/extensions/solver_test.go b/pkg/cnab/extensions/solver_test.go
index 750ff4ed..6addb6f1 100644
--- a/pkg/cnab/extensions/solver_test.go
+++ b/pkg/cnab/extensions/solver_test.go
@@ -10,6 +10,8 @@ import (
 )
 
 func TestDependencySolver_ResolveDependencies(t *testing.T) {
+	t.Parallel()
+
 	bun := bundle.Bundle{
 		Custom: map[string]interface{}{
 			DependenciesKey: Dependencies{
@@ -46,6 +48,8 @@ func TestDependencySolver_ResolveDependencies(t *testing.T) {
 }
 
 func TestDependencySolver_ResolveVersion(t *testing.T) {
+	t.Parallel()
+
 	testcases := []struct {
 		name        string
 		dep         Dependency
@@ -77,6 +81,9 @@ func TestDependencySolver_ResolveVersion(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			tc := tc
+
 			s := DependencySolver{}
 			version, err := s.ResolveVersion("mysql", tc.dep)
 
diff --git a/pkg/cnab/provider/action_test.go b/pkg/cnab/provider/action_test.go
index be234f43..952afbc1 100644
--- a/pkg/cnab/provider/action_test.go
+++ b/pkg/cnab/provider/action_test.go
@@ -11,6 +11,8 @@ import (
 )
 
 func TestAddReloccation(t *testing.T) {
+	t.Parallel()
+
 	data, err := ioutil.ReadFile("testdata/relocation-mapping.json")
 	require.NoError(t, err)
 
diff --git a/pkg/cnab/provider/credentials_test.go b/pkg/cnab/provider/credentials_test.go
index 398b999f..0e943e6f 100644
--- a/pkg/cnab/provider/credentials_test.go
+++ b/pkg/cnab/provider/credentials_test.go
@@ -12,6 +12,8 @@ import (
 )
 
 func TestRuntime_loadCredentials(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	r.TestCredentials.TestSecrets.AddSecret("password", "mypassword")
diff --git a/pkg/cnab/provider/driver_test.go b/pkg/cnab/provider/driver_test.go
index 90e1d184..339b0d3d 100644
--- a/pkg/cnab/provider/driver_test.go
+++ b/pkg/cnab/provider/driver_test.go
@@ -11,7 +11,11 @@ import (
 )
 
 func TestNewDriver_Docker(t *testing.T) {
+	t.Parallel()
+
 	t.Run("vanilla docker", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		driver, err := d.newDriver(DriverNameDocker, "myclaim", ActionArguments{})
 
@@ -20,6 +24,8 @@ func TestNewDriver_Docker(t *testing.T) {
 	})
 
 	t.Run("docker with host access", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		d.FileSystem.Create("/var/run/docker.sock")
 		args := ActionArguments{
@@ -33,6 +39,8 @@ func TestNewDriver_Docker(t *testing.T) {
 	})
 
 	t.Run("docker with host access, mismatch driver name", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		args := ActionArguments{
 			AllowDockerHostAccess: true,
@@ -44,6 +52,8 @@ func TestNewDriver_Docker(t *testing.T) {
 	})
 
 	t.Run("docker with host access, missing docker daemon", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		args := ActionArguments{
 			AllowDockerHostAccess: true,
@@ -54,6 +64,8 @@ func TestNewDriver_Docker(t *testing.T) {
 	})
 
 	t.Run("docker with host access, default config", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		// Currently, toggling Privileged is the only config exposed to users
 		// Here we supply no override, so expect Privileged to be false
@@ -79,6 +91,8 @@ func TestNewDriver_Docker(t *testing.T) {
 	})
 
 	t.Run("docker with host access, privileged true", func(t *testing.T) {
+		t.Parallel()
+
 		d := NewTestRuntime(t)
 		// Currently, toggling Privileged is the only config exposed to users
 		// Here we supply an override, so expect Privileged to be set to the override
diff --git a/pkg/cnab/provider/install_test.go b/pkg/cnab/provider/install_test.go
index 2dbab80c..7575e6e7 100644
--- a/pkg/cnab/provider/install_test.go
+++ b/pkg/cnab/provider/install_test.go
@@ -9,6 +9,8 @@ import (
 )
 
 func TestRuntime_Install(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 	r.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "bundle.json")
 
diff --git a/pkg/cnab/provider/invoke_test.go b/pkg/cnab/provider/invoke_test.go
index f39ccfc9..25a8340c 100644
--- a/pkg/cnab/provider/invoke_test.go
+++ b/pkg/cnab/provider/invoke_test.go
@@ -11,6 +11,8 @@ import (
 )
 
 func TestRuntime_ClaimPersistence(t *testing.T) {
+	t.Parallel()
+
 	type input struct {
 		bun          bundle.Bundle
 		action       string
@@ -138,6 +140,8 @@ func TestRuntime_ClaimPersistence(t *testing.T) {
 }
 
 func TestInvoke_NoClaimBubblesUpError(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	args := ActionArguments{
diff --git a/pkg/cnab/provider/parameters_test.go b/pkg/cnab/provider/parameters_test.go
index b9f7f847..7dcc14fb 100644
--- a/pkg/cnab/provider/parameters_test.go
+++ b/pkg/cnab/provider/parameters_test.go
@@ -13,6 +13,8 @@ import (
 )
 
 func Test_loadParameters_paramNotDefined(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 	b := bundle.Bundle{
 		Parameters: map[string]bundle.Parameter{},
@@ -31,6 +33,8 @@ func Test_loadParameters_paramNotDefined(t *testing.T) {
 }
 
 func Test_loadParameters_definitionNotDefined(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	b := bundle.Bundle{
@@ -54,6 +58,8 @@ func Test_loadParameters_definitionNotDefined(t *testing.T) {
 }
 
 func Test_loadParameters_applyTo(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	// Here we set default values, but expect nil/empty
@@ -111,6 +117,8 @@ func Test_loadParameters_applyTo(t *testing.T) {
 }
 
 func Test_loadParameters_applyToBundleDefaults(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	b := bundle.Bundle{
@@ -138,6 +146,8 @@ func Test_loadParameters_applyToBundleDefaults(t *testing.T) {
 }
 
 func Test_loadParameters_requiredButDoesNotApply(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	b := bundle.Bundle{
@@ -165,6 +175,8 @@ func Test_loadParameters_requiredButDoesNotApply(t *testing.T) {
 }
 
 func Test_loadParameters_fileParameter(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	r.TestConfig.TestContext.AddTestFile("testdata/file-param", "/path/to/file")
@@ -202,19 +214,19 @@ func Test_loadParameters_fileParameter(t *testing.T) {
 }
 
 func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
-	r := NewTestRuntime(t)
-	r.TestParameters.AddTestParameters("testdata/paramset.json")
-	r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+	t.Parallel()
 
-	r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
-	b, err := r.ProcessBundle("bundle.json")
-	require.NoError(t, err, "ProcessBundle failed")
+	t.Run("nothing present, use default", func(t *testing.T) {
+		t.Parallel()
 
-	overrides := map[string]string{
-		"foo": "foo_override",
-	}
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
 
-	t.Run("nothing present, use default", func(t *testing.T) {
 		args := ActionArguments{
 			Installation: "mybun",
 			Action:       claim.ActionUpgrade,
@@ -226,6 +238,20 @@ func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
 	})
 
 	t.Run("only override present", func(t *testing.T) {
+		t.Parallel()
+
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
+
+		overrides := map[string]string{
+			"foo": "foo_override",
+		}
+
 		args := ActionArguments{
 			Installation: "mybun",
 			Action:       claim.ActionUpgrade,
@@ -238,6 +264,16 @@ func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
 	})
 
 	t.Run("only parameter source present", func(t *testing.T) {
+		t.Parallel()
+
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
+
 		c := r.TestClaims.CreateClaim("mybun", claim.ActionInstall, b, nil)
 		cr := r.TestClaims.CreateResult(c, claim.StatusSucceeded)
 		r.TestClaims.CreateOutput(c, cr, "foo", []byte("foo_source"))
@@ -253,6 +289,20 @@ func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
 	})
 
 	t.Run("override > parameter source", func(t *testing.T) {
+		t.Parallel()
+
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
+
+		overrides := map[string]string{
+			"foo": "foo_override",
+		}
+
 		c := r.TestClaims.CreateClaim("mybun", claim.ActionInstall, b, nil)
 		cr := r.TestClaims.CreateResult(c, claim.StatusSucceeded)
 		r.TestClaims.CreateOutput(c, cr, "foo", []byte("foo_source"))
@@ -269,6 +319,16 @@ func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
 	})
 
 	t.Run("dependency output without type", func(t *testing.T) {
+		t.Parallel()
+
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
+
 		fooBun := bundle.Bundle{
 			Name:    "foo-setup",
 			Version: bundle.CNABSpecVersion,
@@ -301,6 +361,16 @@ func Test_loadParameters_ParameterSourcePrecedence(t *testing.T) {
 	})
 
 	t.Run("merge parameter values", func(t *testing.T) {
+		t.Parallel()
+
+		r := NewTestRuntime(t)
+		r.TestParameters.AddTestParameters("testdata/paramset.json")
+		r.TestParameters.TestSecrets.AddSecret("foo_secret", "foo_set")
+
+		r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
+		b, err := r.ProcessBundle("bundle.json")
+		require.NoError(t, err, "ProcessBundle failed")
+
 		// foo is set by a the user
 		// bar is set by a parameter source
 		// baz is set by the bundle default
@@ -397,6 +467,9 @@ func Test_Paramapalooza(t *testing.T) {
 
 			for _, tc := range testcases {
 				t.Run(tc.name, func(t *testing.T) {
+					t.Parallel()
+					tc := tc
+
 					d := NewTestRuntime(t)
 
 					bun := bundle.Bundle{
@@ -494,6 +567,8 @@ func Test_Paramapalooza(t *testing.T) {
 }
 
 func TestRuntime_ResolveParameterSources(t *testing.T) {
+	t.Parallel()
+
 	r := NewTestRuntime(t)
 
 	r.TestConfig.TestContext.AddTestFile("testdata/bundle-with-param-sources.json", "bundle.json")
diff --git a/pkg/cnab/provider/uninstall_test.go b/pkg/cnab/provider/uninstall_test.go
index e2c33533..76f0c4b4 100644
--- a/pkg/cnab/provider/uninstall_test.go
+++ b/pkg/cnab/provider/uninstall_test.go
@@ -11,7 +11,11 @@ import (
 )
 
 func TestRuntime_Uninstall(t *testing.T) {
+	t.Parallel()
+
 	t.Run("success", func(t *testing.T) {
+		t.Parallel()
+
 		r := NewTestRuntime(t)
 		r.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "bundle.json")
 
@@ -36,6 +40,8 @@ func TestRuntime_Uninstall(t *testing.T) {
 	})
 
 	t.Run("requires existing claim", func(t *testing.T) {
+		t.Parallel()
+
 		r := NewTestRuntime(t)
 		r.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "bundle.json")
 
diff --git a/pkg/cnab/provider/upgrade_test.go b/pkg/cnab/provider/upgrade_test.go
index 3ca0d382..ac375c1e 100644
--- a/pkg/cnab/provider/upgrade_test.go
+++ b/pkg/cnab/provider/upgrade_test.go
@@ -11,7 +11,11 @@ import (
 )
 
 func TestRuntime_Upgrade(t *testing.T) {
+	t.Parallel()
+
 	t.Run("success", func(t *testing.T) {
+		t.Parallel()
+
 		r := NewTestRuntime(t)
 		r.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "bundle.json")
 
@@ -36,6 +40,8 @@ func TestRuntime_Upgrade(t *testing.T) {
 	})
 
 	t.Run("requires existing claim", func(t *testing.T) {
+		t.Parallel()
+
 		r := NewTestRuntime(t)
 		r.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "bundle.json")
 
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index b48ab557..8e49db40 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -1,7 +1,6 @@
 package config
 
 import (
-	"os"
 	"path/filepath"
 	"testing"
 
@@ -11,7 +10,6 @@ import (
 
 func TestConfig_GetHomeDir(t *testing.T) {
 	c := NewTestConfig(t)
-	c.SetupPorterHome()
 
 	home, err := c.GetHomeDir()
 	require.NoError(t, err)
@@ -21,10 +19,9 @@ func TestConfig_GetHomeDir(t *testing.T) {
 
 func TestConfig_GetHomeDirFromSymlink(t *testing.T) {
 	c := NewTestConfig(t)
-	c.SetupPorterHome()
 
 	// Set up no PORTER_HOME, and /usr/local/bin/porter -> ~/.porter/porter
-	os.Unsetenv(EnvHOME)
+	c.Unsetenv(EnvHOME)
 	getExecutable = func() (string, error) {
 		return "/usr/local/bin/porter", nil
 	}
diff --git a/pkg/config/datastore/loader_test.go b/pkg/config/datastore/loader_test.go
index e7b0df8b..6140808c 100644
--- a/pkg/config/datastore/loader_test.go
+++ b/pkg/config/datastore/loader_test.go
@@ -24,6 +24,7 @@ func TestFromConfigFile(t *testing.T) {
 }
 
 func TestFromFlagsThenEnvVarsThenConfigFile(t *testing.T) {
+	// Cannot be run in parallel because viper reads directly from env vars
 	buildCommand := func(c *config.Config) *cobra.Command {
 		cmd := &cobra.Command{}
 		cmd.Flags().BoolVar(&c.Debug, "debug", false, "debug")
@@ -76,10 +77,9 @@ func TestFromFlagsThenEnvVarsThenConfigFile(t *testing.T) {
 	})
 
 	t.Run("debug env var", func(t *testing.T) {
+		os.Setenv("PORTER_DEBUG", "true")
 		c := config.NewTestConfig(t)
 		c.SetHomeDir("/root/.porter")
-		os.Setenv("PORTER_DEBUG", "true")
-		defer os.Unsetenv("PORTER_DEBUG")
 
 		cmd := buildCommand(c.Config)
 		err := cmd.Execute()
@@ -89,11 +89,24 @@ func TestFromFlagsThenEnvVarsThenConfigFile(t *testing.T) {
 		assert.True(t, c.Debug, "config.Debug was not set correctly")
 	})
 
-	t.Run("debug env var overrides config", func(t *testing.T) {
+	t.Run("invalid debug env var", func(t *testing.T) {
+		os.Setenv("PORTER_DEBUG", "blorp")
 		c := config.NewTestConfig(t)
 		c.SetHomeDir("/root/.porter")
+
+		cmd := buildCommand(c.Config)
+		err := cmd.Execute()
+
+		require.NoError(t, err, "dataloader failed")
+		require.NotNil(t, c.Data, "config.Data was not populated")
+		assert.False(t, c.Debug, "config.Debug was not set correctly")
+	})
+
+	t.Run("debug env var overrides config", func(t *testing.T) {
 		os.Setenv("PORTER_DEBUG", "false")
 		defer os.Unsetenv("PORTER_DEBUG")
+		c := config.NewTestConfig(t)
+		c.SetHomeDir("/root/.porter")
 		c.TestContext.AddTestFile("testdata/config.toml", "/root/.porter/config.toml")
 
 		cmd := buildCommand(c.Config)
@@ -105,10 +118,11 @@ func TestFromFlagsThenEnvVarsThenConfigFile(t *testing.T) {
 	})
 
 	t.Run("flag overrides debug env var overrides config", func(t *testing.T) {
-		c := config.NewTestConfig(t)
-		c.SetHomeDir("/root/.porter")
 		os.Setenv("PORTER_DEBUG", "false")
 		defer os.Unsetenv("PORTER_DEBUG")
+
+		c := config.NewTestConfig(t)
+		c.SetHomeDir("/root/.porter")
 		c.TestContext.AddTestFile("testdata/config.toml", "/root/.porter/config.toml")
 
 		cmd := buildCommand(c.Config)
diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go
deleted file mode 100644
index e53311f4..00000000
--- a/pkg/context/context_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package context
-
-import (
-	"os"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNew_Debug(t *testing.T) {
-	t.Run("porter_debug unset", func(t *testing.T) {
-		os.Unsetenv("PORTER_DEBUG")
-
-		c := New()
-		assert.False(t, c.Debug, "debug should be false when the env var is not present")
-	})
-
-	t.Run("porter_debug false", func(t *testing.T) {
-		defer os.Unsetenv("PORTER_DEBUG")
-		os.Setenv("PORTER_DEBUG", "false")
-		c := New()
-		assert.False(t, c.Debug, "debug should be false when the env var is set to 'false'")
-	})
-
-	t.Run("porter_debug invalid", func(t *testing.T) {
-		defer os.Unsetenv("PORTER_DEBUG")
-		os.Setenv("PORTER_DEBUG", "blorp")
-		c := New()
-		assert.False(t, c.Debug, "debug should be false when the env var is not a bool")
-	})
-
-	t.Run("porter_debug true", func(t *testing.T) {
-		os.Setenv("PORTER_DEBUG", "true")
-		c := New()
-		assert.True(t, c.Debug, "debug should be true when the env var is 'true'")
-	})
-}
diff --git a/pkg/credentials/credentialStorage_test.go b/pkg/credentials/credentialStorage_test.go
index 40c43afd..8c90e39a 100644
--- a/pkg/credentials/credentialStorage_test.go
+++ b/pkg/credentials/credentialStorage_test.go
@@ -9,7 +9,7 @@ import (
 	"get.porter.sh/porter/pkg/storage/filesystem"
 	"github.com/cnabio/cnab-go/credentials"
 	"github.com/cnabio/cnab-go/valuesource"
-	"github.com/hashicorp/go-hclog"
+	hclog "github.com/hashicorp/go-hclog"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -57,7 +57,7 @@ func TestCredentialStorage_Validate_BadSources(t *testing.T) {
 
 func TestCredentialStorage_HaltOnMigrationRequired(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -86,7 +86,7 @@ func TestCredentialStorage_HaltOnMigrationRequired(t *testing.T) {
 
 func TestCredentialStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -104,7 +104,7 @@ func TestCredentialStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T)
 
 func TestCredentialStorage_NoMigrationRequiredForEmptyHome(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
diff --git a/pkg/docs/generator_test.go b/pkg/docs/generator_test.go
index 8c8df4b5..ed2fd359 100644
--- a/pkg/docs/generator_test.go
+++ b/pkg/docs/generator_test.go
@@ -16,7 +16,7 @@ func TestValidateDocsCommand(t *testing.T) {
 		destination string
 		wantError   string
 	}{
-		{"should return error if destination doesn't exist", "./no-existing/destination/directory", "--destination %q doesn't exist"},
+		{"should return error if destination doesn't exist", "/no-existing/destination/directory", "--destination %q doesn't exist"},
 		{"should not return error if destination exists", ".", ""},
 	}
 	for _, tc := range testcases {
@@ -30,9 +30,8 @@ func TestValidateDocsCommand(t *testing.T) {
 				require.NoError(t, err)
 			} else {
 				require.Error(t, err)
-				assert.Equal(t, err.Error(), fmt.Sprintf(tc.wantError, tc.destination))
+				assert.Equal(t, fmt.Sprintf(tc.wantError, tc.destination), err.Error())
 			}
 		})
 	}
-
 }
diff --git a/pkg/exec/builder/execute_test.go b/pkg/exec/builder/execute_test.go
index 384c99dd..ec997471 100644
--- a/pkg/exec/builder/execute_test.go
+++ b/pkg/exec/builder/execute_test.go
@@ -1,7 +1,6 @@
 package builder
 
 import (
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/context"
@@ -44,8 +43,7 @@ func TestExecuteSingleStepAction(t *testing.T) {
 		},
 	}
 
-	os.Setenv(test.ExpectedCommandEnv, "foo")
-	defer os.Unsetenv(test.ExpectedCommandEnv)
+	c.Setenv(test.ExpectedCommandEnv, "foo")
 
 	_, err = ExecuteSingleStepAction(c.Context, a)
 	require.NoError(t, err, "ExecuteSingleStepAction should not have returned an error")
@@ -131,8 +129,7 @@ func TestExecuteStep_HasOrderedArguments(t *testing.T) {
 		SuffixArguments: []string{"."},
 	}
 
-	os.Setenv(test.ExpectedCommandEnv, "docker build -t getporter/porter-hello:latest .")
-	defer os.Unsetenv(test.ExpectedCommandEnv)
+	c.Setenv(test.ExpectedCommandEnv, "docker build -t getporter/porter-hello:latest .")
 
 	_, err := ExecuteStep(c.Context, step)
 	require.NoError(t, err, "ExecuteStep failed")
diff --git a/pkg/exec/exec_test.go b/pkg/exec/exec_test.go
index f017f76f..6b5ae96c 100644
--- a/pkg/exec/exec_test.go
+++ b/pkg/exec/exec_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"fmt"
 	"io/ioutil"
-	"os"
 	"sort"
 	"testing"
 
@@ -40,9 +39,6 @@ func TestAction_UnmarshalYAML(t *testing.T) {
 }
 
 func TestMixin_ExecuteCommand(t *testing.T) {
-	os.Setenv(test.ExpectedCommandEnv, `bash -c echo Hello World`)
-	defer os.Unsetenv(test.ExpectedCommandEnv)
-
 	step := Step{
 		Instruction: Instruction{
 			Command:   "bash",
@@ -56,10 +52,12 @@ func TestMixin_ExecuteCommand(t *testing.T) {
 	b, _ := yaml.Marshal(action)
 
 	fmt.Println(string(b))
-	h := NewTestMixin(t)
-	h.In = bytes.NewReader(b)
+	m := NewTestMixin(t)
+	m.In = bytes.NewReader(b)
+
+	m.Setenv(test.ExpectedCommandEnv, `bash -c echo Hello World`)
 
-	err := h.Execute(ExecuteOptions{})
+	err := m.Execute(ExecuteOptions{})
 
 	require.NoError(t, err)
 }
@@ -119,9 +117,6 @@ func TestMixin_Uninstall(t *testing.T) {
 }
 
 func TestMixin_SuffixArgs(t *testing.T) {
-	os.Setenv(test.ExpectedCommandEnv, `docker build --tag getporter/porter-hello:latest .`)
-	defer os.Unsetenv(test.ExpectedCommandEnv)
-
 	b, err := ioutil.ReadFile("testdata/suffix-args-input.yaml")
 	require.NoError(t, err, "ReadFile failed")
 
@@ -129,9 +124,11 @@ func TestMixin_SuffixArgs(t *testing.T) {
 	err = yaml.Unmarshal(b, &action)
 	require.NoError(t, err, "Unmarshal failed")
 
-	h := NewTestMixin(t)
-	h.In = bytes.NewReader(b)
+	m := NewTestMixin(t)
+	m.In = bytes.NewReader(b)
+
+	m.Setenv(test.ExpectedCommandEnv, `docker build --tag getporter/porter-hello:latest .`)
 
-	err = h.Execute(ExecuteOptions{})
+	err = m.Execute(ExecuteOptions{})
 	require.NoError(t, err)
 }
diff --git a/pkg/exec/execute_test.go b/pkg/exec/execute_test.go
index 2d6a6db2..3c8c4baf 100644
--- a/pkg/exec/execute_test.go
+++ b/pkg/exec/execute_test.go
@@ -3,7 +3,6 @@ package exec
 import (
 	"bytes"
 	"io/ioutil"
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/test"
@@ -21,8 +20,7 @@ func TestMixin_Execute(t *testing.T) {
 	require.NoError(t, err)
 	m.In = bytes.NewBuffer(stdin)
 
-	os.Setenv(test.ExpectedCommandEnv, "foo")
-	defer os.Unsetenv(test.ExpectedCommandEnv)
+	m.Setenv(test.ExpectedCommandEnv, "foo")
 
 	err = m.Execute(ExecuteOptions{})
 	require.NoError(t, err, "Execute should not have returned an error")
diff --git a/pkg/mixin/pkgmgmt_integration_test.go b/pkg/mixin/pkgmgmt_integration_test.go
index 98277686..4aa901f8 100644
--- a/pkg/mixin/pkgmgmt_integration_test.go
+++ b/pkg/mixin/pkgmgmt_integration_test.go
@@ -4,20 +4,16 @@ package mixin
 
 import (
 	"io/ioutil"
-	"os/exec"
 	"testing"
 
 	"get.porter.sh/porter/pkg/config"
-	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestPackageManager_GetSchema(t *testing.T) {
 	c := config.NewTestConfig(t)
-	// Hit the real file system for this test
-	c.FileSystem = &afero.Afero{Fs: afero.NewOsFs()}
-	c.NewCommand = exec.Command
+	c.TestContext.UseFilesystem()
 
 	// bin is my home now
 	binDir := c.TestContext.FindBinDir()
diff --git a/pkg/mixin/pkgmgmt_test.go b/pkg/mixin/pkgmgmt_test.go
index bf923ae2..a360ca85 100644
--- a/pkg/mixin/pkgmgmt_test.go
+++ b/pkg/mixin/pkgmgmt_test.go
@@ -1,7 +1,6 @@
 package mixin
 
 import (
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/pkgmgmt"
@@ -24,12 +23,11 @@ func TestRunner_BuildCommand(t *testing.T) {
 		{"version", "version --output json", "/root/.porter/mixins/exec/exec version --output json"},
 	}
 
-	os.Unsetenv(test.ExpectedCommandEnv)
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			r := client.NewTestRunner(t, "exec", "mixins", false)
 			r.Debug = false
-			os.Setenv(test.ExpectedCommandEnv, tc.wantCommand)
+			r.Setenv(test.ExpectedCommandEnv, tc.wantCommand)
 
 			mgr := PackageManager{}
 			cmd := pkgmgmt.CommandOptions{Command: tc.runnerCommand, PreRun: mgr.PreRunMixinCommandHandler}
diff --git a/pkg/parameters/parameterStorage_test.go b/pkg/parameters/parameterStorage_test.go
index 7ea8e4ff..66cc8418 100644
--- a/pkg/parameters/parameterStorage_test.go
+++ b/pkg/parameters/parameterStorage_test.go
@@ -4,16 +4,15 @@ import (
 	"path/filepath"
 	"testing"
 
-	"get.porter.sh/porter/pkg/storage"
-	"get.porter.sh/porter/pkg/storage/filesystem"
-	"github.com/cnabio/cnab-go/utils/crud"
-	"github.com/hashicorp/go-hclog"
-	"github.com/stretchr/testify/assert"
-
 	"get.porter.sh/porter/pkg/config"
 	"get.porter.sh/porter/pkg/secrets"
 	inmemorysecrets "get.porter.sh/porter/pkg/secrets/in-memory"
+	"get.porter.sh/porter/pkg/storage"
+	"get.porter.sh/porter/pkg/storage/filesystem"
+	"github.com/cnabio/cnab-go/utils/crud"
 	"github.com/cnabio/cnab-go/valuesource"
+	hclog "github.com/hashicorp/go-hclog"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -152,7 +151,7 @@ func TestParameterStorage_Validate(t *testing.T) {
 
 func TestParameterStorage_HaltOnMigrationRequired(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -181,7 +180,7 @@ func TestParameterStorage_HaltOnMigrationRequired(t *testing.T) {
 
 func TestParameterStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -199,7 +198,7 @@ func TestParameterStorage_OperationAllowedWhenNoMigrationDetected(t *testing.T)
 
 func TestParameterStorage_NoMigrationRequiredForEmptyHome(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
diff --git a/pkg/parameters/parameterset_test.go b/pkg/parameters/parameterset_test.go
index b623a2ea..4823ef32 100644
--- a/pkg/parameters/parameterset_test.go
+++ b/pkg/parameters/parameterset_test.go
@@ -10,7 +10,7 @@ import (
 	"github.com/cnabio/cnab-go/schema"
 	"github.com/cnabio/cnab-go/utils/crud"
 	"github.com/cnabio/cnab-go/valuesource"
-	"github.com/hashicorp/go-hclog"
+	hclog "github.com/hashicorp/go-hclog"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -42,7 +42,7 @@ func TestNewParameterSet(t *testing.T) {
 // TODO: (carolynvs) move this into manager_test.go in pkg/storage once parameter set is moved to cnab-go
 func TestManager_MigrateParameters(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
diff --git a/pkg/pkgmgmt/client/filesystem_test.go b/pkg/pkgmgmt/client/filesystem_test.go
index 1d5838a4..76c6c808 100644
--- a/pkg/pkgmgmt/client/filesystem_test.go
+++ b/pkg/pkgmgmt/client/filesystem_test.go
@@ -10,7 +10,6 @@ import (
 
 func TestFileSystem_List(t *testing.T) {
 	c := config.NewTestConfig(t)
-	c.SetupPorterHome()
 
 	p := NewFileSystem(c.Config, "mixins")
 	mixins, err := p.List()
diff --git a/pkg/pkgmgmt/client/install_test.go b/pkg/pkgmgmt/client/install_test.go
index 07f6c809..6f4c347f 100644
--- a/pkg/pkgmgmt/client/install_test.go
+++ b/pkg/pkgmgmt/client/install_test.go
@@ -24,7 +24,6 @@ func TestFileSystem_InstallFromUrl(t *testing.T) {
 	defer ts.Close()
 
 	c := config.NewTestConfig(t)
-	c.SetupPorterHome()
 	p := NewFileSystem(c.Config, "packages")
 
 	opts := pkgmgmt.InstallOptions{
@@ -62,7 +61,6 @@ func TestFileSystem_InstallFromFeedUrl(t *testing.T) {
 	testURL = ts.URL
 
 	c := config.NewTestConfig(t)
-	c.SetupPorterHome()
 	p := NewFileSystem(c.Config, "packages")
 
 	opts := pkgmgmt.InstallOptions{
@@ -114,7 +112,6 @@ func TestFileSystem_Install_RollbackMissingRuntime(t *testing.T) {
 
 func TestFileSystem_Install_PackageInfoSavedWhenNoFileExists(t *testing.T) {
 	c := config.NewTestConfig(t)
-	c.SetupPorterHome()
 	p := NewFileSystem(c.Config, "packages")
 
 	packageURL := "https://cdn.porter.sh/mixins/helm"
diff --git a/pkg/pkgmgmt/client/runner_integration_test.go b/pkg/pkgmgmt/client/runner_integration_test.go
index 07e6b39e..be789cef 100644
--- a/pkg/pkgmgmt/client/runner_integration_test.go
+++ b/pkg/pkgmgmt/client/runner_integration_test.go
@@ -19,7 +19,9 @@ func TestRunner_Run(t *testing.T) {
 	// Provide a way for tests to capture stdout
 	output := &bytes.Buffer{}
 
-	binDir := context.NewTestContext(t).FindBinDir()
+	cxt := context.NewTestContext(t)
+	cxt.UseFilesystem()
+	binDir := cxt.FindBinDir()
 
 	// I'm not using the TestRunner because I want to use the current filesystem, not an isolated one
 	r := NewRunner("exec", filepath.Join(binDir, "mixins/exec"), false)
@@ -54,7 +56,9 @@ func TestRunner_RunWithMaskedOutput(t *testing.T) {
 	sensitiveValues = append(sensitiveValues, " ", "", "\n", "\r", "\t")
 	censoredWriter.SetSensitiveValues(sensitiveValues)
 
-	binDir := context.NewTestContext(t).FindBinDir()
+	cxt := context.NewTestContext(t)
+	cxt.UseFilesystem()
+	binDir := cxt.FindBinDir()
 
 	// I'm not using the TestRunner because I want to use the current filesystem, not an isolated one
 	r := NewRunner("exec", filepath.Join(binDir, "mixins/exec"), false)
diff --git a/pkg/pkgmgmt/feed/generate_test.go b/pkg/pkgmgmt/feed/generate_test.go
index 230dcc79..d9c65a33 100644
--- a/pkg/pkgmgmt/feed/generate_test.go
+++ b/pkg/pkgmgmt/feed/generate_test.go
@@ -84,7 +84,7 @@ func TestGenerate_RegexMatch(t *testing.T) {
 	}{{
 		name:      "no bins",
 		mixinName: "",
-		wantError: `failed to traverse the bin directory: open bin: file does not exist`,
+		wantError: `failed to traverse the bin directory: open /bin: file does not exist`,
 	}, {
 		name:      "valid mixin name",
 		mixinName: "my-42nd-mixin",
diff --git a/pkg/plugins/pluggable/pluggable_integration_test.go b/pkg/plugins/pluggable/pluggable_integration_test.go
index 3f74e2f4..3ae53bf8 100644
--- a/pkg/plugins/pluggable/pluggable_integration_test.go
+++ b/pkg/plugins/pluggable/pluggable_integration_test.go
@@ -3,8 +3,6 @@
 package pluggable
 
 import (
-	"io/ioutil"
-	"os"
 	"os/exec"
 	"path"
 	"testing"
@@ -15,12 +13,8 @@ import (
 )
 
 func TestPlugins_CatchStderr(t *testing.T) {
-	home, err := ioutil.TempDir("/tmp", "porter")
-	require.NoError(t, err, "could not create porter home directory")
-
 	c := config.NewTestConfig(t)
-	c.SetHomeDir(home)
-	c.NewCommand = exec.Command
+	c.SetupIntegrationTest()
 
 	t.Run("plugin throws an error", func(t *testing.T) {
 		pluginsPath, _ := c.GetPluginsDir()
@@ -30,7 +24,7 @@ func TestPlugins_CatchStderr(t *testing.T) {
 		require.NoError(t, err, "could not create plugin dir")
 
 		// testplugin binary will be in bin. refer "test-integration" in Makefile
-		err = exec.Command("cp", path.Join(os.Getenv("PROJECT_ROOT"), "bin", pluginName), path.Join(pluginsPath, pluginName)).Run()
+		err = exec.Command("cp", path.Join(c.Getenv("PROJECT_ROOT"), "bin", pluginName), path.Join(pluginsPath, pluginName)).Run()
 		require.NoError(t, err, "could not copy test binary")
 
 		cfg := PluginTypeConfig{
diff --git a/pkg/porter/build_test.go b/pkg/porter/build_test.go
index 1e3c913d..3279020c 100644
--- a/pkg/porter/build_test.go
+++ b/pkg/porter/build_test.go
@@ -16,7 +16,6 @@ import (
 
 func TestPorter_buildBundle(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	configTpl, err := p.Templates.GetManifest()
 	require.Nil(t, err)
@@ -71,7 +70,6 @@ func TestPorter_LintDuringBuild(t *testing.T) {
 
 	t.Run("failing lint should stop build", func(t *testing.T) {
 		p := NewTestPorter(t)
-		p.TestConfig.SetupPorterHome()
 		testMixins := p.Mixins.(*mixin.TestMixinProvider)
 		testMixins.LintResults = lintResults
 
@@ -86,7 +84,6 @@ func TestPorter_LintDuringBuild(t *testing.T) {
 
 	t.Run("ignores lint error with --no-lint", func(t *testing.T) {
 		p := NewTestPorter(t)
-		p.TestConfig.SetupPorterHome()
 		testMixins := p.Mixins.(*mixin.TestMixinProvider)
 		testMixins.LintResults = lintResults
 
@@ -102,7 +99,6 @@ func TestPorter_LintDuringBuild(t *testing.T) {
 
 func TestPorter_paramRequired(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 	p.TestConfig.TestContext.AddTestFile("./testdata/paramafest.yaml", config.Name)
 
 	err := p.LoadManifest()
diff --git a/pkg/porter/cnab_test.go b/pkg/porter/cnab_test.go
index eb621ccd..9e3d8d11 100644
--- a/pkg/porter/cnab_test.go
+++ b/pkg/porter/cnab_test.go
@@ -1,11 +1,8 @@
 package porter
 
 import (
-	"os"
-	"path/filepath"
 	"testing"
 
-	"get.porter.sh/porter/pkg/build"
 	"get.porter.sh/porter/pkg/context"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -14,8 +11,7 @@ import (
 func TestSharedOptions_defaultBundleFiles(t *testing.T) {
 	cxt := context.NewTestContext(t)
 
-	pwd, _ := os.Getwd()
-	_, err := cxt.FileSystem.Create(filepath.Join(pwd, "porter.yaml"))
+	_, err := cxt.FileSystem.Create("porter.yaml")
 	require.NoError(t, err)
 
 	opts := sharedOptions{}
@@ -23,7 +19,7 @@ func TestSharedOptions_defaultBundleFiles(t *testing.T) {
 	require.NoError(t, err)
 
 	assert.Equal(t, "porter.yaml", opts.File)
-	assert.Equal(t, build.LOCAL_BUNDLE, opts.CNABFile)
+	assert.Equal(t, ".cnab/bundle.json", opts.CNABFile)
 }
 
 func TestSharedOptions_defaultBundleFiles_AltManifest(t *testing.T) {
@@ -37,16 +33,15 @@ func TestSharedOptions_defaultBundleFiles_AltManifest(t *testing.T) {
 	err := opts.defaultBundleFiles(cxt.Context)
 	require.NoError(t, err)
 
-	assert.Equal(t, filepath.Join("mybun", build.LOCAL_BUNDLE), opts.CNABFile)
+	assert.Equal(t, "mybun/.cnab/bundle.json", opts.CNABFile)
 }
 
 func TestSharedOptions_defaultBundleFiles_CNABFile(t *testing.T) {
 	cxt := context.NewTestContext(t)
 
-	pwd, _ := os.Getwd()
 	// Add existing porter manifest; ensure it isn't processed when cnab-file is spec'd
-	_, err := cxt.FileSystem.Create(filepath.Join(pwd, "porter.yaml"))
-	_, err = cxt.FileSystem.Create(filepath.Join(pwd, "mycnabfile.json"))
+	_, err := cxt.FileSystem.Create("porter.yaml")
+	_, err = cxt.FileSystem.Create("mycnabfile.json")
 	require.NoError(t, err)
 
 	opts := sharedOptions{}
@@ -59,12 +54,10 @@ func TestSharedOptions_defaultBundleFiles_CNABFile(t *testing.T) {
 }
 
 func TestSharedOptions_validateBundleJson(t *testing.T) {
-	pwd, _ := os.Getwd()
-
 	cxt := context.NewTestContext(t)
 
-	cxt.FileSystem.Create("/mybun1/bundle.json")
-	cxt.FileSystem.Create(filepath.Join(pwd, "bundle1.json"))
+	cxt.FileSystem.Create("mybun1/bundle.json")
+	cxt.FileSystem.Create("bundle1.json")
 
 	testcases := []struct {
 		name           string
@@ -73,8 +66,8 @@ func TestSharedOptions_validateBundleJson(t *testing.T) {
 		wantError      string
 	}{
 		{name: "absolute file exists", cnabFile: "/mybun1/bundle.json", wantBundleJson: "/mybun1/bundle.json", wantError: ""},
-		{name: "relative file exists", cnabFile: "bundle1.json", wantBundleJson: filepath.Join(pwd, "bundle1.json"), wantError: ""},
-		{name: "absolute file does not exist", cnabFile: "/mybun2/bundle.json", wantError: "unable to access --cnab-file /mybun2/bundle.json"},
+		{name: "relative file exists", cnabFile: "bundle1.json", wantBundleJson: "/bundle1.json", wantError: ""},
+		{name: "absolute file does not exist", cnabFile: "mybun2/bundle.json", wantError: "unable to access --cnab-file mybun2/bundle.json"},
 		{name: "relative file does not", cnabFile: "bundle2.json", wantError: "unable to access --cnab-file bundle2.json"},
 	}
 
diff --git a/pkg/porter/create_test.go b/pkg/porter/create_test.go
index 8c307e96..ef4e111a 100644
--- a/pkg/porter/create_test.go
+++ b/pkg/porter/create_test.go
@@ -9,7 +9,6 @@ import (
 
 func TestCreate(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	err := p.Create()
 	require.NoError(t, err)
diff --git a/pkg/porter/credentials_test.go b/pkg/porter/credentials_test.go
index 9ebe63b6..52a7dc48 100644
--- a/pkg/porter/credentials_test.go
+++ b/pkg/porter/credentials_test.go
@@ -180,16 +180,15 @@ kool-kreds   2019-06-24`},
 
 func TestGenerateNoCredentialDirectory(t *testing.T) {
 	p := NewTestPorter(t)
-	home := p.TestConfig.TestContext.UseFilesystem()
+	_, home := p.TestConfig.TestContext.UseFilesystem()
 	p.CreateBundleDir()
-	p.TestConfig.TestContext.CopyFile("testdata/bundle.json", filepath.Join(p.BundleDir, "bundle.json"))
+	p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", filepath.Join(p.BundleDir, "bundle.json"))
 
 	// Write credentials to the real file system for this test, not sure if this test is worth keeping
 	fsStore := crud.NewFileSystemStore(home, claim.NewClaimStoreFileExtensions())
 	credStore := credentials.NewCredentialStore(crud.NewBackingStore(fsStore))
 	p.TestCredentials.CredentialStorage.CredentialsStore = credStore
 
-	p.TestConfig.SetupPorterHome()
 	opts := CredentialOptions{
 		Silent: true,
 	}
@@ -221,7 +220,6 @@ func TestGenerateNoCredentialDirectory(t *testing.T) {
 
 func TestGenerateCredentialDirectoryExists(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 	p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json")
 
 	opts := CredentialOptions{
@@ -265,7 +263,6 @@ type CredentialShowTest struct {
 
 func TestShowCredential_NotFound(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 	opts := CredentialShowOptions{
 		PrintOptions: printer.PrintOptions{
 			Format: printer.FormatTable,
@@ -361,7 +358,6 @@ Modified: 2019-06-24
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			p := NewTestPorter(t)
-			p.TestConfig.SetupPorterHome()
 			opts := CredentialShowOptions{
 				PrintOptions: printer.PrintOptions{
 					Format: tc.format,
@@ -396,14 +392,12 @@ type SourceTest struct {
 }
 
 func TestCredentialsEdit(t *testing.T) {
-	os.Setenv("SHELL", "bash")
-	os.Setenv("EDITOR", "vi")
-	os.Setenv(test.ExpectedCommandEnv, "bash -c vi "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
-	defer os.Unsetenv("SHELL")
-	defer os.Unsetenv("EDITOR")
-	defer os.Unsetenv(test.ExpectedCommandEnv)
-
 	p := NewTestPorter(t)
+
+	p.Setenv("SHELL", "bash")
+	p.Setenv("EDITOR", "vi")
+	p.Setenv(test.ExpectedCommandEnv, "bash -c vi "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
+
 	opts := CredentialEditOptions{Name: "kool-kreds"}
 
 	p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
@@ -412,14 +406,10 @@ func TestCredentialsEdit(t *testing.T) {
 }
 
 func TestCredentialsEditEditorPathWithArgument(t *testing.T) {
-	os.Setenv("SHELL", "something")
-	os.Setenv("EDITOR", "C:\\Program Files\\Visual Studio Code\\code.exe --wait")
-	os.Setenv(test.ExpectedCommandEnv, "something -c C:\\Program Files\\Visual Studio Code\\code.exe --wait "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
-	defer os.Unsetenv("SHELL")
-	defer os.Unsetenv("EDITOR")
-	defer os.Unsetenv(test.ExpectedCommandEnv)
-
 	p := NewTestPorter(t)
+	p.Setenv("SHELL", "something")
+	p.Setenv("EDITOR", "C:\\Program Files\\Visual Studio Code\\code.exe --wait")
+	p.Setenv(test.ExpectedCommandEnv, "something -c C:\\Program Files\\Visual Studio Code\\code.exe --wait "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
 	opts := CredentialEditOptions{Name: "kool-kreds"}
 
 	p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
diff --git a/pkg/porter/delete_test.go b/pkg/porter/delete_test.go
index e1d1dee3..dfed2813 100644
--- a/pkg/porter/delete_test.go
+++ b/pkg/porter/delete_test.go
@@ -48,7 +48,6 @@ func TestDeleteInstallation(t *testing.T) {
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			p := NewTestPorter(t)
-			p.TestConfig.SetupPorterHome()
 
 			var err error
 
diff --git a/pkg/porter/generateManifest_test.go b/pkg/porter/generateManifest_test.go
index 15258658..b5ff8e62 100644
--- a/pkg/porter/generateManifest_test.go
+++ b/pkg/porter/generateManifest_test.go
@@ -33,7 +33,6 @@ func TestgenerateInternalManifest(t *testing.T) {
 	}}
 
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 	p.TestConfig.TestContext.AddTestFile("testdata/generateManifest/original.yaml", config.Name)
 
 	for _, tc := range testcases {
diff --git a/pkg/porter/install_test.go b/pkg/porter/install_test.go
index 8fb38fba..54e91149 100644
--- a/pkg/porter/install_test.go
+++ b/pkg/porter/install_test.go
@@ -3,6 +3,7 @@ package porter
 import (
 	"testing"
 
+	"get.porter.sh/porter/pkg/context"
 	"get.porter.sh/porter/pkg/manifest"
 
 	"get.porter.sh/porter/pkg/secrets"
@@ -15,7 +16,6 @@ import (
 
 func TestPorter_applyDefaultOptions(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 	err := p.Create()
 	require.NoError(t, err)
 
@@ -93,6 +93,7 @@ func TestInstallOptions_validateDriver(t *testing.T) {
 		{"invalid driver provided", "dbeug", "", "unsupported driver or driver not found in PATH: dbeug"},
 	}
 
+	cxt := context.NewTestContext(t)
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			opts := InstallOptions{
@@ -102,7 +103,7 @@ func TestInstallOptions_validateDriver(t *testing.T) {
 					},
 				},
 			}
-			err := opts.validateDriver()
+			err := opts.validateDriver(cxt.Context)
 
 			if tc.wantError == "" {
 				require.NoError(t, err)
diff --git a/pkg/porter/lifecycle_integration_test.go b/pkg/porter/lifecycle_integration_test.go
new file mode 100644
index 00000000..7f0c5c87
--- /dev/null
+++ b/pkg/porter/lifecycle_integration_test.go
@@ -0,0 +1,38 @@
+// +build integration
+
+package porter
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestInstallFromTag_ManageFromClaim(t *testing.T) {
+	p := NewTestPorter(t)
+	cacheDir, _ := p.Cache.GetCacheDir()
+	p.TestConfig.TestContext.AddTestDirectory("testdata/cache", cacheDir)
+
+	installOpts := NewInstallOptions()
+	installOpts.Name = "hello"
+	installOpts.Tag = "getporter/porter-hello:v0.1.1"
+	err := installOpts.Validate(nil, p.Porter)
+	require.NoError(t, err, "InstallOptions.Validate failed")
+
+	err = p.InstallBundle(installOpts)
+	require.NoError(t, err, "InstallBundle failed")
+
+	upgradeOpts := NewUpgradeOptions()
+	upgradeOpts.Name = installOpts.Name
+	err = upgradeOpts.Validate(nil, p.Porter)
+
+	err = p.UpgradeBundle(upgradeOpts)
+	require.NoError(t, err, "UpgradeBundle failed")
+
+	uninstallOpts := NewUninstallOptions()
+	uninstallOpts.Name = installOpts.Name
+	err = uninstallOpts.Validate(nil, p.Porter)
+
+	err = p.UninstallBundle(uninstallOpts)
+	require.NoError(t, err, "UninstallBundle failed")
+}
diff --git a/pkg/porter/lifecycle_test.go b/pkg/porter/lifecycle_test.go
index 43d3631b..3303f4c7 100644
--- a/pkg/porter/lifecycle_test.go
+++ b/pkg/porter/lifecycle_test.go
@@ -2,8 +2,6 @@ package porter
 
 import (
 	"errors"
-	"os"
-	"path"
 	"path/filepath"
 	"testing"
 
@@ -16,7 +14,6 @@ import (
 
 func TestBundlePullUpdateOpts_bundleCached(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	home, err := p.TestConfig.GetHomeDir()
 	t.Logf("home dir is: %s", home)
@@ -60,7 +57,6 @@ func TestBundlePullUpdateOpts_pullError(t *testing.T) {
 
 func TestBundlePullUpdateOpts_cacheLies(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	// mess up the cache
 	p.FileSystem.WriteFile("/root/.porter/cache/887e7e65e39277f8744bd00278760b06/cnab/bundle.json", []byte(""), 0644)
@@ -78,7 +74,6 @@ func TestBundlePullUpdateOpts_cacheLies(t *testing.T) {
 
 func TestInstallFromTagIgnoresCurrentBundle(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	err := p.Create()
 	require.NoError(t, err)
@@ -179,10 +174,9 @@ func TestManifestIgnoredWithTag(t *testing.T) {
 		opts := BundleActionOptions{}
 		opts.Tag = "deislabs/kubekahn:latest"
 
-		wd, _ := os.Getwd()
 		// `path.Join(wd...` -> makes cnab.go#defaultBundleFiles#manifestExists `true`
 		// Only when `manifestExists` eq to `true`, default bundle logic will run
-		p.TestConfig.TestContext.AddTestFileContents([]byte(""), path.Join(wd, config.Name))
+		p.TestConfig.TestContext.AddTestFileContents([]byte(""), config.Name)
 		// When execution reach to `readFromFile`, manifest file path will be lost.
 		// So, had to use root manifest file also for error simuation purpose
 		p.TestConfig.TestContext.AddTestFileContents([]byte(""), config.Name)
@@ -191,32 +185,3 @@ func TestManifestIgnoredWithTag(t *testing.T) {
 		require.NoError(t, err, "Validate failed")
 	})
 }
-
-func TestInstallFromTag_ManageFromClaim(t *testing.T) {
-	p := NewTestPorter(t)
-	cacheDir, _ := p.Cache.GetCacheDir()
-	p.TestConfig.TestContext.AddTestDirectory("testdata/cache", cacheDir)
-
-	installOpts := NewInstallOptions()
-	installOpts.Name = "hello"
-	installOpts.Tag = "getporter/porter-hello:v0.1.1"
-	err := installOpts.Validate(nil, p.Porter)
-	require.NoError(t, err, "InstallOptions.Validate failed")
-
-	err = p.InstallBundle(installOpts)
-	require.NoError(t, err, "InstallBundle failed")
-
-	upgradeOpts := NewUpgradeOptions()
-	upgradeOpts.Name = installOpts.Name
-	err = upgradeOpts.Validate(nil, p.Porter)
-
-	err = p.UpgradeBundle(upgradeOpts)
-	require.NoError(t, err, "UpgradeBundle failed")
-
-	uninstallOpts := NewUninstallOptions()
-	uninstallOpts.Name = installOpts.Name
-	err = uninstallOpts.Validate(nil, p.Porter)
-
-	err = p.UninstallBundle(uninstallOpts)
-	require.NoError(t, err, "UninstallBundle failed")
-}
diff --git a/pkg/porter/outputs_test.go b/pkg/porter/outputs_test.go
index 1570e234..e33e0fbc 100644
--- a/pkg/porter/outputs_test.go
+++ b/pkg/porter/outputs_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestPorter_printOutputsTable(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	want := `------------------------------
   Name  Type    Value         
 ------------------------------
@@ -34,7 +34,7 @@ func TestPorter_printOutputsTable(t *testing.T) {
 
 func TestPorter_printDisplayOutput_JSON(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	// Create test claim
 	writeOnly := true
 	b := bundle.Bundle{
@@ -99,7 +99,7 @@ func TestPorter_printDisplayOutput_JSON(t *testing.T) {
 
 func TestPorter_ListOutputs_Truncation(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	fullOutputValue := "this-lengthy-output-will-be-truncated-if-the-output-format-is-table"
 
 	b := bundle.Bundle{
diff --git a/pkg/porter/packages_test.go b/pkg/porter/packages_test.go
index 1912f54e..01c7a094 100644
--- a/pkg/porter/packages_test.go
+++ b/pkg/porter/packages_test.go
@@ -92,7 +92,6 @@ kustomize    A mixin for using the kustomize cli   Don Stewart      https://gith
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			p := NewTestPorter(t)
-			p.TestConfig.SetupPorterHome()
 
 			opts := SearchOptions{
 				PrintOptions: printer.PrintOptions{
@@ -152,7 +151,6 @@ func TestPorter_SearchPackages_Plugins(t *testing.T) {
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			p := NewTestPorter(t)
-			p.TestConfig.SetupPorterHome()
 
 			opts := SearchOptions{
 				PrintOptions: printer.PrintOptions{
diff --git a/pkg/porter/publish_test.go b/pkg/porter/publish_test.go
index 58f01a8d..9d1001ea 100644
--- a/pkg/porter/publish_test.go
+++ b/pkg/porter/publish_test.go
@@ -2,7 +2,6 @@ package porter
 
 import (
 	"os"
-	"path/filepath"
 	"testing"
 
 	"get.porter.sh/porter/pkg/cache"
@@ -15,22 +14,17 @@ import (
 )
 
 func TestPublish_Validate_PorterYamlExists(t *testing.T) {
-
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
-	pwd, err := os.Getwd()
-	require.NoError(t, err, "should not have gotten an error obtaining current working directory")
 
-	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", filepath.Join(pwd, "porter.yaml"))
+	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml")
 	opts := PublishOptions{}
-	err = opts.Validate(p.Context)
+	err := opts.Validate(p.Context)
 	require.NoError(t, err, "validating should not have failed")
-
 }
 
 func TestPublish_Validate_PorterYamlDoesNotExist(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	opts := PublishOptions{}
 	err := opts.Validate(p.Context)
 	require.Error(t, err, "validation should have failed")
@@ -44,17 +38,14 @@ func TestPublish_Validate_PorterYamlDoesNotExist(t *testing.T) {
 
 func TestPublish_Validate_ArchivePath(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
-	pwd, err := p.GetHomeDir()
-	require.NoError(t, err, "should not have gotten an error obtaining current working directory")
 
 	opts := PublishOptions{
-		ArchiveFile: filepath.Join(pwd, "mybuns.tgz"),
+		ArchiveFile: "mybuns.tgz",
 	}
-	err = opts.Validate(p.Context)
-	assert.EqualError(t, err, "unable to access --archive /root/.porter/mybuns.tgz: open /root/.porter/mybuns.tgz: file does not exist")
+	err := opts.Validate(p.Context)
+	assert.EqualError(t, err, "unable to access --archive mybuns.tgz: open /mybuns.tgz: file does not exist")
 
-	p.FileSystem.WriteFile(filepath.Join(pwd, "mybuns.tgz"), []byte("mybuns"), os.ModePerm)
+	p.FileSystem.WriteFile("mybuns.tgz", []byte("mybuns"), os.ModePerm)
 	err = opts.Validate(p.Context)
 	assert.EqualError(t, err, "must provide a value for --tag of the form REGISTRY/bundle:tag")
 
diff --git a/pkg/porter/run_test.go b/pkg/porter/run_test.go
index c231b0d3..89ef7ae2 100644
--- a/pkg/porter/run_test.go
+++ b/pkg/porter/run_test.go
@@ -1,7 +1,6 @@
 package porter
 
 import (
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/config"
@@ -49,10 +48,9 @@ func TestPorter_defaultDebugToOff(t *testing.T) {
 }
 
 func TestPorter_defaultDebugUsesEnvVar(t *testing.T) {
-	os.Setenv(config.EnvDEBUG, "true")
-	defer os.Unsetenv(config.EnvDEBUG)
-
 	p := New() // Don't use the test porter, it has debug on by default
+	p.Setenv(config.EnvDEBUG, "true")
+
 	opts := NewRunOptions(p.Config)
 
 	err := opts.defaultDebug()
diff --git a/pkg/porter/schema_test.go b/pkg/porter/schema_test.go
index e1fbd44e..654585a0 100644
--- a/pkg/porter/schema_test.go
+++ b/pkg/porter/schema_test.go
@@ -12,7 +12,6 @@ import (
 
 func TestPorter_PrintManifestSchema(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	err := p.PrintManifestSchema()
 	require.NoError(t, err)
@@ -27,7 +26,6 @@ func TestPorter_PrintManifestSchema(t *testing.T) {
 
 func TestPorter_ValidateManifestSchema(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
 
 	// Load the default Porter manifest
 	b, err := ioutil.ReadFile("testdata/porter.yaml")
diff --git a/pkg/porter/show_test.go b/pkg/porter/show_test.go
index 7b681e11..e7a38cfc 100644
--- a/pkg/porter/show_test.go
+++ b/pkg/porter/show_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestPorter_ShowBundle(t *testing.T) {
 	p := NewTestPorter(t)
-	p.TestConfig.SetupPorterHome()
+
 	opts := ShowOptions{
 		sharedOptions: sharedOptions{
 			Name: "test",
diff --git a/pkg/porter/version_test.go b/pkg/porter/version_test.go
index bed387ef..847d7777 100644
--- a/pkg/porter/version_test.go
+++ b/pkg/porter/version_test.go
@@ -61,7 +61,6 @@ func TestPrintDebugInfoJsonVersion(t *testing.T) {
 	p := NewTestPorter(t)
 
 	opts := VersionOpts{System: true}
-	p.TestConfig.SetupPorterHome()
 	opts.RawFormat = string(printer.FormatJson)
 	err := opts.Validate()
 	require.Nil(t, err)
@@ -98,7 +97,6 @@ func TestPrintDebugInfoPlainTextVersion(t *testing.T) {
 	p := NewTestPorter(t)
 
 	opts := VersionOpts{System: true}
-	p.TestConfig.SetupPorterHome()
 	err := opts.Validate()
 	require.Nil(t, err)
 	p.PrintVersion(opts)
diff --git a/pkg/runtime/runtime-manifest_test.go b/pkg/runtime/runtime-manifest_test.go
index f8fa8c49..6baf0922 100644
--- a/pkg/runtime/runtime-manifest_test.go
+++ b/pkg/runtime/runtime-manifest_test.go
@@ -2,7 +2,6 @@ package runtime
 
 import (
 	"fmt"
-	"os"
 	"sort"
 	"testing"
 
@@ -20,10 +19,9 @@ import (
 )
 
 func TestResolveMapParam(t *testing.T) {
-	os.Setenv("PERSON", "Ralpha")
-	defer os.Unsetenv("PERSON")
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv("PERSON", "Ralpha")
+
 	m := &manifest.Manifest{
 		Parameters: manifest.ParameterDefinitions{
 			"person": {
@@ -113,7 +111,8 @@ func TestMetadataAvailableForTemplating(t *testing.T) {
 	cxt := context.NewTestContext(t)
 
 	cxt.AddTestFile("testdata/metadata-substitution.yaml", config.Name)
-	m, _ := manifest.LoadManifestFrom(cxt.Context, config.Name)
+	m, err := manifest.LoadManifestFrom(cxt.Context, config.Name)
+	require.NoError(t, err, "LoadManifestFrom")
 	rm := NewRuntimeManifest(cxt.Context, claim.ActionInstall, m)
 
 	before, _ := yaml.Marshal(m.Install[0])
@@ -278,10 +277,8 @@ func TestResolveArrayUnknown(t *testing.T) {
 }
 
 func TestResolveArray(t *testing.T) {
-	os.Setenv("PERSON", "Ralpha")
-	defer os.Unsetenv("PERSON")
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv("PERSON", "Ralpha")
 	m := &manifest.Manifest{
 		Parameters: manifest.ParameterDefinitions{
 			"person": {
@@ -308,12 +305,10 @@ func TestResolveArray(t *testing.T) {
 }
 
 func TestResolveSensitiveParameter(t *testing.T) {
-	os.Setenv("SENSITIVE_PARAM", "deliciou$dubonnet")
-	defer os.Unsetenv("SENSITIVE_PARAM")
-	os.Setenv("REGULAR_PARAM", "regular param value")
-	defer os.Unsetenv("REGULAR_PARAM")
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv("SENSITIVE_PARAM", "deliciou$dubonnet")
+	cxt.Setenv("REGULAR_PARAM", "regular param value")
+
 	m := &manifest.Manifest{
 		Parameters: manifest.ParameterDefinitions{
 			"sensitive_param": {
@@ -353,10 +348,9 @@ func TestResolveSensitiveParameter(t *testing.T) {
 }
 
 func TestResolveCredential(t *testing.T) {
-	os.Setenv("PASSWORD", "deliciou$dubonnet")
-	defer os.Unsetenv("PASSWORD")
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv("PASSWORD", "deliciou$dubonnet")
+
 	m := &manifest.Manifest{
 		Credentials: manifest.CredentialDefinitions{
 			"password": {
@@ -390,17 +384,11 @@ func TestResolveCredential(t *testing.T) {
 }
 
 func TestResolveStep_DependencyOutput(t *testing.T) {
-	os.Setenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT", "password")
-	os.Setenv("PORTER_MYSQL_ROOT_PASSWORD_DEP_OUTPUT", "mysql-password")
-	defer func() {
-		os.Unsetenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT")
-		os.Unsetenv("PORTER_MYSQL_ROOT_PASSWORD_DEP_OUTPUT")
-	}()
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv("PORTER_MYSQL_PASSWORD_DEP_OUTPUT", "password")
+	cxt.Setenv("PORTER_MYSQL_ROOT_PASSWORD_DEP_OUTPUT", "mysql-password")
 
 	m := &manifest.Manifest{
-
 		Dependencies: []manifest.Dependency{
 			{
 				Name: "mysql",
@@ -478,7 +466,7 @@ func TestResolveInMainDict(t *testing.T) {
 
 	installStep := rm.Install[0]
 
-	os.Setenv("COMMAND", "echo hello world")
+	rm.Setenv("COMMAND", "echo hello world")
 	err = rm.ResolveStep(installStep)
 	assert.NoError(t, err)
 
@@ -505,7 +493,7 @@ func TestResolveSliceWithAMap(t *testing.T) {
 
 	installStep := rm.Install[0]
 
-	os.Setenv("COMMAND", "echo hello world")
+	rm.Setenv("COMMAND", "echo hello world")
 	err = rm.ResolveStep(installStep)
 	assert.NoError(t, err)
 
@@ -1016,11 +1004,11 @@ func TestResolveImageRelocationNoMatch(t *testing.T) {
 }
 
 func TestResolveStepEncoding(t *testing.T) {
+	cxt := context.NewTestContext(t)
+
 	wantValue := `{"test":"value"}`
-	os.Setenv("TEST", wantValue)
-	defer os.Unsetenv("TEST")
+	cxt.Setenv("TEST", wantValue)
 
-	cxt := context.NewTestContext(t)
 	m := &manifest.Manifest{
 		Parameters: manifest.ParameterDefinitions{
 			"test": {Name: "test"},
@@ -1043,10 +1031,9 @@ func TestResolveStepEncoding(t *testing.T) {
 }
 
 func TestResolveInstallationName(t *testing.T) {
-	os.Setenv(config.EnvInstallationName, "mybun")
-	defer os.Unsetenv(config.EnvInstallationName)
-
 	cxt := context.NewTestContext(t)
+	cxt.Setenv(config.EnvInstallationName, "mybun")
+
 	m := &manifest.Manifest{}
 	rm := NewRuntimeManifest(cxt.Context, claim.ActionInstall, m)
 
diff --git a/pkg/storage/manager_test.go b/pkg/storage/manager_test.go
index 1bba5b72..c111446a 100644
--- a/pkg/storage/manager_test.go
+++ b/pkg/storage/manager_test.go
@@ -11,7 +11,7 @@ import (
 	"github.com/cnabio/cnab-go/credentials"
 	"github.com/cnabio/cnab-go/schema"
 	"github.com/cnabio/cnab-go/utils/crud"
-	"github.com/hashicorp/go-hclog"
+	hclog "github.com/hashicorp/go-hclog"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -116,7 +116,7 @@ func TestManager_MigrateClaims(t *testing.T) {
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
 			config := config.NewTestConfig(t)
-			home := config.TestContext.UseFilesystem()
+			_, home := config.TestContext.UseFilesystem()
 			config.SetHomeDir(home)
 			defer config.TestContext.Cleanup()
 
@@ -151,7 +151,7 @@ func TestManager_MigrateClaims(t *testing.T) {
 
 	t.Run("no migration", func(t *testing.T) {
 		config := config.NewTestConfig(t)
-		home := config.TestContext.UseFilesystem()
+		_, home := config.TestContext.UseFilesystem()
 		config.SetHomeDir(home)
 		defer config.TestContext.Cleanup()
 
@@ -171,7 +171,7 @@ func TestManager_MigrateClaims(t *testing.T) {
 
 func TestManager_NoMigrationEmptyHome(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -193,7 +193,7 @@ func TestManager_NoMigrationEmptyHome(t *testing.T) {
 
 func TestManager_MigrateInstallClaim(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -224,7 +224,7 @@ func TestManager_MigrateInstallClaim(t *testing.T) {
 
 func TestManager_MigrateUpgradeClaim(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
@@ -285,7 +285,7 @@ func TestManager_ShouldMigrateCredentials(t *testing.T) {
 
 func TestManager_MigrateCredentials(t *testing.T) {
 	config := config.NewTestConfig(t)
-	home := config.TestContext.UseFilesystem()
+	_, home := config.TestContext.UseFilesystem()
 	config.SetHomeDir(home)
 	defer config.TestContext.Cleanup()
 
diff --git a/pkg/test/helper.go b/pkg/test/helper.go
index 4e431a1a..c89a4610 100644
--- a/pkg/test/helper.go
+++ b/pkg/test/helper.go
@@ -14,6 +14,7 @@ const (
 
 func TestMainWithMockedCommandHandlers(m *testing.M) {
 	// Fake out executing a command
+	// It's okay to use os.LookupEnv here because it's running in it's own process, and won't impact running tests in parallel.
 	if _, mockCommand := os.LookupEnv(MockedCommandEnv); mockCommand {
 		if expectedCmdEnv, doAssert := os.LookupEnv(ExpectedCommandEnv); doAssert {
 			gotCmd := strings.Join(os.Args[1:len(os.Args)], " ")
diff --git a/tests/archive_test.go b/tests/archive_test.go
index 6ae3a845..a5625b7e 100644
--- a/tests/archive_test.go
+++ b/tests/archive_test.go
@@ -3,8 +3,8 @@
 package tests
 
 import (
+	"fmt"
 	"os"
-	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -13,17 +13,20 @@ import (
 )
 
 func TestArchive(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "../build/testdata/bundles/mysql"), ".")
+	bundleName := p.AddTestBundleDir("../build/testdata/bundles/mysql", true)
+	tag := fmt.Sprintf("localhost:5000/%s:v0.1.3", bundleName)
 
 	// Currently, archive requires the bundle to already be published.
 	// https://github.com/getporter/porter/issues/697
 	publishOpts := porter.PublishOptions{}
-	publishOpts.Tag = "localhost:5000/mysql:v0.1.3"
+	publishOpts.Tag = tag
 	err := publishOpts.Validate(p.Context)
 	require.NoError(p.T(), err, "validation of publish opts for bundle failed")
 
@@ -32,7 +35,7 @@ func TestArchive(t *testing.T) {
 
 	// Archive bundle
 	archiveOpts := porter.ArchiveOptions{}
-	archiveOpts.Tag = "localhost:5000/mysql:v0.1.3"
+	archiveOpts.Tag = tag
 	err = archiveOpts.Validate([]string{"mybuns.tgz"}, p.Porter)
 	require.NoError(p.T(), err, "validation of archive opts for bundle failed")
 
@@ -47,7 +50,7 @@ func TestArchive(t *testing.T) {
 	publishFromArchiveOpts := porter.PublishOptions{
 		ArchiveFile: "mybuns.tgz",
 		BundlePullOptions: porter.BundlePullOptions{
-			Tag: "localhost:5000/mysql-from-archive:v0.1.2",
+			Tag: fmt.Sprintf("localhost:5000/archived-%s:v0.1.3", bundleName),
 		},
 	}
 	err = publishFromArchiveOpts.Validate(p.Context)
diff --git a/tests/claim_migration_test.go b/tests/claim_migration_test.go
index 77a2f8ec..784b408d 100644
--- a/tests/claim_migration_test.go
+++ b/tests/claim_migration_test.go
@@ -14,6 +14,8 @@ import (
 // Do a migration. This also checks for any problems with our
 // connection handling which can result in panics :-)
 func TestClaimMigration_List(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -23,14 +25,10 @@ func TestClaimMigration_List(t *testing.T) {
 	require.NoError(t, err, "GetHomeDir failed")
 	claimsDir := filepath.Join(home, "claims")
 
-	// Remove any rando stuff copied from the dev bin, you won't find this in CI but a local dev run may have it
-	// Not checking for an error, since the files won't be there on CI
-	p.FileSystem.RemoveAll(claimsDir)
-	p.FileSystem.Remove(filepath.Join(home, "schema.json"))
-
 	// Create unmigrated claim data
 	p.FileSystem.Mkdir(claimsDir, 0755)
 	p.AddTestFile(filepath.Join("../pkg/storage/testdata/claims", "upgraded.json"), filepath.Join(home, "claims", "mybun.json"))
+	p.FileSystem.Remove(filepath.Join(home, "schema.json"))
 
 	err = p.MigrateStorage()
 	require.NoError(t, err, "MigrateStorage failed")
diff --git a/tests/dependencies_test.go b/tests/dependencies_test.go
index 13694b16..d055e9fc 100644
--- a/tests/dependencies_test.go
+++ b/tests/dependencies_test.go
@@ -3,11 +3,10 @@
 package tests
 
 import (
-	"math/rand"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
-	"time"
 
 	"get.porter.sh/porter/pkg/porter"
 	"github.com/cnabio/cnab-go/claim"
@@ -16,6 +15,8 @@ import (
 )
 
 func TestDependenciesLifecycle(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -31,21 +32,17 @@ func TestDependenciesLifecycle(t *testing.T) {
 	uninstallWordpressBundle(p, namespace)
 }
 
-func randomString(len int) string {
-	rand.Seed(time.Now().UnixNano())
-	bytes := make([]byte, len)
-	for i := 0; i < len; i++ {
-		//A=97 and Z = 97+25
-		bytes[i] = byte(97 + rand.Intn(25))
-	}
-	return string(bytes)
-}
-
 func publishMySQLBundle(p *porter.TestPorter) {
-	mysqlBundlePath := filepath.Join(p.TestDir, "../build/testdata/bundles/mysql")
-	err := os.Chdir(mysqlBundlePath)
-	require.NoError(p.T(), err, "could not change into the test mysql bundle directory")
-	defer os.Chdir(p.BundleDir)
+	bunDir, err := ioutil.TempDir("", "porter-mysql")
+	require.NoError(p.T(), err, "could not create temp directory to publish the mysql bundle")
+	defer os.RemoveAll(bunDir)
+
+	// Rebuild the bundle from a temp directory so that we don't modify the source directory
+	// and leave modified files around.
+	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "../build/testdata/bundles/mysql"), bunDir)
+	pwd := p.Getwd()
+	p.Chdir(bunDir)
+	defer p.Chdir(pwd)
 
 	publishOpts := porter.PublishOptions{}
 	err = publishOpts.Validate(p.Context)
@@ -62,7 +59,7 @@ func installWordpressBundle(p *porter.TestPorter) (namespace string) {
 	// Install the bundle that has dependencies
 	p.CopyDirectory(filepath.Join(p.TestDir, "../build/testdata/bundles/wordpress"), ".", false)
 
-	namespace = randomString(10)
+	namespace = p.RandomString(10)
 	installOpts := porter.NewInstallOptions()
 	installOpts.CredentialIdentifiers = []string{"ci"}
 	installOpts.Params = []string{
diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go
index 36b984d6..c047fb09 100644
--- a/tests/e2e/main_test.go
+++ b/tests/e2e/main_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 
 	"get.porter.sh/porter/pkg/context"
-	"github.com/carolynvs/magex/shx"
+	"github.com/magefile/mage/sh"
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
 )
@@ -66,14 +66,13 @@ func (t Test) RequirePorter(args ...string) {
 // Run a porter command, printing stderr when the command fails.
 func (t Test) PorterE(args ...string) error {
 	args = append(args, "--debug")
-	err := shx.RunE(t.PorterPath, args...)
+	p := sh.Command(t.PorterPath, args...).Stdout(nil)
+	p.Cmd.Env = []string{"PORTER_HOME=" + t.PorterHomeDir}
+	_, _, err := p.Run()
 	return errors.Wrapf(err, "error running porter %s", args)
 }
 
 func (t Test) Teardown() {
-	t.T.Log("Unsetting PORTER_HOME")
-	os.Unsetenv("PORTER_HOME")
-
 	t.T.Log("Removing temp test PORTER_HOME")
 	os.RemoveAll(t.PorterHomeDir)
 
@@ -116,6 +115,5 @@ func (t *Test) createPorterHome() error {
 		return errors.Wrap(err, "could not copy mixins/ into test PORTER_HOME")
 	}
 
-	os.Setenv("PORTER_HOME", t.PorterHomeDir)
 	return nil
 }
diff --git a/tests/install_test.go b/tests/install_test.go
index 730c933d..6640222e 100644
--- a/tests/install_test.go
+++ b/tests/install_test.go
@@ -3,7 +3,7 @@
 package tests
 
 import (
-	"os"
+	"fmt"
 	"path/filepath"
 	"testing"
 
@@ -13,6 +13,8 @@ import (
 )
 
 func TestInstall_relativePathPorterHome(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest() // This creates a temp porter home directory
 	defer p.CleanupIntegrationTest()
@@ -21,15 +23,12 @@ func TestInstall_relativePathPorterHome(t *testing.T) {
 	// Crux for this test: change Porter's home dir to a relative path
 	homeDir, err := p.Config.GetHomeDir()
 	require.NoError(t, err)
-	curDir, err := os.Getwd()
-	require.NoError(t, err)
-	relDir, err := filepath.Rel(curDir, homeDir)
+	relDir, err := filepath.Rel(p.Getwd(), homeDir)
 	require.NoError(t, err)
 	p.SetHomeDir(relDir)
 
 	// Bring in a porter manifest that has an install action defined
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-custom-action.yaml"), "porter.yaml")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/helpers.sh"), "helpers.sh")
+	p.AddTestBundleDir("testdata/bundles/bundle-with-custom-action", true)
 
 	installOpts := porter.NewInstallOptions()
 	err = installOpts.Validate([]string{}, p.Porter)
@@ -41,15 +40,14 @@ func TestInstall_relativePathPorterHome(t *testing.T) {
 }
 
 func TestInstall_fileParam(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-file-params.yaml"), "porter.yaml")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/helpers.sh"), "helpers.sh")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/myfile"), "./myfile")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/myotherfile"), "./myotherfile")
+	p.AddTestBundleDir("testdata/bundles/bundle-with-file-params", false)
 
 	installOpts := porter.NewInstallOptions()
 	installOpts.Params = []string{"myfile=./myfile"}
@@ -75,18 +73,18 @@ func TestInstall_fileParam(t *testing.T) {
 }
 
 func TestInstall_fileParam_fromTag(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-file-params.yaml"), "porter.yaml")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/helpers.sh"), "helpers.sh")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/myfile"), "./myfile")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/myotherfile"), "./myotherfile")
+	bundleName := p.AddTestBundleDir("testdata/bundles/bundle-with-file-params", true)
+	tag := fmt.Sprintf("localhost:5000/%s:v0.1.0", bundleName)
 
 	publishOpts := porter.PublishOptions{}
-	publishOpts.Tag = "localhost:5000/file-param:v0.1.0"
+	publishOpts.Tag = tag
 	err := publishOpts.Validate(p.Context)
 	require.NoError(t, err, "validation of publish opts for bundle failed")
 
@@ -94,7 +92,7 @@ func TestInstall_fileParam_fromTag(t *testing.T) {
 	require.NoError(t, err, "publish of bundle failed")
 
 	installOpts := porter.NewInstallOptions()
-	installOpts.Tag = "localhost:5000/file-param:v0.1.0"
+	installOpts.Tag = tag
 	installOpts.Params = []string{"myfile=./myfile"}
 	installOpts.ParameterSets = []string{filepath.Join(p.TestDir, "testdata/parameter-set-with-file-param.json")}
 
@@ -106,12 +104,14 @@ func TestInstall_fileParam_fromTag(t *testing.T) {
 }
 
 func TestInstall_withDockerignore(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "testdata/bundles/outputs-example"), ".")
+	p.AddTestBundleDir("testdata/bundles/outputs-example", true)
 
 	// Create .dockerignore file which ignores the helpers script
 	err := p.FileSystem.WriteFile(".dockerignore", []byte("helpers.sh"), 0644)
diff --git a/tests/invoke_test.go b/tests/invoke_test.go
index 7cc49ea2..003b0591 100644
--- a/tests/invoke_test.go
+++ b/tests/invoke_test.go
@@ -3,7 +3,6 @@
 package tests
 
 import (
-	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -13,6 +12,8 @@ import (
 )
 
 func TestInvokeCustomAction(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -22,8 +23,7 @@ func TestInvokeCustomAction(t *testing.T) {
 	err := p.Create()
 	require.NoError(t, err)
 
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-custom-action.yaml"), "porter.yaml")
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/helpers.sh"), "helpers.sh")
+	p.AddTestBundleDir("testdata/bundles/bundle-with-custom-action", true)
 
 	installOpts := porter.NewInstallOptions()
 	err = installOpts.Validate([]string{}, p.Porter)
diff --git a/tests/outputs_test.go b/tests/outputs_test.go
index 505f1fbe..75660b7d 100644
--- a/tests/outputs_test.go
+++ b/tests/outputs_test.go
@@ -4,7 +4,6 @@ package tests
 
 import (
 	"fmt"
-	"path/filepath"
 	"testing"
 
 	"get.porter.sh/porter/pkg/porter"
@@ -14,6 +13,8 @@ import (
 )
 
 func TestExecOutputs(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -74,11 +75,7 @@ func installExecOutputsBundle(p *porter.TestPorter) {
 	err := p.Create()
 	require.NoError(p.T(), err)
 
-	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "../examples/exec-outputs"), ".")
-
-	x := p.Context.FileSystem
-	files, _ := x.ReadDir(".")
-	fmt.Println(files)
+	p.AddTestBundleDir("../examples/exec-outputs", true)
 
 	installOpts := porter.NewInstallOptions()
 	err = installOpts.Validate([]string{}, p.Porter)
@@ -97,12 +94,14 @@ func invokeExecOutputsBundle(p *porter.TestPorter, action string) {
 }
 
 func TestStepLevelAndBundleLevelOutputs(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "testdata/bundles/outputs-example"), ".")
+	p.AddTestBundleDir("testdata/bundles/outputs-example", true)
 
 	// Install the bundle
 	// A step-level output will be used during this action
diff --git a/tests/pull_test.go b/tests/pull_test.go
index 2e445dbb..9c031cce 100644
--- a/tests/pull_test.go
+++ b/tests/pull_test.go
@@ -11,6 +11,8 @@ import (
 )
 
 func TestPull_ContentDigestMissing(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
diff --git a/tests/rebuild_test.go b/tests/rebuild_test.go
index 65c8f8de..415347aa 100644
--- a/tests/rebuild_test.go
+++ b/tests/rebuild_test.go
@@ -3,7 +3,6 @@
 package tests
 
 import (
-	"path/filepath"
 	"strings"
 	"testing"
 
@@ -17,6 +16,8 @@ import (
 )
 
 func TestRebuild_InstallNewBundle(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -35,6 +36,8 @@ func TestRebuild_InstallNewBundle(t *testing.T) {
 }
 
 func TestRebuild_UpgradeModifiedBundle(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
@@ -74,13 +77,15 @@ func TestRebuild_UpgradeModifiedBundle(t *testing.T) {
 }
 
 func TestRebuild_GenerateCredentialsNewBundle(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
 	// Create a bundle that uses credentials
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-credentials.yaml"), "porter.yaml")
+	p.AddTestBundleDir("testdata/bundles/bundle-with-credentials", true)
 
 	credentialOptions := porter.CredentialOptions{}
 	credentialOptions.Silent = true
@@ -94,13 +99,15 @@ func TestRebuild_GenerateCredentialsNewBundle(t *testing.T) {
 }
 
 func TestRebuild_GenerateCredentialsExistingBundle(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
 	// Create a bundle that uses credentials
-	p.TestConfig.TestContext.AddTestFile(filepath.Join(p.TestDir, "testdata/bundle-with-credentials.yaml"), "porter.yaml")
+	p.AddTestBundleDir("testdata/bundles/bundle-with-credentials", true)
 
 	credentialOptions := porter.CredentialOptions{}
 	credentialOptions.Silent = true
diff --git a/tests/suppress_output_test.go b/tests/suppress_output_test.go
index 818d19dc..5a46524a 100644
--- a/tests/suppress_output_test.go
+++ b/tests/suppress_output_test.go
@@ -3,7 +3,6 @@
 package tests
 
 import (
-	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -12,12 +11,14 @@ import (
 )
 
 func TestSuppressOutput(t *testing.T) {
+	t.Parallel()
+
 	p := porter.NewTestPorter(t)
 	p.SetupIntegrationTest()
 	defer p.CleanupIntegrationTest()
 	p.Debug = false
 
-	p.TestConfig.TestContext.AddTestDirectory(filepath.Join(p.TestDir, "testdata/bundles/suppressed-output-example"), ".")
+	p.AddTestBundleDir("testdata/bundles/suppressed-output-example", true)
 
 	// Install (Output suppressed)
 	installOpts := porter.NewInstallOptions()
diff --git a/tests/testdata/helpers.sh b/tests/testdata/bundles/bundle-with-credentials/helpers.sh
similarity index 100%
rename from tests/testdata/helpers.sh
rename to tests/testdata/bundles/bundle-with-credentials/helpers.sh
diff --git a/tests/testdata/bundle-with-credentials.yaml b/tests/testdata/bundles/bundle-with-credentials/porter.yaml
similarity index 100%
rename from tests/testdata/bundle-with-credentials.yaml
rename to tests/testdata/bundles/bundle-with-credentials/porter.yaml
diff --git a/tests/testdata/bundles/bundle-with-custom-action/helpers.sh b/tests/testdata/bundles/bundle-with-custom-action/helpers.sh
new file mode 100755
index 00000000..da60b124
--- /dev/null
+++ b/tests/testdata/bundles/bundle-with-custom-action/helpers.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+install() {
+  echo Hello World
+}
+
+upgrade() {
+  echo World 2.0
+}
+
+uninstall() {
+  echo Goodbye World
+}
+
+zombies() {
+  echo oh noes my brains
+}
+
+dump-myfile() {
+  cat /root/myfile
+}
+
+# Call the requested function and pass the arguments as-is
+"$@"
diff --git a/tests/testdata/bundle-with-custom-action.yaml b/tests/testdata/bundles/bundle-with-custom-action/porter.yaml
similarity index 100%
rename from tests/testdata/bundle-with-custom-action.yaml
rename to tests/testdata/bundles/bundle-with-custom-action/porter.yaml
diff --git a/tests/testdata/bundles/bundle-with-file-params/helpers.sh b/tests/testdata/bundles/bundle-with-file-params/helpers.sh
new file mode 100755
index 00000000..da60b124
--- /dev/null
+++ b/tests/testdata/bundles/bundle-with-file-params/helpers.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+install() {
+  echo Hello World
+}
+
+upgrade() {
+  echo World 2.0
+}
+
+uninstall() {
+  echo Goodbye World
+}
+
+zombies() {
+  echo oh noes my brains
+}
+
+dump-myfile() {
+  cat /root/myfile
+}
+
+# Call the requested function and pass the arguments as-is
+"$@"
diff --git a/tests/testdata/myfile b/tests/testdata/bundles/bundle-with-file-params/myfile
similarity index 100%
rename from tests/testdata/myfile
rename to tests/testdata/bundles/bundle-with-file-params/myfile
diff --git a/tests/testdata/myotherfile b/tests/testdata/bundles/bundle-with-file-params/myotherfile
similarity index 100%
rename from tests/testdata/myotherfile
rename to tests/testdata/bundles/bundle-with-file-params/myotherfile
diff --git a/tests/testdata/bundle-with-file-params.yaml b/tests/testdata/bundles/bundle-with-file-params/porter.yaml
similarity index 100%
rename from tests/testdata/bundle-with-file-params.yaml
rename to tests/testdata/bundles/bundle-with-file-params/porter.yaml
diff --git a/tests/testdata/bundles/suppressed-output-example/helpers.sh b/tests/testdata/bundles/suppressed-output-example/helpers.sh
old mode 100644
new mode 100755
diff --git a/tests/uninstall_test.go b/tests/uninstall_test.go
index 1879a301..954a125d 100644
--- a/tests/uninstall_test.go
+++ b/tests/uninstall_test.go
@@ -4,7 +4,6 @@ package tests
 
 import (
 	"fmt"
-	"os"
 	"testing"
 
 	"get.porter.sh/porter/pkg/porter"
@@ -12,6 +11,8 @@ import (
 )
 
 func TestUninstall_DeleteInstallation(t *testing.T) {
+	t.Parallel()
+
 	testcases := []struct {
 		name                string
 		notInstalled        bool
@@ -77,9 +78,9 @@ func TestUninstall_DeleteInstallation(t *testing.T) {
 				driver.Filepath = "testdata/drivers/exit-driver-fail.sh"
 			}
 
-			path := os.Getenv("PATH")
+			path := p.Getenv("PATH")
 			dir := p.AddTestDriver(driver)
-			defer os.Setenv("PATH", path)
+			defer p.Setenv("PATH", path)
 			defer p.TestConfig.TestContext.FileSystem.RemoveAll(dir)
 
 			// Uninstall bundle with custom command driver