lib: Address https://v.io/i/1220
- reverts https://v.io/c/20483
- changes v23test to not share a bin dir across test main's
(bin dir is still shared across tests within a given test
main, but that's safe since "go test" will not run such
tests concurrently by default)
- changes v23test.StartRootMountTable and
v23test.StartSyncbase to use gosh.Shell.FuncCmd
(refactoring syncbased and mounttabled as needed to make
this possible)
A side effect of this change is that tests should run much
faster, because for the common case of tests that previously
used StartRootMountTable and/or StartSyncbase, but didn't
directly call BuildGoPkg, we'll no longer build Go packages
during test execution.
MultiPart: 2/5
Change-Id: Iba81f1db56ca21f2d887e26496d7a87ad288b076
diff --git a/gosh/shell.go b/gosh/shell.go
index 3c9df0b..3bab48d 100644
--- a/gosh/shell.go
+++ b/gosh/shell.go
@@ -150,7 +150,11 @@
sh.handleError(sh.wait())
}
-// Move moves a file.
+// Move moves a file from 'oldpath' to 'newpath'. It first attempts os.Rename;
+// if that fails, it copies 'oldpath' to 'newpath', then deletes 'oldpath'.
+// Requires that 'newpath' does not exist, and that the parent directory of
+// 'newpath' does exist. Currently only supports moving an individual file;
+// moving a directory is not yet supported.
func (sh *Shell) Move(oldpath, newpath string) {
sh.Ok()
sh.handleError(sh.move(oldpath, newpath))
@@ -340,7 +344,7 @@
return res
}
-func copyFile(from, to string) error {
+func copyFile(to, from string) error {
fi, err := os.Stat(from)
if err != nil {
return err
@@ -363,18 +367,33 @@
}
func (sh *Shell) move(oldpath, newpath string) error {
- var err error
- if err = os.Rename(oldpath, newpath); err != nil {
- // Concurrent, same-directory rename operations sometimes fail on certain
- // filesystems, so we retry once after a random backoff.
- time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
- err = os.Rename(oldpath, newpath)
- }
- // If the error was a LinkError, try copying the file over.
- if _, ok := err.(*os.LinkError); !ok {
+ fi, err := os.Stat(oldpath)
+ if err != nil {
return err
}
- if err := copyFile(oldpath, newpath); err != nil {
+ if fi.Mode().IsDir() {
+ return errors.New("gosh: moving a directory is not yet supported")
+ }
+ if _, err := os.Stat(newpath); !os.IsNotExist(err) {
+ return errors.New("gosh: destination file must not exist")
+ }
+ if _, err := os.Stat(filepath.Dir(newpath)); err != nil {
+ if os.IsNotExist(err) {
+ return errors.New("gosh: destination file's parent directory must exist")
+ }
+ return err
+ }
+ if err := os.Rename(oldpath, newpath); err == nil {
+ return nil
+ }
+ // Concurrent, same-directory rename operations sometimes fail on certain
+ // systems, so we retry once after a random backoff.
+ time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
+ if err := os.Rename(oldpath, newpath); err == nil {
+ return nil
+ }
+ // Try copying the file over.
+ if err := copyFile(newpath, oldpath); err != nil {
return err
}
return os.Remove(oldpath)
@@ -540,6 +559,9 @@
// parent process, it returns immediately with no effect. In a child process for
// a Shell.FuncCmd command, it runs the specified function, then exits.
func InitMain() {
+ if calledInitMain {
+ panic("gosh: already called gosh.InitMain")
+ }
calledInitMain = true
s := os.Getenv(envInvocation)
if s == "" {
@@ -629,5 +651,6 @@
if err := sh.move(tempBinPath, binPath); err != nil {
return "", err
}
+ sh.tb.Logf("Built executable: %s\n", binPath)
return binPath, nil
}
diff --git a/gosh/shell_test.go b/gosh/shell_test.go
index 6362f77..74fea0b 100644
--- a/gosh/shell_test.go
+++ b/gosh/shell_test.go
@@ -7,7 +7,6 @@
// TODO(sadovsky): Add more tests:
// - effects of Shell.Cleanup
// - Shell.{PropagateChildOutput,ChildOutputDir,Vars,Args}
-// - Shell.{Move,MakeTempFile}
// - Cmd.{IgnoreParentExit,ExitAfter,PropagateOutput}
// - Cmd.Clone
@@ -83,8 +82,8 @@
sh.ContinueOnError = continueOnError
}
-////////////////////////////////////////
-// Simple functions
+////////////////////////////////////////////////////////////////////////////////
+// Simple registered functions
// Simplified versions of various Unix commands.
var (
@@ -128,8 +127,8 @@
})
)
-////////////////////////////////////////
-// Tests
+////////////////////////////////////////////////////////////////////////////////
+// Shell tests
type customTB struct {
t *testing.T
@@ -221,6 +220,229 @@
eq(t, getwdEvalSymlinks(t), startDir)
}
+func TestMakeTempDir(t *testing.T) {
+ sh := gosh.NewShell(t)
+ defer sh.Cleanup()
+
+ name := sh.MakeTempDir()
+ fi, err := os.Stat(name)
+ ok(t, err)
+ eq(t, fi.Mode().IsDir(), true)
+}
+
+func TestMakeTempFile(t *testing.T) {
+ sh := gosh.NewShell(t)
+ defer sh.Cleanup()
+
+ file := sh.MakeTempFile()
+ fi, err := file.Stat()
+ ok(t, err)
+ eq(t, fi.Mode().IsRegular(), true)
+}
+
+func TestMove(t *testing.T) {
+ sh := gosh.NewShell(t)
+ defer sh.Cleanup()
+
+ // TODO(sadovsky): Run all tests twice: once with oldpath and newpath on the
+ // same volume, and once with oldpath and newpath on different volumes.
+ src, dst := sh.MakeTempDir(), sh.MakeTempDir()
+ // Foo files exist, bar files do not.
+ srcFoo, dstFoo := filepath.Join(src, "srcFoo"), filepath.Join(dst, "dstFoo")
+ srcBar, dstBar := filepath.Join(src, "srcBar"), filepath.Join(dst, "dstBar")
+ ioutil.WriteFile(srcFoo, []byte("srcFoo"), 0600)
+ ioutil.WriteFile(dstFoo, []byte("dstFoo"), 0600)
+
+ // Move should fail if source does not exist.
+ setsErr(t, sh, func() { sh.Move(srcBar, dstBar) })
+
+ // Move should fail if source is a directory.
+ setsErr(t, sh, func() { sh.Move(src, dstBar) })
+
+ // Move should fail if destination exists, regardless of whether it is a
+ // regular file or a directory.
+ setsErr(t, sh, func() { sh.Move(srcFoo, dstFoo) })
+ setsErr(t, sh, func() { sh.Move(srcFoo, dst) })
+
+ // Move should fail if destination's parent does not exist.
+ setsErr(t, sh, func() { sh.Move(srcFoo, filepath.Join(dst, "subdir", "a")) })
+
+ // Move should succeed if source exists and is a file, destination does not
+ // exist, and destination's parent exists.
+ sh.Move(srcFoo, dstBar)
+ if _, err := os.Stat(srcFoo); !os.IsNotExist(err) {
+ t.Fatalf("got %v, expected IsNotExist", err)
+ }
+ buf, err := ioutil.ReadFile(dstBar)
+ ok(t, err)
+ eq(t, string(buf), "srcFoo")
+}
+
+func TestShellWait(t *testing.T) {
+ sh := gosh.NewShell(t)
+ defer sh.Cleanup()
+
+ d0 := time.Duration(0)
+ d200 := 200 * time.Millisecond
+
+ c0 := sh.FuncCmd(sleepFunc, d0, 0) // not started
+ c1 := sh.Cmd("/#invalid#/!binary!") // failed to start
+ c2 := sh.FuncCmd(sleepFunc, d200, 0) // running and will succeed
+ c3 := sh.FuncCmd(sleepFunc, d200, 1) // running and will fail
+ c4 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded
+ c5 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded, called wait
+ c6 := sh.FuncCmd(sleepFunc, d0, 1) // failed
+ c7 := sh.FuncCmd(sleepFunc, d0, 1) // failed, called wait
+
+ c3.ExitErrorIsOk = true
+ c6.ExitErrorIsOk = true
+ c7.ExitErrorIsOk = true
+
+ // Make sure c1 fails to start. This indirectly tests that Shell.Cleanup works
+ // even if Cmd.Start failed.
+ setsErr(t, sh, c1.Start)
+
+ // Start commands, then wait for them to exit.
+ for _, c := range []*gosh.Cmd{c2, c3, c4, c5, c6, c7} {
+ c.Start()
+ }
+ // Wait for a bit to allow the zero-sleep commands to exit.
+ time.Sleep(100 * time.Millisecond)
+ c5.Wait()
+ c7.Wait()
+ sh.Wait()
+
+ // It should be possible to run existing unstarted commands, and to create and
+ // run new commands, after calling Shell.Wait.
+ c0.Run()
+ sh.FuncCmd(sleepFunc, d0, 0).Run()
+ sh.FuncCmd(sleepFunc, d0, 0).Start()
+
+ // Call Shell.Wait again.
+ sh.Wait()
+}
+
+// Tests that Shell.Ok panics under various conditions.
+func TestOkPanics(t *testing.T) {
+ func() { // errDidNotCallNewShell
+ sh := gosh.Shell{}
+ defer func() { neq(t, recover(), nil) }()
+ sh.Ok()
+ }()
+ func() { // errShellErrIsNotNil
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
+ defer sh.Cleanup()
+ sh.Err = fakeError
+ defer func() { neq(t, recover(), nil) }()
+ sh.Ok()
+ }()
+ func() { // errAlreadyCalledCleanup
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
+ sh.Cleanup()
+ defer func() { neq(t, recover(), nil) }()
+ sh.Ok()
+ }()
+}
+
+// Tests that Shell.HandleError panics under various conditions.
+func TestHandleErrorPanics(t *testing.T) {
+ func() { // errDidNotCallNewShell
+ sh := gosh.Shell{}
+ defer func() { neq(t, recover(), nil) }()
+ sh.HandleError(fakeError)
+ }()
+ func() { // errShellErrIsNotNil
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
+ defer sh.Cleanup()
+ sh.Err = fakeError
+ defer func() { neq(t, recover(), nil) }()
+ sh.HandleError(fakeError)
+ }()
+ func() { // errAlreadyCalledCleanup
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
+ sh.Cleanup()
+ defer func() { neq(t, recover(), nil) }()
+ sh.HandleError(fakeError)
+ }()
+}
+
+// Tests that Shell.Cleanup panics under various conditions.
+func TestCleanupPanics(t *testing.T) {
+ func() { // errDidNotCallNewShell
+ sh := gosh.Shell{}
+ defer func() { neq(t, recover(), nil) }()
+ sh.Cleanup()
+ }()
+}
+
+// Tests that Shell.Cleanup succeeds even if sh.Err is not nil.
+func TestCleanupAfterError(t *testing.T) {
+ sh := gosh.NewShell(t)
+ sh.Err = fakeError
+ sh.Cleanup()
+}
+
+// Tests that Shell.Cleanup can be called multiple times.
+func TestMultipleCleanup(t *testing.T) {
+ sh := gosh.NewShell(t)
+ sh.Cleanup()
+ sh.Cleanup()
+}
+
+// Tests that Shell.HandleError logs errors using an appropriate runtime.Caller
+// skip value.
+func TestHandleErrorLogging(t *testing.T) {
+ tb := &customTB{t: t, buf: &bytes.Buffer{}}
+ sh := gosh.NewShell(tb)
+ defer sh.Cleanup()
+
+ // Call HandleError, then check that the stack trace and error got logged.
+ tb.Reset()
+ sh.HandleError(fakeError)
+ _, file, line, _ := runtime.Caller(0)
+ got, wantSuffix := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
+ if !strings.HasSuffix(got, wantSuffix) {
+ t.Fatalf("got %v, want suffix %v", got, wantSuffix)
+ }
+ if got == wantSuffix {
+ t.Fatalf("missing stack trace: %v", got)
+ }
+ sh.Err = nil
+
+ // Same as above, but with ContinueOnError set to true. Only the error should
+ // get logged.
+ sh.ContinueOnError = true
+ tb.Reset()
+ sh.HandleError(fakeError)
+ _, file, line, _ = runtime.Caller(0)
+ got, want := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
+ eq(t, got, want)
+ sh.Err = nil
+
+ // Same as above, but calling HandleErrorWithSkip, with skip set to 1.
+ tb.Reset()
+ sh.HandleErrorWithSkip(fakeError, 1)
+ _, file, line, _ = runtime.Caller(0)
+ got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
+ eq(t, got, want)
+ sh.Err = nil
+
+ // Same as above, but with skip set to 2.
+ tb.Reset()
+ sh.HandleErrorWithSkip(fakeError, 2)
+ _, file, line, _ = runtime.Caller(1)
+ got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line, fakeError)
+ eq(t, got, want)
+ sh.Err = nil
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Cmd tests
+
const (
helloWorldPkg = "v.io/x/lib/gosh/internal/hello_world"
helloWorldStr = "Hello, world!\n"
@@ -266,51 +488,6 @@
eq(t, c.Stdout(), helloWorldStr)
}
-// Tests BuildGoPkg's handling of the -o flag.
-func TestBuildGoPkg(t *testing.T) {
- if testing.Short() {
- t.Skip()
- }
- sh := gosh.NewShell(t)
- defer sh.Cleanup()
-
- // Set -o to an absolute name.
- relName := "hw"
- absName := filepath.Join(sh.MakeTempDir(), relName)
- eq(t, gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", absName), absName)
- c := sh.Cmd(absName)
- eq(t, c.Stdout(), helloWorldStr)
-
- // Set -o to a relative name with no path separators.
- binDir := sh.MakeTempDir()
- absName = filepath.Join(binDir, relName)
- eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relName), absName)
- c = sh.Cmd(absName)
- eq(t, c.Stdout(), helloWorldStr)
-
- // Set -o to a relative name that contains a path separator.
- relNameWithSlash := filepath.Join("subdir", relName)
- absName = filepath.Join(binDir, relNameWithSlash)
- eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relNameWithSlash), absName)
- c = sh.Cmd(absName)
- eq(t, c.Stdout(), helloWorldStr)
-
- // Missing location after -o.
- setsErr(t, sh, func() { gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o") })
-
- // Multiple -o.
- absName = filepath.Join(sh.MakeTempDir(), relName)
- gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", relName, "-o", absName)
- c = sh.Cmd(absName)
- eq(t, c.Stdout(), helloWorldStr)
-
- // Use --o instead of -o.
- absName = filepath.Join(sh.MakeTempDir(), relName)
- gosh.BuildGoPkg(sh, "", helloWorldPkg, "--o", absName)
- c = sh.Cmd(absName)
- eq(t, c.Stdout(), helloWorldStr)
-}
-
// Tests that Shell.Cmd uses Shell.Vars["PATH"] to locate executables with
// relative names.
func TestLookPath(t *testing.T) {
@@ -387,7 +564,7 @@
}
// Tests that AwaitVars returns immediately when the process exits.
-func TestAwaitProcessExit(t *testing.T) {
+func TestAwaitVarsProcessExit(t *testing.T) {
sh := gosh.NewShell(t)
defer sh.Cleanup()
@@ -717,50 +894,6 @@
setsErr(t, sh, func() { c.Terminate(os.Interrupt) })
}
-func TestShellWait(t *testing.T) {
- sh := gosh.NewShell(t)
- defer sh.Cleanup()
-
- d0 := time.Duration(0)
- d200 := 200 * time.Millisecond
-
- c0 := sh.FuncCmd(sleepFunc, d0, 0) // not started
- c1 := sh.Cmd("/#invalid#/!binary!") // failed to start
- c2 := sh.FuncCmd(sleepFunc, d200, 0) // running and will succeed
- c3 := sh.FuncCmd(sleepFunc, d200, 1) // running and will fail
- c4 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded
- c5 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded, called wait
- c6 := sh.FuncCmd(sleepFunc, d0, 1) // failed
- c7 := sh.FuncCmd(sleepFunc, d0, 1) // failed, called wait
-
- c3.ExitErrorIsOk = true
- c6.ExitErrorIsOk = true
- c7.ExitErrorIsOk = true
-
- // Make sure c1 fails to start. This indirectly tests that Shell.Cleanup works
- // even if Cmd.Start failed.
- setsErr(t, sh, c1.Start)
-
- // Start commands, then wait for them to exit.
- for _, c := range []*gosh.Cmd{c2, c3, c4, c5, c6, c7} {
- c.Start()
- }
- // Wait for a bit to allow the zero-sleep commands to exit.
- time.Sleep(100 * time.Millisecond)
- c5.Wait()
- c7.Wait()
- sh.Wait()
-
- // It should be possible to run existing unstarted commands, and to create and
- // run new commands, after calling Shell.Wait.
- c0.Run()
- sh.FuncCmd(sleepFunc, d0, 0).Run()
- sh.FuncCmd(sleepFunc, d0, 0).Start()
-
- // Call Shell.Wait again.
- sh.Wait()
-}
-
func TestExitErrorIsOk(t *testing.T) {
sh := gosh.NewShell(t)
defer sh.Cleanup()
@@ -823,124 +956,6 @@
return 0, w.error
}
-// Tests that Shell.Ok panics under various conditions.
-func TestOkPanics(t *testing.T) {
- func() { // errDidNotCallNewShell
- sh := gosh.Shell{}
- defer func() { neq(t, recover(), nil) }()
- sh.Ok()
- }()
- func() { // errShellErrIsNotNil
- sh := gosh.NewShell(t)
- sh.ContinueOnError = true
- defer sh.Cleanup()
- sh.Err = fakeError
- defer func() { neq(t, recover(), nil) }()
- sh.Ok()
- }()
- func() { // errAlreadyCalledCleanup
- sh := gosh.NewShell(t)
- sh.ContinueOnError = true
- sh.Cleanup()
- defer func() { neq(t, recover(), nil) }()
- sh.Ok()
- }()
-}
-
-// Tests that Shell.HandleError panics under various conditions.
-func TestHandleErrorPanics(t *testing.T) {
- func() { // errDidNotCallNewShell
- sh := gosh.Shell{}
- defer func() { neq(t, recover(), nil) }()
- sh.HandleError(fakeError)
- }()
- func() { // errShellErrIsNotNil
- sh := gosh.NewShell(t)
- sh.ContinueOnError = true
- defer sh.Cleanup()
- sh.Err = fakeError
- defer func() { neq(t, recover(), nil) }()
- sh.HandleError(fakeError)
- }()
- func() { // errAlreadyCalledCleanup
- sh := gosh.NewShell(t)
- sh.ContinueOnError = true
- sh.Cleanup()
- defer func() { neq(t, recover(), nil) }()
- sh.HandleError(fakeError)
- }()
-}
-
-// Tests that Shell.Cleanup panics under various conditions.
-func TestCleanupPanics(t *testing.T) {
- func() { // errDidNotCallNewShell
- sh := gosh.Shell{}
- defer func() { neq(t, recover(), nil) }()
- sh.Cleanup()
- }()
-}
-
-// Tests that Shell.Cleanup succeeds even if sh.Err is not nil.
-func TestCleanupAfterError(t *testing.T) {
- sh := gosh.NewShell(t)
- sh.Err = fakeError
- sh.Cleanup()
-}
-
-// Tests that Shell.Cleanup can be called multiple times.
-func TestMultipleCleanup(t *testing.T) {
- sh := gosh.NewShell(t)
- sh.Cleanup()
- sh.Cleanup()
-}
-
-// Tests that Shell.HandleError logs errors using an appropriate runtime.Caller
-// skip value.
-func TestHandleErrorLogging(t *testing.T) {
- tb := &customTB{t: t, buf: &bytes.Buffer{}}
- sh := gosh.NewShell(tb)
- defer sh.Cleanup()
-
- // Call HandleError, then check that the stack trace and error got logged.
- tb.Reset()
- sh.HandleError(fakeError)
- _, file, line, _ := runtime.Caller(0)
- got, wantSuffix := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
- if !strings.HasSuffix(got, wantSuffix) {
- t.Fatalf("got %v, want suffix %v", got, wantSuffix)
- }
- if got == wantSuffix {
- t.Fatalf("missing stack trace: %v", got)
- }
- sh.Err = nil
-
- // Same as above, but with ContinueOnError set to true. Only the error should
- // get logged.
- sh.ContinueOnError = true
- tb.Reset()
- sh.HandleError(fakeError)
- _, file, line, _ = runtime.Caller(0)
- got, want := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
- eq(t, got, want)
- sh.Err = nil
-
- // Same as above, but calling HandleErrorWithSkip, with skip set to 1.
- tb.Reset()
- sh.HandleErrorWithSkip(fakeError, 1)
- _, file, line, _ = runtime.Caller(0)
- got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError)
- eq(t, got, want)
- sh.Err = nil
-
- // Same as above, but with skip set to 2.
- tb.Reset()
- sh.HandleErrorWithSkip(fakeError, 2)
- _, file, line, _ = runtime.Caller(1)
- got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line, fakeError)
- eq(t, got, want)
- sh.Err = nil
-}
-
var cmdFailureFunc = gosh.RegisterFunc("cmdFailureFunc", func(nStdout, nStderr int) error {
if _, err := os.Stdout.Write([]byte(strings.Repeat("A", nStdout))); err != nil {
return err
@@ -1033,3 +1048,51 @@
gosh.InitMain()
os.Exit(m.Run())
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Other tests
+
+// Tests BuildGoPkg's handling of the -o flag.
+func TestBuildGoPkg(t *testing.T) {
+ if testing.Short() {
+ t.Skip()
+ }
+ sh := gosh.NewShell(t)
+ defer sh.Cleanup()
+
+ // Set -o to an absolute name.
+ relName := "hw"
+ absName := filepath.Join(sh.MakeTempDir(), relName)
+ eq(t, gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", absName), absName)
+ c := sh.Cmd(absName)
+ eq(t, c.Stdout(), helloWorldStr)
+
+ // Set -o to a relative name with no path separators.
+ binDir := sh.MakeTempDir()
+ absName = filepath.Join(binDir, relName)
+ eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relName), absName)
+ c = sh.Cmd(absName)
+ eq(t, c.Stdout(), helloWorldStr)
+
+ // Set -o to a relative name that contains a path separator.
+ relNameWithSlash := filepath.Join("subdir", relName)
+ absName = filepath.Join(binDir, relNameWithSlash)
+ eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relNameWithSlash), absName)
+ c = sh.Cmd(absName)
+ eq(t, c.Stdout(), helloWorldStr)
+
+ // Missing location after -o.
+ setsErr(t, sh, func() { gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o") })
+
+ // Multiple -o.
+ absName = filepath.Join(sh.MakeTempDir(), relName)
+ gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", relName, "-o", absName)
+ c = sh.Cmd(absName)
+ eq(t, c.Stdout(), helloWorldStr)
+
+ // Use --o instead of -o.
+ absName = filepath.Join(sh.MakeTempDir(), relName)
+ gosh.BuildGoPkg(sh, "", helloWorldPkg, "--o", absName)
+ c = sh.Cmd(absName)
+ eq(t, c.Stdout(), helloWorldStr)
+}
diff --git a/lookpath/lookpath.go b/lookpath/lookpath.go
index 7ee21a6..0a448a5 100644
--- a/lookpath/lookpath.go
+++ b/lookpath/lookpath.go
@@ -60,7 +60,7 @@
}
return file, nil
}
- return "", &exec.Error{name, exec.ErrNotFound}
+ return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
// LookPrefix returns the absolute paths of all executables with the given name
@@ -114,7 +114,7 @@
sort.Sort(byBase(all))
return all, nil
}
- return nil, &exec.Error{prefix + "*", exec.ErrNotFound}
+ return nil, &exec.Error{Name: prefix + "*", Err: exec.ErrNotFound}
}
type byBase []string