This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new a0bd8ae32 test: add unit test for protocol/invocation (#3140)
a0bd8ae32 is described below

commit a0bd8ae3269da190e357a5a4e24822ef3916bd53
Author: Akashisang <[email protected]>
AuthorDate: Tue Dec 23 12:47:39 2025 +0800

    test: add unit test for protocol/invocation (#3140)
    
    * test: add unit test for protocol/invocation
---
 protocol/invocation/rpcinvocation_test.go | 504 ++++++++++++++++++++++++++++++
 1 file changed, 504 insertions(+)

diff --git a/protocol/invocation/rpcinvocation_test.go 
b/protocol/invocation/rpcinvocation_test.go
index 6111f9a74..534197bcc 100644
--- a/protocol/invocation/rpcinvocation_test.go
+++ b/protocol/invocation/rpcinvocation_test.go
@@ -18,6 +18,11 @@
 package invocation
 
 import (
+       "context"
+       "fmt"
+       "net/http"
+       "reflect"
+       "sync"
        "testing"
 )
 
@@ -28,6 +33,9 @@ import (
 import (
        "dubbo.apache.org/dubbo-go/v3/common"
        "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/protocol/base"
+       "dubbo.apache.org/dubbo-go/v3/protocol/result"
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
 )
 
 func TestRPCInvocation_ServiceKey(t *testing.T) {
@@ -73,3 +81,499 @@ func TestRPCInvocation_ServiceKey(t *testing.T) {
        }))
        assert.Equal(t, providerUrl.ServiceKey(), invocation.ServiceKey())
 }
+
+func TestNewRPCInvocation(t *testing.T) {
+       methodName := "testMethod"
+       arguments := []any{"arg1", 123, true}
+       attachments := map[string]any{
+               "key1": "value1",
+               "key2": "value2",
+       }
+
+       invocation := NewRPCInvocation(methodName, arguments, attachments)
+
+       assert.NotNil(t, invocation)
+       assert.Equal(t, methodName, invocation.MethodName())
+       assert.Equal(t, arguments, invocation.Arguments())
+       assert.Equal(t, attachments, invocation.Attachments())
+       assert.NotNil(t, invocation.Attributes())
+       assert.Empty(t, invocation.Attributes())
+}
+
+func TestNewRPCInvocationWithOptions(t *testing.T) {
+       methodName := "testMethod"
+       paramTypes := []reflect.Type{reflect.TypeOf(""), reflect.TypeOf(0)}
+       paramTypeNames := []string{"string", "int"}
+       paramValues := []reflect.Value{reflect.ValueOf("test"), 
reflect.ValueOf(123)}
+       paramRawValues := []any{"test", 123}
+       arguments := []any{"arg1", 456}
+       reply := "test_reply"
+       callback := func() {}
+       attachments := map[string]any{"key": "value"}
+
+       invocation := NewRPCInvocationWithOptions(
+               WithMethodName(methodName),
+               WithParameterTypes(paramTypes),
+               WithParameterTypeNames(paramTypeNames),
+               WithParameterValues(paramValues),
+               WithParameterRawValues(paramRawValues),
+               WithArguments(arguments),
+               WithReply(reply),
+               WithCallBack(callback),
+               WithAttachments(attachments),
+       )
+
+       assert.NotNil(t, invocation)
+       assert.Equal(t, methodName, invocation.MethodName())
+       assert.Equal(t, paramTypes, invocation.ParameterTypes())
+       assert.Equal(t, paramTypeNames, invocation.ParameterTypeNames())
+       assert.Equal(t, paramValues, invocation.ParameterValues())
+       assert.Equal(t, paramRawValues, invocation.ParameterRawValues())
+       assert.Equal(t, arguments, invocation.Arguments())
+       assert.Equal(t, reply, invocation.Reply())
+       assert.NotNil(t, invocation.CallBack())
+       assert.Equal(t, attachments, invocation.Attachments())
+}
+
+func TestRPCInvocation_MethodName(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions(WithMethodName("testMethod"))
+       assert.Equal(t, "testMethod", invocation.MethodName())
+}
+
+func TestRPCInvocation_ActualMethodName(t *testing.T) {
+       // Test non-generic invocation
+       invocation := NewRPCInvocationWithOptions(
+               WithMethodName("normalMethod"),
+               WithArguments([]any{"arg1"}),
+       )
+       assert.Equal(t, "normalMethod", invocation.ActualMethodName())
+
+       // Test generic invocation with $invoke
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.Generic),
+               WithArguments([]any{"actualMethod", 
[]string{"java.lang.String"}, []any{"param"}}),
+       )
+       assert.Equal(t, "actualMethod", invocation.ActualMethodName())
+
+       // Test generic invocation with $invokeAsync
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.GenericAsync),
+               WithArguments([]any{"actualAsyncMethod", 
[]string{"java.lang.String"}, []any{"param"}}),
+       )
+       assert.Equal(t, "actualAsyncMethod", invocation.ActualMethodName())
+}
+
+func TestRPCInvocation_IsGenericInvocation(t *testing.T) {
+       // Test non-generic invocation
+       invocation := NewRPCInvocationWithOptions(
+               WithMethodName("normalMethod"),
+               WithArguments([]any{"arg1"}),
+       )
+       assert.False(t, invocation.IsGenericInvocation())
+
+       // Test generic invocation with $invoke
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.Generic),
+               WithArguments([]any{"actualMethod", 
[]string{"java.lang.String"}, []any{"param"}}),
+       )
+       assert.True(t, invocation.IsGenericInvocation())
+
+       // Test generic invocation with $invokeAsync
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.GenericAsync),
+               WithArguments([]any{"actualMethod", 
[]string{"java.lang.String"}, []any{"param"}}),
+       )
+       assert.True(t, invocation.IsGenericInvocation())
+
+       // Test generic method name but insufficient arguments
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.Generic),
+               WithArguments([]any{"arg1"}),
+       )
+       assert.False(t, invocation.IsGenericInvocation())
+
+       // Test generic method name but nil arguments
+       invocation = NewRPCInvocationWithOptions(
+               WithMethodName(constant.Generic),
+       )
+       assert.False(t, invocation.IsGenericInvocation())
+}
+
+func TestRPCInvocation_SetAndGetReply(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       assert.Nil(t, invocation.Reply())
+
+       reply := "test_reply"
+       invocation.SetReply(reply)
+       assert.Equal(t, reply, invocation.Reply())
+}
+
+func TestRPCInvocation_SetAndGetCallBack(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       assert.Nil(t, invocation.CallBack())
+
+       callback := func() string { return "callback" }
+       invocation.SetCallBack(callback)
+       assert.NotNil(t, invocation.CallBack())
+}
+
+func TestRPCInvocation_Attachments(t *testing.T) {
+       attachments := map[string]any{
+               "key1": "value1",
+               "key2": 123,
+       }
+       invocation := NewRPCInvocationWithOptions(WithAttachments(attachments))
+       assert.Equal(t, attachments, invocation.Attachments())
+}
+
+func TestRPCInvocation_GetAttachmentInterface(t *testing.T) {
+       attachments := map[string]any{
+               "key1": "value1",
+               "key2": 123,
+       }
+       invocation := NewRPCInvocationWithOptions(WithAttachments(attachments))
+
+       // Test existing key
+       val := invocation.GetAttachmentInterface("key1")
+       assert.Equal(t, "value1", val)
+
+       val = invocation.GetAttachmentInterface("key2")
+       assert.Equal(t, 123, val)
+
+       // Test non-existing key
+       val = invocation.GetAttachmentInterface("non-existing")
+       assert.Nil(t, val)
+
+       // Test nil attachments
+       invocation2 := NewRPCInvocationWithOptions()
+       val = invocation2.GetAttachmentInterface("key1")
+       assert.Nil(t, val)
+}
+
+func TestRPCInvocation_SetAndGetAttachment(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+
+       // Test setting attachment
+       invocation.SetAttachment("key1", "value1")
+       val, ok := invocation.GetAttachment("key1")
+       assert.True(t, ok)
+       assert.Equal(t, "value1", val)
+
+       // Test getting non-existing key
+       val, ok = invocation.GetAttachment("non-existing")
+       assert.False(t, ok)
+       assert.Empty(t, val)
+
+       // Test string array attachment (for triple protocol)
+       invocation.SetAttachment("key2", []string{"value2", "value3"})
+       val, ok = invocation.GetAttachment("key2")
+       assert.True(t, ok)
+       assert.Equal(t, "value2", val)
+
+       // Test non-string attachment
+       invocation.SetAttachment("key3", 123)
+       val, ok = invocation.GetAttachment("key3")
+       assert.False(t, ok)
+       assert.Empty(t, val)
+
+       // Test empty string array
+       invocation.SetAttachment("key4", []string{})
+       val, ok = invocation.GetAttachment("key4")
+       assert.False(t, ok)
+       assert.Empty(t, val)
+}
+
+func TestRPCInvocation_GetAttachmentWithDefaultValue(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       invocation.SetAttachment("key1", "value1")
+
+       // Test existing key
+       val := invocation.GetAttachmentWithDefaultValue("key1", "default")
+       assert.Equal(t, "value1", val)
+
+       // Test non-existing key
+       val = invocation.GetAttachmentWithDefaultValue("non-existing", 
"default")
+       assert.Equal(t, "default", val)
+}
+
+func TestRPCInvocation_Attributes(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       assert.NotNil(t, invocation.Attributes())
+       assert.Empty(t, invocation.Attributes())
+}
+
+func TestRPCInvocation_SetAndGetAttribute(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+
+       // Test setting attribute
+       invocation.SetAttribute("key1", "value1")
+       val, ok := invocation.GetAttribute("key1")
+       assert.True(t, ok)
+       assert.Equal(t, "value1", val)
+
+       // Test getting non-existing key
+       val, ok = invocation.GetAttribute("non-existing")
+       assert.False(t, ok)
+       assert.Nil(t, val)
+
+       // Test with different types
+       invocation.SetAttribute("key2", 123)
+       val, ok = invocation.GetAttribute("key2")
+       assert.True(t, ok)
+       assert.Equal(t, 123, val)
+}
+
+func TestRPCInvocation_GetAttributeWithDefaultValue(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       invocation.SetAttribute("key1", "value1")
+
+       // Test existing key
+       val := invocation.GetAttributeWithDefaultValue("key1", "default")
+       assert.Equal(t, "value1", val)
+
+       // Test non-existing key
+       val = invocation.GetAttributeWithDefaultValue("non-existing", "default")
+       assert.Equal(t, "default", val)
+
+       // Test with nil attributes (should not happen but test defensive code)
+       invocation2 := &RPCInvocation{}
+       val = invocation2.GetAttributeWithDefaultValue("key1", "default")
+       assert.Equal(t, "default", val)
+}
+
+func TestRPCInvocation_SetAndGetInvoker(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       assert.Nil(t, invocation.Invoker())
+
+       // Create a mock invoker
+       url, err := common.NewURL("dubbo://127.0.0.1:20000/test")
+       assert.NoError(t, err)
+       mockInvoker := &mockInvoker{url: url}
+
+       invocation.SetInvoker(mockInvoker)
+       assert.NotNil(t, invocation.Invoker())
+       assert.Equal(t, mockInvoker, invocation.Invoker())
+}
+
+func TestRPCInvocation_GetAttachmentAsContext(t *testing.T) {
+       attachments := map[string]any{
+               "key1": "value1",
+               "key2": []string{"value2", "value3"},
+               "key3": 123, // non-string value should be skipped
+       }
+       invocation := NewRPCInvocationWithOptions(WithAttachments(attachments))
+
+       ctx := invocation.GetAttachmentAsContext()
+       assert.NotNil(t, ctx)
+
+       // Extract header from context
+       header := triple_protocol.ExtractFromOutgoingContext(ctx)
+       assert.NotNil(t, header)
+
+       // Verify that string attachments are in the header
+       // NewOutgoingContext stores keys as lowercase, so check both ways
+       assert.Contains(t, header, "key1")
+       assert.Equal(t, []string{"value1"}, header["key1"]) //nolint:staticcheck
+
+       assert.Contains(t, header, "key2")
+       assert.Equal(t, []string{"value2", "value3"}, header["key2"]) 
//nolint:staticcheck
+
+       // key3 (int) should not be in the header since it's not a string
+       assert.NotContains(t, header, "key3")
+}
+
+func TestRPCInvocation_MergeAttachmentFromContext(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+
+       // Create a context with outgoing metadata
+       ctx := context.Background()
+       header := http.Header{}
+       header.Set("key1", "value1")
+       header.Add("key2", "value2")
+       header.Add("key2", "value3")
+       ctx = triple_protocol.NewOutgoingContext(ctx, header)
+
+       invocation.MergeAttachmentFromContext(ctx)
+
+       // Verify merged attachments
+       val, ok := invocation.GetAttachment("key1")
+       assert.True(t, ok)
+       assert.Equal(t, "value1", val)
+
+       // For multiple values, it should be stored as array
+       valInterface := invocation.GetAttachmentInterface("key2")
+       assert.NotNil(t, valInterface)
+       valArray, ok := valInterface.([]string)
+       assert.True(t, ok)
+       assert.Equal(t, 2, len(valArray))
+
+       // Test with nil context header (context without outgoing header)
+       invocation2 := NewRPCInvocationWithOptions()
+       invocation2.MergeAttachmentFromContext(context.Background())
+       // Should not panic and attachments should remain nil or empty
+       if invocation2.Attachments() != nil {
+               assert.Empty(t, invocation2.Attachments())
+       }
+}
+
+func TestRPCInvocation_WithAttachment(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions(
+               WithAttachment("key1", "value1"),
+               WithAttachment("key2", 123),
+       )
+
+       val, ok := invocation.GetAttachment("key1")
+       assert.True(t, ok)
+       assert.Equal(t, "value1", val)
+
+       val2 := invocation.GetAttachmentInterface("key2")
+       assert.Equal(t, 123, val2)
+}
+
+func TestRPCInvocation_WithInvoker(t *testing.T) {
+       url, err := common.NewURL("dubbo://127.0.0.1:20000/test")
+       assert.NoError(t, err)
+       mockInvoker := &mockInvoker{url: url}
+
+       invocation := NewRPCInvocationWithOptions(WithInvoker(mockInvoker))
+       assert.NotNil(t, invocation.Invoker())
+       assert.Equal(t, mockInvoker, invocation.Invoker())
+}
+
+func TestRPCInvocation_WithParameterTypeNames_EmptySlice(t *testing.T) {
+       // Test with empty slice
+       invocation := NewRPCInvocationWithOptions(
+               WithParameterTypeNames([]string{}),
+       )
+       assert.Nil(t, invocation.ParameterTypeNames())
+
+       // Test with non-empty slice
+       invocation = NewRPCInvocationWithOptions(
+               WithParameterTypeNames([]string{"string", "int"}),
+       )
+       assert.Equal(t, []string{"string", "int"}, 
invocation.ParameterTypeNames())
+}
+
+func TestRPCInvocation_ServiceKeyWithGroupAndVersion(t *testing.T) {
+       // Test with group and version
+       invocation := 
NewRPCInvocationWithOptions(WithAttachments(map[string]any{
+               constant.InterfaceKey: "com.test.Service",
+               constant.GroupKey:     "testGroup",
+               constant.VersionKey:   "1.0.0",
+       }))
+       assert.Equal(t, "testGroup/com.test.Service:1.0.0", 
invocation.ServiceKey())
+
+       // Test with path having leading slash
+       invocation = NewRPCInvocationWithOptions(WithAttachments(map[string]any{
+               constant.PathKey:    "/com.test.Service",
+               constant.GroupKey:   "testGroup",
+               constant.VersionKey: "1.0.0",
+       }))
+       assert.Equal(t, "testGroup/com.test.Service:1.0.0", 
invocation.ServiceKey())
+
+       // Test with only interface
+       invocation = NewRPCInvocationWithOptions(WithAttachments(map[string]any{
+               constant.InterfaceKey: "com.test.Service",
+       }))
+       assert.Equal(t, "com.test.Service", invocation.ServiceKey())
+}
+
+func TestRPCInvocation_ConcurrentAccess(t *testing.T) {
+       invocation := NewRPCInvocationWithOptions()
+       var wg sync.WaitGroup
+
+       // Test concurrent SetAttachment and GetAttachment with unique keys
+       numGoroutines := 10
+       wg.Add(numGoroutines)
+       for i := 0; i < numGoroutines; i++ {
+               go func(n int) {
+                       defer wg.Done()
+                       key := fmt.Sprintf("attachment_key_%d", n)
+                       expectedValue := fmt.Sprintf("value_%d", n)
+
+                       invocation.SetAttachment(key, expectedValue)
+                       val, ok := invocation.GetAttachment(key)
+
+                       assert.True(t, ok, "Attachment should exist for key 
%s", key)
+                       assert.Equal(t, expectedValue, val, "Attachment value 
mismatch for key %s", key)
+               }(i)
+       }
+       wg.Wait()
+
+       // Verify all attachments were set correctly
+       for i := 0; i < numGoroutines; i++ {
+               key := fmt.Sprintf("attachment_key_%d", i)
+               expectedValue := fmt.Sprintf("value_%d", i)
+               val, ok := invocation.GetAttachment(key)
+               assert.True(t, ok, "Final check: Attachment should exist for 
key %s", key)
+               assert.Equal(t, expectedValue, val, "Final check: Attachment 
value mismatch for key %s", key)
+       }
+
+       // Test concurrent SetAttribute and GetAttribute with unique keys
+       wg.Add(numGoroutines)
+       for i := 0; i < numGoroutines; i++ {
+               go func(n int) {
+                       defer wg.Done()
+                       key := fmt.Sprintf("attribute_key_%d", n)
+                       expectedValue := n * 100
+
+                       invocation.SetAttribute(key, expectedValue)
+                       val, ok := invocation.GetAttribute(key)
+
+                       assert.True(t, ok, "Attribute should exist for key %s", 
key)
+                       assert.Equal(t, expectedValue, val, "Attribute value 
mismatch for key %s", key)
+               }(i)
+       }
+       wg.Wait()
+
+       // Verify all attributes were set correctly
+       for i := 0; i < numGoroutines; i++ {
+               key := fmt.Sprintf("attribute_key_%d", i)
+               expectedValue := i * 100
+               val, ok := invocation.GetAttribute(key)
+               assert.True(t, ok, "Final check: Attribute should exist for key 
%s", key)
+               assert.Equal(t, expectedValue, val, "Final check: Attribute 
value mismatch for key %s", key)
+       }
+
+       // Test SetInvoker and Invoker
+       // Note: We test only SetInvoker here because Invoker() doesn't use 
locks,
+       // and concurrent read/write would cause data races (which is a 
limitation of the current implementation).
+       // Testing concurrent writes only is still useful to verify 
SetInvoker's lock works correctly.
+       url, err := common.NewURL("dubbo://127.0.0.1:20000/test")
+       assert.NoError(t, err)
+       mockInvoker := &mockInvoker{url: url}
+
+       wg.Add(numGoroutines)
+       for i := 0; i < numGoroutines; i++ {
+               go func() {
+                       defer wg.Done()
+                       invocation.SetInvoker(mockInvoker)
+               }()
+       }
+       wg.Wait()
+
+       // Final verification after all writes are complete
+       finalInvoker := invocation.Invoker()
+       assert.NotNil(t, finalInvoker, "Final check: Invoker should not be nil")
+       assert.Equal(t, mockInvoker, finalInvoker, "Final check: Invoker should 
match")
+}
+
+// mockInvoker is a simple mock implementation of base.Invoker for testing
+type mockInvoker struct {
+       url *common.URL
+}
+
+func (m *mockInvoker) GetURL() *common.URL {
+       return m.url
+}
+
+func (m *mockInvoker) IsAvailable() bool {
+       return true
+}
+
+func (m *mockInvoker) Destroy() {
+}
+
+func (m *mockInvoker) Invoke(ctx context.Context, inv base.Invocation) 
result.Result {
+       return &result.RPCResult{}
+}

Reply via email to