Source file src/cmd/internal/script/scripttest/setup.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package scripttest adapts the script engine for use in tests.
     6  package scripttest
     7  
     8  import (
     9  	"errors"
    10  	"internal/testenv"
    11  	"io"
    12  	"io/fs"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  )
    20  
    21  // SetupTestGoRoot sets up a temporary GOROOT for use with script test
    22  // execution. It copies the existing goroot bin and pkg dirs using
    23  // symlinks (if possible) or raw copying. Return value is the path to
    24  // the newly created testgoroot dir.
    25  func SetupTestGoRoot(t *testing.T, tmpdir string, goroot string) string {
    26  	mustMkdir := func(path string) {
    27  		if err := os.MkdirAll(path, 0777); err != nil {
    28  			t.Fatalf("SetupTestGoRoot mkdir %s failed: %v", path, err)
    29  		}
    30  	}
    31  
    32  	replicateDir := func(srcdir, dstdir string) {
    33  		files, err := os.ReadDir(srcdir)
    34  		if err != nil {
    35  			t.Fatalf("inspecting %s: %v", srcdir, err)
    36  		}
    37  		for _, file := range files {
    38  			fn := file.Name()
    39  			linkOrCopy(t, filepath.Join(srcdir, fn), filepath.Join(dstdir, fn))
    40  		}
    41  	}
    42  
    43  	// Create various dirs in testgoroot.
    44  	findToolOnce.Do(func() { findToolSub(t) })
    45  	if toolsub == "" {
    46  		t.Fatal("failed to find toolsub")
    47  	}
    48  
    49  	tomake := []string{
    50  		"bin",
    51  		"src",
    52  		"pkg",
    53  		filepath.Join("pkg", "include"),
    54  		toolsub,
    55  	}
    56  	made := []string{}
    57  	tgr := filepath.Join(tmpdir, "testgoroot")
    58  	mustMkdir(tgr)
    59  	for _, targ := range tomake {
    60  		path := filepath.Join(tgr, targ)
    61  		mustMkdir(path)
    62  		made = append(made, path)
    63  	}
    64  
    65  	// Replicate selected portions of the content.
    66  	replicateDir(filepath.Join(goroot, "bin"), made[0])
    67  	replicateDir(filepath.Join(goroot, "src"), made[1])
    68  	replicateDir(filepath.Join(goroot, "pkg", "include"), made[3])
    69  	replicateDir(filepath.Join(goroot, toolsub), made[4])
    70  
    71  	return tgr
    72  }
    73  
    74  // ReplaceGoToolInTestGoRoot replaces the go tool binary toolname with
    75  // an alternate executable newtoolpath within a test GOROOT directory
    76  // previously created by SetupTestGoRoot.
    77  func ReplaceGoToolInTestGoRoot(t *testing.T, testgoroot, toolname, newtoolpath string) {
    78  	findToolOnce.Do(func() { findToolSub(t) })
    79  	if toolsub == "" {
    80  		t.Fatal("failed to find toolsub")
    81  	}
    82  
    83  	exename := toolname
    84  	if runtime.GOOS == "windows" {
    85  		exename += ".exe"
    86  	}
    87  	toolpath := filepath.Join(testgoroot, toolsub, exename)
    88  	if err := os.Remove(toolpath); err != nil && !errors.Is(err, fs.ErrNotExist) {
    89  		t.Fatalf("removing %s: %v", toolpath, err)
    90  	}
    91  	linkOrCopy(t, newtoolpath, toolpath)
    92  }
    93  
    94  // toolsub is the tool subdirectory underneath GOROOT.
    95  var toolsub string
    96  
    97  // findToolOnce runs findToolSub only once.
    98  var findToolOnce sync.Once
    99  
   100  // findToolSub sets toolsub to the value used by the current go command.
   101  func findToolSub(t *testing.T) {
   102  	gocmd := testenv.Command(t, testenv.GoToolPath(t), "env", "GOHOSTARCH")
   103  	gocmd = testenv.CleanCmdEnv(gocmd)
   104  	goHostArchBytes, err := gocmd.CombinedOutput()
   105  	if err != nil {
   106  		t.Fatalf("%s failed: %v\n%s", gocmd, err, goHostArchBytes)
   107  	}
   108  	goHostArch := strings.TrimSpace(string(goHostArchBytes))
   109  	toolsub = filepath.Join("pkg", "tool", runtime.GOOS+"_"+goHostArch)
   110  }
   111  
   112  // linkOrCopy creates a link to src at dst, or if the symlink fails
   113  // (platform doesn't support) then copies src to dst.
   114  func linkOrCopy(t *testing.T, src, dst string) {
   115  	err := os.Symlink(src, dst)
   116  	if err == nil {
   117  		return
   118  	}
   119  	fi, err := os.Stat(src)
   120  	if err != nil {
   121  		t.Fatalf("copying %s to %s: %v", src, dst, err)
   122  	}
   123  	if fi.IsDir() {
   124  		if err := os.CopyFS(dst, os.DirFS(src)); err != nil {
   125  			t.Fatalf("copying %s to %s: %v", src, dst, err)
   126  		}
   127  		return
   128  	}
   129  	srcf, err := os.Open(src)
   130  	if err != nil {
   131  		t.Fatalf("copying %s to %s: %v", src, dst, err)
   132  	}
   133  	defer srcf.Close()
   134  	perm := os.O_WRONLY | os.O_CREATE | os.O_EXCL
   135  	dstf, err := os.OpenFile(dst, perm, 0o777)
   136  	if err != nil {
   137  		t.Fatalf("copying %s to %s: %v", src, dst, err)
   138  	}
   139  	_, err = io.Copy(dstf, srcf)
   140  	if closeErr := dstf.Close(); err == nil {
   141  		err = closeErr
   142  	}
   143  	if err != nil {
   144  		t.Fatalf("copying %s to %s: %v", src, dst, err)
   145  	}
   146  }
   147  

View as plain text