@@ -271,6 +271,28 @@ func testWebhookConverter(t *testing.T, watchCache bool) {
271271 etcd3watcher .TestOnlySetFatalOnDecodeError (false )
272272 defer etcd3watcher .TestOnlySetFatalOnDecodeError (true )
273273
274+ // To avoid the high cost of restarting the API server for every test case, we start
275+ // the infrastructure (API Server + Webhook Server) ONCE at the beginning of the test.
276+ //
277+ // We use a 'dynamicWebhookHandler' to swap the conversion logic (the handler)
278+ // for each test case without restarting the actual HTTP server.
279+ //
280+ // This allows us to start the webhook server ONCE at the beginning of the test.
281+ // Crucially, this allows us to enforce teardown order: API Server stops -> Webhook Server stops.
282+
283+ // Create the mutable handler.
284+ proxyHandler := & dynamicWebhookHandler {}
285+
286+ // Start Webhook Server FIRST.
287+ // This ensures its deferred teardown runs LAST (after API server stop).
288+ webhookTearDown , webhookClientConfig , err := StartConversionWebhookServer (proxyHandler )
289+ if err != nil {
290+ t .Fatal (err )
291+ }
292+ defer webhookTearDown ()
293+
294+ // Start API Server SECOND.
295+ // This ensures its deferred teardown runs FIRST.
274296 tearDown , config , options , err := fixtures .StartDefaultServer (t , fmt .Sprintf ("--watch-cache=%v" , watchCache ))
275297 if err != nil {
276298 t .Fatal (err )
@@ -315,12 +337,12 @@ func testWebhookConverter(t *testing.T, watchCache bool) {
315337 for _ , test := range tests {
316338 t .Run (test .group , func (t * testing.T ) {
317339 upCh , handler := closeOnCall (test .handler )
318- tearDown , webhookClientConfig , err := StartConversionWebhookServer (handler )
319- if err != nil {
320- t .Fatal (err )
321- }
322- defer tearDown ()
323340
341+ // Inject the logic for this specific test case
342+ proxyHandler .set (handler )
343+ defer proxyHandler .set (nil )
344+
345+ // Configure the CRD to use the shared webhook server
324346 ctc .setConversionWebhook (t , webhookClientConfig , test .reviewVersions )
325347 defer ctc .removeConversionWebhook (t )
326348
@@ -1615,3 +1637,30 @@ func TestWebhookConversion_WhitespaceCABundleEtcdBypass(t *testing.T) {
16151637 verifyMultiVersionObject (t , "v1beta1" , obj )
16161638
16171639}
1640+
1641+ // dynamicWebhookHandler is a thread-safe http. Handler that allows swapping
1642+ // the underlying delegate handler at runtime. This is useful for sharing a single
1643+ // server instance across multiple test cases that require different behaviors.
1644+ type dynamicWebhookHandler struct {
1645+ mu sync.RWMutex
1646+ delegate http.Handler
1647+ }
1648+
1649+ // ServeHTTP implements http.Handler. It delegates the request to the currently
1650+ // configured handler. If no handler is set, it returns an internal server error.
1651+ func (h * dynamicWebhookHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
1652+ h .mu .RLock ()
1653+ defer h .mu .RUnlock ()
1654+ if h .delegate != nil {
1655+ h .delegate .ServeHTTP (w , r )
1656+ } else {
1657+ http .Error (w , "unexpected call" , http .StatusInternalServerError )
1658+ }
1659+ }
1660+
1661+ // set safely swaps the underlying delegate handler.
1662+ func (h * dynamicWebhookHandler ) set (delegate http.Handler ) {
1663+ h .mu .Lock ()
1664+ defer h .mu .Unlock ()
1665+ h .delegate = delegate
1666+ }
0 commit comments