Source file src/cmd/go/scriptconds_test.go

     1  // Copyright 2018 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 main_test
     6  
     7  import (
     8  	"cmd/go/internal/cfg"
     9  	"cmd/go/internal/script"
    10  	"cmd/go/internal/script/scripttest"
    11  	"errors"
    12  	"fmt"
    13  	"internal/buildcfg"
    14  	"internal/platform"
    15  	"internal/testenv"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"runtime"
    20  	"runtime/debug"
    21  	"strings"
    22  	"sync"
    23  )
    24  
    25  func scriptConditions() map[string]script.Cond {
    26  	conds := scripttest.DefaultConds()
    27  
    28  	add := func(name string, cond script.Cond) {
    29  		if _, ok := conds[name]; ok {
    30  			panic(fmt.Sprintf("condition %q is already registered", name))
    31  		}
    32  		conds[name] = cond
    33  	}
    34  
    35  	lazyBool := func(summary string, f func() bool) script.Cond {
    36  		return script.OnceCondition(summary, func() (bool, error) { return f(), nil })
    37  	}
    38  
    39  	add("abscc", script.Condition("default $CC path is absolute and exists", defaultCCIsAbsolute))
    40  	add("asan", sysCondition("-asan", platform.ASanSupported, true))
    41  	add("buildmode", script.PrefixCondition("go supports -buildmode=<suffix>", hasBuildmode))
    42  	add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive))
    43  	add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs))
    44  	add("cgo", script.BoolCondition("host CGO_ENABLED", testenv.HasCGO()))
    45  	add("cgolinkext", script.Condition("platform requires external linking for cgo", cgoLinkExt))
    46  	add("cross", script.BoolCondition("cmd/go GOOS/GOARCH != GOHOSTOS/GOHOSTARCH", goHostOS != runtime.GOOS || goHostArch != runtime.GOARCH))
    47  	add("fuzz", sysCondition("-fuzz", platform.FuzzSupported, false))
    48  	add("fuzz-instrumented", sysCondition("-fuzz with instrumentation", platform.FuzzInstrumented, false))
    49  	add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit))
    50  	add("GODEBUG", script.PrefixCondition("GODEBUG contains <suffix>", hasGodebug))
    51  	add("GOEXPERIMENT", script.PrefixCondition("GOEXPERIMENT <suffix> is enabled", hasGoexperiment))
    52  	add("go-builder", script.BoolCondition("GO_BUILDER_NAME is non-empty", testenv.Builder() != ""))
    53  	add("link", lazyBool("testenv.HasLink()", testenv.HasLink))
    54  	add("mismatched-goroot", script.Condition("test's GOROOT_FINAL does not match the real GOROOT", isMismatchedGoroot))
    55  	add("msan", sysCondition("-msan", platform.MSanSupported, true))
    56  	add("mustlinkext", script.Condition("platform always requires external linking", mustLinkExt))
    57  	add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet))
    58  	add("race", sysCondition("-race", platform.RaceDetectorSupported, true))
    59  	add("symlink", lazyBool("testenv.HasSymlink()", testenv.HasSymlink))
    60  	add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
    61  
    62  	return conds
    63  }
    64  
    65  func defaultCCIsAbsolute(s *script.State) (bool, error) {
    66  	GOOS, _ := s.LookupEnv("GOOS")
    67  	GOARCH, _ := s.LookupEnv("GOARCH")
    68  	defaultCC := cfg.DefaultCC(GOOS, GOARCH)
    69  	if filepath.IsAbs(defaultCC) {
    70  		if _, err := exec.LookPath(defaultCC); err == nil {
    71  			return true, nil
    72  		}
    73  	}
    74  	return false, nil
    75  }
    76  
    77  func ccIs(s *script.State, want string) (bool, error) {
    78  	CC, _ := s.LookupEnv("CC")
    79  	if CC != "" {
    80  		return CC == want, nil
    81  	}
    82  	GOOS, _ := s.LookupEnv("GOOS")
    83  	GOARCH, _ := s.LookupEnv("GOARCH")
    84  	return cfg.DefaultCC(GOOS, GOARCH) == want, nil
    85  }
    86  
    87  func isMismatchedGoroot(s *script.State) (bool, error) {
    88  	gorootFinal, _ := s.LookupEnv("GOROOT_FINAL")
    89  	if gorootFinal == "" {
    90  		gorootFinal, _ = s.LookupEnv("GOROOT")
    91  	}
    92  	return gorootFinal != testGOROOT, nil
    93  }
    94  
    95  func sysCondition(flag string, f func(goos, goarch string) bool, needsCgo bool) script.Cond {
    96  	return script.Condition(
    97  		"GOOS/GOARCH supports "+flag,
    98  		func(s *script.State) (bool, error) {
    99  			GOOS, _ := s.LookupEnv("GOOS")
   100  			GOARCH, _ := s.LookupEnv("GOARCH")
   101  			cross := goHostOS != GOOS || goHostArch != GOARCH
   102  			return (!needsCgo || (testenv.HasCGO() && !cross)) && f(GOOS, GOARCH), nil
   103  		})
   104  }
   105  
   106  func hasBuildmode(s *script.State, mode string) (bool, error) {
   107  	GOOS, _ := s.LookupEnv("GOOS")
   108  	GOARCH, _ := s.LookupEnv("GOARCH")
   109  	return platform.BuildModeSupported(runtime.Compiler, mode, GOOS, GOARCH), nil
   110  }
   111  
   112  var scriptNetEnabled sync.Map // testing.TB → already enabled
   113  
   114  func hasNet(s *script.State, host string) (bool, error) {
   115  	if !testenv.HasExternalNetwork() {
   116  		return false, nil
   117  	}
   118  
   119  	// TODO(bcmills): Add a flag or environment variable to allow skipping tests
   120  	// for specific hosts and/or skipping all net tests except for specific hosts.
   121  
   122  	t, ok := tbFromContext(s.Context())
   123  	if !ok {
   124  		return false, errors.New("script Context unexpectedly missing testing.TB key")
   125  	}
   126  
   127  	if netTestSem != nil {
   128  		// When the number of external network connections is limited, we limit the
   129  		// number of net tests that can run concurrently so that the overall number
   130  		// of network connections won't exceed the limit.
   131  		_, dup := scriptNetEnabled.LoadOrStore(t, true)
   132  		if !dup {
   133  			// Acquire a net token for this test until the test completes.
   134  			netTestSem <- struct{}{}
   135  			t.Cleanup(func() {
   136  				<-netTestSem
   137  				scriptNetEnabled.Delete(t)
   138  			})
   139  		}
   140  	}
   141  
   142  	// Since we have confirmed that the network is available,
   143  	// allow cmd/go to use it.
   144  	s.Setenv("TESTGONETWORK", "")
   145  	return true, nil
   146  }
   147  
   148  func hasGodebug(s *script.State, value string) (bool, error) {
   149  	godebug, _ := s.LookupEnv("GODEBUG")
   150  	for _, p := range strings.Split(godebug, ",") {
   151  		if strings.TrimSpace(p) == value {
   152  			return true, nil
   153  		}
   154  	}
   155  	return false, nil
   156  }
   157  
   158  func hasGoexperiment(s *script.State, value string) (bool, error) {
   159  	GOOS, _ := s.LookupEnv("GOOS")
   160  	GOARCH, _ := s.LookupEnv("GOARCH")
   161  	goexp, _ := s.LookupEnv("GOEXPERIMENT")
   162  	flags, err := buildcfg.ParseGOEXPERIMENT(GOOS, GOARCH, goexp)
   163  	if err != nil {
   164  		return false, err
   165  	}
   166  	for _, exp := range flags.All() {
   167  		if value == exp {
   168  			return true, nil
   169  		}
   170  		if strings.TrimPrefix(value, "no") == strings.TrimPrefix(exp, "no") {
   171  			return false, nil
   172  		}
   173  	}
   174  	return false, fmt.Errorf("unrecognized GOEXPERIMENT %q", value)
   175  }
   176  
   177  func isCaseSensitive() (bool, error) {
   178  	tmpdir, err := os.MkdirTemp(testTmpDir, "case-sensitive")
   179  	if err != nil {
   180  		return false, fmt.Errorf("failed to create directory to determine case-sensitivity: %w", err)
   181  	}
   182  	defer os.RemoveAll(tmpdir)
   183  
   184  	fcap := filepath.Join(tmpdir, "FILE")
   185  	if err := os.WriteFile(fcap, []byte{}, 0644); err != nil {
   186  		return false, fmt.Errorf("error writing file to determine case-sensitivity: %w", err)
   187  	}
   188  
   189  	flow := filepath.Join(tmpdir, "file")
   190  	_, err = os.ReadFile(flow)
   191  	switch {
   192  	case err == nil:
   193  		return false, nil
   194  	case os.IsNotExist(err):
   195  		return true, nil
   196  	default:
   197  		return false, fmt.Errorf("unexpected error reading file when determining case-sensitivity: %w", err)
   198  	}
   199  }
   200  
   201  func isTrimpath() (bool, error) {
   202  	info, _ := debug.ReadBuildInfo()
   203  	if info == nil {
   204  		return false, errors.New("missing build info")
   205  	}
   206  
   207  	for _, s := range info.Settings {
   208  		if s.Key == "-trimpath" && s.Value == "true" {
   209  			return true, nil
   210  		}
   211  	}
   212  	return false, nil
   213  }
   214  
   215  func hasWorkingGit() bool {
   216  	if runtime.GOOS == "plan9" {
   217  		// The Git command is usually not the real Git on Plan 9.
   218  		// See https://golang.org/issues/29640.
   219  		return false
   220  	}
   221  	_, err := exec.LookPath("git")
   222  	return err == nil
   223  }
   224  
   225  func cgoLinkExt(s *script.State) (bool, error) {
   226  	GOOS, _ := s.LookupEnv("GOOS")
   227  	GOARCH, _ := s.LookupEnv("GOARCH")
   228  	return platform.MustLinkExternal(GOOS, GOARCH, true), nil
   229  }
   230  
   231  func mustLinkExt(s *script.State) (bool, error) {
   232  	GOOS, _ := s.LookupEnv("GOOS")
   233  	GOARCH, _ := s.LookupEnv("GOARCH")
   234  	return platform.MustLinkExternal(GOOS, GOARCH, false), nil
   235  }
   236  

View as plain text