Repository: buildpack/pack
Pull request #385 (2019-11-15 21:45:22)

Refactor/stringset
Adds a `stringset` package to simplify some set-related logic, especially for mixins

- Also added a few small auxiliary refactors in tests, etc.


Patch:
diff --git a/build.go b/build.go
index 20a115b6..022a922e 100644
--- a/build.go
+++ b/build.go
@@ -23,6 +23,7 @@ import (
 	"github.com/buildpack/pack/internal/dist"
 	"github.com/buildpack/pack/internal/paths"
 	"github.com/buildpack/pack/internal/stack"
+	"github.com/buildpack/pack/internal/stringset"
 	"github.com/buildpack/pack/internal/style"
 )
 
@@ -214,23 +215,10 @@ func assembleAvailableMixins(buildMixins, runMixins []string) []string {
 	//    Buildpack requires: [A, B]
 	//    Match? No
 
-	var common, buildOnly, runOnly []string
-	bMixins := map[string]interface{}{}
+	buildOnly := stack.FindStageMixins(buildMixins, "build")
+	runOnly := stack.FindStageMixins(runMixins, "run")
+	_, _, common := stringset.Compare(buildMixins, runMixins)
 
-	for _, m := range buildMixins {
-		if strings.HasPrefix(m, "build:") {
-			buildOnly = append(buildOnly, m)
-		}
-		bMixins[m] = nil
-	}
-	for _, m := range runMixins {
-		if strings.HasPrefix(m, "run:") {
-			runOnly = append(runOnly, m)
-		}
-		if _, ok := bMixins[m]; ok {
-			common = append(common, m)
-		}
-	}
 	return append(common, append(buildOnly, runOnly...)...)
 }
 
diff --git a/go.mod b/go.mod
index bc131b5b..cbeca02e 100644
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,10 @@ require (
 	github.com/dgodd/dockerdial v1.0.1
 	github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
 	github.com/docker/go-connections v0.4.0
-	github.com/envoyproxy/go-control-plane v0.6.9 // indirect
-	github.com/gogo/googleapis v1.1.0 // indirect
 	github.com/golang/mock v1.3.1
 	github.com/google/go-cmp v0.3.0
 	github.com/google/go-containerregistry v0.0.0-20191018211754-b77a90c667af
 	github.com/heroku/color v0.0.6
-	github.com/lyft/protoc-gen-validate v0.0.13 // indirect
 	github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e
 	github.com/onsi/gomega v1.7.0
 	github.com/pkg/errors v0.8.1
diff --git a/go.sum b/go.sum
index 63805c36..c6eb4be3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,14 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 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/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
+github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
 github.com/apex/log v1.1.2-0.20190827100214-baa5455d1012 h1:r9k3B0K539tmbDOdyCIuz/6qtn8q+lp+qvEStcFUIdM=
@@ -27,38 +31,47 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgodd/dockerdial v1.0.1 h1:6XixGQxeytsIdVsbI4y+EHuv8QcxxH6ySJZLkm3jk5o=
 github.com/dgodd/dockerdial v1.0.1/go.mod h1:MJmK56eTylC3KU7DR6bc3k9jmbpURO8rDOsGbfj+c6o=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017 h1:2HQmlpI3yI9deH18Q6xiSOIjXD4sLI55Y/gfpa8/558=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v0.0.0-20180327202408-83389a148052/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
 github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20181027010111-b8e87cfdad8d/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 h1:Cvj7S8I4Xpx78KAl6TwTmMHuHlZ/0SM60NUneGJQ7IE=
 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
 github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/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/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-containerregistry v0.0.0-20191018211754-b77a90c667af h1:gCcst+zXdvatJYFDtPd3C3VNEGcoFOOMwFydnFIfnF0=
 github.com/google/go-containerregistry v0.0.0-20191018211754-b77a90c667af/go.mod h1:9kIomAeXUmwhqeYS2zoEuQ0sc2GOVmNW7t3y9aNQL1o=
@@ -68,12 +81,17 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
+github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0=
 github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -83,12 +101,14 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 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=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -100,6 +120,7 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e h1:Qa6dnn8DlasdXRnacluu8HzPts0S1I9zvvUPDbBnXFI=
 github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -107,31 +128,39 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
+github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA=
 github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
 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/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -142,6 +171,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
 github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
@@ -169,11 +199,13 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -189,9 +221,11 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/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/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=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -207,20 +241,26 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8 h1:x913Lq/RebkvUmRSdQ8MNb0GZKn+SR1ESfoetcQSeak=
 google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
 google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/dist/buildpack_descriptor.go b/internal/dist/buildpack_descriptor.go
index 9cacf036..76fc086f 100644
--- a/internal/dist/buildpack_descriptor.go
+++ b/internal/dist/buildpack_descriptor.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/buildpack/pack/internal/api"
+	"github.com/buildpack/pack/internal/stringset"
 	"github.com/buildpack/pack/internal/style"
 )
 
@@ -20,12 +21,7 @@ func (b *BuildpackDescriptor) EscapedID() string {
 	return strings.Replace(b.Info.ID, "/", "_", -1)
 }
 
-func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins []string, validateRunOnlyMixins bool) error {
-	avail := map[string]interface{}{}
-	for _, m := range providedMixins {
-		avail[m] = nil
-	}
-
+func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins []string, validateRunStageMixins bool) error {
 	if len(b.Stacks) == 0 {
 		return nil // Order buildpack, no validation required
 	}
@@ -35,15 +31,19 @@ func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins
 		return err
 	}
 
-	var missing []string
-	for _, m := range bpMixins {
-		ignored := !validateRunOnlyMixins && strings.HasPrefix(m, "run:")
-		if _, ok := avail[m]; !ignored && !ok {
-			missing = append(missing, m)
+	if !validateRunStageMixins {
+		var filtered []string
+		for _, m := range bpMixins {
+			if !strings.HasPrefix(m, "run:") {
+				filtered = append(filtered, m)
+			}
 		}
+		bpMixins = filtered
 	}
-	sort.Strings(missing)
+
+	_, missing, _ := stringset.Compare(providedMixins, bpMixins)
 	if len(missing) > 0 {
+		sort.Strings(missing)
 		return fmt.Errorf("buildpack %s requires missing mixin(s): %s", style.Symbol(b.Info.FullName()), strings.Join(missing, ", "))
 	}
 	return nil
diff --git a/internal/stack/mixins.go b/internal/stack/mixins.go
index 70e7d163..dbcda6af 100644
--- a/internal/stack/mixins.go
+++ b/internal/stack/mixins.go
@@ -5,67 +5,51 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/buildpack/pack/internal/stringset"
 	"github.com/buildpack/pack/internal/style"
 )
 
 const MixinsLabel = "io.buildpacks.stack.mixins"
 
 func ValidateMixins(buildImageName string, buildImageMixins []string, runImageName string, runImageMixins []string) error {
-	bMixins, err := mixinSet(buildImageMixins, buildImageName, false)
-	if err != nil {
-		return err
+	if invalid := FindStageMixins(buildImageMixins, "run"); len(invalid) > 0 {
+		sort.Strings(invalid)
+		return fmt.Errorf("%s contains run-only mixin(s): %s", style.Symbol(buildImageName), strings.Join(invalid, ", "))
 	}
 
-	rMixins, err := mixinSet(runImageMixins, runImageName, true)
-	if err != nil {
-		return err
+	if invalid := FindStageMixins(runImageMixins, "build"); len(invalid) > 0 {
+		sort.Strings(invalid)
+		return fmt.Errorf("%s contains build-only mixin(s): %s", style.Symbol(runImageName), strings.Join(invalid, ", "))
 	}
 
-	if err := validateMissing(rMixins, bMixins, runImageName); err != nil {
-		return err
+	buildImageMixins = removeStageMixins(buildImageMixins, "build")
+	runImageMixins = removeStageMixins(runImageMixins, "run")
+
+	_, missing, _ := stringset.Compare(runImageMixins, buildImageMixins)
+
+	if len(missing) > 0 {
+		sort.Strings(missing)
+		return fmt.Errorf("%s missing required mixin(s): %s", style.Symbol(runImageName), strings.Join(missing, ", "))
 	}
 	return nil
 }
 
-func mixinSet(mixins []string, imageName string, run bool) (map[string]interface{}, error) {
-	set := map[string]interface{}{}
-	invalidPrefix := "run"
-	excludePrefix := "build"
-	if run {
-		invalidPrefix = "build"
-		excludePrefix = "run"
-	}
-
+func FindStageMixins(mixins []string, stage string) []string {
 	var invalid []string
 	for _, m := range mixins {
-		if strings.HasPrefix(m, invalidPrefix+":") {
+		if strings.HasPrefix(m, stage+":") {
 			invalid = append(invalid, m)
-			continue
-		}
-		if strings.HasPrefix(m, excludePrefix+":") {
-			continue
 		}
-		set[m] = nil
 	}
-
-	if len(invalid) > 0 {
-		sort.Strings(invalid)
-		return nil, fmt.Errorf("%s contains %s-only mixin(s): %s", style.Symbol(imageName), invalidPrefix, strings.Join(invalid, ", "))
-	}
-	return set, nil
+	return invalid
 }
 
-func validateMissing(actual, required map[string]interface{}, actualImageName string) error {
-	var missing []string
-	for m := range required {
-		if _, ok := actual[m]; !ok {
-			missing = append(missing, m)
+func removeStageMixins(mixins []string, stage string) []string {
+	var filtered []string
+	for _, m := range mixins {
+		if !strings.HasPrefix(m, stage+":") {
+			filtered = append(filtered, m)
 		}
 	}
-
-	if len(missing) > 0 {
-		sort.Strings(missing)
-		return fmt.Errorf("%s missing required mixin(s): %s", style.Symbol(actualImageName), strings.Join(missing, ", "))
-	}
-	return nil
+	return filtered
 }
diff --git a/internal/stringset/stringset.go b/internal/stringset/stringset.go
new file mode 100644
index 00000000..feb7842e
--- /dev/null
+++ b/internal/stringset/stringset.go
@@ -0,0 +1,37 @@
+package stringset
+
+// FromSlice converts the given slice to a set in the form of unique keys in a map.
+// The value associated with each key should not be relied upon. A value is present
+// in the set if its key is present in the map, regardless of the key's value.
+func FromSlice(strings []string) map[string]interface{} {
+	set := map[string]interface{}{}
+	for _, s := range strings {
+		set[s] = nil
+	}
+	return set
+}
+
+// Compare performs a set comparison between two slices. `extra` represents elements present in
+// `strings1` but not `strings2`. `missing` represents elements present in `strings2` that are
+// missing from `strings1`. `common` represents elements present in both slices. Since the input
+// slices are treated as sets, duplicates will be removed in any outputs.
+func Compare(strings1, strings2 []string) (extra []string, missing []string, common []string) {
+	set1 := FromSlice(strings1)
+	set2 := FromSlice(strings2)
+
+	for s := range set1 {
+		if _, ok := set2[s]; !ok {
+			extra = append(extra, s)
+			continue
+		}
+		common = append(common, s)
+	}
+
+	for s := range set2 {
+		if _, ok := set1[s]; !ok {
+			missing = append(missing, s)
+		}
+	}
+
+	return extra, missing, common
+}


Test patch:
diff --git a/build_test.go b/build_test.go
index dfa4cc51..7cc5c8a1 100644
--- a/build_test.go
+++ b/build_test.go
@@ -35,7 +35,7 @@ import (
 
 func TestBuild(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	rand.Seed(time.Now().UTC().UnixNano())
 	spec.Run(t, "build", testBuild, spec.Report(report.Terminal{}))
 }
diff --git a/builder/config_test.go b/builder/config_test.go
index d7323ee5..c7dfe3a1 100644
--- a/builder/config_test.go
+++ b/builder/config_test.go
@@ -16,7 +16,7 @@ import (
 
 func TestConfig(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "testConfig", testConfig, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
@@ -87,8 +87,7 @@ func testConfig(t *testing.T, when spec.G, it spec.S) {
 					_, warns, err := builder.ReadConfig(builderConfigPath)
 					h.AssertNil(t, err)
 
-					h.AssertEq(t, len(warns), 1)
-					h.AssertSliceContains(t, warns, "'latest' field on a buildpack is obsolete and will be ignored")
+					h.AssertSliceContainsOnly(t, warns, "'latest' field on a buildpack is obsolete and will be ignored")
 				})
 			})
 
@@ -114,8 +113,7 @@ func testConfig(t *testing.T, when spec.G, it spec.S) {
 					_, warns, err := builder.ReadConfig(builderConfigPath)
 					h.AssertNil(t, err)
 
-					h.AssertEq(t, len(warns), 1)
-					h.AssertSliceContains(t, warns, "'groups' field is obsolete in favor of 'order'")
+					h.AssertSliceContainsOnly(t, warns, "'groups' field is obsolete in favor of 'order'")
 				})
 			})
 
@@ -132,8 +130,7 @@ func testConfig(t *testing.T, when spec.G, it spec.S) {
 					_, warns, err := builder.ReadConfig(builderConfigPath)
 					h.AssertNil(t, err)
 
-					h.AssertEq(t, len(warns), 1)
-					h.AssertSliceContains(t, warns, "empty 'order' definition")
+					h.AssertSliceContainsOnly(t, warns, "empty 'order' definition")
 				})
 			})
 		})
diff --git a/create_builder_test.go b/create_builder_test.go
index 935341c4..bbbfdb07 100644
--- a/create_builder_test.go
+++ b/create_builder_test.go
@@ -30,7 +30,7 @@ import (
 
 func TestCreateBuilder(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "create_builder", testCreateBuilder, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/create_buildpackage_test.go b/create_buildpackage_test.go
index f859728e..81b68d2e 100644
--- a/create_buildpackage_test.go
+++ b/create_buildpackage_test.go
@@ -25,7 +25,7 @@ import (
 
 func TestCreatePackage(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "CreatePackage", testCreatePackage, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/inspect_builder_test.go b/inspect_builder_test.go
index e5e4e248..b574c011 100644
--- a/inspect_builder_test.go
+++ b/inspect_builder_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestInspectBuilder(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "InspectBuilder", testInspectBuilder, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/inspect_image_test.go b/inspect_image_test.go
index 63fe9c68..4e625d55 100644
--- a/inspect_image_test.go
+++ b/inspect_image_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestInspectImage(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "InspectImage", testInspectImage, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/app/run_test.go b/internal/app/run_test.go
index a2328208..416b505e 100644
--- a/internal/app/run_test.go
+++ b/internal/app/run_test.go
@@ -24,7 +24,7 @@ import (
 func TestApp(t *testing.T) {
 	h.RequireDocker(t)
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	rand.Seed(time.Now().UTC().UnixNano())
 	spec.Run(t, "app", testApp, spec.Sequential(), spec.Report(report.Terminal{}))
 }
diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go
index e6cb2146..28721334 100644
--- a/internal/archive/archive_test.go
+++ b/internal/archive/archive_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestArchive(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	rand.Seed(time.Now().UTC().UnixNano())
 	spec.Run(t, "Archive", testArchive, spec.Sequential(), spec.Report(report.Terminal{}))
 }
diff --git a/internal/blob/downloader_test.go b/internal/blob/downloader_test.go
index 69709be4..4bc79efe 100644
--- a/internal/blob/downloader_test.go
+++ b/internal/blob/downloader_test.go
@@ -23,7 +23,7 @@ import (
 
 func TestDownloader(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Downloader", testDownloader, spec.Sequential(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/build/phase_test.go b/internal/build/phase_test.go
index e7ec51ca..41b804d3 100644
--- a/internal/build/phase_test.go
+++ b/internal/build/phase_test.go
@@ -35,7 +35,7 @@ func TestPhase(t *testing.T) {
 	rand.Seed(time.Now().UTC().UnixNano())
 
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 
 	h.RequireDocker(t)
 
diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go
index 75598c0a..eda14ffc 100644
--- a/internal/builder/builder_test.go
+++ b/internal/builder/builder_test.go
@@ -32,7 +32,7 @@ import (
 
 func TestBuilder(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Builder", testBuilder, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
@@ -721,17 +721,17 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
 				h.AssertEq(t, len(layers["buildpack-1-id"]["buildpack-1-version-1"].Order), 0)
 				h.AssertEq(t, len(layers["buildpack-1-id"]["buildpack-1-version-1"].Stacks), 1)
 				h.AssertEq(t, layers["buildpack-1-id"]["buildpack-1-version-1"].Stacks[0].ID, "some.stack.id")
-				h.AssertSliceContains(t, layers["buildpack-1-id"]["buildpack-1-version-1"].Stacks[0].Mixins, "mixinX", "mixinY")
+				h.AssertSliceContainsOnly(t, layers["buildpack-1-id"]["buildpack-1-version-1"].Stacks[0].Mixins, "mixinX", "mixinY")
 
 				h.AssertEq(t, len(layers["buildpack-1-id"]["buildpack-1-version-2"].Order), 0)
 				h.AssertEq(t, len(layers["buildpack-1-id"]["buildpack-1-version-2"].Stacks), 1)
 				h.AssertEq(t, layers["buildpack-1-id"]["buildpack-1-version-2"].Stacks[0].ID, "some.stack.id")
-				h.AssertSliceContains(t, layers["buildpack-1-id"]["buildpack-1-version-2"].Stacks[0].Mixins, "mixinX", "mixinY")
+				h.AssertSliceContainsOnly(t, layers["buildpack-1-id"]["buildpack-1-version-2"].Stacks[0].Mixins, "mixinX", "mixinY")
 
 				h.AssertEq(t, len(layers["buildpack-2-id"]["buildpack-2-version-1"].Order), 0)
 				h.AssertEq(t, len(layers["buildpack-2-id"]["buildpack-2-version-1"].Stacks), 1)
 				h.AssertEq(t, layers["buildpack-2-id"]["buildpack-2-version-1"].Stacks[0].ID, "some.stack.id")
-				h.AssertSliceContains(t, layers["buildpack-2-id"]["buildpack-2-version-1"].Stacks[0].Mixins, "build:mixinA", "run:mixinB")
+				h.AssertSliceContainsOnly(t, layers["buildpack-2-id"]["buildpack-2-version-1"].Stacks[0].Mixins, "build:mixinA", "run:mixinB")
 
 				h.AssertEq(t, len(layers["order-buildpack-id"]["order-buildpack-version"].Order), 1)
 				h.AssertEq(t, len(layers["order-buildpack-id"]["order-buildpack-version"].Order[0].Group), 2)
@@ -1033,7 +1033,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			it("gets mixins from image", func() {
-				h.AssertSliceContains(t, bldr.Mixins(), "mixinX", "mixinY", "build:mixinA")
+				h.AssertSliceContainsOnly(t, bldr.Mixins(), "mixinX", "mixinY", "build:mixinA")
 			})
 
 			when("metadata is missing", func() {
diff --git a/internal/builder/compat_test.go b/internal/builder/compat_test.go
index bd7e3a5a..308e1c1e 100644
--- a/internal/builder/compat_test.go
+++ b/internal/builder/compat_test.go
@@ -25,7 +25,7 @@ import (
 
 func TestCompat(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Compat", testCompat, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/builder/lifecycle_test.go b/internal/builder/lifecycle_test.go
index 19441768..7af0a599 100644
--- a/internal/builder/lifecycle_test.go
+++ b/internal/builder/lifecycle_test.go
@@ -19,7 +19,7 @@ import (
 
 func TestLifecycle(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "testLifecycle", testLifecycle, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/buildpackage/builder_test.go b/internal/buildpackage/builder_test.go
index 52d43ae3..8bb7f4f4 100644
--- a/internal/buildpackage/builder_test.go
+++ b/internal/buildpackage/builder_test.go
@@ -19,7 +19,7 @@ import (
 
 func TestPackageBuilder(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "PackageBuilder", testPackageBuilder, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/cache/image_cache_test.go b/internal/cache/image_cache_test.go
index 0378855c..3cffc6c7 100644
--- a/internal/cache/image_cache_test.go
+++ b/internal/cache/image_cache_test.go
@@ -22,7 +22,7 @@ import (
 func TestImageCache(t *testing.T) {
 	h.RequireDocker(t)
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	rand.Seed(time.Now().UTC().UnixNano())
 
 	spec.Run(t, "ImageCache", testImageCache, spec.Parallel(), spec.Report(report.Terminal{}))
diff --git a/internal/cache/volume_cache_test.go b/internal/cache/volume_cache_test.go
index 02f64152..cd1894f7 100644
--- a/internal/cache/volume_cache_test.go
+++ b/internal/cache/volume_cache_test.go
@@ -22,7 +22,7 @@ import (
 func TestVolumeCache(t *testing.T) {
 	h.RequireDocker(t)
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	rand.Seed(time.Now().UTC().UnixNano())
 
 	spec.Run(t, "VolumeCache", testCache, spec.Parallel(), spec.Report(report.Terminal{}))
diff --git a/internal/commands/create_builder_test.go b/internal/commands/create_builder_test.go
index a94c6fa5..57232915 100644
--- a/internal/commands/create_builder_test.go
+++ b/internal/commands/create_builder_test.go
@@ -21,7 +21,7 @@ import (
 
 func TestCreateBuilderCommand(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Commands", testCreateBuilderCommand, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/commands/inspect_builder_test.go b/internal/commands/inspect_builder_test.go
index ad44b80f..aefc9bc4 100644
--- a/internal/commands/inspect_builder_test.go
+++ b/internal/commands/inspect_builder_test.go
@@ -26,7 +26,7 @@ import (
 
 func TestInspectBuilderCommand(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Commands", testInspectBuilderCommand, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/commands/inspect_image_test.go b/internal/commands/inspect_image_test.go
index b7269e75..d3793849 100644
--- a/internal/commands/inspect_image_test.go
+++ b/internal/commands/inspect_image_test.go
@@ -27,7 +27,7 @@ import (
 
 func TestInspectImageCommand(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Commands", testInspectImageCommand, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/commands/set_default_builder_test.go b/internal/commands/set_default_builder_test.go
index 3e845fc6..bb01fdb1 100644
--- a/internal/commands/set_default_builder_test.go
+++ b/internal/commands/set_default_builder_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestSetDefaultBuilder(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Commands", testSetDefaultBuilderCommand, spec.Random(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/commands/suggest_builders_test.go b/internal/commands/suggest_builders_test.go
index ce6ea0f8..af8c4b33 100644
--- a/internal/commands/suggest_builders_test.go
+++ b/internal/commands/suggest_builders_test.go
@@ -21,7 +21,7 @@ import (
 
 func TestSuggestBuilders(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "Commands", testSuggestBuildersCommand, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index a570f232..3d308994 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -17,7 +17,7 @@ import (
 
 func TestConfig(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "config", testConfig, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
@@ -150,8 +150,7 @@ func testConfig(t *testing.T, when spec.G, it spec.S) {
 
 				h.AssertEq(t, len(cfg.RunImages), 1)
 				h.AssertEq(t, cfg.RunImages[0].Image, "some/run-image")
-				h.AssertEq(t, len(cfg.RunImages[0].Mirrors), 1)
-				h.AssertSliceContains(t, cfg.RunImages[0].Mirrors, "some-other/run")
+				h.AssertSliceContainsOnly(t, cfg.RunImages[0].Mirrors, "some-other/run")
 			})
 		})
 
@@ -165,8 +164,7 @@ func testConfig(t *testing.T, when spec.G, it spec.S) {
 
 				h.AssertEq(t, len(cfg.RunImages), 1)
 				h.AssertEq(t, cfg.RunImages[0].Image, "some/run-image")
-				h.AssertEq(t, len(cfg.RunImages[0].Mirrors), 1)
-				h.AssertSliceContains(t, cfg.RunImages[0].Mirrors, "some-other/run")
+				h.AssertSliceContainsOnly(t, cfg.RunImages[0].Mirrors, "some-other/run")
 			})
 		})
 	})
diff --git a/internal/dist/buildpack_descriptor_test.go b/internal/dist/buildpack_descriptor_test.go
index b05d2efa..8259875d 100644
--- a/internal/dist/buildpack_descriptor_test.go
+++ b/internal/dist/buildpack_descriptor_test.go
@@ -12,7 +12,7 @@ import (
 
 func TestBuildpackDescriptor(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "testBuildpackDescriptor", testBuildpackDescriptor, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/dist/buildpack_test.go b/internal/dist/buildpack_test.go
index 068353e6..6b372fb3 100644
--- a/internal/dist/buildpack_test.go
+++ b/internal/dist/buildpack_test.go
@@ -17,7 +17,7 @@ import (
 
 func TestBuildpack(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "buildpack", testBuildpack, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/internal/image/fetcher_test.go b/internal/image/fetcher_test.go
index ab6efbcd..8143e239 100644
--- a/internal/image/fetcher_test.go
+++ b/internal/image/fetcher_test.go
@@ -26,7 +26,7 @@ func TestFetcher(t *testing.T) {
 	rand.Seed(time.Now().UTC().UnixNano())
 
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 
 	h.RequireDocker(t)
 
diff --git a/internal/stack/mixins_test.go b/internal/stack/mixins_test.go
index 4a24482f..25f22f8f 100644
--- a/internal/stack/mixins_test.go
+++ b/internal/stack/mixins_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestMixinValidation(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "testMixinValidation", testMixinValidation, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
@@ -60,4 +60,14 @@ func testMixinValidation(t *testing.T, when spec.G, it spec.S) {
 			h.AssertError(t, err, "'some/run' contains build-only mixin(s): build:mixinA, build:mixinB")
 		})
 	})
+
+	when("#FindStageMixins", func() {
+		it("returns mixins with stage prefix", func() {
+			mixins := []string{"mixinA", "run:mixinB", "mixinC", "run:mixinD", "build:mixinE"}
+
+			runMixins := stack.FindStageMixins(mixins, "run")
+
+			h.AssertEq(t, runMixins, []string{"run:mixinB", "run:mixinD"})
+		})
+	})
 }
diff --git a/internal/stringset/stringset_test.go b/internal/stringset/stringset_test.go
new file mode 100644
index 00000000..f173c946
--- /dev/null
+++ b/internal/stringset/stringset_test.go
@@ -0,0 +1,82 @@
+package stringset_test
+
+import (
+	"sort"
+	"testing"
+
+	"github.com/heroku/color"
+	"github.com/sclevine/spec"
+	"github.com/sclevine/spec/report"
+
+	"github.com/buildpack/pack/internal/stringset"
+	h "github.com/buildpack/pack/testhelpers"
+)
+
+func TestStringSet(t *testing.T) {
+	color.Disable(true)
+	defer color.Disable(false)
+	spec.Run(t, "testStringSet", testStringSet, spec.Parallel(), spec.Report(report.Terminal{}))
+}
+
+// NOTE: Do NOT use AssertSliceContains() or variants of it in theses tests, as they use `stringset`
+func testStringSet(t *testing.T, when spec.G, it spec.S) {
+	when("#FromSlice", func() {
+		it("returns a map with elements as unique keys", func() {
+			slice := []string{"a", "b", "a", "c"}
+
+			set := stringset.FromSlice(slice)
+
+			h.AssertEq(t, len(set), 3)
+
+			_, ok := set["a"]
+			h.AssertTrue(t, ok)
+
+			_, ok = set["b"]
+			h.AssertTrue(t, ok)
+
+			_, ok = set["c"]
+			h.AssertTrue(t, ok)
+		})
+	})
+
+	when("#Compare", func() {
+		it("returns elements in slice 1 but not in slice 2", func() {
+			slice1 := []string{"a", "b", "c", "d"}
+			slice2 := []string{"a", "c"}
+
+			extra, _, _ := stringset.Compare(slice1, slice2)
+
+			h.AssertEq(t, len(extra), 2)
+
+			sort.Strings(extra)
+			h.AssertEq(t, extra[0], "b")
+			h.AssertEq(t, extra[1], "d")
+		})
+
+		it("returns elements in slice 2 missing from slice 1", func() {
+			slice1 := []string{"a", "c"}
+			slice2 := []string{"a", "b", "c", "d"}
+
+			_, missing, _ := stringset.Compare(slice1, slice2)
+
+			h.AssertEq(t, len(missing), 2)
+
+			sort.Strings(missing)
+			h.AssertEq(t, missing[0], "b")
+			h.AssertEq(t, missing[1], "d")
+		})
+
+		it("returns elements present in both slices", func() {
+			slice1 := []string{"a", "b", "c"}
+			slice2 := []string{"b", "c", "d"}
+
+			_, _, common := stringset.Compare(slice1, slice2)
+
+			h.AssertEq(t, len(common), 2)
+
+			sort.Strings(common)
+			h.AssertEq(t, common[0], "b")
+			h.AssertEq(t, common[1], "c")
+		})
+	})
+}
diff --git a/rebase_test.go b/rebase_test.go
index f256e040..d3f204e0 100644
--- a/rebase_test.go
+++ b/rebase_test.go
@@ -16,7 +16,7 @@ import (
 
 func TestRebase(t *testing.T) {
 	color.Disable(true)
-	defer func() { color.Disable(false) }()
+	defer color.Disable(false)
 	spec.Run(t, "rebase_factory", testRebase, spec.Parallel(), spec.Report(report.Terminal{}))
 }
 
diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go
index 92af6b17..968b974f 100644
--- a/testhelpers/testhelpers.go
+++ b/testhelpers/testhelpers.go
@@ -34,6 +34,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/buildpack/pack/internal/archive"
+	"github.com/buildpack/pack/internal/stringset"
 	"github.com/buildpack/pack/internal/style"
 )
 
@@ -53,6 +54,14 @@ func AssertEq(t *testing.T, actual, expected interface{}) {
 	}
 }
 
+func AssertTrue(t *testing.T, actual interface{}) {
+	AssertEq(t, actual, true)
+}
+
+func AssertFalse(t *testing.T, actual interface{}) {
+	AssertEq(t, actual, false)
+}
+
 func AssertUnique(t *testing.T, items ...interface{}) {
 	t.Helper()
 	itemMap := map[interface{}]interface{}{}
@@ -86,7 +95,7 @@ func AssertContains(t *testing.T, actual, expected string) {
 	t.Helper()
 	if !strings.Contains(actual, expected) {
 		t.Fatalf(
-			"Expected: '%s' to contain '%s'\n\nDiff:%s",
+			"Expected '%s' to contain '%s'\n\nDiff:%s",
 			actual,
 			expected,
 			cmp.Diff(expected, actual),
@@ -97,52 +106,47 @@ func AssertContains(t *testing.T, actual, expected string) {
 func AssertContainsMatch(t *testing.T, actual, exp string) {
 	t.Helper()
 	if !hasMatches(actual, exp) {
-		t.Fatalf("Expected: '%s' to match expression '%s'", actual, exp)
+		t.Fatalf("Expected '%s' to match expression '%s'", actual, exp)
 	}
 }
 
 func AssertNotContainsMatch(t *testing.T, actual, exp string) {
 	t.Helper()
 	if hasMatches(actual, exp) {
-		t.Fatalf("Expected: '%s' not to match expression '%s'", actual, exp)
+		t.Fatalf("Expected '%s' not to match expression '%s'", actual, exp)
 	}
 }
 
 func AssertNotContains(t *testing.T, actual, expected string) {
 	t.Helper()
 	if strings.Contains(actual, expected) {
-		t.Fatalf("Expected: '%s' to not contain '%s'", actual, expected)
+		t.Fatalf("Expected '%s' to not contain '%s'", actual, expected)
 	}
 }
 
-func AssertSliceContains(t *testing.T, slice []string, values ...string) {
+func AssertSliceContains(t *testing.T, slice []string, expected ...string) {
 	t.Helper()
-	sLookup := map[string]interface{}{}
-	for _, s := range slice {
-		sLookup[s] = nil
-	}
-	remainingVals := map[string]interface{}{}
-	for _, v := range values {
-		remainingVals[v] = nil
+	_, missing, _ := stringset.Compare(slice, expected)
+	if len(missing) > 0 {
+		t.Fatalf("Expected %s to contain elements %s", slice, missing)
 	}
-	for _, s := range slice {
-		if _, ok := sLookup[s]; ok {
-			delete(remainingVals, s)
-		}
+}
+
+func AssertSliceContainsOnly(t *testing.T, slice []string, expected ...string) {
+	t.Helper()
+	extra, missing, _ := stringset.Compare(slice, expected)
+	if len(missing) > 0 {
+		t.Fatalf("Expected %s to contain elements %s", slice, missing)
 	}
-	if len(remainingVals) > 0 {
-		var rem []string
-		for v := range remainingVals {
-			rem = append(rem, v)
-		}
-		t.Fatalf("Expected: '%s' to contain elements '%s'", slice, rem)
+	if len(extra) > 0 {
+		t.Fatalf("Expected %s to not contain elements %s", slice, extra)
 	}
 }
 
 func AssertMatch(t *testing.T, actual string, expected string) {
 	t.Helper()
 	if !regexp.MustCompile(expected).MatchString(actual) {
-		t.Fatalf("Expected: '%s' to match regex '%s'", actual, expected)
+		t.Fatalf("Expected '%s' to match regex '%s'", actual, expected)
 	}
 }