Skip to content

Commit 2e922d6

Browse files
committed
fix(test): move SIGHUP tests to separate file for Windows compatibility
Use build tag instead of runtime skip to exclude Unix-specific SIGHUP signal tests from Windows builds. Signed-off-by: Marc Nuri <[email protected]>
1 parent dce1987 commit 2e922d6

File tree

2 files changed

+209
-213
lines changed

2 files changed

+209
-213
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
//go:build !windows
2+
3+
package cmd
4+
5+
import (
6+
"bytes"
7+
"os"
8+
"path/filepath"
9+
"slices"
10+
"strings"
11+
"syscall"
12+
"testing"
13+
"time"
14+
15+
"github.com/containers/kubernetes-mcp-server/internal/test"
16+
"github.com/containers/kubernetes-mcp-server/pkg/config"
17+
"github.com/containers/kubernetes-mcp-server/pkg/mcp"
18+
"github.com/stretchr/testify/suite"
19+
"k8s.io/klog/v2"
20+
"k8s.io/klog/v2/textlogger"
21+
)
22+
23+
// SIGHUPSuite tests the SIGHUP configuration reload behavior
24+
type SIGHUPSuite struct {
25+
suite.Suite
26+
mockServer *test.MockServer
27+
server *mcp.Server
28+
tempDir string
29+
dropInConfigDir string
30+
logBuffer *bytes.Buffer
31+
}
32+
33+
func (s *SIGHUPSuite) SetupTest() {
34+
s.mockServer = test.NewMockServer()
35+
s.mockServer.Handle(&test.DiscoveryClientHandler{})
36+
s.tempDir = s.T().TempDir()
37+
s.dropInConfigDir = filepath.Join(s.tempDir, "conf.d")
38+
s.Require().NoError(os.Mkdir(s.dropInConfigDir, 0755))
39+
40+
// Set up klog to write to our buffer so we can verify log messages
41+
s.logBuffer = &bytes.Buffer{}
42+
logger := textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(2), textlogger.Output(s.logBuffer)))
43+
klog.SetLoggerWithOptions(logger)
44+
}
45+
46+
func (s *SIGHUPSuite) TearDownTest() {
47+
if s.server != nil {
48+
s.server.Close()
49+
}
50+
if s.mockServer != nil {
51+
s.mockServer.Close()
52+
}
53+
}
54+
55+
func (s *SIGHUPSuite) InitServer(configPath, configDir string) {
56+
cfg, err := config.Read(configPath, configDir)
57+
s.Require().NoError(err)
58+
cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T())
59+
60+
s.server, err = mcp.NewServer(mcp.Configuration{
61+
StaticConfig: cfg,
62+
})
63+
s.Require().NoError(err)
64+
// Set up SIGHUP handler
65+
opts := &MCPServerOptions{
66+
ConfigPath: configPath,
67+
ConfigDir: configDir,
68+
}
69+
opts.setupSIGHUPHandler(s.server)
70+
}
71+
72+
func (s *SIGHUPSuite) TestSIGHUPReloadsConfigFromFile() {
73+
// Create initial config file - start with only core toolset (no helm)
74+
configPath := filepath.Join(s.tempDir, "config.toml")
75+
s.Require().NoError(os.WriteFile(configPath, []byte(`
76+
toolsets = ["core", "config"]
77+
`), 0644))
78+
s.InitServer(configPath, "")
79+
80+
s.Run("helm tools are not initially available", func() {
81+
s.False(slices.Contains(s.server.GetEnabledTools(), "helm_list"))
82+
})
83+
84+
// Modify the config file to add helm toolset
85+
s.Require().NoError(os.WriteFile(configPath, []byte(`
86+
toolsets = ["core", "config", "helm"]
87+
`), 0644))
88+
89+
// Send SIGHUP to current process
90+
s.Require().NoError(syscall.Kill(syscall.Getpid(), syscall.SIGHUP))
91+
92+
s.Run("helm tools become available after SIGHUP", func() {
93+
s.Require().Eventually(func() bool {
94+
return slices.Contains(s.server.GetEnabledTools(), "helm_list")
95+
}, 2*time.Second, 50*time.Millisecond)
96+
})
97+
}
98+
99+
func (s *SIGHUPSuite) TestSIGHUPReloadsFromDropInDirectory() {
100+
// Create initial config file - with helm enabled
101+
configPath := filepath.Join(s.tempDir, "config.toml")
102+
s.Require().NoError(os.WriteFile(configPath, []byte(`
103+
toolsets = ["core", "config", "helm"]
104+
`), 0644))
105+
106+
// Create initial drop-in file that removes helm
107+
dropInPath := filepath.Join(s.dropInConfigDir, "10-override.toml")
108+
s.Require().NoError(os.WriteFile(dropInPath, []byte(`
109+
toolsets = ["core", "config"]
110+
`), 0644))
111+
112+
s.InitServer(configPath, "")
113+
114+
s.Run("drop-in override removes helm from initial config", func() {
115+
s.False(slices.Contains(s.server.GetEnabledTools(), "helm_list"))
116+
})
117+
118+
// Update drop-in file to add helm back
119+
s.Require().NoError(os.WriteFile(dropInPath, []byte(`
120+
toolsets = ["core", "config", "helm"]
121+
`), 0644))
122+
123+
// Send SIGHUP
124+
s.Require().NoError(syscall.Kill(syscall.Getpid(), syscall.SIGHUP))
125+
126+
s.Run("helm tools become available after updating drop-in and sending SIGHUP", func() {
127+
s.Require().Eventually(func() bool {
128+
return slices.Contains(s.server.GetEnabledTools(), "helm_list")
129+
}, 2*time.Second, 50*time.Millisecond)
130+
})
131+
}
132+
133+
func (s *SIGHUPSuite) TestSIGHUPWithInvalidConfigContinues() {
134+
// Create initial config file - start with only core toolset (no helm)
135+
configPath := filepath.Join(s.tempDir, "config.toml")
136+
s.Require().NoError(os.WriteFile(configPath, []byte(`
137+
toolsets = ["core", "config"]
138+
`), 0644))
139+
s.InitServer(configPath, "")
140+
141+
s.Run("helm tools are not initially available", func() {
142+
s.False(slices.Contains(s.server.GetEnabledTools(), "helm_list"))
143+
})
144+
145+
// Write invalid TOML to config file
146+
s.Require().NoError(os.WriteFile(configPath, []byte(`
147+
toolsets = "not a valid array
148+
`), 0644))
149+
150+
// Send SIGHUP - should not panic, should continue with old config
151+
s.Require().NoError(syscall.Kill(syscall.Getpid(), syscall.SIGHUP))
152+
153+
s.Run("logs error when config is invalid", func() {
154+
s.Require().Eventually(func() bool {
155+
return strings.Contains(s.logBuffer.String(), "Failed to reload configuration")
156+
}, 2*time.Second, 50*time.Millisecond)
157+
})
158+
159+
s.Run("tools remain unchanged after failed reload", func() {
160+
s.True(slices.Contains(s.server.GetEnabledTools(), "events_list"))
161+
s.False(slices.Contains(s.server.GetEnabledTools(), "helm_list"))
162+
})
163+
164+
// Now fix the config and add helm
165+
s.Require().NoError(os.WriteFile(configPath, []byte(`
166+
toolsets = ["core", "config", "helm"]
167+
`), 0644))
168+
169+
// Send another SIGHUP
170+
s.Require().NoError(syscall.Kill(syscall.Getpid(), syscall.SIGHUP))
171+
172+
s.Run("helm tools become available after fixing config and sending SIGHUP", func() {
173+
s.Require().Eventually(func() bool {
174+
return slices.Contains(s.server.GetEnabledTools(), "helm_list")
175+
}, 2*time.Second, 50*time.Millisecond)
176+
})
177+
}
178+
179+
func (s *SIGHUPSuite) TestSIGHUPWithConfigDirOnly() {
180+
// Create initial drop-in file without helm
181+
dropInPath := filepath.Join(s.dropInConfigDir, "10-settings.toml")
182+
s.Require().NoError(os.WriteFile(dropInPath, []byte(`
183+
toolsets = ["core", "config"]
184+
`), 0644))
185+
186+
s.InitServer("", s.dropInConfigDir)
187+
188+
s.Run("helm tools are not initially available", func() {
189+
s.False(slices.Contains(s.server.GetEnabledTools(), "helm_list"))
190+
})
191+
192+
// Update drop-in file to add helm
193+
s.Require().NoError(os.WriteFile(dropInPath, []byte(`
194+
toolsets = ["core", "config", "helm"]
195+
`), 0644))
196+
197+
// Send SIGHUP
198+
s.Require().NoError(syscall.Kill(syscall.Getpid(), syscall.SIGHUP))
199+
200+
s.Run("helm tools become available after SIGHUP with config-dir only", func() {
201+
s.Require().Eventually(func() bool {
202+
return slices.Contains(s.server.GetEnabledTools(), "helm_list")
203+
}, 2*time.Second, 50*time.Millisecond)
204+
})
205+
}
206+
207+
func TestSIGHUP(t *testing.T) {
208+
suite.Run(t, new(SIGHUPSuite))
209+
}

0 commit comments

Comments
 (0)