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 7382d07b7ad51a86bf0fbbf19de71aa7829e7657
Author: QIT Development Team <[email protected]>
AuthorDate: Fri Jul 17 21:01:09 2026 -0400

    Restructure: Unified JMS test matrix (2×2 → 6×6 incremental)
    
    Changes:
    - New test_jms_unified.py with N×N client matrix structure
    - Matches AMQP test_types.py pattern for consistency
    - Consolidates test_jms.py (baseline) + test_jms_interop.py (cross-client)
    - Phase 2b.1: Python + JMS enabled (2×2 = 4 combinations)
    - Future: Incrementally add JS, C++, .NET, Java (→ 6×6 = 36 combinations)
    
    Structure:
    - ENABLED_CLIENTS list for incremental activation
    - CLIENT_INFO metadata for each client
    - Single test function: test_jms_textmessage_interop
    - Parametrized: sender × receiver × text_value
    - Result: 2 × 2 × 5 = 20 tests
    
    Benefits:
    ✓ Consistent with AMQP tests (same matrix pattern)
    ✓ JMS baseline preserved (JMS→JMS always in matrix)
    ✓ No duplication (JMS→JMS tested once)
    ✓ Incremental growth (enable 1 client at a time)
    ✓ Clear reporting (single unified matrix)
    
    Test Breakdown (Phase 2b.1):
    - python-proton → python-proton (5 tests)
    - python-proton → jms (5 tests)
    - jms → python-proton (5 tests)
    - jms → jms (5 tests)
    Total: 20 tests (was 15 in split structure)
    
    Note: Old test files (test_jms.py, test_jms_interop.py) will be
    deprecated once Build #69 validates this structure.
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
---
 tests/test_jms_unified.py | 313 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 313 insertions(+)

diff --git a/tests/test_jms_unified.py b/tests/test_jms_unified.py
new file mode 100644
index 0000000..9b790d5
--- /dev/null
+++ b/tests/test_jms_unified.py
@@ -0,0 +1,313 @@
+"""
+JMS Interoperability Tests - Unified Client Matrix
+
+Tests JMS message interoperability across all AMQP clients and JMS client.
+Structure matches test_types.py (AMQP tests) for consistency.
+
+Test Matrix: N×N (sender × receiver)
+- Phase 2b.1: Python + JMS (2×2 = 4 tests)
+- Phase 2b.2: + JavaScript (3×3 = 9 tests)
+- Phase 2b.3: + C++ (4×4 = 16 tests)
+- Phase 2b.4: + .NET (5×5 = 25 tests)
+- Phase 2b.5: + Java ProtonJ2 (6×6 = 36 tests)
+
+Message Types: Incremental
+- Phase 2b: TextMessage only
+- Phase 2c: + BytesMessage, MapMessage, StreamMessage, Message
+- Phase 2d: + Headers, Properties
+"""
+
+import json
+import os
+import subprocess
+from pathlib import Path
+from typing import Any
+
+import pytest
+
+
+# =============================================================================
+# Test Data
+# =============================================================================
+
+# TextMessage test values (Phase 2b - initial implementation)
+TEXT_MESSAGE_VALUES = [
+    "",  # Empty string
+    "Hello, world",  # Simple ASCII
+    "Charlie's \"peach\"",  # Quotes and apostrophe
+    "Unicode: ñ 日本語 🎉",  # Unicode characters
+    "The quick brown fox jumped over the lazy dog.",  # Longer text
+]
+
+# Future: BytesMessage, MapMessage, StreamMessage, Message
+# Future: Headers (JMSCorrelationID, JMSReplyTo, JMSType)
+# Future: Properties (boolean, byte, short, int, long, float, double, string)
+
+
+# =============================================================================
+# Client Configurations
+# =============================================================================
+
+# Enabled clients for testing (incrementally expand)
+ENABLED_CLIENTS = [
+    "python-proton",  # Phase 2b.1 ✅
+    "jms",            # Phase 2b.1 ✅
+    # "javascript-rhea",  # Phase 2b.2 (future)
+    # "cpp-proton",       # Phase 2b.3 (future)
+    # "dotnet-proton",    # Phase 2b.4 (future)
+    # "java-protonj2",    # Phase 2b.5 (future)
+]
+
+# Client metadata
+CLIENT_INFO = {
+    "python-proton": {
+        "name": "Python Proton",
+        "shim_path": "shims/python-proton/shim.py",
+        "jms_mode": True,  # Supports JMS emulation via --jms-mode flag
+    },
+    "javascript-rhea": {
+        "name": "JavaScript Rhea",
+        "shim_path": "shims/javascript-rhea/shim.js",
+        "jms_mode": True,  # Will support JMS emulation (Phase 2b.2)
+    },
+    "cpp-proton": {
+        "name": "C++ Proton",
+        "shim_path": "shims/cpp-proton/sender",  # Different pattern
+        "jms_mode": True,  # Will support JMS emulation (Phase 2b.3)
+    },
+    "dotnet-proton": {
+        "name": ".NET Proton",
+        "shim_path": "shims/dotnet-proton/Sender.exe",  # Different pattern
+        "jms_mode": True,  # Will support JMS emulation (Phase 2b.4)
+    },
+    "java-protonj2": {
+        "name": "Java ProtonJ2",
+        "shim_path": "shims/java-protonj2/sender.sh",
+        "jms_mode": True,  # Will support JMS emulation (Phase 2b.5)
+    },
+    "jms": {
+        "name": "Qpid JMS Client",
+        "shim_path": "shims/java-qpid-jms/sender.sh",
+        "jms_mode": False,  # Native JMS, no emulation needed
+    },
+}
+
+
+# =============================================================================
+# Fixtures
+# =============================================================================
+
[email protected]
+def broker_url():
+    """Get broker URL from environment or use default."""
+    return os.environ.get("QIT_BROKER_URL", "localhost:5672")
+
+
[email protected]
+def test_queue():
+    """Generate unique queue name for test isolation."""
+    import random
+    import string
+
+    suffix = "".join(random.choices(string.ascii_lowercase + string.digits, 
k=8))
+    return f"qit.test.jms.{suffix}"
+
+
[email protected]
+def project_root():
+    """Get project root directory."""
+    return Path(__file__).parent.parent
+
+
+# =============================================================================
+# Shim Runners
+# =============================================================================
+
+def run_sender(
+    client: str,
+    broker_url: str,
+    queue: str,
+    messages: list[dict[str, Any]],
+    project_root: Path,
+) -> dict[str, Any]:
+    """Run sender shim for any client."""
+    client_info = CLIENT_INFO[client]
+    shim_path = project_root / client_info["shim_path"]
+
+    if client == "jms":
+        # JMS sender (native JMS format)
+        cmd = [
+            str(shim_path),
+            "--broker", broker_url,
+            "--queue", queue,
+            "--type", "JMS_TEXTMESSAGE_TYPE",
+            "--data", json.dumps(messages),
+        ]
+    elif client == "python-proton":
+        # Python sender with JMS emulation
+        cmd = [
+            "python3", 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: JavaScript, C++, .NET, Java ProtonJ2
+        pytest.skip(f"Sender for {client} not yet implemented")
+
+    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
+    if result.returncode != 0:
+        pytest.fail(f"{client_info['name']} sender failed: {result.stderr}")
+
+    return json.loads(result.stdout)
+
+
+def run_receiver(
+    client: str,
+    broker_url: str,
+    queue: str,
+    count: int,
+    project_root: Path,
+    timeout: int = 30,
+) -> dict[str, Any]:
+    """Run receiver shim for any client."""
+    client_info = CLIENT_INFO[client]
+    shim_path = project_root / client_info["shim_path"]
+
+    if client == "jms":
+        # JMS receiver
+        cmd = [
+            str(shim_path.parent / "receiver.sh"),
+            "--broker", broker_url,
+            "--queue", queue,
+            "--count", str(count),
+            "--timeout", str(timeout),
+        ]
+    elif client == "python-proton":
+        # Python receiver (automatically detects JMS annotation)
+        cmd = [
+            "python3", str(shim_path),
+            "receive",
+            "--broker", f"amqp://{broker_url}",
+            "--queue", queue,
+            "--count", str(count),
+            "--timeout", str(timeout),
+        ]
+    else:
+        # Future: JavaScript, C++, .NET, Java ProtonJ2
+        pytest.skip(f"Receiver for {client} not yet implemented")
+
+    result = subprocess.run(cmd, capture_output=True, text=True, 
timeout=timeout + 10)
+    if result.returncode != 0:
+        pytest.fail(f"{client_info['name']} receiver failed: {result.stderr}")
+
+    return json.loads(result.stdout)
+
+
+# =============================================================================
+# Test Helpers
+# =============================================================================
+
+def normalize_message_type(msg_type: str) -> str:
+    """Normalize message type for comparison (string ↔ text for 
TextMessage)."""
+    if msg_type in ("string", "text"):
+        return "text"
+    return msg_type
+
+
+def compare_messages(sent: list[dict], received: list[dict], sender: str, 
receiver: str) -> None:
+    """Compare sent and received messages."""
+    if len(sent) != len(received):
+        pytest.fail(
+            f"{sender}→{receiver}: Message count mismatch - "
+            f"sent {len(sent)}, received {len(received)}"
+        )
+
+    for i, (s, r) in enumerate(zip(sent, received)):
+        # Normalize types
+        sent_type = normalize_message_type(s["type"])
+        recv_type = normalize_message_type(r["type"])
+
+        assert sent_type == recv_type, (
+            f"{sender}→{receiver}: Message {i} type mismatch - "
+            f"sent {s['type']}, received {r['type']}"
+        )
+
+        assert s["value"] == r["value"], (
+            f"{sender}→{receiver}: Message {i} value mismatch - "
+            f"sent {repr(s['value'])}, received {repr(r['value'])}"
+        )
+
+
+# =============================================================================
+# Test Matrix
+# =============================================================================
+
[email protected]("sender_client", ENABLED_CLIENTS)
[email protected]("receiver_client", ENABLED_CLIENTS)
[email protected]("text_value", TEXT_MESSAGE_VALUES)
+def test_jms_textmessage_interop(
+    sender_client: str,
+    receiver_client: str,
+    text_value: str,
+    broker_url: str,
+    test_queue: str,
+    project_root: Path,
+):
+    """
+    Test JMS TextMessage interoperability across all enabled clients.
+
+    This creates a full N×N matrix where N = number of enabled clients.
+    Each test sends a message from one client and receives with another.
+
+    Examples:
+    - python-proton → python-proton (self-test with JMS annotation)
+    - python-proton → jms (AMQP JMS emulation → native JMS)
+    - jms → python-proton (native JMS → AMQP JMS detection)
+    - jms → jms (JMS baseline, always works)
+    """
+    # Prepare message
+    if sender_client == "jms":
+        # JMS expects type="text" for TextMessage
+        messages = [{"index": 0, "type": "text", "value": text_value}]
+    else:
+        # AMQP clients use type="string" (converted to TextMessage via JMS 
annotation)
+        messages = [{"index": 0, "type": "string", "value": text_value}]
+
+    # Send message
+    send_result = run_sender(sender_client, broker_url, test_queue, messages, 
project_root)
+
+    # Receive message
+    recv_result = run_receiver(receiver_client, broker_url, test_queue, 
len(messages), project_root)
+    received = recv_result["messages"]
+
+    # Compare
+    compare_messages(messages, received, sender_client, receiver_client)
+
+
+# =============================================================================
+# Future: Additional Test Dimensions
+# =============================================================================
+
+# Phase 2c: BytesMessage, MapMessage, StreamMessage, Message
+# @pytest.mark.parametrize("sender_client", ENABLED_CLIENTS)
+# @pytest.mark.parametrize("receiver_client", ENABLED_CLIENTS)
+# def test_jms_bytesmessage_interop(sender_client, receiver_client, ...):
+#     pass
+
+# Phase 2d: Headers
+# @pytest.mark.parametrize("sender_client", ENABLED_CLIENTS)
+# @pytest.mark.parametrize("receiver_client", ENABLED_CLIENTS)
+# def test_jms_headers_interop(sender_client, receiver_client, ...):
+#     pass
+
+# Phase 2e: Properties
+# @pytest.mark.parametrize("sender_client", ENABLED_CLIENTS)
+# @pytest.mark.parametrize("receiver_client", ENABLED_CLIENTS)
+# def test_jms_properties_interop(sender_client, receiver_client, ...):
+#     pass


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

Reply via email to