This is an automated email from the ASF dual-hosted git repository. alexstocks pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
commit e6cad9c56c9a73559a486585a8a9c570e516e3a7 Author: Oxidaner <[email protected]> AuthorDate: Mon Dec 22 07:41:01 2025 +0800 test: Add unit tests for graceful_shutdown package (#3128) * Add unit tests for graceful_shutdown package --- graceful_shutdown/options_test.go | 102 ++++++++++++++++++++ graceful_shutdown/shutdown_test.go | 192 +++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) diff --git a/graceful_shutdown/options_test.go b/graceful_shutdown/options_test.go new file mode 100644 index 000000000..f5a262cec --- /dev/null +++ b/graceful_shutdown/options_test.go @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package graceful_shutdown + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/global" +) + +func TestDefaultOptions(t *testing.T) { + opts := defaultOptions() + assert.NotNil(t, opts) + assert.NotNil(t, opts.Shutdown) + assert.Equal(t, "60s", opts.Shutdown.Timeout) + assert.Equal(t, "3s", opts.Shutdown.StepTimeout) + assert.Equal(t, "3s", opts.Shutdown.ConsumerUpdateWaitTime) + assert.Equal(t, "", opts.Shutdown.OfflineRequestWindowTimeout) // No default value + assert.True(t, *opts.Shutdown.InternalSignal) +} + +func TestNewOptions(t *testing.T) { + // Test with default options + opts := NewOptions() + assert.NotNil(t, opts) + assert.NotNil(t, opts.Shutdown) + + // Test with custom options + customTimeout := 120 * time.Second + customStepTimeout := 10 * time.Second + customConsumerUpdateWaitTime := 5 * time.Second + customOfflineRequestWindowTimeout := 2 * time.Second + + opts = NewOptions( + WithTimeout(customTimeout), + WithStepTimeout(customStepTimeout), + WithConsumerUpdateWaitTime(customConsumerUpdateWaitTime), + WithOfflineRequestWindowTimeout(customOfflineRequestWindowTimeout), + WithoutInternalSignal(), + ) + + assert.Equal(t, customTimeout.String(), opts.Shutdown.Timeout) + assert.Equal(t, customStepTimeout.String(), opts.Shutdown.StepTimeout) + assert.Equal(t, customConsumerUpdateWaitTime.String(), opts.Shutdown.ConsumerUpdateWaitTime) + assert.Equal(t, customOfflineRequestWindowTimeout.String(), opts.Shutdown.OfflineRequestWindowTimeout) + assert.False(t, *opts.Shutdown.InternalSignal) +} + +func TestOptionFunctions(t *testing.T) { + // Test WithTimeout + opts := defaultOptions() + WithTimeout(120 * time.Second)(opts) + assert.Equal(t, "2m0s", opts.Shutdown.Timeout) + + // Test WithStepTimeout + WithStepTimeout(5 * time.Second)(opts) + assert.Equal(t, "5s", opts.Shutdown.StepTimeout) + + // Test WithConsumerUpdateWaitTime + WithConsumerUpdateWaitTime(10 * time.Second)(opts) + assert.Equal(t, "10s", opts.Shutdown.ConsumerUpdateWaitTime) + + // Test WithOfflineRequestWindowTimeout + WithOfflineRequestWindowTimeout(2 * time.Second)(opts) + assert.Equal(t, "2s", opts.Shutdown.OfflineRequestWindowTimeout) + + // Test WithoutInternalSignal + WithoutInternalSignal()(opts) + assert.False(t, *opts.Shutdown.InternalSignal) + + // Test WithRejectRequest + WithRejectRequest()(opts) + assert.True(t, opts.Shutdown.RejectRequest.Load()) + + // Test SetShutdownConfig + customConfig := global.DefaultShutdownConfig() + customConfig.Timeout = "300s" + SetShutdownConfig(customConfig)(opts) + assert.Equal(t, customConfig, opts.Shutdown) +} diff --git a/graceful_shutdown/shutdown_test.go b/graceful_shutdown/shutdown_test.go new file mode 100644 index 000000000..8aa2e665e --- /dev/null +++ b/graceful_shutdown/shutdown_test.go @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package graceful_shutdown + +import ( + "context" + "sync" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/filter" + "dubbo.apache.org/dubbo-go/v3/global" + "dubbo.apache.org/dubbo-go/v3/protocol/base" + "dubbo.apache.org/dubbo-go/v3/protocol/result" +) + +// MockFilter implements filter.Filter and config.Setter for testing +type MockFilter struct { + mock.Mock +} + +func (m *MockFilter) Set(key string, value any) { + m.Called(key, value) +} + +func (m *MockFilter) Invoke(ctx context.Context, invoker base.Invoker, invocation base.Invocation) result.Result { + return nil +} + +func (m *MockFilter) OnResponse(ctx context.Context, result result.Result, invoker base.Invoker, invocation base.Invocation) result.Result { + return nil +} + +func TestInit(t *testing.T) { + // Reset initOnce and protocols for testing + initOnce = sync.Once{} + protocols = nil + proMu = sync.Mutex{} + + // Register mock filters + mockConsumerFilter := &MockFilter{} + mockProviderFilter := &MockFilter{} + + // Expect Set method calls + mockConsumerFilter.On("Set", mock.Anything, mock.Anything).Return() + mockProviderFilter.On("Set", mock.Anything, mock.Anything).Return() + + // Register mock filters + extension.SetFilter(constant.GracefulShutdownConsumerFilterKey, func() filter.Filter { + return mockConsumerFilter + }) + extension.SetFilter(constant.GracefulShutdownProviderFilterKey, func() filter.Filter { + return mockProviderFilter + }) + + // Test with default options + Init() + + // Test with custom options + customTimeout := 120 * time.Second + Init(WithTimeout(customTimeout)) + + // Remove mock filters + extension.UnregisterFilter(constant.GracefulShutdownConsumerFilterKey) + extension.UnregisterFilter(constant.GracefulShutdownProviderFilterKey) +} + +func TestRegisterProtocol(t *testing.T) { + // Reset protocols for testing + protocols = make(map[string]struct{}) + proMu = sync.Mutex{} + + // Register some protocols + RegisterProtocol("dubbo") + RegisterProtocol("rest") + RegisterProtocol("tri") + + // Check if protocols are registered correctly + proMu.Lock() + defer proMu.Unlock() + + assert.Contains(t, protocols, "dubbo") + assert.Contains(t, protocols, "rest") + assert.Contains(t, protocols, "tri") + assert.Len(t, protocols, 3) +} + +func TestTotalTimeout(t *testing.T) { + // Test with default timeout + config := global.DefaultShutdownConfig() + timeout := totalTimeout(config) + assert.Equal(t, defaultTimeout, timeout) + + // Test with custom timeout + config.Timeout = "120s" + timeout = totalTimeout(config) + assert.Equal(t, 120*time.Second, timeout) + + // Test with invalid timeout + config.Timeout = "invalid" + timeout = totalTimeout(config) + assert.Equal(t, defaultTimeout, timeout) + + // Test with timeout less than default + config.Timeout = "30s" + timeout = totalTimeout(config) + assert.Equal(t, defaultTimeout, timeout) // Should use default if less than default +} + +func TestParseDuration(t *testing.T) { + // Test with valid duration + res := parseDuration("10s", "test", 5*time.Second) + assert.Equal(t, 10*time.Second, res) + + // Test with invalid duration + res = parseDuration("invalid", "test", 5*time.Second) + assert.Equal(t, 5*time.Second, res) + + // Test with empty string + res = parseDuration("", "test", 5*time.Second) + assert.Equal(t, 5*time.Second, res) +} + +func TestWaitAndAcceptNewRequests(t *testing.T) { + // Test with positive step timeout + config := global.DefaultShutdownConfig() + config.StepTimeout = "100ms" + config.ProviderActiveCount.Store(0) + + start := time.Now() + waitAndAcceptNewRequests(config) + elapsed := time.Since(start) + + // Should wait for ConsumerUpdateWaitTime (default 3s) plus a little extra for processing + assert.GreaterOrEqual(t, elapsed, 3*time.Second) + + // Test with negative step timeout (should skip waiting) + config.StepTimeout = "-1s" + start = time.Now() + waitAndAcceptNewRequests(config) + elapsed = time.Since(start) + + // Should only wait for ConsumerUpdateWaitTime + assert.Less(t, elapsed, 3*time.Second+100*time.Millisecond) +} + +func TestWaitForSendingAndReceivingRequests(t *testing.T) { + // Test with active consumer requests + config := global.DefaultShutdownConfig() + config.StepTimeout = "100ms" + config.ConsumerActiveCount.Store(1) + + start := time.Now() + waitForSendingAndReceivingRequests(config) + elapsed := time.Since(start) + + // Should wait for step timeout + assert.GreaterOrEqual(t, elapsed, 100*time.Millisecond) + + // Test with no active consumer requests + config.ConsumerActiveCount.Store(0) + start = time.Now() + waitForSendingAndReceivingRequests(config) + elapsed = time.Since(start) + + // Should return immediately + assert.Less(t, elapsed, 50*time.Millisecond) +}
