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]
