| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package scripttest adapts the script engine for use in tests. | |
| package scripttest | |
| import ( | |
| "errors" | |
| "internal/testenv" | |
| "io" | |
| "io/fs" | |
| "os" | |
| "path/filepath" | |
| "runtime" | |
| "strings" | |
| "sync" | |
| "testing" | |
| ) | |
| // SetupTestGoRoot sets up a temporary GOROOT for use with script test | |
| // execution. It copies the existing goroot bin and pkg dirs using | |
| // symlinks (if possible) or raw copying. Return value is the path to | |
| // the newly created testgoroot dir. | |
| func SetupTestGoRoot(t *testing.T, tmpdir string, goroot string) string { | |
| mustMkdir := func(path string) { | |
| if err := os.MkdirAll(path, 0777); err != nil { | |
| t.Fatalf("SetupTestGoRoot mkdir %s failed: %v", path, err) | |
| } | |
| } | |
| replicateDir := func(srcdir, dstdir string) { | |
| files, err := os.ReadDir(srcdir) | |
| if err != nil { | |
| t.Fatalf("inspecting %s: %v", srcdir, err) | |
| } | |
| for _, file := range files { | |
| fn := file.Name() | |
| linkOrCopy(t, filepath.Join(srcdir, fn), filepath.Join(dstdir, fn)) | |
| } | |
| } | |
| // Create various dirs in testgoroot. | |
| findToolOnce.Do(func() { findToolSub(t) }) | |
| if toolsub == "" { | |
| t.Fatal("failed to find toolsub") | |
| } | |
| tomake := []string{ | |
| "bin", | |
| "src", | |
| "pkg", | |
| filepath.Join("pkg", "include"), | |
| toolsub, | |
| } | |
| made := []string{} | |
| tgr := filepath.Join(tmpdir, "testgoroot") | |
| mustMkdir(tgr) | |
| for _, targ := range tomake { | |
| path := filepath.Join(tgr, targ) | |
| mustMkdir(path) | |
| made = append(made, path) | |
| } | |
| // Replicate selected portions of the content. | |
| replicateDir(filepath.Join(goroot, "bin"), made[0]) | |
| replicateDir(filepath.Join(goroot, "src"), made[1]) | |
| replicateDir(filepath.Join(goroot, "pkg", "include"), made[3]) | |
| replicateDir(filepath.Join(goroot, toolsub), made[4]) | |
| return tgr | |
| } | |
| // ReplaceGoToolInTestGoRoot replaces the go tool binary toolname with | |
| // an alternate executable newtoolpath within a test GOROOT directory | |
| // previously created by SetupTestGoRoot. | |
| func ReplaceGoToolInTestGoRoot(t *testing.T, testgoroot, toolname, newtoolpath string) { | |
| findToolOnce.Do(func() { findToolSub(t) }) | |
| if toolsub == "" { | |
| t.Fatal("failed to find toolsub") | |
| } | |
| exename := toolname | |
| if runtime.GOOS == "windows" { | |
| exename += ".exe" | |
| } | |
| toolpath := filepath.Join(testgoroot, toolsub, exename) | |
| if err := os.Remove(toolpath); err != nil && !errors.Is(err, fs.ErrNotExist) { | |
| t.Fatalf("removing %s: %v", toolpath, err) | |
| } | |
| linkOrCopy(t, newtoolpath, toolpath) | |
| } | |
| // toolsub is the tool subdirectory underneath GOROOT. | |
| var toolsub string | |
| // findToolOnce runs findToolSub only once. | |
| var findToolOnce sync.Once | |
| // findToolSub sets toolsub to the value used by the current go command. | |
| func findToolSub(t *testing.T) { | |
| gocmd := testenv.Command(t, testenv.GoToolPath(t), "env", "GOHOSTARCH") | |
| gocmd = testenv.CleanCmdEnv(gocmd) | |
| goHostArchBytes, err := gocmd.CombinedOutput() | |
| if err != nil { | |
| t.Fatalf("%s failed: %v\n%s", gocmd, err, goHostArchBytes) | |
| } | |
| goHostArch := strings.TrimSpace(string(goHostArchBytes)) | |
| toolsub = filepath.Join("pkg", "tool", runtime.GOOS+"_"+goHostArch) | |
| } | |
| // linkOrCopy creates a link to src at dst, or if the symlink fails | |
| // (platform doesn't support) then copies src to dst. | |
| func linkOrCopy(t *testing.T, src, dst string) { | |
| err := os.Symlink(src, dst) | |
| if err == nil { | |
| return | |
| } | |
| fi, err := os.Stat(src) | |
| if err != nil { | |
| t.Fatalf("copying %s to %s: %v", src, dst, err) | |
| } | |
| if fi.IsDir() { | |
| if err := os.CopyFS(dst, os.DirFS(src)); err != nil { | |
| t.Fatalf("copying %s to %s: %v", src, dst, err) | |
| } | |
| return | |
| } | |
| srcf, err := os.Open(src) | |
| if err != nil { | |
| t.Fatalf("copying %s to %s: %v", src, dst, err) | |
| } | |
| defer srcf.Close() | |
| perm := os.O_WRONLY | os.O_CREATE | os.O_EXCL | |
| dstf, err := os.OpenFile(dst, perm, 0o777) | |
| if err != nil { | |
| t.Fatalf("copying %s to %s: %v", src, dst, err) | |
| } | |
| _, err = io.Copy(dstf, srcf) | |
| if closeErr := dstf.Close(); err == nil { | |
| err = closeErr | |
| } | |
| if err != nil { | |
| t.Fatalf("copying %s to %s: %v", src, dst, err) | |
| } | |
| } |