Files
tailscale/tstest/deptest/deptest.go
T
Brad Fitzpatrick 2434bc69fc util/syspolicy/{setting,ptype}: move PreferenceOption and Visibility to new leaf package
Step 3 in the series. See earlier cc532efc20 and d05e6dc09e.

This step moves some types into a new leaf "ptype" package out of the
big "settings" package. The policyclient.Client will later get new
methods to return those things (as well as Duration and Uint64, which
weren't done at the time of the earlier prototype).

Updates #16998
Updates #12614

Change-Id: I4d72d8079de3b5351ed602eaa72863372bd474a2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-02 10:28:35 -07:00

126 lines
3.7 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// The deptest package contains a shared implementation of negative
// dependency tests for other packages, making sure we don't start
// depending on certain packages.
package deptest
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"slices"
"strings"
"sync"
"testing"
"tailscale.com/util/set"
)
type DepChecker struct {
GOOS string // optional
GOARCH string // optional
OnDep func(string) // if non-nil, called per import
BadDeps map[string]string // package => why
WantDeps set.Set[string] // packages expected
Tags string // comma-separated
ExtraEnv []string // extra environment for "go list" (e.g. CGO_ENABLED=1)
}
func (c DepChecker) Check(t *testing.T) {
if runtime.GOOS == "windows" {
// Slow and avoid caring about "go.exe" etc.
t.Skip("skipping dep tests on windows hosts")
}
t.Helper()
cmd := exec.Command("go", "list", "-json", "-tags="+c.Tags, ".")
var extraEnv []string
if c.GOOS != "" {
extraEnv = append(extraEnv, "GOOS="+c.GOOS)
}
if c.GOARCH != "" {
extraEnv = append(extraEnv, "GOARCH="+c.GOARCH)
}
extraEnv = append(extraEnv, c.ExtraEnv...)
cmd.Env = append(os.Environ(), extraEnv...)
out, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
var res struct {
Deps []string
}
if err := json.Unmarshal(out, &res); err != nil {
t.Fatal(err)
}
tsRoot := sync.OnceValue(func() string {
out, err := exec.Command("go", "list", "-f", "{{.Dir}}", "tailscale.com").Output()
if err != nil {
t.Fatalf("failed to find tailscale.com root: %v", err)
}
return strings.TrimSpace(string(out))
})
for _, dep := range res.Deps {
if c.OnDep != nil {
c.OnDep(dep)
}
if why, ok := c.BadDeps[dep]; ok {
t.Errorf("package %q is not allowed as a dependency (env: %q); reason: %s", dep, extraEnv, why)
}
}
// Make sure the BadDeps packages actually exists. If they got renamed or
// moved around, we should update the test referencing the old name.
// Doing this in the general case requires network access at runtime
// (resolving a package path to its module, possibly doing the ?go-get=1
// meta tag dance), so we just check the common case of
// "tailscale.com/*" packages for now, with the assumption that all
// "tailscale.com/*" packages are in the same module, which isn't
// necessarily true in the general case.
for dep := range c.BadDeps {
if suf, ok := strings.CutPrefix(dep, "tailscale.com/"); ok {
pkgDir := filepath.Join(tsRoot(), suf)
if _, err := os.Stat(pkgDir); err != nil {
t.Errorf("listed BadDep %q doesn't seem to exist anymore: %v", dep, err)
}
}
}
for dep := range c.WantDeps {
if !slices.Contains(res.Deps, dep) {
t.Errorf("expected package %q to be a dependency (env: %q)", dep, extraEnv)
}
}
t.Logf("got %d dependencies", len(res.Deps))
}
// ImportAliasCheck checks that all packages are imported according to Tailscale
// conventions.
func ImportAliasCheck(t testing.TB, relDir string) {
dir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
dir = filepath.Join(dir, relDir)
cmd := exec.Command("git", "grep", "-n", "-F", `"golang.org/x/exp/`)
cmd.Dir = dir
matches, err := cmd.CombinedOutput()
if err != nil {
t.Logf("ignoring error: %v, %s", err, matches)
return
}
badRx := regexp.MustCompile(`^([^:]+:\d+):\s+"golang\.org/x/exp/(slices|maps)"`)
if s := strings.TrimSpace(string(matches)); s != "" {
for _, line := range strings.Split(s, "\n") {
if m := badRx.FindStringSubmatch(line); m != nil {
t.Errorf("%s: the x/exp/%s package should be imported as x%s", m[1], m[2], m[2])
}
}
}
}