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/internal/script"
    10  	"cmd/internal/script/scripttest"
    11  	"errors"
    12  	"fmt"
    13  	"internal/buildcfg"
    14  	"internal/testenv"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"runtime/debug"
    21  	"sync"
    22  	"testing"
    23  
    24  	"golang.org/x/mod/semver"
    25  )
    26  
    27  func scriptConditions(t *testing.T) map[string]script.Cond {
    28  	conds := scripttest.DefaultConds()
    29  
    30  	scripttest.AddToolChainScriptConditions(t, conds, goHostOS, goHostArch)
    31  
    32  	add := func(name string, cond script.Cond) {
    33  		if _, ok := conds[name]; ok {
    34  			panic(fmt.Sprintf("condition %q is already registered", name))
    35  		}
    36  		conds[name] = cond
    37  	}
    38  
    39  	lazyBool := func(summary string, f func() bool) script.Cond {
    40  		return script.OnceCondition(summary, func() (bool, error) { return f(), nil })
    41  	}
    42  
    43  	add("abscc", script.Condition("default $CC path is absolute and exists", defaultCCIsAbsolute))
    44  	add("bzr", lazyBool("the 'bzr' executable exists and provides the standard CLI", hasWorkingBzr))
    45  	add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive))
    46  	add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs))
    47  	add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit))
    48  	add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
    49  	add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet))
    50  	add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
    51  	add("default-cgo", lazyBool("when CGO_ENABLED=1|0 was set in make.bash", defaultCgo))
    52  
    53  	return conds
    54  }
    55  
    56  func defaultCCIsAbsolute(s *script.State) (bool, error) {
    57  	GOOS, _ := s.LookupEnv("GOOS")
    58  	GOARCH, _ := s.LookupEnv("GOARCH")
    59  	defaultCC := cfg.DefaultCC(GOOS, GOARCH)
    60  	if filepath.IsAbs(defaultCC) {
    61  		if _, err := exec.LookPath(defaultCC); err == nil {
    62  			return true, nil
    63  		}
    64  	}
    65  	return false, nil
    66  }
    67  
    68  func ccIs(s *script.State, want string) (bool, error) {
    69  	CC, _ := s.LookupEnv("CC")
    70  	if CC != "" {
    71  		return CC == want, nil
    72  	}
    73  	GOOS, _ := s.LookupEnv("GOOS")
    74  	GOARCH, _ := s.LookupEnv("GOARCH")
    75  	return cfg.DefaultCC(GOOS, GOARCH) == want, nil
    76  }
    77  
    78  var scriptNetEnabled sync.Map // testing.TB → already enabled
    79  
    80  func hasNet(s *script.State, host string) (bool, error) {
    81  	if !testenv.HasExternalNetwork() {
    82  		return false, nil
    83  	}
    84  
    85  	// TODO(bcmills): Add a flag or environment variable to allow skipping tests
    86  	// for specific hosts and/or skipping all net tests except for specific hosts.
    87  
    88  	t, ok := tbFromContext(s.Context())
    89  	if !ok {
    90  		return false, errors.New("script Context unexpectedly missing testing.TB key")
    91  	}
    92  
    93  	if netTestSem != nil {
    94  		// When the number of external network connections is limited, we limit the
    95  		// number of net tests that can run concurrently so that the overall number
    96  		// of network connections won't exceed the limit.
    97  		_, dup := scriptNetEnabled.LoadOrStore(t, true)
    98  		if !dup {
    99  			// Acquire a net token for this test until the test completes.
   100  			netTestSem <- struct{}{}
   101  			t.Cleanup(func() {
   102  				<-netTestSem
   103  				scriptNetEnabled.Delete(t)
   104  			})
   105  		}
   106  	}
   107  
   108  	// Since we have confirmed that the network is available,
   109  	// allow cmd/go to use it.
   110  	s.Setenv("TESTGONETWORK", "")
   111  	return true, nil
   112  }
   113  
   114  func isCaseSensitive() (bool, error) {
   115  	tmpdir, err := os.MkdirTemp(testTmpDir, "case-sensitive")
   116  	if err != nil {
   117  		return false, fmt.Errorf("failed to create directory to determine case-sensitivity: %w", err)
   118  	}
   119  	defer os.RemoveAll(tmpdir)
   120  
   121  	fcap := filepath.Join(tmpdir, "FILE")
   122  	if err := os.WriteFile(fcap, []byte{}, 0644); err != nil {
   123  		return false, fmt.Errorf("error writing file to determine case-sensitivity: %w", err)
   124  	}
   125  
   126  	flow := filepath.Join(tmpdir, "file")
   127  	_, err = os.ReadFile(flow)
   128  	switch {
   129  	case err == nil:
   130  		return false, nil
   131  	case os.IsNotExist(err):
   132  		return true, nil
   133  	default:
   134  		return false, fmt.Errorf("unexpected error reading file when determining case-sensitivity: %w", err)
   135  	}
   136  }
   137  
   138  func isTrimpath() (bool, error) {
   139  	info, _ := debug.ReadBuildInfo()
   140  	if info == nil {
   141  		return false, errors.New("missing build info")
   142  	}
   143  
   144  	for _, s := range info.Settings {
   145  		if s.Key == "-trimpath" && s.Value == "true" {
   146  			return true, nil
   147  		}
   148  	}
   149  	return false, nil
   150  }
   151  
   152  func hasWorkingGit() bool {
   153  	if runtime.GOOS == "plan9" {
   154  		// The Git command is usually not the real Git on Plan 9.
   155  		// See https://golang.org/issues/29640.
   156  		return false
   157  	}
   158  	_, err := exec.LookPath("git")
   159  	return err == nil
   160  }
   161  
   162  // Capture the major, minor and (optionally) patch version, but ignore anything later
   163  var gitVersLineExtract = regexp.MustCompile(`git version\s+(\d+\.\d+(?:\.\d+)?)`)
   164  
   165  func gitVersion() (string, error) {
   166  	gitOut, runErr := exec.Command("git", "version").CombinedOutput()
   167  	if runErr != nil {
   168  		return "v0", fmt.Errorf("failed to execute git version: %w", runErr)
   169  	}
   170  	matches := gitVersLineExtract.FindSubmatch(gitOut)
   171  	if len(matches) < 2 {
   172  		return "v0", fmt.Errorf("git version extraction regexp did not match version line: %q", gitOut)
   173  	}
   174  	return "v" + string(matches[1]), nil
   175  }
   176  
   177  func hasAtLeastGitVersion(minVers string) (bool, error) {
   178  	gitVers, gitVersErr := gitVersion()
   179  	if gitVersErr != nil {
   180  		return false, gitVersErr
   181  	}
   182  	return semver.Compare(minVers, gitVers) <= 0, nil
   183  }
   184  
   185  func gitSupportsSHA256() (bool, error) {
   186  	return hasAtLeastGitVersion("v2.29")
   187  }
   188  
   189  func hasWorkingBzr() bool {
   190  	bzr, err := exec.LookPath("bzr")
   191  	if err != nil {
   192  		return false
   193  	}
   194  	// Check that 'bzr help' exits with code 0.
   195  	// See go.dev/issue/71504 for an example where 'bzr' exists in PATH but doesn't work.
   196  	err = exec.Command(bzr, "help").Run()
   197  	return err == nil
   198  }
   199  
   200  func defaultCgo() bool {
   201  	return buildcfg.DefaultCGO_ENABLED == "1" || buildcfg.DefaultCGO_ENABLED == "0"
   202  }
   203  

View as plain text