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 53d5b516deeaf9dd28bd7ee19dab0ae50d6212f3 Author: Xuetao Li <[email protected]> AuthorDate: Mon Dec 22 07:52:50 2025 +0800 test: add test cases for `filter` (#3118) * test: add unit tests for ./filter/ --- filter/access_key_test.go | 47 ++++++++ filter/adaptivesvc/filter_test.go | 130 +++++++++++++++++++++++ filter/adaptivesvc/limiter/hill_climbing_test.go | 89 ++++++++++++++++ filter/adaptivesvc/limiter/utils_test.go | 44 ++++++++ filter/adaptivesvc/limiter_mapper_test.go | 71 +++++++++++++ filter/generic/util_test.go | 92 ++++++++++++++++ 6 files changed, 473 insertions(+) diff --git a/filter/access_key_test.go b/filter/access_key_test.go new file mode 100644 index 000000000..01d47f471 --- /dev/null +++ b/filter/access_key_test.go @@ -0,0 +1,47 @@ +/* + * 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 filter + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +// Mock the AccessKeyStorage interface +func TestAccessKeyPair(t *testing.T) { + // Test case 1: Test that AccessKeyPair can be properly instantiated and fields are correctly set + accessKeyPair := &AccessKeyPair{ + AccessKey: "test-access-key", + SecretKey: "test-secret-key", + ConsumerSide: "consumer-side", + ProviderSide: "provider-side", + Creator: "creator", + Options: "options", + } + + // Assert that the fields are correctly set + assert.Equal(t, "test-access-key", accessKeyPair.AccessKey) + assert.Equal(t, "test-secret-key", accessKeyPair.SecretKey) + assert.Equal(t, "consumer-side", accessKeyPair.ConsumerSide) + assert.Equal(t, "provider-side", accessKeyPair.ProviderSide) + assert.Equal(t, "creator", accessKeyPair.Creator) + assert.Equal(t, "options", accessKeyPair.Options) +} diff --git a/filter/adaptivesvc/filter_test.go b/filter/adaptivesvc/filter_test.go new file mode 100644 index 000000000..c95f6dffd --- /dev/null +++ b/filter/adaptivesvc/filter_test.go @@ -0,0 +1,130 @@ +/* + * 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 adaptivesvc + +import ( + "context" + "testing" +) + +import ( + "github.com/golang/mock/gomock" + + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/filter/adaptivesvc/limiter" + "dubbo.apache.org/dubbo-go/v3/protocol/invocation" + "dubbo.apache.org/dubbo-go/v3/protocol/mock" + "dubbo.apache.org/dubbo-go/v3/protocol/result" +) + +type mockUpdater struct { + called bool +} + +func (m *mockUpdater) DoUpdate() error { + m.called = true + return nil +} + +func (m *mockUpdater) Report(_ uint64) {} + +func TestAdaptiveServiceProviderFilter_Invoke(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + u, _ := common.NewURL("dubbo://127.0.0.1:20000/com.test.Service") + methodName := "GetInfo" + filter := newAdaptiveServiceProviderFilter() + + t.Run("AdaptiveDisabled", func(t *testing.T) { + invoc := invocation.NewRPCInvocation(methodName, nil, nil) + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).Return(&result.RPCResult{Rest: "ok"}) + + res := filter.Invoke(context.Background(), invoker, invoc) + assert.Nil(t, res.Error()) + }) + + t.Run("AdaptiveEnabled_AcquireSuccess", func(t *testing.T) { + invoc := invocation.NewRPCInvocation(methodName, nil, map[string]any{ + constant.AdaptiveServiceEnabledKey: constant.AdaptiveServiceIsEnabled, + }) + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().GetURL().Return(u).AnyTimes() + invoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).Return(&result.RPCResult{Rest: "ok"}) + + res := filter.Invoke(context.Background(), invoker, invoc) + assert.Nil(t, res.Error()) + + updater, _ := invoc.GetAttribute(constant.AdaptiveServiceUpdaterKey) + assert.NotNil(t, updater) + }) +} + +func TestAdaptiveServiceProviderFilter_OnResponse(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + u, _ := common.NewURL("dubbo://127.0.0.1:20000/com.test.Service") + methodName := "GetInfo" + filter := newAdaptiveServiceProviderFilter() + + t.Run("DisabledInResponse", func(t *testing.T) { + invoc := invocation.NewRPCInvocation(methodName, nil, nil) + res := &result.RPCResult{Rest: "ok"} + invoker := mock.NewMockInvoker(ctrl) + + ret := filter.OnResponse(context.Background(), res, invoker, invoc) + assert.Equal(t, res, ret) + }) + + t.Run("InterruptedErrorShouldSkip", func(t *testing.T) { + invoc := invocation.NewRPCInvocation(methodName, nil, nil) + res := &result.RPCResult{Err: wrapErrAdaptiveSvcInterrupted("limit exceeded")} + invoker := mock.NewMockInvoker(ctrl) + + ret := filter.OnResponse(context.Background(), res, invoker, invoc) + assert.True(t, isErrAdaptiveSvcInterrupted(ret.Error())) + }) + + t.Run("SuccessWithAttachments", func(t *testing.T) { + invoc := invocation.NewRPCInvocation(methodName, nil, nil) + updater := &mockUpdater{} + invoc.SetAttribute(constant.AdaptiveServiceUpdaterKey, updater) + + res := &result.RPCResult{Rest: "ok"} + res.AddAttachment(constant.AdaptiveServiceEnabledKey, constant.AdaptiveServiceIsEnabled) + + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().GetURL().Return(u).AnyTimes() + + _, _ = limiterMapperSingleton.newAndSetMethodLimiter(u, methodName, limiter.HillClimbingLimiter) + + ret := filter.OnResponse(context.Background(), res, invoker, invoc) + + assert.Nil(t, ret.Error()) + assert.NotEmpty(t, ret.Attachment(constant.AdaptiveServiceRemainingKey, "")) + assert.NotEmpty(t, ret.Attachment(constant.AdaptiveServiceInflightKey, "")) + assert.True(t, updater.called) + }) +} diff --git a/filter/adaptivesvc/limiter/hill_climbing_test.go b/filter/adaptivesvc/limiter/hill_climbing_test.go new file mode 100644 index 000000000..7ad75666c --- /dev/null +++ b/filter/adaptivesvc/limiter/hill_climbing_test.go @@ -0,0 +1,89 @@ +/* + * 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 limiter + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +// Test for HillClimbing limiter's Acquire method +func TestHillClimbing_Acquire(t *testing.T) { + limiter := NewHillClimbing().(*HillClimbing) + + // Simulating that there is remaining capacity + limiter.limitation.Store(100) + limiter.inflight.Store(50) + + updater, err := limiter.Acquire() + assert.NotNil(t, updater) + assert.NoError(t, err) + + // Simulating no remaining capacity + limiter.limitation.Store(50) + limiter.inflight.Store(50) + + updater, err = limiter.Acquire() + assert.Nil(t, updater) + assert.Error(t, err, ErrReachLimitation) +} + +// Test the HillClimbingUpdater's DoUpdate method +func TestHillClimbingUpdater_DoUpdate(t *testing.T) { + limiter := NewHillClimbing().(*HillClimbing) + updater := NewHillClimbingUpdater(limiter) + + // Simulate the limiter update with arbitrary values for RTT and inflight + // Normally, this would adjust the limiter's limitation based on RTT and inflight metrics + err := updater.DoUpdate() + assert.NoError(t, err) +} + +// Test adjustLimitation method with different options +func TestHillClimbingUpdater_AdjustLimitation(t *testing.T) { + limiter := NewHillClimbing().(*HillClimbing) + updater := NewHillClimbingUpdater(limiter) + + // Simulate a scenario where the limiter is set to extend its capacity + err := updater.adjustLimitation(HillClimbingOptionExtend) + assert.NoError(t, err) + assert.True(t, limiter.limitation.Load() > initialLimitation) + + // Simulate a scenario where the limiter is set to shrink its capacity + err = updater.adjustLimitation(HillClimbingOptionShrink) + assert.NoError(t, err) + assert.True(t, limiter.limitation.Load() < initialLimitation) +} + +// Test HillClimbing's Remaining capacity behavior +func TestHillClimbing_Remaining(t *testing.T) { + limiter := NewHillClimbing().(*HillClimbing) + limiter.limitation.Store(100) + limiter.inflight.Store(30) + + remaining := limiter.Remaining() + assert.Equal(t, uint64(70), remaining) + + // Simulate that inflight requests exceed the limitation + limiter.inflight.Store(120) + remaining = limiter.Remaining() + assert.Equal(t, uint64(0), remaining) +} diff --git a/filter/adaptivesvc/limiter/utils_test.go b/filter/adaptivesvc/limiter/utils_test.go new file mode 100644 index 000000000..483056487 --- /dev/null +++ b/filter/adaptivesvc/limiter/utils_test.go @@ -0,0 +1,44 @@ +/* + * 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 limiter + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestMinDuration(t *testing.T) { + // Test when lhs is smaller than rhs + dur1 := 2 * time.Second + dur2 := 3 * time.Second + result := minDuration(dur1, dur2) + assert.Equal(t, dur1, result) + + // Test when rhs is smaller than lhs + result = minDuration(dur2, dur1) + assert.Equal(t, dur1, result) + + // Test when both durations are equal + dur3 := 2 * time.Second + result = minDuration(dur3, dur3) + assert.Equal(t, dur3, result) +} diff --git a/filter/adaptivesvc/limiter_mapper_test.go b/filter/adaptivesvc/limiter_mapper_test.go new file mode 100644 index 000000000..13ef47f82 --- /dev/null +++ b/filter/adaptivesvc/limiter_mapper_test.go @@ -0,0 +1,71 @@ +/* + * 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 adaptivesvc + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/filter/adaptivesvc/limiter" +) + +func TestLimiterMapper_newAndSetMethodLimiter(t *testing.T) { + // Initialize the limiterMapper and mock URL + mapper := newLimiterMapper() + url := &common.URL{Path: "/testService"} + methodName := "testMethod" + + // Test creating a new limiter + l, err := mapper.newAndSetMethodLimiter(url, methodName, limiter.HillClimbingLimiter) + assert.NoError(t, err) + assert.NotNil(t, l) + + // Test that the same limiter is returned if already created + l2, err := mapper.newAndSetMethodLimiter(url, methodName, limiter.HillClimbingLimiter) + assert.NoError(t, err) + assert.Same(t, l, l2) +} + +func TestLimiterMapper_getMethodLimiter(t *testing.T) { + // Initialize the limiterMapper and mock URL + mapper := newLimiterMapper() + url := &common.URL{Path: "/testService"} + methodName := "testMethod" + + // Add a limiter to the mapper + _, err := mapper.newAndSetMethodLimiter(url, methodName, limiter.HillClimbingLimiter) + assert.NoError(t, err) + + // Test getting an existing limiter + l, err := mapper.getMethodLimiter(url, methodName) + assert.NoError(t, err) + assert.NotNil(t, l) + + // Test getting a limiter that does not exist + url2 := &common.URL{Path: "/testService2"} + l, err = mapper.getMethodLimiter(url2, "nonExistentMethod") + assert.Error(t, err) + assert.Nil(t, l) + assert.Equal(t, ErrLimiterNotFoundOnMapper, err) +} diff --git a/filter/generic/util_test.go b/filter/generic/util_test.go new file mode 100644 index 000000000..5fdc066c1 --- /dev/null +++ b/filter/generic/util_test.go @@ -0,0 +1,92 @@ +/* + * 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 generic + +import ( + "testing" +) + +import ( + "github.com/golang/mock/gomock" + + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/filter/generic/generalizer" + "dubbo.apache.org/dubbo-go/v3/protocol/invocation" + "dubbo.apache.org/dubbo-go/v3/protocol/mock" +) + +func TestIsCallingToGenericService(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + u1, _ := common.NewURL("dubbo://127.0.0.1:20000/ComTest?generic=true") + invoker1 := mock.NewMockInvoker(ctrl) + invoker1.EXPECT().GetURL().Return(u1).AnyTimes() + invoc1 := invocation.NewRPCInvocation("GetUser", []any{"ID"}, nil) + assert.True(t, isCallingToGenericService(invoker1, invoc1)) + + u2, _ := common.NewURL("dubbo://127.0.0.1:20000/ComTest") + invoker2 := mock.NewMockInvoker(ctrl) + invoker2.EXPECT().GetURL().Return(u2).AnyTimes() + assert.False(t, isCallingToGenericService(invoker2, invoc1)) + + invoc3 := invocation.NewRPCInvocation(constant.Generic, []any{"m", "t", "a"}, nil) + assert.False(t, isCallingToGenericService(invoker1, invoc3)) +} + +func TestIsMakingAGenericCall(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + u, _ := common.NewURL("dubbo://127.0.0.1:20000/ComTest?generic=true") + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().GetURL().Return(u).AnyTimes() + + invoc1 := invocation.NewRPCInvocation(constant.Generic, []any{"method", "types", "args"}, nil) + assert.True(t, isMakingAGenericCall(invoker, invoc1)) + + invoc2 := invocation.NewRPCInvocation(constant.Generic, []any{"method"}, nil) + assert.False(t, isMakingAGenericCall(invoker, invoc2)) + + invoc3 := invocation.NewRPCInvocation("GetUser", []any{"a", "b", "c"}, nil) + assert.False(t, isMakingAGenericCall(invoker, invoc3)) +} + +func TestIsGeneric(t *testing.T) { + assert.True(t, isGeneric("true")) + assert.True(t, isGeneric("True")) + assert.False(t, isGeneric("false")) + assert.False(t, isGeneric("")) + assert.False(t, isGeneric("bean")) // 目前代码逻辑仅匹配 "true" +} + +func TestGetGeneralizer(t *testing.T) { + g1 := getGeneralizer(constant.GenericSerializationDefault) + assert.IsType(t, generalizer.GetMapGeneralizer(), g1) + + g2 := getGeneralizer(constant.GenericSerializationGson) + assert.IsType(t, generalizer.GetGsonGeneralizer(), g2) + + g3 := getGeneralizer("unsupported_type") + assert.IsType(t, generalizer.GetMapGeneralizer(), g3) +}
