Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gexec) Add CompileTest functions. Close #410 #411

Merged
merged 3 commits into from Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions gexec/_fixture/firefly/main_test.go
@@ -0,0 +1,7 @@
package main_test

import "testing"

func Test(t *testing.T) {
t.Log("Hum, it seems okay.")
}
150 changes: 136 additions & 14 deletions gexec/build.go
Expand Up @@ -3,6 +3,8 @@
package gexec

import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"go/build"
Expand Down Expand Up @@ -46,6 +48,135 @@ func BuildIn(gopath string, packagePath string, args ...string) (compiledPath st
return doBuild(gopath, packagePath, nil, args...)
}

func doBuild(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
executable, err := newExecutablePath(gopath, packagePath)
if err != nil {
return "", err
}

cmdArgs := append([]string{"build"}, args...)
cmdArgs = append(cmdArgs, "-o", executable, packagePath)

build := exec.Command("go", cmdArgs...)
build.Env = replaceGoPath(os.Environ(), gopath)
build.Env = append(build.Env, env...)

output, err := build.CombinedOutput()
if err != nil {
return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
}

return executable, nil
}

/*
CompileTest uses go test to compile the test package at packagePath. The resulting binary is saved off in a temporary directory.
A path pointing to this binary is returned.

CompileTest uses the $GOPATH set in your environment. If $GOPATH is not set and you are using Go 1.8+,
it will use the default GOPATH instead. It passes the variadic args on to `go test`.
*/
func CompileTest(packagePath string, args ...string) (compiledPath string, err error) {
return doCompileTest(build.Default.GOPATH, packagePath, nil, args...)
}

/*
GetAndCompileTest is identical to CompileTest but `go get` the package before compiling tests.
*/
func GetAndCompileTest(packagePath string, args ...string) (compiledPath string, err error) {
if err := getForTest(build.Default.GOPATH, packagePath, nil); err != nil {
return "", err
}

return doCompileTest(build.Default.GOPATH, packagePath, nil, args...)
}

/*
CompileTestWithEnvironment is identical to CompileTest but allows you to specify env vars to be set at build time.
*/
func CompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
return doCompileTest(build.Default.GOPATH, packagePath, env, args...)
}

/*
GetAndCompileTestWithEnvironment is identical to GetAndCompileTest but allows you to specify env vars to be set at build time.
*/
func GetAndCompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
if err := getForTest(build.Default.GOPATH, packagePath, env); err != nil {
return "", err
}

return doCompileTest(build.Default.GOPATH, packagePath, env, args...)
}

/*
CompileTestIn is identical to CompileTest but allows you to specify a custom $GOPATH (the first argument).
*/
func CompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
return doCompileTest(gopath, packagePath, nil, args...)
}

/*
GetAndCompileTestIn is identical to GetAndCompileTest but allows you to specify a custom $GOPATH (the first argument).
*/
func GetAndCompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
if err := getForTest(gopath, packagePath, nil); err != nil {
return "", err
}

return doCompileTest(gopath, packagePath, nil, args...)
}

func isLocalPackage(packagePath string) bool {
return strings.HasPrefix(packagePath, ".")
}

func getForTest(gopath, packagePath string, env []string) error {
if isLocalPackage(packagePath) {
return nil
}

return doGet(gopath, packagePath, env, "-t")
}

func doGet(gopath, packagePath string, env []string, args ...string) error {
args = append(args, packagePath)
args = append([]string{"get"}, args...)

goGet := exec.Command("go", args...)
goGet.Dir = gopath
goGet.Env = replaceGoPath(os.Environ(), gopath)
goGet.Env = append(goGet.Env, env...)

output, err := goGet.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to get %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
}

return nil
}

func doCompileTest(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
executable, err := newExecutablePath(gopath, packagePath, ".test")
if err != nil {
return "", err
}

cmdArgs := append([]string{"test", "-c"}, args...)
cmdArgs = append(cmdArgs, "-o", executable, packagePath)

build := exec.Command("go", cmdArgs...)
build.Env = replaceGoPath(os.Environ(), gopath)
build.Env = append(build.Env, env...)

output, err := build.CombinedOutput()
if err != nil {
return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
}

return executable, nil
}

func replaceGoPath(environ []string, newGoPath string) []string {
newEnviron := []string{}
for _, v := range environ {
Expand All @@ -56,7 +187,7 @@ func replaceGoPath(environ []string, newGoPath string) []string {
return append(newEnviron, "GOPATH="+newGoPath)
}

func doBuild(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
func newExecutablePath(gopath, packagePath string, suffixes ...string) (string, error) {
tmpDir, err := temporaryDirectory()
if err != nil {
return "", err
Expand All @@ -66,23 +197,14 @@ func doBuild(gopath, packagePath string, env []string, args ...string) (compiled
return "", errors.New("$GOPATH not provided when building " + packagePath)
}

executable := filepath.Join(tmpDir, path.Base(packagePath))
hash := md5.Sum([]byte(packagePath))
filename := fmt.Sprintf("%s-%x%s", path.Base(packagePath), hex.EncodeToString(hash[:]), strings.Join(suffixes, ""))
executable := filepath.Join(tmpDir, filename)

if runtime.GOOS == "windows" {
executable += ".exe"
}

cmdArgs := append([]string{"build"}, args...)
cmdArgs = append(cmdArgs, "-o", executable, packagePath)

build := exec.Command("go", cmdArgs...)
build.Env = replaceGoPath(os.Environ(), gopath)
build.Env = append(build.Env, env...)

output, err := build.CombinedOutput()
if err != nil {
return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
}

return executable, nil
}

Expand Down
177 changes: 174 additions & 3 deletions gexec/build_test.go
Expand Up @@ -14,6 +14,31 @@ import (
var packagePath = "./_fixture/firefly"

var _ = Describe(".Build", func() {
When("there have been previous calls to CompileTest", func() {
BeforeEach(func() {
_, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
})

It("compiles the specified package", func() {
compiledPath, err := gexec.Build(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

Context("and CleanupBuildArtifacts has been called", func() {
BeforeEach(func() {
gexec.CleanupBuildArtifacts()
})

It("compiles the specified package", func() {
fireflyPath, err := gexec.Build(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(fireflyPath).Should(BeAnExistingFile())
})
})
})

When("there have been previous calls to Build", func() {
BeforeEach(func() {
_, err := gexec.Build(packagePath)
Expand All @@ -32,8 +57,7 @@ var _ = Describe(".Build", func() {
})

It("compiles the specified package", func() {
var err error
fireflyPath, err = gexec.Build(packagePath)
fireflyPath, err := gexec.Build(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(fireflyPath).Should(BeAnExistingFile())
})
Expand Down Expand Up @@ -93,8 +117,9 @@ var _ = Describe(".BuildIn", func() {
})

It("appends the gopath env var", func() {
_, err := gexec.BuildIn(gopath, target)
compiledPath, err := gexec.BuildIn(gopath, target)
Expect(err).NotTo(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

It("resets GOPATH to its original value", func() {
Expand All @@ -104,6 +129,152 @@ var _ = Describe(".BuildIn", func() {
})
})

var _ = Describe(".CompileTest", func() {
Context("a remote package", func() {
const remotePackage = "github.com/onsi/ginkgo/types"

It("compiles the specified test package", func() {
compiledPath, err := gexec.GetAndCompileTest(remotePackage)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})
})

When("there have been previous calls to CompileTest", func() {
BeforeEach(func() {
_, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
})

It("compiles the specified test package", func() {
compiledPath, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

Context("and CleanupBuildArtifacts has been called", func() {
BeforeEach(func() {
gexec.CleanupBuildArtifacts()
})

It("compiles the specified test package", func() {
fireflyTestPath, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(fireflyTestPath).Should(BeAnExistingFile())
})
})
})

When("there have been previous calls to Build", func() {
BeforeEach(func() {
_, err := gexec.Build(packagePath)
Expect(err).ShouldNot(HaveOccurred())
})

It("compiles the specified test package", func() {
compiledPath, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

Context("and CleanupBuildArtifacts has been called", func() {
BeforeEach(func() {
gexec.CleanupBuildArtifacts()
})

It("compiles the specified test package", func() {
fireflyTestPath, err := gexec.CompileTest(packagePath)
Expect(err).ShouldNot(HaveOccurred())
Expect(fireflyTestPath).Should(BeAnExistingFile())
})
})
})
})

var _ = Describe(".CompileTestWithEnvironment", func() {
var err error
env := []string{
"GOOS=linux",
"GOARCH=amd64",
}

Context("a remote package", func() {
const remotePackage = "github.com/onsi/ginkgo/types"

It("compiles the specified test package with the specified env vars", func() {
compiledPath, err := gexec.GetAndCompileTestWithEnvironment(remotePackage, env)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})
})

It("compiles the specified test package with the specified env vars", func() {
compiledPath, err := gexec.CompileTestWithEnvironment(packagePath, env)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

It("returns the environment to a good state", func() {
_, err = gexec.CompileTestWithEnvironment(packagePath, env)
Expect(err).ShouldNot(HaveOccurred())
Expect(os.Environ()).ShouldNot(ContainElement("GOOS=linux"))
})
})

var _ = Describe(".CompiledTestIn", func() {
const (
target = "github.com/onsi/gomega/gexec/_fixture/firefly"
)

var (
original string
gopath string
)

BeforeEach(func() {
var err error
original = os.Getenv("GOPATH")
gopath, err = ioutil.TempDir("", "")
Expect(err).NotTo(HaveOccurred())
copyFile(filepath.Join("_fixture", "firefly", "main.go"), filepath.Join(gopath, "src", target), "main.go")
Expect(os.Setenv("GOPATH", filepath.Join(os.TempDir(), "emptyFakeGopath"))).To(Succeed())
Expect(os.Environ()).To(ContainElement(fmt.Sprintf("GOPATH=%s", filepath.Join(os.TempDir(), "emptyFakeGopath"))))
})

AfterEach(func() {
if original == "" {
Expect(os.Unsetenv("GOPATH")).To(Succeed())
} else {
Expect(os.Setenv("GOPATH", original)).To(Succeed())
}
if gopath != "" {
os.RemoveAll(gopath)
}
})

Context("a remote package", func() {
const remotePackage = "github.com/onsi/ginkgo/types"

It("compiles the specified test package", func() {
compiledPath, err := gexec.GetAndCompileTestIn(gopath, remotePackage)
Expect(err).ShouldNot(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})
})

It("appends the gopath env var", func() {
compiledPath, err := gexec.CompileTestIn(gopath, target)
Expect(err).NotTo(HaveOccurred())
Expect(compiledPath).Should(BeAnExistingFile())
})

It("resets GOPATH to its original value", func() {
_, err := gexec.CompileTestIn(gopath, target)
Expect(err).NotTo(HaveOccurred())
Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath")))
})
})

func copyFile(source, directory, basename string) {
Expect(os.MkdirAll(directory, 0755)).To(Succeed())
content, err := ioutil.ReadFile(source)
Expand Down