From 6620758f03100159bf833cac7a94e773ac419c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20P=C3=A9ronnet?= Date: Sat, 6 Feb 2021 12:14:10 +0100 Subject: [PATCH 1/4] feat(gexec) Add CompileTest functions. Close #410 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds following methods: - CompileTest(packagePath string, args ...string) (compiledPath string, err error) - CompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) - CompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) Signed-off-by: Pierre Péronnet --- gexec/_fixture/firefly/main_test.go | 7 + gexec/build.go | 88 ++++- gexec/build_test.go | 147 ++++++++- gexec/gexec_suite_test.go | 3 + gexec/session_test.go | 492 ++++++++++++++++------------ 5 files changed, 505 insertions(+), 232 deletions(-) create mode 100644 gexec/_fixture/firefly/main_test.go diff --git a/gexec/_fixture/firefly/main_test.go b/gexec/_fixture/firefly/main_test.go new file mode 100644 index 000000000..9552fd2b0 --- /dev/null +++ b/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.") +} diff --git a/gexec/build.go b/gexec/build.go index 741d845f4..8b4a72ef0 100644 --- a/gexec/build.go +++ b/gexec/build.go @@ -3,6 +3,8 @@ package gexec import ( + "crypto/md5" + "encoding/hex" "errors" "fmt" "go/build" @@ -46,6 +48,73 @@ 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...) +} + +/* +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...) +} + +/* +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...) +} + +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 { @@ -56,7 +125,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 @@ -66,23 +135,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 } diff --git a/gexec/build_test.go b/gexec/build_test.go index 67b7f9000..de3997b1e 100644 --- a/gexec/build_test.go +++ b/gexec/build_test.go @@ -14,6 +14,32 @@ 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() { + var err error + 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) @@ -93,8 +119,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() { @@ -104,6 +131,124 @@ var _ = Describe(".BuildIn", func() { }) }) +var _ = Describe(".CompileTest", func() { + 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() { + var err error + 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() { + var err error + 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", + } + + 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) + } + }) + + 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) diff --git a/gexec/gexec_suite_test.go b/gexec/gexec_suite_test.go index dc8e1f40c..14f7f003f 100644 --- a/gexec/gexec_suite_test.go +++ b/gexec/gexec_suite_test.go @@ -9,12 +9,15 @@ import ( ) var fireflyPath string +var fireflyTestPath string func TestGexec(t *testing.T) { BeforeSuite(func() { var err error fireflyPath, err = gexec.Build("./_fixture/firefly") Expect(err).ShouldNot(HaveOccurred()) + fireflyTestPath, err = gexec.CompileTest("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) }) AfterSuite(func() { diff --git a/gexec/session_test.go b/gexec/session_test.go index 28ccbe3bd..fe7ac8d8e 100644 --- a/gexec/session_test.go +++ b/gexec/session_test.go @@ -17,314 +17,372 @@ import ( ) var _ = Describe("Session", func() { - var command *exec.Cmd - var session *Session + Context("firefly binary", func() { + var command *exec.Cmd + var session *Session - var outWriter, errWriter io.Writer + var outWriter, errWriter io.Writer - BeforeEach(func() { - outWriter = nil - errWriter = nil - }) - - JustBeforeEach(func() { - command = exec.Command(fireflyPath) - var err error - session, err = Start(command, outWriter, errWriter) - Expect(err).ShouldNot(HaveOccurred()) - }) - - Context("running a command", func() { - It("should start the process", func() { - Expect(command.Process).ShouldNot(BeNil()) + BeforeEach(func() { + outWriter = nil + errWriter = nil }) - It("should wrap the process's stdout and stderr with gbytes buffers", func(done Done) { - Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) - Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!")) - defer session.Out.CancelDetects() - - select { - case <-session.Out.Detect("Can we maybe vote on the whole murdering people issue"): - Eventually(session).Should(Exit(0)) - case <-session.Out.Detect("I swear by my pretty floral bonnet, I will end you."): - Eventually(session).Should(Exit(1)) - case <-session.Out.Detect("My work's illegal, but at least it's honest."): - Eventually(session).Should(Exit(2)) - } - - close(done) + JustBeforeEach(func() { + command = exec.Command(fireflyPath) + var err error + session, err = Start(command, outWriter, errWriter) + Expect(err).ShouldNot(HaveOccurred()) }) - It("should satisfy the gbytes.BufferProvider interface, passing Stdout", func() { - Eventually(session).Should(Say("We've done the impossible, and that makes us mighty")) - Eventually(session).Should(Exit()) + Context("running a command", func() { + It("should start the process", func() { + Expect(command.Process).ShouldNot(BeNil()) + }) + + It("should wrap the process's stdout and stderr with gbytes buffers", func(done Done) { + Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) + Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!")) + defer session.Out.CancelDetects() + + select { + case <-session.Out.Detect("Can we maybe vote on the whole murdering people issue"): + Eventually(session).Should(Exit(0)) + case <-session.Out.Detect("I swear by my pretty floral bonnet, I will end you."): + Eventually(session).Should(Exit(1)) + case <-session.Out.Detect("My work's illegal, but at least it's honest."): + Eventually(session).Should(Exit(2)) + } + + close(done) + }) + + It("should satisfy the gbytes.BufferProvider interface, passing Stdout", func() { + Eventually(session).Should(Say("We've done the impossible, and that makes us mighty")) + Eventually(session).Should(Exit()) + }) }) - }) - Describe("providing the exit code", func() { - It("should provide the app's exit code", func() { - Expect(session.ExitCode()).Should(Equal(-1)) + Describe("providing the exit code", func() { + It("should provide the app's exit code", func() { + Expect(session.ExitCode()).Should(Equal(-1)) - Eventually(session).Should(Exit()) - Expect(session.ExitCode()).Should(BeNumerically(">=", 0)) - Expect(session.ExitCode()).Should(BeNumerically("<", 3)) + Eventually(session).Should(Exit()) + Expect(session.ExitCode()).Should(BeNumerically(">=", 0)) + Expect(session.ExitCode()).Should(BeNumerically("<", 3)) + }) }) - }) - Describe("wait", func() { - It("should wait till the command exits", func() { - Expect(session.ExitCode()).Should(Equal(-1)) - Expect(session.Wait().ExitCode()).Should(BeNumerically(">=", 0)) - Expect(session.Wait().ExitCode()).Should(BeNumerically("<", 3)) + Describe("wait", func() { + It("should wait till the command exits", func() { + Expect(session.ExitCode()).Should(Equal(-1)) + Expect(session.Wait().ExitCode()).Should(BeNumerically(">=", 0)) + Expect(session.Wait().ExitCode()).Should(BeNumerically("<", 3)) + }) }) - }) - Describe("exited", func() { - It("should close when the command exits", func() { - Eventually(session.Exited).Should(BeClosed()) - Expect(session.ExitCode()).ShouldNot(Equal(-1)) + Describe("exited", func() { + It("should close when the command exits", func() { + Eventually(session.Exited).Should(BeClosed()) + Expect(session.ExitCode()).ShouldNot(Equal(-1)) + }) }) - }) - Describe("kill", func() { - It("should kill the command", func() { - session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("kill", func() { + It("should kill the command", func() { + session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session.Kill() - Eventually(session).Should(Exit(128 + 9)) + session.Kill() + Eventually(session).Should(Exit(128 + 9)) + }) }) - }) - Describe("interrupt", func() { - It("should interrupt the command", func() { - session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("interrupt", func() { + It("should interrupt the command", func() { + session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session.Interrupt() - Eventually(session).Should(Exit(128 + 2)) + session.Interrupt() + Eventually(session).Should(Exit(128 + 2)) + }) }) - }) - Describe("terminate", func() { - It("should terminate the command", func() { - session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("terminate", func() { + It("should terminate the command", func() { + session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session.Terminate() - Eventually(session).Should(Exit(128 + 15)) + session.Terminate() + Eventually(session).Should(Exit(128 + 15)) + }) }) - }) - Describe("signal", func() { - It("should send the signal to the command", func() { - session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("signal", func() { + It("should send the signal to the command", func() { + session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session.Signal(syscall.SIGABRT) - Eventually(session).Should(Exit(128 + 6)) - }) + session.Signal(syscall.SIGABRT) + Eventually(session).Should(Exit(128 + 6)) + }) - It("should ignore sending a signal if the command did not start", func() { - session, err := Start(exec.Command("notexisting"), GinkgoWriter, GinkgoWriter) - Expect(err).To(HaveOccurred()) + It("should ignore sending a signal if the command did not start", func() { + session, err := Start(exec.Command("notexisting"), GinkgoWriter, GinkgoWriter) + Expect(err).To(HaveOccurred()) - Expect(func() { session.Signal(syscall.SIGUSR1) }).NotTo(Panic()) + Expect(func() { session.Signal(syscall.SIGUSR1) }).NotTo(Panic()) + }) }) - }) - Context("tracking sessions", func() { - BeforeEach(func() { - KillAndWait() - }) + Context("tracking sessions", func() { + BeforeEach(func() { + KillAndWait() + }) - Describe("kill", func() { - It("should kill all the started sessions", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("kill", func() { + It("should kill all the started sessions", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - Kill() + Kill() - Eventually(session1).Should(Exit(128 + 9)) - Eventually(session2).Should(Exit(128 + 9)) - Eventually(session3).Should(Exit(128 + 9)) - }) + Eventually(session1).Should(Exit(128 + 9)) + Eventually(session2).Should(Exit(128 + 9)) + Eventually(session3).Should(Exit(128 + 9)) + }) - It("should not track unstarted sessions", func() { - _, err := Start(exec.Command("does not exist", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).Should(HaveOccurred()) + It("should not track unstarted sessions", func() { + _, err := Start(exec.Command("does not exist", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).Should(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + + Kill() - Kill() + Eventually(session2).Should(Exit(128 + 9)) + Eventually(session3).Should(Exit(128 + 9)) + }) - Eventually(session2).Should(Exit(128 + 9)) - Eventually(session3).Should(Exit(128 + 9)) }) - }) + Describe("killAndWait", func() { + It("should kill all the started sessions and wait for them to finish", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - Describe("killAndWait", func() { - It("should kill all the started sessions and wait for them to finish", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + KillAndWait() + Expect(session1).Should(Exit(128+9), "Should have exited") + Expect(session2).Should(Exit(128+9), "Should have exited") + Expect(session3).Should(Exit(128+9), "Should have exited") + }) + }) - KillAndWait() - Expect(session1).Should(Exit(128+9), "Should have exited") - Expect(session2).Should(Exit(128+9), "Should have exited") - Expect(session3).Should(Exit(128+9), "Should have exited") + Describe("terminate", func() { + It("should terminate all the started sessions", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + + Terminate() + + Eventually(session1).Should(Exit(128 + 15)) + Eventually(session2).Should(Exit(128 + 15)) + Eventually(session3).Should(Exit(128 + 15)) + }) }) - }) - Describe("terminate", func() { - It("should terminate all the started sessions", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("terminateAndWait", func() { + It("should terminate all the started sessions, and wait for them to exit", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - Terminate() + TerminateAndWait() - Eventually(session1).Should(Exit(128 + 15)) - Eventually(session2).Should(Exit(128 + 15)) - Eventually(session3).Should(Exit(128 + 15)) + Expect(session1).Should(Exit(128+15), "Should have exited") + Expect(session2).Should(Exit(128+15), "Should have exited") + Expect(session3).Should(Exit(128+15), "Should have exited") + }) }) - }) - Describe("terminateAndWait", func() { - It("should terminate all the started sessions, and wait for them to exit", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("signal", func() { + It("should signal all the started sessions", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - TerminateAndWait() + Signal(syscall.SIGABRT) - Expect(session1).Should(Exit(128+15), "Should have exited") - Expect(session2).Should(Exit(128+15), "Should have exited") - Expect(session3).Should(Exit(128+15), "Should have exited") + Eventually(session1).Should(Exit(128 + 6)) + Eventually(session2).Should(Exit(128 + 6)) + Eventually(session3).Should(Exit(128 + 6)) + }) }) - }) - Describe("signal", func() { - It("should signal all the started sessions", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Describe("interrupt", func() { + It("should interrupt all the started sessions, and not wait", func() { + session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) - Signal(syscall.SIGABRT) + Interrupt() - Eventually(session1).Should(Exit(128 + 6)) - Eventually(session2).Should(Exit(128 + 6)) - Eventually(session3).Should(Exit(128 + 6)) + Eventually(session1).Should(Exit(128 + 2)) + Eventually(session2).Should(Exit(128 + 2)) + Eventually(session3).Should(Exit(128 + 2)) + }) }) }) - Describe("interrupt", func() { - It("should interrupt all the started sessions, and not wait", func() { - session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + When("the command exits", func() { + It("should close the buffers", func() { + Eventually(session).Should(Exit()) - session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Expect(session.Out.Closed()).Should(BeTrue()) + Expect(session.Err.Closed()).Should(BeTrue()) - session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) + Expect(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) + }) - Interrupt() + var So = It - Eventually(session1).Should(Exit(128 + 2)) - Eventually(session2).Should(Exit(128 + 2)) - Eventually(session3).Should(Exit(128 + 2)) + So("this means that eventually should short circuit", func() { + t := time.Now() + failures := InterceptGomegaFailures(func() { + Eventually(session).Should(Say("blah blah blah blah blah")) + }) + Expect(time.Since(t)).Should(BeNumerically("<=", 500*time.Millisecond)) + Expect(failures).Should(HaveLen(1)) }) }) - }) - When("the command exits", func() { - It("should close the buffers", func() { - Eventually(session).Should(Exit()) + When("wrapping out and err", func() { + var ( + outWriterBuffer, errWriterBuffer *Buffer + ) - Expect(session.Out.Closed()).Should(BeTrue()) - Expect(session.Err.Closed()).Should(BeTrue()) + BeforeEach(func() { + outWriterBuffer = NewBuffer() + outWriter = outWriterBuffer + errWriterBuffer = NewBuffer() + errWriter = errWriterBuffer + }) - Expect(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) - }) + It("should route to both the provided writers and the gbytes buffers", func() { + Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) + Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!")) + + Expect(outWriterBuffer.Contents()).Should(ContainSubstring("We've done the impossible, and that makes us mighty")) + Expect(errWriterBuffer.Contents()).Should(ContainSubstring("Ah, curse your sudden but inevitable betrayal!")) - var So = It + Eventually(session).Should(Exit()) - So("this means that eventually should short circuit", func() { - t := time.Now() - failures := InterceptGomegaFailures(func() { - Eventually(session).Should(Say("blah blah blah blah blah")) + Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents())) + Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents())) }) - Expect(time.Since(t)).Should(BeNumerically("<=", 500*time.Millisecond)) - Expect(failures).Should(HaveLen(1)) - }) - }) - When("wrapping out and err", func() { - var ( - outWriterBuffer, errWriterBuffer *Buffer - ) + When("discarding the output of the command", func() { + BeforeEach(func() { + outWriter = ioutil.Discard + errWriter = ioutil.Discard + }) - BeforeEach(func() { - outWriterBuffer = NewBuffer() - outWriter = outWriterBuffer - errWriterBuffer = NewBuffer() - errWriter = errWriterBuffer + It("executes succesfuly", func() { + Eventually(session).Should(Exit()) + }) + }) }) + }) - It("should route to both the provided writers and the gbytes buffers", func() { - Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty")) - Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!")) + Context("firefly tests", func() { + var command *exec.Cmd + var session *Session - Expect(outWriterBuffer.Contents()).Should(ContainSubstring("We've done the impossible, and that makes us mighty")) - Expect(errWriterBuffer.Contents()).Should(ContainSubstring("Ah, curse your sudden but inevitable betrayal!")) + var outWriter, errWriter io.Writer - Eventually(session).Should(Exit()) + BeforeEach(func() { + outWriter = nil + errWriter = nil + }) - Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents())) - Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents())) + JustBeforeEach(func() { + command = exec.Command(fireflyTestPath) + var err error + session, err = Start(command, outWriter, errWriter) + Expect(err).ShouldNot(HaveOccurred()) }) - When("discarding the output of the command", func() { + When("wrapping out and err", func() { + var ( + outWriterBuffer, errWriterBuffer *Buffer + ) + BeforeEach(func() { - outWriter = ioutil.Discard - errWriter = ioutil.Discard + outWriterBuffer = NewBuffer() + outWriter = outWriterBuffer + errWriterBuffer = NewBuffer() + errWriter = errWriterBuffer }) - It("executes succesfuly", func() { + It("should route to both the provided writers and the gbytes buffers", func() { + Eventually(session.Out).Should(Say("PASS")) + Eventually(session.Err).Should(Say("")) + + Expect(outWriterBuffer.Contents()).Should(ContainSubstring("PASS")) + Expect(errWriterBuffer.Contents()).Should(BeEmpty()) + Eventually(session).Should(Exit()) + + Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents())) + Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents())) + }) + + When("discarding the output of the command", func() { + BeforeEach(func() { + outWriter = ioutil.Discard + errWriter = ioutil.Discard + }) + + It("executes succesfuly", func() { + Eventually(session).Should(Exit()) + }) }) }) }) From d83c4779258514c1a2015cf41c6f4b24d3f12f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20P=C3=A9ronnet?= Date: Sat, 6 Feb 2021 12:37:51 +0100 Subject: [PATCH 2/4] fix(gexec) Binaries for tests are built when needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This solve the issue in test where suite scoped firefly binary was removed by CleanupBuildArtifacts() in build_test.go Signed-off-by: Pierre Péronnet --- gexec/build_test.go | 12 ++++-------- gexec/exit_matcher_test.go | 4 +++- gexec/gexec_suite_test.go | 11 ----------- gexec/session_test.go | 11 +++++++++++ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/gexec/build_test.go b/gexec/build_test.go index de3997b1e..3f44fb64a 100644 --- a/gexec/build_test.go +++ b/gexec/build_test.go @@ -32,8 +32,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()) }) @@ -58,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()) }) @@ -150,8 +148,7 @@ var _ = Describe(".CompileTest", func() { }) It("compiles the specified test package", func() { - var err error - fireflyTestPath, err = gexec.CompileTest(packagePath) + fireflyTestPath, err := gexec.CompileTest(packagePath) Expect(err).ShouldNot(HaveOccurred()) Expect(fireflyTestPath).Should(BeAnExistingFile()) }) @@ -176,8 +173,7 @@ var _ = Describe(".CompileTest", func() { }) It("compiles the specified test package", func() { - var err error - fireflyTestPath, err = gexec.CompileTest(packagePath) + fireflyTestPath, err := gexec.CompileTest(packagePath) Expect(err).ShouldNot(HaveOccurred()) Expect(fireflyTestPath).Should(BeAnExistingFile()) }) diff --git a/gexec/exit_matcher_test.go b/gexec/exit_matcher_test.go index 9abc3226f..68dfdc256 100644 --- a/gexec/exit_matcher_test.go +++ b/gexec/exit_matcher_test.go @@ -21,7 +21,9 @@ var _ = Describe("ExitMatcher", func() { var session *Session BeforeEach(func() { - var err error + fireflyPath, err := Build("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) + command = exec.Command(fireflyPath, "0") session, err = Start(command, nil, nil) Expect(err).ShouldNot(HaveOccurred()) diff --git a/gexec/gexec_suite_test.go b/gexec/gexec_suite_test.go index 14f7f003f..1d4ab51dc 100644 --- a/gexec/gexec_suite_test.go +++ b/gexec/gexec_suite_test.go @@ -8,18 +8,7 @@ import ( "testing" ) -var fireflyPath string -var fireflyTestPath string - func TestGexec(t *testing.T) { - BeforeSuite(func() { - var err error - fireflyPath, err = gexec.Build("./_fixture/firefly") - Expect(err).ShouldNot(HaveOccurred()) - fireflyTestPath, err = gexec.CompileTest("./_fixture/firefly") - Expect(err).ShouldNot(HaveOccurred()) - }) - AfterSuite(func() { gexec.CleanupBuildArtifacts() }) diff --git a/gexec/session_test.go b/gexec/session_test.go index fe7ac8d8e..1c8015b4e 100644 --- a/gexec/session_test.go +++ b/gexec/session_test.go @@ -18,6 +18,7 @@ import ( var _ = Describe("Session", func() { Context("firefly binary", func() { + var fireflyPath string var command *exec.Cmd var session *Session @@ -26,6 +27,11 @@ var _ = Describe("Session", func() { BeforeEach(func() { outWriter = nil errWriter = nil + + var err error + fireflyPath, err = Build("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) + }) JustBeforeEach(func() { @@ -332,6 +338,7 @@ var _ = Describe("Session", func() { }) Context("firefly tests", func() { + var fireflyTestPath string var command *exec.Cmd var session *Session @@ -340,6 +347,10 @@ var _ = Describe("Session", func() { BeforeEach(func() { outWriter = nil errWriter = nil + + var err error + fireflyTestPath, err = CompileTest("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) }) JustBeforeEach(func() { From 0f67d8ca00e9caf2a62e7ad95f434403e43c27ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20P=C3=A9ronnet?= Date: Sat, 6 Feb 2021 18:48:43 +0100 Subject: [PATCH 3/4] feat(gexec) Add methods to get and compile tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds following methods: - GetAndCompileTest(packagePath string, args ...string) (compiledPath string, err error) - GetAndCompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) - GetAndCompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) Signed-off-by: Pierre Péronnet --- gexec/build.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ gexec/build_test.go | 30 ++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/gexec/build.go b/gexec/build.go index 8b4a72ef0..c7aba62b7 100644 --- a/gexec/build.go +++ b/gexec/build.go @@ -80,6 +80,17 @@ func CompileTest(packagePath string, args ...string) (compiledPath string, err e 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. */ @@ -87,6 +98,17 @@ func CompileTestWithEnvironment(packagePath string, env []string, args ...string 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). */ @@ -94,6 +116,46 @@ func CompileTestIn(gopath string, packagePath string, args ...string) (compiledP 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 { diff --git a/gexec/build_test.go b/gexec/build_test.go index 3f44fb64a..99ee1e575 100644 --- a/gexec/build_test.go +++ b/gexec/build_test.go @@ -130,6 +130,16 @@ 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) @@ -188,6 +198,16 @@ var _ = Describe(".CompileTestWithEnvironment", func() { "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()) @@ -232,6 +252,16 @@ var _ = Describe(".CompiledTestIn", func() { } }) + 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()) From 77780ea7583a6748a08a03bf713137d64904378e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20P=C3=A9ronnet?= Date: Sat, 13 Feb 2021 18:00:54 +0100 Subject: [PATCH 4/4] feat(gexec) Introduce GoPackage type to build and compile tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Péronnet --- gexec/build.go | 193 ++---------- gexec/build_test.go | 435 ++++++++++++++++----------- gexec/exit_matcher_test.go | 5 +- gexec/gopackage/gopackage.go | 99 ++++++ gexec/gotestpackage/gotestpackage.go | 132 ++++++++ gexec/session_test.go | 11 +- 6 files changed, 515 insertions(+), 360 deletions(-) create mode 100644 gexec/gopackage/gopackage.go create mode 100644 gexec/gotestpackage/gotestpackage.go diff --git a/gexec/build.go b/gexec/build.go index c7aba62b7..0d2f431d5 100644 --- a/gexec/build.go +++ b/gexec/build.go @@ -3,19 +3,13 @@ package gexec import ( - "crypto/md5" - "encoding/hex" - "errors" - "fmt" "go/build" "io/ioutil" "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strings" "sync" + + "github.com/onsi/gomega/gexec/gopackage" + "github.com/onsi/gomega/gexec/gotestpackage" ) var ( @@ -23,189 +17,46 @@ var ( tmpDir string ) -/* -Build uses go build to compile the package at packagePath. The resulting binary is saved off in a temporary directory. -A path pointing to this binary is returned. - -Build 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 build`. -*/ -func Build(packagePath string, args ...string) (compiledPath string, err error) { - return doBuild(build.Default.GOPATH, packagePath, nil, args...) -} - -/* -BuildWithEnvironment is identical to Build but allows you to specify env vars to be set at build time. -*/ -func BuildWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) { - return doBuild(build.Default.GOPATH, packagePath, env, args...) +type GoPackage interface { + Build(args ...string) (string, error) + BuildWithEnvironment(envs []string, args ...string) (string, error) + BuildIn(gopath string, args ...string) (string, error) } -/* -BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument). -*/ -func BuildIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) { - 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() +func Get(packageName string) (GoPackage, error) { + tmpDir, err := temporaryDirectory() if err != nil { - return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output)) + return nil, err } - return executable, nil + return gopackage.Get(tmpDir, packageName), 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() +func GetTests(packagePath string) (GoPackage, error) { + tmpDir, err := temporaryDirectory() if err != nil { - return fmt.Errorf("Failed to get %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output)) + return nil, err } - return nil + return gotestpackage.Get(tmpDir, build.Default.GOPATH, packagePath, 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() +func GetTestsWithEnvironment(envs []string, packagePath string) (GoPackage, error) { + tmpDir, err := temporaryDirectory() if err != nil { - return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output)) + return nil, err } - return executable, nil + return gotestpackage.Get(tmpDir, build.Default.GOPATH, packagePath, envs) } -func replaceGoPath(environ []string, newGoPath string) []string { - newEnviron := []string{} - for _, v := range environ { - if !strings.HasPrefix(v, "GOPATH=") { - newEnviron = append(newEnviron, v) - } - } - return append(newEnviron, "GOPATH="+newGoPath) -} - -func newExecutablePath(gopath, packagePath string, suffixes ...string) (string, error) { +func GetTestsIn(gopath, packagePath string) (GoPackage, error) { tmpDir, err := temporaryDirectory() if err != nil { - return "", err - } - - if len(gopath) == 0 { - return "", errors.New("$GOPATH not provided when building " + 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" + return nil, err } - return executable, nil + return gotestpackage.Get(tmpDir, gopath, packagePath, nil) } /* diff --git a/gexec/build_test.go b/gexec/build_test.go index 99ee1e575..a873346f4 100644 --- a/gexec/build_test.go +++ b/gexec/build_test.go @@ -4,280 +4,347 @@ import ( "fmt" "io/ioutil" "os" + "path" "path/filepath" + "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) -var packagePath = "./_fixture/firefly" +const currentPackage = "github.com/onsi/gomega" -var _ = Describe(".Build", func() { - When("there have been previous calls to CompileTest", func() { - BeforeEach(func() { - _, err := gexec.CompileTest(packagePath) - Expect(err).ShouldNot(HaveOccurred()) +var _ = Context("a local package", func() { + suiteTest("./_fixture/firefly") + + Context("with remote url", func() { + suiteTest("github.com/onsi/gomega/gexec/_fixture/firefly") + }) +}) + +var _ = Context("a remote package", func() { + suiteTest("github.com/onsi/ginkgo/types") +}) + +func suiteTest(packagePath string) { + Describe(".Get", func() { + It("get the specified package", func() { + _, err := gexec.Get(packagePath) + Expect(err).NotTo(HaveOccurred()) }) + }) - It("compiles the specified package", func() { - compiledPath, err := gexec.Build(packagePath) + Describe(".GetTests", func() { + It("get the specified package", func() { + _, err := gexec.GetTests(packagePath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Describe(".Build", func() { + var goPackage gexec.GoPackage + + BeforeEach(func() { + var err error + goPackage, err = gexec.Get(packagePath) Expect(err).ShouldNot(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) }) - Context("and CleanupBuildArtifacts has been called", func() { + When("there have been previous calls to CompileTest", func() { BeforeEach(func() { - gexec.CleanupBuildArtifacts() + p, err := gexec.GetTests(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = p.Build() + Expect(err).NotTo(HaveOccurred()) }) It("compiles the specified package", func() { - fireflyPath, err := gexec.Build(packagePath) + compiledPath, err := goPackage.Build() Expect(err).ShouldNot(HaveOccurred()) - Expect(fireflyPath).Should(BeAnExistingFile()) + Expect(compiledPath).Should(BeAnExistingFile()) }) - }) - }) - When("there have been previous calls to Build", func() { - BeforeEach(func() { - _, err := gexec.Build(packagePath) - Expect(err).ShouldNot(HaveOccurred()) - }) + Context("and CleanupBuildArtifacts has been called", func() { + BeforeEach(func() { + gexec.CleanupBuildArtifacts() + }) - It("compiles the specified package", func() { - compiledPath, err := gexec.Build(packagePath) - Expect(err).ShouldNot(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) + It("compiles the specified package", func() { + fireflyPath, err := goPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + Expect(fireflyPath).Should(BeAnExistingFile()) + }) + }) }) - Context("and CleanupBuildArtifacts has been called", func() { + When("there have been previous calls to Build", func() { BeforeEach(func() { - gexec.CleanupBuildArtifacts() + p, err := gexec.Get(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = p.Build() + Expect(err).NotTo(HaveOccurred()) }) It("compiles the specified package", func() { - fireflyPath, err := gexec.Build(packagePath) + compiledPath, err := goPackage.Build() Expect(err).ShouldNot(HaveOccurred()) - Expect(fireflyPath).Should(BeAnExistingFile()) + Expect(compiledPath).Should(BeAnExistingFile()) + }) + + Context("and CleanupBuildArtifacts has been called", func() { + BeforeEach(func() { + gexec.CleanupBuildArtifacts() + }) + + It("compiles the specified package", func() { + fireflyPath, err := goPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + Expect(fireflyPath).Should(BeAnExistingFile()) + }) }) }) }) -}) -var _ = Describe(".BuildWithEnvironment", func() { - var err error - env := []string{ - "GOOS=linux", - "GOARCH=amd64", - } - - It("compiles the specified package with the specified env vars", func() { - compiledPath, err := gexec.BuildWithEnvironment(packagePath, env) - Expect(err).ShouldNot(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) - }) + var _ = Describe(".BuildWithEnvironment", func() { + var err error + env := []string{ + "GOOS=linux", + "GOARCH=amd64", + } - It("returns the environment to a good state", func() { - _, err = gexec.BuildWithEnvironment(packagePath, env) - Expect(err).ShouldNot(HaveOccurred()) - Expect(os.Environ()).ShouldNot(ContainElement("GOOS=linux")) - }) -}) + var goPackage gexec.GoPackage -var _ = Describe(".BuildIn", func() { - const ( - target = "github.com/onsi/gomega/gexec/_fixture/firefly/" - ) + BeforeEach(func() { + var err error + goPackage, err = gexec.Get(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + }) - var ( - original string - gopath string - ) + It("compiles the specified package with the specified env vars", func() { + compiledPath, err := goPackage.BuildWithEnvironment(env) + Expect(err).ShouldNot(HaveOccurred()) + Expect(compiledPath).Should(BeAnExistingFile()) + }) - 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")))) + It("returns the environment to a good state", func() { + _, err = goPackage.BuildWithEnvironment(env) + Expect(err).ShouldNot(HaveOccurred()) + Expect(os.Environ()).ShouldNot(ContainElement("GOOS=linux")) + }) }) - AfterEach(func() { - if original == "" { - Expect(os.Unsetenv("GOPATH")).To(Succeed()) - } else { - Expect(os.Setenv("GOPATH", original)).To(Succeed()) - } - if gopath != "" { - os.RemoveAll(gopath) - } - }) + var _ = Describe(".BuildIn", func() { + var ( + original string + gopath string + goPackage gexec.GoPackage + ) - It("appends the gopath env var", func() { - compiledPath, err := gexec.BuildIn(gopath, target) - Expect(err).NotTo(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) - }) + BeforeEach(func() { + var err error + original = os.Getenv("GOPATH") + gopath, err = ioutil.TempDir("", "") + Expect(err).NotTo(HaveOccurred()) - It("resets GOPATH to its original value", func() { - _, err := gexec.BuildIn(gopath, target) - Expect(err).NotTo(HaveOccurred()) - Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath"))) - }) -}) + wd, err := os.Getwd() + Expect(err).NotTo(HaveOccurred()) + destination := filepath.Join(gopath, "src", currentPackage) + copy(path.Join(wd, ".."), destination) -var _ = Describe(".CompileTest", func() { - Context("a remote package", func() { - const remotePackage = "github.com/onsi/ginkgo/types" + 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")))) - It("compiles the specified test package", func() { - compiledPath, err := gexec.GetAndCompileTest(remotePackage) + goPackage, err = gexec.Get(packagePath) Expect(err).ShouldNot(HaveOccurred()) + }) + + AfterEach(func() { + if original == "" { + Expect(os.Unsetenv("GOPATH")).To(Succeed()) + } else { + Expect(os.Setenv("GOPATH", original)).To(Succeed()) + } + if gopath != "" { + os.RemoveAll(gopath) + } + }) + + It("appends the gopath env var", func() { + compiledPath, err := goPackage.BuildIn(gopath) + Expect(err).NotTo(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("resets GOPATH to its original value", func() { + _, err := goPackage.BuildIn(gopath) + Expect(err).NotTo(HaveOccurred()) + Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath"))) }) + }) + + var _ = Describe(".CompileTest", func() { + var goPackage gexec.GoPackage - It("compiles the specified test package", func() { - compiledPath, err := gexec.CompileTest(packagePath) + BeforeEach(func() { + var err error + goPackage, err = gexec.GetTests(packagePath) Expect(err).ShouldNot(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) }) - Context("and CleanupBuildArtifacts has been called", func() { + When("there have been previous calls to Build", func() { BeforeEach(func() { - gexec.CleanupBuildArtifacts() + _, err := goPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) }) It("compiles the specified test package", func() { - fireflyTestPath, err := gexec.CompileTest(packagePath) + compiledPath, err := goPackage.Build() Expect(err).ShouldNot(HaveOccurred()) - Expect(fireflyTestPath).Should(BeAnExistingFile()) + Expect(compiledPath).Should(BeAnExistingFile()) }) - }) - }) - When("there have been previous calls to Build", func() { - BeforeEach(func() { - _, err := gexec.Build(packagePath) - Expect(err).ShouldNot(HaveOccurred()) - }) + Context("and CleanupBuildArtifacts has been called", func() { + BeforeEach(func() { + gexec.CleanupBuildArtifacts() + }) - It("compiles the specified test package", func() { - compiledPath, err := gexec.CompileTest(packagePath) - Expect(err).ShouldNot(HaveOccurred()) - Expect(compiledPath).Should(BeAnExistingFile()) + It("compiles the specified test package", func() { + fireflyTestPath, err := goPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + Expect(fireflyTestPath).Should(BeAnExistingFile()) + }) + }) }) - Context("and CleanupBuildArtifacts has been called", func() { + When("there have been previous calls to Build", func() { BeforeEach(func() { - gexec.CleanupBuildArtifacts() + p, err := gexec.Get(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = p.Build() + Expect(err).NotTo(HaveOccurred()) }) It("compiles the specified test package", func() { - fireflyTestPath, err := gexec.CompileTest(packagePath) + compiledPath, err := goPackage.Build() Expect(err).ShouldNot(HaveOccurred()) - Expect(fireflyTestPath).Should(BeAnExistingFile()) + Expect(compiledPath).Should(BeAnExistingFile()) + }) + + Context("and CleanupBuildArtifacts has been called", func() { + BeforeEach(func() { + gexec.CleanupBuildArtifacts() + }) + + It("compiles the specified test package", func() { + fireflyTestPath, err := goPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + Expect(fireflyTestPath).Should(BeAnExistingFile()) + }) }) }) }) -}) -var _ = Describe(".CompileTestWithEnvironment", func() { - var err error - env := []string{ - "GOOS=linux", - "GOARCH=amd64", - } + 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" + var goPackage gexec.GoPackage + + BeforeEach(func() { + var err error + goPackage, err = gexec.GetTests(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + }) It("compiles the specified test package with the specified env vars", func() { - compiledPath, err := gexec.GetAndCompileTestWithEnvironment(remotePackage, env) + compiledPath, err := goPackage.BuildWithEnvironment(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 = goPackage.BuildWithEnvironment(env) + Expect(err).ShouldNot(HaveOccurred()) + Expect(os.Environ()).ShouldNot(ContainElement("GOOS=linux")) + }) }) - 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")) - }) -}) + Describe(".CompiledTestIn", func() { + var ( + original string + gopath string + goPackage gexec.GoPackage + ) -var _ = Describe(".CompiledTestIn", func() { - const ( - target = "github.com/onsi/gomega/gexec/_fixture/firefly" - ) + BeforeEach(func() { + var err error + original = os.Getenv("GOPATH") + gopath, err = ioutil.TempDir("", "") + Expect(err).NotTo(HaveOccurred()) - var ( - original string - gopath string - ) + wd, err := os.Getwd() + Expect(err).NotTo(HaveOccurred()) + destination := filepath.Join(gopath, "src", currentPackage) + copy(path.Join(wd, ".."), destination) - 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")))) - }) + 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) - } - }) + goPackage, err = gexec.Get(packagePath) + Expect(err).ShouldNot(HaveOccurred()) + }) - Context("a remote package", func() { - const remotePackage = "github.com/onsi/ginkgo/types" + AfterEach(func() { + if original == "" { + Expect(os.Unsetenv("GOPATH")).To(Succeed()) + } else { + Expect(os.Setenv("GOPATH", original)).To(Succeed()) + } + if gopath != "" { + os.RemoveAll(gopath) + } + }) - It("compiles the specified test package", func() { - compiledPath, err := gexec.GetAndCompileTestIn(gopath, remotePackage) - Expect(err).ShouldNot(HaveOccurred()) + It("appends the gopath env var", func() { + compiledPath, err := goPackage.BuildIn(gopath) + Expect(err).NotTo(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 := goPackage.BuildIn(gopath) + Expect(err).NotTo(HaveOccurred()) + Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath"))) + }) }) +} - 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 copy(source, destination string) { + Expect(os.MkdirAll(destination, 0755)).To(Succeed()) + + err := filepath.Walk(source, func(filePath string, info os.FileInfo, err error) error { + relPath := strings.Replace(filePath, source, "", 1) + if relPath == "" { + return nil + } -func copyFile(source, directory, basename string) { - Expect(os.MkdirAll(directory, 0755)).To(Succeed()) - content, err := ioutil.ReadFile(source) + if info.IsDir() { + return os.Mkdir(filepath.Join(destination, relPath), 0755) + } else { + data, err := ioutil.ReadFile(filepath.Join(source, relPath)) + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(destination, relPath), data, info.Mode()) + } + }) Expect(err).NotTo(HaveOccurred()) - Expect(ioutil.WriteFile(filepath.Join(directory, basename), content, 0644)).To(Succeed()) } diff --git a/gexec/exit_matcher_test.go b/gexec/exit_matcher_test.go index 68dfdc256..e95d24052 100644 --- a/gexec/exit_matcher_test.go +++ b/gexec/exit_matcher_test.go @@ -21,7 +21,10 @@ var _ = Describe("ExitMatcher", func() { var session *Session BeforeEach(func() { - fireflyPath, err := Build("./_fixture/firefly") + fireflyPackage, err := Get("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) + + fireflyPath, err := fireflyPackage.Build() Expect(err).ShouldNot(HaveOccurred()) command = exec.Command(fireflyPath, "0") diff --git a/gexec/gopackage/gopackage.go b/gexec/gopackage/gopackage.go new file mode 100644 index 000000000..edb4891f4 --- /dev/null +++ b/gexec/gopackage/gopackage.go @@ -0,0 +1,99 @@ +package gopackage + +import ( + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "go/build" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" +) + +type Package struct { + destination string + packagePath string +} + +func Get(destination, packagePath string) *Package { + return &Package{ + destination: destination, + packagePath: packagePath, + } +} + +/* +Build uses go build to compile the package. The resulting binary is saved off in a temporary directory. +A path pointing to this binary is returned. + +Build 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 build`. +*/ +func (p *Package) Build(args ...string) (compiledPath string, err error) { + return p.doBuild(build.Default.GOPATH, nil, args...) +} + +/* +BuildWithEnvironment is identical to Build but allows you to specify env vars to be set at build time. +*/ +func (p *Package) BuildWithEnvironment(env []string, args ...string) (compiledPath string, err error) { + return p.doBuild(build.Default.GOPATH, env, args...) +} + +/* +BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument). +*/ +func (p *Package) BuildIn(gopath string, args ...string) (compiledPath string, err error) { + return p.doBuild(gopath, nil, args...) +} + +func (p *Package) doBuild(gopath string, env []string, args ...string) (compiledPath string, err error) { + executable, err := p.newExecutablePath(gopath) + if err != nil { + return "", err + } + + cmdArgs := append([]string{"build"}, args...) + cmdArgs = append(cmdArgs, "-o", executable, p.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", p.packagePath, err, string(output)) + } + + return executable, nil +} + +func replaceGoPath(environ []string, newGoPath string) []string { + newEnviron := []string{} + for _, v := range environ { + if !strings.HasPrefix(v, "GOPATH=") { + newEnviron = append(newEnviron, v) + } + } + return append(newEnviron, "GOPATH="+newGoPath) +} + +func (p *Package) newExecutablePath(gopath string, suffixes ...string) (string, error) { + if len(gopath) == 0 { + return "", errors.New("$GOPATH not provided when building " + p.packagePath) + } + + hash := md5.Sum([]byte(p.packagePath)) + filename := fmt.Sprintf("%s-%x%s", path.Base(p.packagePath), hex.EncodeToString(hash[:]), strings.Join(suffixes, "")) + executable := filepath.Join(p.destination, filename) + + if runtime.GOOS == "windows" { + executable += ".exe" + } + + return executable, nil +} diff --git a/gexec/gotestpackage/gotestpackage.go b/gexec/gotestpackage/gotestpackage.go new file mode 100644 index 000000000..2ee90bed1 --- /dev/null +++ b/gexec/gotestpackage/gotestpackage.go @@ -0,0 +1,132 @@ +package gotestpackage + +import ( + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "go/build" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" +) + +type Package struct { + destination string + packagePath string +} + +func Get(destination, gopath, packagePath string, envs []string) (*Package, error) { + if err := getForTest(gopath, packagePath, envs); err != nil { + return nil, err + } + + return &Package{ + destination: destination, + packagePath: packagePath, + }, nil +} + +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 replaceGoPath(environ []string, newGoPath string) []string { + newEnviron := []string{} + for _, v := range environ { + if !strings.HasPrefix(v, "GOPATH=") { + newEnviron = append(newEnviron, v) + } + } + return append(newEnviron, "GOPATH="+newGoPath) +} + +/* +Build uses go test to compile the test package. 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 (p *Package) Build(args ...string) (compiledPath string, err error) { + return p.doCompileTest(build.Default.GOPATH, nil, args...) +} + +/* +BuildWithEnvironment is identical to Build but allows you to specify env vars to be set at build time. +*/ +func (p *Package) BuildWithEnvironment(env []string, args ...string) (compiledPath string, err error) { + return p.doCompileTest(build.Default.GOPATH, env, args...) +} + +/* +BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument). +*/ +func (p *Package) BuildIn(gopath string, args ...string) (compiledPath string, err error) { + return p.doCompileTest(gopath, nil, args...) +} + +func (p *Package) doCompileTest(gopath string, env []string, args ...string) (compiledPath string, err error) { + executable, err := p.newExecutablePath(gopath, ".test") + if err != nil { + return "", err + } + + cmdArgs := append([]string{"test", "-c"}, args...) + cmdArgs = append(cmdArgs, "-o", executable, p.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", p.packagePath, err, string(output)) + } + + return executable, nil +} + +func (p *Package) newExecutablePath(gopath string, suffixes ...string) (string, error) { + if len(gopath) == 0 { + return "", errors.New("$GOPATH not provided when building " + p.packagePath) + } + + hash := md5.Sum([]byte(p.packagePath)) + filename := fmt.Sprintf("%s-%x%s", path.Base(p.packagePath), hex.EncodeToString(hash[:]), strings.Join(suffixes, "")) + executable := filepath.Join(p.destination, filename) + + if runtime.GOOS == "windows" { + executable += ".exe" + } + + return executable, nil +} diff --git a/gexec/session_test.go b/gexec/session_test.go index 1c8015b4e..8721396c3 100644 --- a/gexec/session_test.go +++ b/gexec/session_test.go @@ -28,10 +28,11 @@ var _ = Describe("Session", func() { outWriter = nil errWriter = nil - var err error - fireflyPath, err = Build("./_fixture/firefly") + fireflyPackage, err := Get("./_fixture/firefly") Expect(err).ShouldNot(HaveOccurred()) + fireflyPath, err = fireflyPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) }) JustBeforeEach(func() { @@ -348,8 +349,10 @@ var _ = Describe("Session", func() { outWriter = nil errWriter = nil - var err error - fireflyTestPath, err = CompileTest("./_fixture/firefly") + fireflyPackage, err := GetTests("./_fixture/firefly") + Expect(err).ShouldNot(HaveOccurred()) + + fireflyTestPath, err = fireflyPackage.Build() Expect(err).ShouldNot(HaveOccurred()) })