Source file src/internal/testenv/testenv_test.go

     1  // Copyright 2022 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 testenv_test
     6  
     7  import (
     8  	"internal/platform"
     9  	"internal/testenv"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  )
    16  
    17  func TestGoToolLocation(t *testing.T) {
    18  	testenv.MustHaveGoBuild(t)
    19  
    20  	var exeSuffix string
    21  	if runtime.GOOS == "windows" {
    22  		exeSuffix = ".exe"
    23  	}
    24  
    25  	// Tests are defined to run within their package source directory,
    26  	// and this package's source directory is $GOROOT/src/internal/testenv.
    27  	// The 'go' command is installed at $GOROOT/bin/go, so if the environment
    28  	// is correct then testenv.GoTool() should be identical to ../../../bin/go.
    29  
    30  	relWant := "../../../bin/go" + exeSuffix
    31  	absWant, err := filepath.Abs(relWant)
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  
    36  	wantInfo, err := os.Stat(absWant)
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  	t.Logf("found go tool at %q (%q)", relWant, absWant)
    41  
    42  	goTool, err := testenv.GoTool()
    43  	if err != nil {
    44  		t.Fatalf("testenv.GoTool(): %v", err)
    45  	}
    46  	t.Logf("testenv.GoTool() = %q", goTool)
    47  
    48  	gotInfo, err := os.Stat(goTool)
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	if !os.SameFile(wantInfo, gotInfo) {
    53  		t.Fatalf("%q is not the same file as %q", absWant, goTool)
    54  	}
    55  }
    56  
    57  func TestHasGoBuild(t *testing.T) {
    58  	if !testenv.HasGoBuild() {
    59  		switch runtime.GOOS {
    60  		case "js", "wasip1":
    61  			// No exec syscall, so these shouldn't be able to 'go build'.
    62  			t.Logf("HasGoBuild is false on %s", runtime.GOOS)
    63  			return
    64  		}
    65  
    66  		b := testenv.Builder()
    67  		if b == "" {
    68  			// We shouldn't make assumptions about what kind of sandbox or build
    69  			// environment external Go users may be running in.
    70  			t.Skipf("skipping: 'go build' unavailable")
    71  		}
    72  
    73  		// Since we control the Go builders, we know which ones ought
    74  		// to be able to run 'go build'. Check that they can.
    75  		//
    76  		// (Note that we don't verify that any builders *can't* run 'go build'.
    77  		// If a builder starts running 'go build' tests when it shouldn't,
    78  		// we will presumably find out about it when those tests fail.)
    79  		switch runtime.GOOS {
    80  		case "ios":
    81  			if isCorelliumBuilder(b) {
    82  				// The corellium environment is self-hosting, so it should be able
    83  				// to build even though real "ios" devices can't exec.
    84  			} else {
    85  				// The usual iOS sandbox does not allow the app to start another
    86  				// process. If we add builders on stock iOS devices, they presumably
    87  				// will not be able to exec, so we may as well allow that now.
    88  				t.Logf("HasGoBuild is false on %s", b)
    89  				return
    90  			}
    91  		case "android":
    92  			if isEmulatedBuilder(b) && platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) {
    93  				// As of 2023-05-02, the test environment on the emulated builders is
    94  				// missing a C linker.
    95  				t.Logf("HasGoBuild is false on %s", b)
    96  				return
    97  			}
    98  		}
    99  
   100  		if strings.Contains(b, "-noopt") {
   101  			// The -noopt builder sets GO_GCFLAGS, which causes tests of 'go build' to
   102  			// be skipped.
   103  			t.Logf("HasGoBuild is false on %s", b)
   104  			return
   105  		}
   106  
   107  		t.Fatalf("HasGoBuild unexpectedly false on %s", b)
   108  	}
   109  
   110  	t.Logf("HasGoBuild is true; checking consistency with other functions")
   111  
   112  	hasExec := false
   113  	hasExecGo := false
   114  	t.Run("MustHaveExec", func(t *testing.T) {
   115  		testenv.MustHaveExec(t)
   116  		hasExec = true
   117  	})
   118  	t.Run("MustHaveExecPath", func(t *testing.T) {
   119  		testenv.MustHaveExecPath(t, "go")
   120  		hasExecGo = true
   121  	})
   122  	if !hasExec {
   123  		t.Errorf(`MustHaveExec(t) skipped unexpectedly`)
   124  	}
   125  	if !hasExecGo {
   126  		t.Errorf(`MustHaveExecPath(t, "go") skipped unexpectedly`)
   127  	}
   128  
   129  	dir := t.TempDir()
   130  	mainGo := filepath.Join(dir, "main.go")
   131  	if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0644); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	cmd := testenv.Command(t, "go", "build", "-o", os.DevNull, mainGo)
   135  	out, err := cmd.CombinedOutput()
   136  	if err != nil {
   137  		t.Fatalf("%v: %v\n%s", cmd, err, out)
   138  	}
   139  }
   140  
   141  func TestMustHaveExec(t *testing.T) {
   142  	hasExec := false
   143  	t.Run("MustHaveExec", func(t *testing.T) {
   144  		testenv.MustHaveExec(t)
   145  		t.Logf("MustHaveExec did not skip")
   146  		hasExec = true
   147  	})
   148  
   149  	switch runtime.GOOS {
   150  	case "js", "wasip1":
   151  		if hasExec {
   152  			// js and wasip1 lack an “exec” syscall.
   153  			t.Errorf("expected MustHaveExec to skip on %v", runtime.GOOS)
   154  		}
   155  	case "ios":
   156  		if b := testenv.Builder(); isCorelliumBuilder(b) && !hasExec {
   157  			// Most ios environments can't exec, but the corellium builder can.
   158  			t.Errorf("expected MustHaveExec not to skip on %v", b)
   159  		}
   160  	default:
   161  		if b := testenv.Builder(); b != "" && !hasExec {
   162  			t.Errorf("expected MustHaveExec not to skip on %v", b)
   163  		}
   164  	}
   165  }
   166  
   167  func TestCleanCmdEnvPWD(t *testing.T) {
   168  	// Test that CleanCmdEnv sets PWD if cmd.Dir is set.
   169  	switch runtime.GOOS {
   170  	case "plan9", "windows":
   171  		t.Skipf("PWD is not used on %s", runtime.GOOS)
   172  	}
   173  	dir := t.TempDir()
   174  	cmd := testenv.Command(t, testenv.GoToolPath(t), "help")
   175  	cmd.Dir = dir
   176  	cmd = testenv.CleanCmdEnv(cmd)
   177  
   178  	for _, env := range cmd.Env {
   179  		if strings.HasPrefix(env, "PWD=") {
   180  			pwd := strings.TrimPrefix(env, "PWD=")
   181  			if pwd != dir {
   182  				t.Errorf("unexpected PWD: want %s, got %s", dir, pwd)
   183  			}
   184  			return
   185  		}
   186  	}
   187  	t.Error("PWD not set in cmd.Env")
   188  }
   189  
   190  func isCorelliumBuilder(builderName string) bool {
   191  	// Support both the old infra's builder names and the LUCI builder names.
   192  	// The former's names are ad-hoc so we could maintain this invariant on
   193  	// the builder side. The latter's names are structured, and "corellium" will
   194  	// appear as a "host" suffix after the GOOS and GOARCH, which always begin
   195  	// with an underscore.
   196  	return strings.HasSuffix(builderName, "-corellium") || strings.Contains(builderName, "_corellium")
   197  }
   198  
   199  func isEmulatedBuilder(builderName string) bool {
   200  	// Support both the old infra's builder names and the LUCI builder names.
   201  	// The former's names are ad-hoc so we could maintain this invariant on
   202  	// the builder side. The latter's names are structured, and the signifier
   203  	// of emulation "emu" will appear as a "host" suffix after the GOOS and
   204  	// GOARCH because it modifies the run environment in such a way that it
   205  	// the target GOOS and GOARCH may not match the host. This suffix always
   206  	// begins with an underscore.
   207  	return strings.HasSuffix(builderName, "-emu") || strings.Contains(builderName, "_emu")
   208  }
   209  

View as plain text