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

kpvdr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-interop-test.git

commit 1b21705a49d5aad3dfb0a8875e629fb1cf638ac8
Author: QIT Development Team <[email protected]>
AuthorDate: Fri Jul 17 23:05:12 2026 -0400

    Phase 2b.4: Add .NET Proton JMS emulation
    
    Expand JMS interop test matrix from 4×4 to 5×5 (125 tests).
    
    Changes:
    - .NET shim: Add --jms-mode flag support
    - .NET sender: Add JMS message type annotation via SetAnnotation()
    - .NET receiver: Detect and decode JMS TextMessage via 
HasAnnotation()/GetAnnotation()
    - Enable dotnet-proton in ENABLED_CLIENTS list
    
    Technical details:
    - Used message.SetAnnotation("x-opt-jms-msg-type", sbyte) for annotation
    - Used message.HasAnnotation() and GetAnnotation() for detection
    - Map AMQP string → JMS TextMessage (type 5)
    - Decode JMS TextMessage as type 'text'
    
    Test results: 125/125 tests passing locally (5×5 matrix)
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
---
 shims/dotnet-proton/src/Program.cs  |  8 +++--
 shims/dotnet-proton/src/Receiver.cs | 64 ++++++++++++++++++++++++++++++++++++-
 shims/dotnet-proton/src/Sender.cs   | 31 +++++++++++++++++-
 tests/test_jms_unified.py           | 33 ++++++++++++++++---
 4 files changed, 126 insertions(+), 10 deletions(-)

diff --git a/shims/dotnet-proton/src/Program.cs 
b/shims/dotnet-proton/src/Program.cs
index 9b3c5da..fffc770 100644
--- a/shims/dotnet-proton/src/Program.cs
+++ b/shims/dotnet-proton/src/Program.cs
@@ -22,25 +22,27 @@ namespace Qit.Shim
             var sendTypeOption = new Option<string>("--type", "AMQP type") { 
IsRequired = true };
             var sendCountOption = new Option<int>("--count", "Message count") 
{ IsRequired = false };
             var sendDataOption = new Option<string>("--data", "JSON test 
data") { IsRequired = true };
+            var sendJmsModeOption = new Option<bool>("--jms-mode", () => 
false, "Enable JMS emulation mode");
 
             sendCommand.AddOption(sendBrokerOption);
             sendCommand.AddOption(sendQueueOption);
             sendCommand.AddOption(sendTypeOption);
             sendCommand.AddOption(sendCountOption);
             sendCommand.AddOption(sendDataOption);
+            sendCommand.AddOption(sendJmsModeOption);
 
-            sendCommand.SetHandler((broker, queue, type, count, data) =>
+            sendCommand.SetHandler((broker, queue, type, count, data, jmsMode) 
=>
             {
                 try
                 {
-                    Sender.Send(broker, queue, type, data);
+                    Sender.Send(broker, queue, type, data, jmsMode);
                 }
                 catch (Exception ex)
                 {
                     Console.Error.WriteLine($"Error: {ex.Message}");
                     Environment.Exit(1);
                 }
-            }, sendBrokerOption, sendQueueOption, sendTypeOption, 
sendCountOption, sendDataOption);
+            }, sendBrokerOption, sendQueueOption, sendTypeOption, 
sendCountOption, sendDataOption, sendJmsModeOption);
 
             // Receive command
             var receiveCommand = new Command("receive", "Receive AMQP 
messages");
diff --git a/shims/dotnet-proton/src/Receiver.cs 
b/shims/dotnet-proton/src/Receiver.cs
index 1e0f0e0..b541c25 100644
--- a/shims/dotnet-proton/src/Receiver.cs
+++ b/shims/dotnet-proton/src/Receiver.cs
@@ -44,7 +44,25 @@ namespace Qit.Shim
                     }
 
                     IMessage<object> message = delivery.Message();
-                    var decoded = TypeCodec.Decode(message.Body);
+
+                    // Check for JMS message type annotation
+                    sbyte jmsType = -1;
+                    if (message.HasAnnotation("x-opt-jms-msg-type"))
+                    {
+                        jmsType = 
Convert.ToSByte(message.GetAnnotation("x-opt-jms-msg-type"));
+                    }
+
+                    DecodedMessage decoded;
+                    if (jmsType >= 0)
+                    {
+                        // Decode as JMS message
+                        decoded = DecodeJmsMessage(message.Body, jmsType);
+                    }
+                    else
+                    {
+                        // Decode as regular AMQP message
+                        decoded = TypeCodec.Decode(message.Body);
+                    }
 
                     messages.Add(new MessageResult
                     {
@@ -80,5 +98,49 @@ namespace Qit.Shim
             var uri = new Uri(broker.StartsWith("amqp://") ? broker : 
$"amqp://{broker}");
             return (uri.Host, uri.Port > 0 ? uri.Port : 5672);
         }
+
+        private static DecodedMessage DecodeJmsMessage(object body, sbyte 
jmsType)
+        {
+            // JMS message type constants
+            const sbyte JMS_MESSAGE = 0;
+            const sbyte JMS_TEXT_MESSAGE = 5;
+            const sbyte JMS_BYTES_MESSAGE = 3;
+            const sbyte JMS_MAP_MESSAGE = 2;
+            const sbyte JMS_STREAM_MESSAGE = 4;
+
+            if (jmsType == JMS_TEXT_MESSAGE)
+            {
+                // TextMessage: body is string in AmqpValue section
+                return new DecodedMessage
+                {
+                    Type = "text",  // Use 'text' to match JMS shim output
+                    Value = body as string
+                };
+            }
+            else if (jmsType == JMS_BYTES_MESSAGE)
+            {
+                // BytesMessage: body is binary in Data section
+                if (body is byte[] bytes)
+                {
+                    return new DecodedMessage
+                    {
+                        Type = "bytes",
+                        Value = BitConverter.ToString(bytes).Replace("-", 
"").ToLower()
+                    };
+                }
+            }
+            else if (jmsType == JMS_MESSAGE)
+            {
+                // Empty message
+                return new DecodedMessage
+                {
+                    Type = "null",
+                    Value = null
+                };
+            }
+
+            // Unknown JMS type, fall back to regular AMQP decoding
+            return TypeCodec.Decode(body);
+        }
     }
 }
diff --git a/shims/dotnet-proton/src/Sender.cs 
b/shims/dotnet-proton/src/Sender.cs
index 19f41e6..ad9e06c 100644
--- a/shims/dotnet-proton/src/Sender.cs
+++ b/shims/dotnet-proton/src/Sender.cs
@@ -11,7 +11,7 @@ namespace Qit.Shim
 {
     public static class Sender
     {
-        public static void Send(string broker, string queue, string type, 
string data)
+        public static void Send(string broker, string queue, string type, 
string data, bool jmsMode = false)
         {
             try
             {
@@ -40,6 +40,18 @@ namespace Qit.Shim
                     message.MessageId = testMsg.Index.ToString();
                     message.Body = TypeCodec.Encode(type, testMsg.Value);
 
+                    // Add JMS annotations if in JMS mode
+                    if (jmsMode)
+                    {
+                        sbyte jmsType = GetJmsMessageType(type);
+                        if (jmsType >= 0)
+                        {
+                            // NOTE: Key MUST be symbol, value MUST be signed 
byte
+                            // This matches Qpid JMS Client wire format
+                            message.SetAnnotation("x-opt-jms-msg-type", 
jmsType);
+                        }
+                    }
+
                     sender.Send(message);
 
                     messages.Add(new MessageResult
@@ -71,6 +83,23 @@ namespace Qit.Shim
             var uri = new Uri(broker.StartsWith("amqp://") ? broker : 
$"amqp://{broker}");
             return (uri.Host, uri.Port > 0 ? uri.Port : 5672);
         }
+
+        private static sbyte GetJmsMessageType(string amqpType)
+        {
+            // JMS message type constants (from Qpid JMS Client)
+            const sbyte JMS_MESSAGE = 0;        // Empty message
+            const sbyte JMS_TEXT_MESSAGE = 5;   // String/text
+            const sbyte JMS_BYTES_MESSAGE = 3;  // Binary data
+
+            // Map AMQP types to JMS message types
+            return amqpType switch
+            {
+                "string" => JMS_TEXT_MESSAGE,
+                "binary" => JMS_BYTES_MESSAGE,
+                "null" => JMS_MESSAGE,
+                _ => -1  // Invalid
+            };
+        }
     }
 
     public class TestMessage
diff --git a/tests/test_jms_unified.py b/tests/test_jms_unified.py
index 7aeb097..fcf0ac5 100644
--- a/tests/test_jms_unified.py
+++ b/tests/test_jms_unified.py
@@ -54,7 +54,7 @@ ENABLED_CLIENTS = [
     "jms",              # Phase 2b.1 ✅
     "javascript-rhea",  # Phase 2b.2 ✅
     "cpp-proton",       # Phase 2b.3 ✅
-    # "dotnet-proton",    # Phase 2b.4 (future)
+    "dotnet-proton",    # Phase 2b.4 ✅
     # "java-protonj2",    # Phase 2b.5 (future)
 ]
 
@@ -77,8 +77,8 @@ CLIENT_INFO = {
     },
     "dotnet-proton": {
         "name": ".NET Proton",
-        "shim_path": "shims/dotnet-proton/Sender.exe",  # Different pattern
-        "jms_mode": True,  # Will support JMS emulation (Phase 2b.4)
+        "shim_path": "shims/dotnet-proton/shim.sh",
+        "jms_mode": True,  # Phase 2b.4 ✅
     },
     "java-protonj2": {
         "name": "Java ProtonJ2",
@@ -182,8 +182,21 @@ def run_sender(
         ]
         if client_info["jms_mode"]:
             cmd.append("--jms-mode")
+    elif client == "dotnet-proton":
+        # .NET sender with JMS emulation
+        cmd = [
+            str(shim_path),
+            "send",
+            "--broker", f"amqp://{broker_url}",
+            "--queue", queue,
+            "--type", "string",
+            "--count", str(len(messages)),
+            "--data", json.dumps(messages),
+        ]
+        if client_info["jms_mode"]:
+            cmd.append("--jms-mode")
     else:
-        # Future: .NET, Java ProtonJ2
+        # Future: Java ProtonJ2
         pytest.skip(f"Sender for {client} not yet implemented")
 
     result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
@@ -244,8 +257,18 @@ def run_receiver(
             "--count", str(count),
             "--timeout", str(timeout),
         ]
+    elif client == "dotnet-proton":
+        # .NET receiver (automatically detects JMS annotation)
+        cmd = [
+            str(shim_path),
+            "receive",
+            "--broker", f"amqp://{broker_url}",
+            "--queue", queue,
+            "--count", str(count),
+            "--timeout", str(timeout),
+        ]
     else:
-        # Future: .NET, Java ProtonJ2
+        # Future: Java ProtonJ2
         pytest.skip(f"Receiver for {client} not yet implemented")
 
     result = subprocess.run(cmd, capture_output=True, text=True, 
timeout=timeout + 10)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to