diff --git a/feature/taildrop/integration_test.go b/feature/taildrop/integration_test.go index ad66aa827..7c7ef882a 100644 --- a/feature/taildrop/integration_test.go +++ b/feature/taildrop/integration_test.go @@ -9,6 +9,7 @@ "errors" "fmt" "io" + "runtime" "testing" "time" @@ -40,6 +41,9 @@ func TestTaildropIntegration_Fresh(t *testing.T) { // // This exercises an ipnext hook ordering issue we hit earlier. func testTaildropIntegration(t *testing.T, freshProfiles bool) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) controlOpt := integration.ConfigureControl(func(s *testcontrol.Server) { s.AllNodesSameUser = true // required for Taildrop diff --git a/tstest/integration/capmap_test.go b/tstest/integration/capmap_test.go index 48e9dae49..5bd9432f8 100644 --- a/tstest/integration/capmap_test.go +++ b/tstest/integration/capmap_test.go @@ -5,6 +5,7 @@ import ( "errors" + "runtime" "testing" "time" @@ -14,6 +15,9 @@ // TestPeerCapMap tests that the node capability map (CapMap) is included in peer information. func TestPeerCapMap(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) @@ -96,6 +100,9 @@ func TestPeerCapMap(t *testing.T) { // TestSetNodeCapMap tests that SetNodeCapMap updates are propagated to peers. func TestSetNodeCapMap(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) diff --git a/tstest/integration/integration.go b/tstest/integration/integration.go index fe62bc5a2..eb3dcce48 100644 --- a/tstest/integration/integration.go +++ b/tstest/integration/integration.go @@ -9,6 +9,7 @@ import ( "bytes" + "cmp" "context" "crypto/tls" "encoding/json" @@ -26,6 +27,7 @@ "path/filepath" "regexp" "runtime" + "slices" "strconv" "strings" "sync" @@ -60,8 +62,7 @@ verboseTailscaled = flag.Bool("verbose-tailscaled", false, "verbose tailscaled logging") verboseTailscale = flag.Bool("verbose-tailscale", false, "verbose tailscale CLI logging") - // runWindowsServiceTests enables the Windows service-mode integration tests. - // On by default in CI; tests opt in via NewTestEnv(t, canRunAsServiceOnWindows()). + // runWindowsServiceTests enables the Windows service-mode integration tests, on by default in CI. runWindowsServiceTests = flag.Bool("run-windows-service-tests", cibuild.On(), "run Windows service-mode integration tests") ) @@ -541,37 +542,10 @@ func (f ConfigureControl) ModifyTestEnv(te *TestEnv) { f(te.Control) } -// canRunAsServiceOnWindowsOpt is the TestEnvOpt returned by canRunAsServiceOnWindows. -type canRunAsServiceOnWindowsOpt struct{} - -func (canRunAsServiceOnWindowsOpt) ModifyTestEnv(te *TestEnv) { - // Only run as a service on Windows; on other platforms the test runs - // the normal userspace daemon with a faked Windows GOOS, as it always has. - if runtime.GOOS == "windows" { - te.windowsService = true - } -} - -// canRunAsServiceOnWindows enables the test to run on Windows. -// TODO(#20464): remove this and explicitly skip tests that need more work -// before they can run on Windows, instead of requiring tests to opt in with this option. -func canRunAsServiceOnWindows() TestEnvOpt { return canRunAsServiceOnWindowsOpt{} } - // NewTestEnv starts a bunch of services and returns a new test environment. // NewTestEnv arranges for the environment's resources to be cleaned up on exit. func NewTestEnv(t testing.TB, opts ...TestEnvOpt) *TestEnv { - // Integration tests skip on Windows unless a test opts in via canRunAsServiceOnWindows. - // Pre-scan the opts before starting any servers so a skip leaks nothing. - canRunAsService := false - for _, o := range opts { - if _, ok := o.(canRunAsServiceOnWindowsOpt); ok { - canRunAsService = true - } - } if runtime.GOOS == "windows" { - if !canRunAsService { - t.Skip("integration tests skip on Windows unless the test calls canRunAsServiceOnWindows") - } if !*runWindowsServiceTests { t.Skip("Windows service tests disabled (--run-windows-service-tests=false)") } @@ -587,6 +561,7 @@ func NewTestEnv(t testing.TB, opts ...TestEnvOpt) *TestEnv { binaries := GetBinaries(t) e := &TestEnv{ t: t, + windowsService: runtime.GOOS == "windows", cli: binaries.Tailscale.Path, daemon: binaries.Tailscaled.Path, LogCatcher: logc, @@ -1130,6 +1105,16 @@ func (n *TestNode) TailscaleForOutput(arg ...string) *exec.Cmd { // Tailscale returns a command that runs the tailscale CLI with the provided arguments. // It does not start the process. func (n *TestNode) Tailscale(arg ...string) *exec.Cmd { + isUp := len(arg) > 0 && arg[0] == "up" + if isUp && cmp.Or(n.upFlagGOOS, runtime.GOOS) == "windows" { + isBareUp := len(arg) == 1 + // --unattended keeps the current profile after the CLI exits; without it + // Windows switches to an empty background profile and the node drops to NoState. + // TODO(yaruk): also run tests without --unattended; see #20751. + if !isBareUp && !slices.Contains(arg, "--unattended") { + arg = append(arg, "--unattended") + } + } cmd := exec.Command(n.env.cli) cmd.Args = append(cmd.Args, "--socket="+n.sockFile) cmd.Args = append(cmd.Args, arg...) diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index d11b0bbed..0a84f52b5 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -207,6 +207,9 @@ func TestExpectedFeaturesLinked(t *testing.T) { } func TestCollectPanic(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("has a Windows panic-capture race; see #20443") + } tstest.Parallel(t) env := NewTestEnv(t) n := NewTestNode(t, env) @@ -846,6 +849,9 @@ func(control *testcontrol.Server) { } func TestConfigFileAuthKey(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("--config is unsupported by the Windows service; see #20871") + } t.Parallel() const authKey = "opensesame" env := NewTestEnv(t, ConfigureControl(func(control *testcontrol.Server) { @@ -871,6 +877,9 @@ func TestConfigFileAuthKey(t *testing.T) { } func TestTwoNodes(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) @@ -956,6 +965,9 @@ func TestTwoNodes(t *testing.T) { // tests two nodes where the first gets a incremental MapResponse (with only // PeersRemoved set) saying that the second node disappeared. func TestIncrementalMapUpdatePeersRemoved(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) @@ -1043,6 +1055,9 @@ func TestIncrementalMapUpdatePeersRemoved(t *testing.T) { // This covers VIP additions at runtime, where the VIP route is not reachable // before the map mutation but is reachable over TSMP afterward. func TestIncrementalMapUpdatePeerAllowedIPsReachability(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) @@ -1268,6 +1283,9 @@ func TestC2NPingRequest(t *testing.T) { // Issue 2434: when "down" (WantRunning false), tailscaled shouldn't // be connected to control. func TestNoControlConnWhenDown(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("restarting the daemon with preserved state needs harness support; see #20750") + } tstest.Parallel(t) env := NewTestEnv(t) n1 := NewTestNode(t, env) @@ -1317,7 +1335,7 @@ func TestNoControlConnWhenDown(t *testing.T) { // without the GUI to kick off a Start. func TestOneNodeUpWindowsStyle(t *testing.T) { tstest.Parallel(t) - env := NewTestEnv(t, canRunAsServiceOnWindows()) + env := NewTestEnv(t) n1 := NewTestNode(t, env) n1.upFlagGOOS = "windows" @@ -1335,6 +1353,9 @@ func TestOneNodeUpWindowsStyle(t *testing.T) { // jailed node cannot initiate connections to the other node however the other // node can initiate connections to the jailed node. func TestClientSideJailing(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/17419") tstest.Parallel(t) env := NewTestEnv(t) @@ -1447,6 +1468,9 @@ func TestClientSideJailing(t *testing.T) { // TestNATPing creates two nodes, n1 and n2, sets up masquerades for both and // tries to do bi-directional pings between them. func TestNATPing(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/12169") tstest.Parallel(t) for _, v6 := range []bool{false, true} { @@ -1575,6 +1599,9 @@ func TestNATPing(t *testing.T) { } func TestLogoutRemovesAllPeers(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t) // Spin up some nodes. @@ -1634,6 +1661,9 @@ func TestAutoUpdateDefaults_cap(t *testing.T) { testAutoUpdateDefaults(t, true) // useCap is whether to use NodeAttrDefaultAutoUpdate (as opposed to the old // DeprecatedDefaultAutoUpdate top-level MapResponse field). func testAutoUpdateDefaults(t *testing.T, useCap bool) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } t.Cleanup(feature.HookCanAutoUpdate.SetForTest(func() bool { return true })) env := NewTestEnv(t) @@ -2118,6 +2148,9 @@ func TestNetstackUDPLoopback(t *testing.T) { } func TestEncryptStateMigration(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("--encrypt-state is unsupported by the Windows service; see #20872") + } if !hostinfo.New().TPM.Present() { t.Skip("TPM not available") } @@ -2178,6 +2211,9 @@ func TestEncryptStateMigration(t *testing.T) { // relay between all 3 nodes, and "tailscale debug peer-relay-sessions" returns // expected values. func TestPeerRelayPing(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/17251") tstest.Parallel(t) @@ -2318,6 +2354,9 @@ func TestPeerRelayPing(t *testing.T) { } func TestC2NDebugNetmap(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t, ConfigureControl(func(s *testcontrol.Server) { s.CollectServices = opt.False @@ -2456,7 +2495,9 @@ func TestC2NDebugNetmap(t *testing.T) { } func TestTailnetLock(t *testing.T) { - + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } // If you run `tailscale lock log` on a node where Tailnet Lock isn't // enabled, you get an error explaining that. t.Run("log-when-not-enabled", func(t *testing.T) { @@ -2598,6 +2639,9 @@ func TestTailnetLock(t *testing.T) { } func TestNodeWithBadStateFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("service harness can't seed a corrupt state file before start; see #20750") + } tstest.Parallel(t) env := NewTestEnv(t) n1 := NewTestNode(t, env) diff --git a/tstest/integration/whois_test.go b/tstest/integration/whois_test.go index 258a22987..6171110d7 100644 --- a/tstest/integration/whois_test.go +++ b/tstest/integration/whois_test.go @@ -8,6 +8,7 @@ "errors" "fmt" "net" + "runtime" "testing" "time" @@ -21,6 +22,9 @@ // netstack forwards the connection to localhost, and the listener // calls WhoIs on n2's LocalAPI to identify the remote peer as n1. func TestUserspaceWhoIsProxyMap(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("multiple nodes need the userspace-peer harness; see #20711") + } tstest.Parallel(t) env := NewTestEnv(t)