From fd4680c02ccc69a3d1960620e53304ff9c35e1ad Mon Sep 17 00:00:00 2001 From: Yaruk Asghar Date: Fri, 10 Jul 2026 13:37:48 -0700 Subject: [PATCH] tstest/integration: run Windows service test via the normal windows CI run Updates #20381 Signed-off-by: Yaruk Asghar --- .github/workflows/test.yml | 12 +----- tstest/integration/integration.go | 38 ++++++++++++++++--- tstest/integration/integration_test.go | 18 --------- .../integration/integration_windows_test.go | 31 +++++++++++++++ 4 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 tstest/integration/integration_windows_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99718c87a..4a41c16ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -238,8 +238,6 @@ jobs: shard: "1/2" - key: "win-shard-2-2" shard: "2/2" - - key: "win-service" - name: "service" steps: - name: checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -264,7 +262,7 @@ jobs: - name: test shell: bash - if: startsWith(matrix.key, 'win-shard-') # sharded testwrapper legs only + if: matrix.key != 'win-bench' # skip on bench builder working-directory: src run: ./tool/go run ./cmd/testwrapper sharded:${{ matrix.shard }} env: @@ -278,14 +276,6 @@ jobs: env: NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - - name: windows service integration test - shell: bash - if: matrix.key == 'win-service' # tailscaled as a real service, outside testwrapper's retry clamp - working-directory: src - run: ./tool/go test ./tstest/integration -run '^TestOneNodeUpWindowsStyle$' -run-windows-service-tests -timeout 20m -count=1 -v - env: - NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - - name: Print stats shell: pwsh if: steps.cigocacher-setup.outputs.success == 'true' diff --git a/tstest/integration/integration.go b/tstest/integration/integration.go index 0b8c8fc0b..25826d6d9 100644 --- a/tstest/integration/integration.go +++ b/tstest/integration/integration.go @@ -50,6 +50,7 @@ "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/types/nettype" + "tailscale.com/util/cibuild" "tailscale.com/util/rands" "tailscale.com/util/zstdframe" "tailscale.com/version" @@ -59,9 +60,10 @@ verboseTailscaled = flag.Bool("verbose-tailscaled", false, "verbose tailscaled logging") verboseTailscale = flag.Bool("verbose-tailscale", false, "verbose tailscale CLI logging") - // runWindowsServiceTests opts Windows tests into starting tailscaled as a - // service instead of a userspace child process. Tests run serially in this mode. - runWindowsServiceTests = flag.Bool("run-windows-service-tests", false, "run tailscaled as a Windows service") + // runWindowsServiceTests enables the Windows service-mode integration tests + // (tailscaled installed as a service). On by default in CI. Tests opt in via + // NewTestEnv(t, WindowsServiceMode()) and run serially in this mode. + runWindowsServiceTests = flag.Bool("run-windows-service-tests", cibuild.On(), "run Windows service-mode integration tests") ) // MainError is an error that's set if an error conditions happens outside of a @@ -540,11 +542,36 @@ func (f ConfigureControl) ModifyTestEnv(te *TestEnv) { f(te.Control) } +// windowsServiceOpt is the TestEnvOpt returned by WindowsServiceMode. +type windowsServiceOpt struct{} + +func (windowsServiceOpt) ModifyTestEnv(te *TestEnv) { te.windowsService = true } + +// WindowsServiceMode makes NewTestEnv run tailscaled as a Windows service +// (install-system-daemon) rather than a userspace child process. It's a no-op +// off Windows. Only tests that pass it run on Windows; all others skip there. +func WindowsServiceMode() TestEnvOpt { return windowsServiceOpt{} } + // 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 { - if runtime.GOOS == "windows" && !*runWindowsServiceTests { - t.Skip("Windows only runs with --run-windows-service-tests (tailscaled as a service)") + // Integration tests skip on Windows unless a test opts into service mode + // via WindowsServiceMode(); a Windows service is a singleton, so the generic + // (often multi-node) tests can't run against it. Pre-scan the opts for the + // service marker before starting any servers so a skip leaks nothing. + serviceMode := false + for _, o := range opts { + if _, ok := o.(windowsServiceOpt); ok { + serviceMode = true + } + } + if runtime.GOOS == "windows" { + if !serviceMode { + t.Skip("integration tests skip on Windows unless run in service mode") + } + if !*runWindowsServiceTests { + t.Skip("Windows service tests disabled (--run-windows-service-tests=false)") + } } derpMap := RunDERPAndSTUN(t, logger.Discard, "127.0.0.1") logc := new(LogCatcher) @@ -557,7 +584,6 @@ func NewTestEnv(t testing.TB, opts ...TestEnvOpt) *TestEnv { binaries := GetBinaries(t) e := &TestEnv{ t: t, - windowsService: runtime.GOOS == "windows" && *runWindowsServiceTests, cli: binaries.Tailscale.Path, daemon: binaries.Tailscaled.Path, LogCatcher: logc, diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index 60a8ff849..75e7a21ac 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -1181,24 +1181,6 @@ func TestNoControlConnWhenDown(t *testing.T) { d2.MustCleanShutdown(t) } -// Issue 2137: make sure Windows tailscaled works with the CLI alone, -// without the GUI to kick off a Start. -func TestOneNodeUpWindowsStyle(t *testing.T) { - tstest.Parallel(t) - env := NewTestEnv(t) - n1 := NewTestNode(t, env) - n1.upFlagGOOS = "windows" - - d1 := n1.StartDaemonAsIPNGOOS("windows") - n1.AwaitResponding() - n1.MustUp("--unattended") - - t.Logf("Got IP: %v", n1.AwaitIP4()) - n1.AwaitRunning() - - d1.MustCleanShutdown(t) -} - // TestClientSideJailing tests that when one node is jailed for another, the // jailed node cannot initiate connections to the other node however the other // node can initiate connections to the jailed node. diff --git a/tstest/integration/integration_windows_test.go b/tstest/integration/integration_windows_test.go new file mode 100644 index 000000000..c1b51b9b7 --- /dev/null +++ b/tstest/integration/integration_windows_test.go @@ -0,0 +1,31 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build windows + +package integration + +import ( + "testing" + + "tailscale.com/tstest" +) + +// Issue 2137: make sure Windows tailscaled works with the CLI alone, without +// the GUI to kick off a Start. Runs tailscaled as a real Windows service via +// WindowsServiceMode; see NewTestEnv's Windows gating. +func TestOneNodeUpWindowsStyle(t *testing.T) { + tstest.Parallel(t) + env := NewTestEnv(t, WindowsServiceMode()) + n1 := NewTestNode(t, env) + n1.upFlagGOOS = "windows" + + d1 := n1.StartDaemonAsIPNGOOS("windows") + n1.AwaitResponding() + n1.MustUp("--unattended") + + t.Logf("Got IP: %v", n1.AwaitIP4()) + n1.AwaitRunning() + + d1.MustCleanShutdown(t) +}