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
The following commit(s) were added to refs/heads/main by this push:
new 3e08839 Add shim auto-discovery and --shims/--exclude-shims CLI
filtering
3e08839 is described below
commit 3e08839b32f64a31640b87ba9eb71b18828c9332
Author: QIT Development Team <[email protected]>
AuthorDate: Wed Aug 12 15:50:36 2026 -0400
Add shim auto-discovery and --shims/--exclude-shims CLI filtering
Replace hardcoded CLIENT_INFO dicts and per-shim registration blocks
with manifest-based auto-discovery. Each shim directory now contains a
shim.json declaring name, type, and broker_prefix. The discover_shims()
function scans shims/*/shim.json at collection time, eliminating the
need to edit Python source when adding new shims.
Unify the JMS shim entry point: java-qpid-jms now has a shim.sh
dispatcher (delegates to sender.sh/receiver.sh) so all shims share
the same shim.sh {send|receive} invocation pattern.
Add --shims and --exclude-shims pytest options for Jenkins downstream
runs that need to restrict which shims participate in the test matrix.
Assisted-by: Claude Opus 4.6 <[email protected]>
---
docs/SHIM_HOWTO.md | 49 +++--
pyproject.toml | 1 +
scripts/run-rat.sh | 2 +-
shims/cpp-proton/shim.json | 5 +
shims/dotnet-proton/shim.json | 5 +
shims/java-protonj2/shim.json | 5 +
shims/java-qpid-jms/shim.json | 5 +
tests/conftest.py => shims/java-qpid-jms/shim.sh | 26 ++-
shims/javascript-rhea/shim.json | 5 +
shims/python-proton/shim.json | 5 +
src/qit/cli/main.py | 69 +------
src/qit/core/shim.py | 43 +++-
tests/conftest.py | 48 ++++-
tests/shim_registry.py | 58 ++++++
tests/test_amqp_headers.py | 86 ++------
tests/test_jms_unified.py | 242 +++--------------------
tests/test_large_content.py | 232 ++++++----------------
17 files changed, 336 insertions(+), 550 deletions(-)
diff --git a/docs/SHIM_HOWTO.md b/docs/SHIM_HOWTO.md
index b38c1c9..03036f2 100644
--- a/docs/SHIM_HOWTO.md
+++ b/docs/SHIM_HOWTO.md
@@ -363,33 +363,48 @@ exec java -cp "${JAR}:${DEPS}"
org.apache.qpid.qit.ShimMain "$@"
## Registering a Shim
-Shims are registered in the `CLIENT_INFO` dictionary in each test file. The
-standard pattern uses lambdas for the command construction:
-
-```python
-CLIENT_INFO = {
- "my-client": {
- "name": "My Client Library",
- "send_cmd": lambda path: ["my-shim", str(path /
"shims/my-client/shim.sh"), "send"],
- "recv_cmd": lambda path: ["my-shim", str(path /
"shims/my-client/shim.sh"), "receive"],
- "broker_prefix": "amqp://",
- },
+Shims are registered automatically via **auto-discovery**. No Python source
+files need editing. Place a `shim.json` manifest in the shim directory:
+
+```json
+{
+ "name": "My Client Library",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
}
```
-- `name`: display name for test output
-- `send_cmd`/`recv_cmd`: lambdas taking the project root `Path`, returning the
- base command list. The framework appends `--broker`, `--queue`, `--type`,
- `--data`, etc.
-- `broker_prefix`: prepended to the raw broker URL (`"amqp://"` for most
+Fields:
+
+- `name` — display name for test output
+- `type` — `"amqp"` or `"jms"`. Determines which test suites include this shim
+ and whether `--jms-mode` is needed for JMS-emulation tests
+- `broker_prefix` — prepended to the raw broker URL (`"amqp://"` for most
clients, `""` for JMS clients that use their own URL format)
-Add the new client key to each test file where the shim should participate.
+Unknown fields are ignored, so manifests are forward-compatible.
+
+At test collection time, `discover_shims()` scans `shims/*/shim.json`,
validates
+that `shim.sh` exists alongside each manifest, and builds the shim registry.
+The directory name becomes the shim key (e.g. `shims/my-client/` → key
+`"my-client"`).
+
+### Filtering shims at test time
+
+Two pytest CLI options control which shims participate:
+
+- `--shims python-proton,cpp-proton` — whitelist: only run tests involving
+ these shims
+- `--exclude-shims javascript-rhea` — blacklist: skip tests involving these
+ shims (applied after `--shims`)
+
+Both accept comma-separated shim keys. Omit both to test all discovered shims.
## Directory Layout
```
shims/my-client/
+├── shim.json # manifest (required for auto-discovery)
├── shim.sh # wrapper script (entry point)
├── shim.py # or src/, pom.xml, etc.
└── README.md # optional: build/setup instructions
diff --git a/pyproject.toml b/pyproject.toml
index 503f607..72f3d64 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,6 +59,7 @@ build-backend = "hatchling.build"
packages = ["src/qit"]
[tool.pytest.ini_options]
+pythonpath = ["tests"]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
diff --git a/scripts/run-rat.sh b/scripts/run-rat.sh
index 25576d8..4aa4ced 100755
--- a/scripts/run-rat.sh
+++ b/scripts/run-rat.sh
@@ -47,7 +47,7 @@ java -jar "${RAT_JAR}" -d . \
-e .venv -e __pycache__ -e build -e target -e obj -e bin -e node_modules \
-e .eggs -e artemis-local -e test-results -e .git -e .claude -e
.pytest_cache \
-e uv.lock -e .rat-excludes \
- -e CHANGES -e .gitignore \
+ -e CHANGES -e .gitignore -e shim.json \
-e Dockerfile.artemis -e compose.yaml -e broker.xml.snippet \
"${MD_EXCLUDES[@]}" \
"$@"
diff --git a/shims/cpp-proton/shim.json b/shims/cpp-proton/shim.json
new file mode 100644
index 0000000..683c77e
--- /dev/null
+++ b/shims/cpp-proton/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": "C++ Proton",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
+}
diff --git a/shims/dotnet-proton/shim.json b/shims/dotnet-proton/shim.json
new file mode 100644
index 0000000..f57263c
--- /dev/null
+++ b/shims/dotnet-proton/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": ".NET Proton",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
+}
diff --git a/shims/java-protonj2/shim.json b/shims/java-protonj2/shim.json
new file mode 100644
index 0000000..277400a
--- /dev/null
+++ b/shims/java-protonj2/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": "Java ProtonJ2",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
+}
diff --git a/shims/java-qpid-jms/shim.json b/shims/java-qpid-jms/shim.json
new file mode 100644
index 0000000..46f8ae0
--- /dev/null
+++ b/shims/java-qpid-jms/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": "Java Qpid JMS",
+ "type": "jms",
+ "broker_prefix": ""
+}
diff --git a/tests/conftest.py b/shims/java-qpid-jms/shim.sh
old mode 100644
new mode 100755
similarity index 61%
copy from tests/conftest.py
copy to shims/java-qpid-jms/shim.sh
index 7d51638..1fc74b6
--- a/tests/conftest.py
+++ b/shims/java-qpid-jms/shim.sh
@@ -1,3 +1,4 @@
+#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -17,21 +18,16 @@
# under the License.
#
-import pytest
+# QIT 2.0 - JMS Shim Wrapper
+# Dispatches send/receive subcommands to sender.sh/receiver.sh
+set -e
-def pytest_addoption(parser):
- parser.addoption(
- "--large-content",
- action="store_true",
- default=False,
- help="Run extended large content tests (10MB)",
- )
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SUBCMD="$1"; shift
-
-def pytest_collection_modifyitems(config, items):
- if not config.getoption("--large-content"):
- skip = pytest.mark.skip(reason="needs --large-content option to run")
- for item in items:
- if "large_content" in item.keywords:
- item.add_marker(skip)
+case "$SUBCMD" in
+ send) exec "$SCRIPT_DIR/sender.sh" "$@" ;;
+ receive) exec "$SCRIPT_DIR/receiver.sh" "$@" ;;
+ *) echo "Usage: shim.sh {send|receive} [args...]" >&2; exit 1 ;;
+esac
diff --git a/shims/javascript-rhea/shim.json b/shims/javascript-rhea/shim.json
new file mode 100644
index 0000000..7a70b1d
--- /dev/null
+++ b/shims/javascript-rhea/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": "JavaScript Rhea",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
+}
diff --git a/shims/python-proton/shim.json b/shims/python-proton/shim.json
new file mode 100644
index 0000000..11617a5
--- /dev/null
+++ b/shims/python-proton/shim.json
@@ -0,0 +1,5 @@
+{
+ "name": "Python Proton",
+ "type": "amqp",
+ "broker_prefix": "amqp://"
+}
diff --git a/src/qit/cli/main.py b/src/qit/cli/main.py
index 1998ed8..7b4d3bd 100644
--- a/src/qit/cli/main.py
+++ b/src/qit/cli/main.py
@@ -164,74 +164,25 @@ def test_amqp_types(
from pathlib import Path
from qit.core import BrokerConfig, BrokerManager, Orchestrator, Shim,
ShimConfig
+ from qit.core.shim import discover_shims
from qit.types import AmqpComplexTypes, AmqpPrimitiveTypes
click.echo("QIT - AMQP Types Test")
click.echo("=" * 80)
- # Discover available shims
project_root = Path(__file__).parent.parent.parent.parent
- shim_dir = project_root / "shims"
+ discovered = discover_shims(project_root / "shims")
available_shims = {}
-
- # Python shim
- python_shim_path = shim_dir / "python-proton" / "shim.sh"
- if python_shim_path.exists():
- available_shims["python-proton"] = Shim(
- ShimConfig(
- name="python-proton",
- language="python",
- client="Apache Qpid Proton Python",
- executable=python_shim_path,
- )
- )
-
- # JavaScript/Rhea shim
- js_shim_path = shim_dir / "javascript-rhea" / "shim.sh"
- if js_shim_path.exists():
- available_shims["javascript-rhea"] = Shim(
- ShimConfig(
- name="javascript-rhea",
- language="javascript",
- client="Rhea AMQP Client",
- executable=js_shim_path,
- )
- )
-
- # C++ Proton shim
- cpp_shim_path = shim_dir / "cpp-proton" / "shim.sh"
- if cpp_shim_path.exists():
- available_shims["cpp-proton"] = Shim(
- ShimConfig(
- name="cpp-proton",
- language="cpp",
- client="Apache Qpid Proton C++",
- executable=cpp_shim_path,
- )
- )
-
- # .NET Proton shim
- dotnet_shim_path = shim_dir / "dotnet-proton" / "shim.sh"
- if dotnet_shim_path.exists():
- available_shims["dotnet-proton"] = Shim(
- ShimConfig(
- name="dotnet-proton",
- language="csharp",
- client="Apache Qpid Proton .NET",
- executable=dotnet_shim_path,
- )
- )
-
- # Java ProtonJ2 shim
- java_shim_path = shim_dir / "java-protonj2" / "shim.sh"
- if java_shim_path.exists():
- available_shims["java-protonj2"] = Shim(
+ for key, info in discovered.items():
+ if info.shim_type == "jms":
+ continue
+ available_shims[key] = Shim(
ShimConfig(
- name="java-protonj2",
- language="java",
- client="Apache Qpid ProtonJ2",
- executable=java_shim_path,
+ name=key,
+ language=key.split("-")[0],
+ client=info.name,
+ executable=info.shim_dir / "shim.sh",
)
)
diff --git a/src/qit/core/shim.py b/src/qit/core/shim.py
index d60a616..743ecc5 100644
--- a/src/qit/core/shim.py
+++ b/src/qit/core/shim.py
@@ -21,15 +21,56 @@
Shim interface for AMQP client implementations.
Defines the protocol for communication between the test orchestrator
-and native client shims.
+and native client shims, plus auto-discovery of shim directories.
"""
import json
+import logging
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Any
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class ShimInfo:
+ """Metadata about a discovered shim, loaded from shim.json."""
+
+ name: str
+ key: str
+ shim_dir: Path
+ shim_type: str
+ broker_prefix: str
+
+
+def discover_shims(shims_dir: Path) -> dict[str, ShimInfo]:
+ """Scan shims_dir/*/shim.json and return validated ShimInfo dict keyed by
directory name."""
+ shims: dict[str, ShimInfo] = {}
+ if not shims_dir.is_dir():
+ return shims
+ for manifest in sorted(shims_dir.glob("*/shim.json")):
+ shim_dir = manifest.parent
+ key = shim_dir.name
+ try:
+ with open(manifest) as f:
+ data = json.load(f)
+ shim_sh = shim_dir / "shim.sh"
+ if not shim_sh.exists():
+ logger.warning("Skipping %s: shim.sh not found", key)
+ continue
+ shims[key] = ShimInfo(
+ name=data["name"],
+ key=key,
+ shim_dir=shim_dir,
+ shim_type=data["type"],
+ broker_prefix=data.get("broker_prefix", "amqp://"),
+ )
+ except (json.JSONDecodeError, KeyError) as exc:
+ logger.warning("Skipping %s: invalid shim.json: %s", key, exc)
+ return shims
+
@dataclass
class ShimConfig:
diff --git a/tests/conftest.py b/tests/conftest.py
index 7d51638..b83370b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -19,6 +19,8 @@
import pytest
+from shim_registry import DISCOVERED_SHIMS, PROJECT_ROOT
+
def pytest_addoption(parser):
parser.addoption(
@@ -27,11 +29,53 @@ def pytest_addoption(parser):
default=False,
help="Run extended large content tests (10MB)",
)
+ parser.addoption(
+ "--shims",
+ default=None,
+ help="Comma-separated list of shim keys to include (e.g.
python-proton,cpp-proton)",
+ )
+ parser.addoption(
+ "--exclude-shims",
+ default=None,
+ help="Comma-separated list of shim keys to exclude (e.g.
javascript-rhea)",
+ )
+
+
+def _extract_shim_keys(item) -> set[str]:
+ """Extract shim keys from a test item's parametrize callspec."""
+ keys = set()
+ if hasattr(item, "callspec"):
+ for val in item.callspec.params.values():
+ if isinstance(val, str) and val in DISCOVERED_SHIMS:
+ keys.add(val)
+ return keys
def pytest_collection_modifyitems(config, items):
if not config.getoption("--large-content"):
- skip = pytest.mark.skip(reason="needs --large-content option to run")
+ skip_large = pytest.mark.skip(reason="needs --large-content option to
run")
for item in items:
if "large_content" in item.keywords:
- item.add_marker(skip)
+ item.add_marker(skip_large)
+
+ include_opt = config.getoption("--shims")
+ exclude_opt = config.getoption("--exclude-shims")
+ if not include_opt and not exclude_opt:
+ return
+
+ include = {k.strip() for k in include_opt.split(",")} if include_opt else
None
+ exclude = {k.strip() for k in exclude_opt.split(",")} if exclude_opt else
set()
+
+ for item in items:
+ shim_keys = _extract_shim_keys(item)
+ if not shim_keys:
+ continue
+ if include is not None and not shim_keys.issubset(include):
+ item.add_marker(pytest.mark.skip(reason="shim not in --shims
list"))
+ elif shim_keys & exclude:
+ item.add_marker(pytest.mark.skip(reason="shim in --exclude-shims
list"))
+
+
[email protected](scope="session")
+def project_root():
+ return PROJECT_ROOT
diff --git a/tests/shim_registry.py b/tests/shim_registry.py
new file mode 100644
index 0000000..12ad690
--- /dev/null
+++ b/tests/shim_registry.py
@@ -0,0 +1,58 @@
+#
+# 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.
+#
+
+"""
+Shim registry for test parametrization.
+
+Auto-discovers shims at import time and provides pre-built
+client lists and pair lists for pytest parametrize decorators.
+"""
+
+import itertools
+from pathlib import Path
+
+import pytest
+
+from qit.core.shim import ShimInfo, discover_shims
+
+PROJECT_ROOT = Path(__file__).parent.parent
+DISCOVERED_SHIMS: dict[str, ShimInfo] = discover_shims(PROJECT_ROOT / "shims")
+
+AMQP_CLIENTS = sorted(
+ k for k, v in DISCOVERED_SHIMS.items() if v.shim_type == "amqp"
+)
+JMS_CLIENTS = sorted(
+ k for k, v in DISCOVERED_SHIMS.items() if v.shim_type == "jms"
+)
+ALL_CLIENTS = sorted(DISCOVERED_SHIMS.keys())
+
+AMQP_PAIRS = [
+ pytest.param(s, r, id=f"{s}->{r}")
+ for s, r in itertools.product(AMQP_CLIENTS, repeat=2)
+]
+
+_jms = JMS_CLIENTS[0] if JMS_CLIENTS else None
+STAR_PAIRS = []
+if _jms:
+ STAR_PAIRS = (
+ [pytest.param(_jms, c, id=f"{_jms}->{c}") for c in AMQP_CLIENTS]
+ + [pytest.param(c, _jms, id=f"{c}->{_jms}") for c in AMQP_CLIENTS]
+ + [pytest.param(_jms, _jms, id=f"{_jms}->{_jms}")]
+ )
+ALL_PAIRS = STAR_PAIRS + AMQP_PAIRS
diff --git a/tests/test_amqp_headers.py b/tests/test_amqp_headers.py
index cf76604..f2b7587 100644
--- a/tests/test_amqp_headers.py
+++ b/tests/test_amqp_headers.py
@@ -20,21 +20,13 @@
"""
AMQP Message Header Section Interoperability Tests (Phase 2d)
-Tests AMQP 1.0 Header section fields across the 5×5 AMQP client matrix:
+Tests AMQP 1.0 Header section fields across the AMQP client matrix:
durable, priority, ttl, first-acquirer, delivery-count
Test Pairs:
-- AMQP N×N (25 pairs): all 5 AMQP clients against each other (no JMS)
-
-275 total tests:
- durable: 25 pairs × 2 values = 50
- priority: 25 pairs × 4 values = 100
- ttl: 25 pairs × 2 values = 50
- first_acquirer: 25 pairs × 2 values = 50
- delivery_count: 25 pairs × 1 (verify 0) = 25
+- AMQP N×N: all AMQP clients against each other (no JMS)
"""
-import itertools
import json
import os
import subprocess
@@ -43,56 +35,7 @@ from typing import Any
import pytest
-
-# =============================================================================
-# Client Configurations
-# =============================================================================
-
-AMQP_CLIENTS = [
- "python-proton",
- "javascript-rhea",
- "cpp-proton",
- "dotnet-proton",
- "java-protonj2",
-]
-
-AMQP_PAIRS = [
- pytest.param(s, r, id=f"{s}->{r}")
- for s, r in itertools.product(AMQP_CLIENTS, repeat=2)
-]
-
-CLIENT_INFO = {
- "python-proton": {
- "name": "Python Proton",
- "send_cmd": lambda path: ["python3", str(path /
"shims/python-proton/shim.py"), "send"],
- "recv_cmd": lambda path: ["python3", str(path /
"shims/python-proton/shim.py"), "receive"],
- "broker_prefix": "amqp://",
- },
- "javascript-rhea": {
- "name": "JavaScript Rhea",
- "send_cmd": lambda path: ["node", str(path /
"shims/javascript-rhea/shim.js"), "send"],
- "recv_cmd": lambda path: ["node", str(path /
"shims/javascript-rhea/shim.js"), "receive"],
- "broker_prefix": "amqp://",
- },
- "cpp-proton": {
- "name": "C++ Proton",
- "send_cmd": lambda path: [str(path /
"shims/cpp-proton/build/qit-shim-cpp"), "send"],
- "recv_cmd": lambda path: [str(path /
"shims/cpp-proton/build/qit-shim-cpp"), "receive"],
- "broker_prefix": "amqp://",
- },
- "dotnet-proton": {
- "name": ".NET Proton",
- "send_cmd": lambda path: [str(path / "shims/dotnet-proton/shim.sh"),
"send"],
- "recv_cmd": lambda path: [str(path / "shims/dotnet-proton/shim.sh"),
"receive"],
- "broker_prefix": "amqp://",
- },
- "java-protonj2": {
- "name": "Java ProtonJ2",
- "send_cmd": lambda path: [str(path / "shims/java-protonj2/shim.sh"),
"send"],
- "recv_cmd": lambda path: [str(path / "shims/java-protonj2/shim.sh"),
"receive"],
- "broker_prefix": "amqp://",
- },
-}
+from shim_registry import AMQP_CLIENTS, AMQP_PAIRS, DISCOVERED_SHIMS
# =============================================================================
@@ -112,11 +55,6 @@ def test_queue():
return f"qit.test.amqp_header.{suffix}"
[email protected]
-def project_root():
- return Path(__file__).parent.parent
-
-
# =============================================================================
# Shim Runners
# =============================================================================
@@ -129,12 +67,13 @@ def run_sender(
message_header: dict[str, Any] | None = None,
timeout: int = 30,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
messages = [{"index": 0, "type": "string", "value": "header-test"}]
- cmd = info["send_cmd"](project_root) + [
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "send",
"--broker", broker,
"--queue", queue,
"--type", "string",
@@ -147,7 +86,7 @@ def run_sender(
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout)
if result.returncode != 0:
- pytest.fail(f"{info['name']} sender failed: {result.stderr}")
+ pytest.fail(f"{shim.name} sender failed: {result.stderr}")
return json.loads(result.stdout)
@@ -159,10 +98,11 @@ def run_receiver(
project_root: Path,
timeout: int = 30,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
- cmd = info["recv_cmd"](project_root) + [
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "receive",
"--broker", broker,
"--queue", queue,
"--count", "1",
@@ -171,7 +111,7 @@ def run_receiver(
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout + 5)
if result.returncode != 0:
- pytest.fail(f"{info['name']} receiver failed: {result.stderr}")
+ pytest.fail(f"{shim.name} receiver failed: {result.stderr}")
return json.loads(result.stdout)
diff --git a/tests/test_jms_unified.py b/tests/test_jms_unified.py
index 89b5e43..335d478 100644
--- a/tests/test_jms_unified.py
+++ b/tests/test_jms_unified.py
@@ -26,13 +26,10 @@ side, validating that each AMQP client can correctly send
to and receive
from a native JMS endpoint.
Star Pairs (11 total):
-- JMS -> AMQP client (5 pairs: python-proton, javascript-rhea, cpp-proton,
- dotnet-proton, java-protonj2)
-- AMQP client -> JMS (5 pairs: same clients in reverse)
+- JMS -> AMQP client (5 pairs)
+- AMQP client -> JMS (5 pairs)
- JMS -> JMS (baseline)
-Test Count (Phase 2d): 143 body + 132 header = 275 tests
-
Message Types: Incremental
- Phase 2b: TextMessage only
- Phase 2c: + BytesMessage, Message, MapMessage, StreamMessage
@@ -48,6 +45,8 @@ from typing import Any
import pytest
+from shim_registry import DISCOVERED_SHIMS, STAR_PAIRS
+
# =============================================================================
# Test Data
@@ -152,64 +151,6 @@ JMS_PROPS_STRING = {
}
-# =============================================================================
-# Client Configurations
-# =============================================================================
-
-# Hub of the star: native JMS client
-JMS_CLIENT = "jms"
-
-# Spokes of the star: AMQP clients that emulate JMS via --jms-mode
-AMQP_CLIENTS = [
- "python-proton",
- "javascript-rhea",
- "cpp-proton",
- "dotnet-proton",
- "java-protonj2",
-]
-
-# Star test pairs: JMS is always on at least one side
-STAR_PAIRS = (
- [pytest.param(JMS_CLIENT, c, id=f"jms->{c}") for c in AMQP_CLIENTS]
- + [pytest.param(c, JMS_CLIENT, id=f"{c}->jms") for c in AMQP_CLIENTS]
- + [pytest.param(JMS_CLIENT, JMS_CLIENT, id="jms->jms")]
-)
-
-# 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/build/qit-shim-cpp",
- "jms_mode": True, # Phase 2b.3 ✅
- },
- "dotnet-proton": {
- "name": ".NET Proton",
- "shim_path": "shims/dotnet-proton/shim.sh",
- "jms_mode": True, # Phase 2b.4 ✅
- },
- "java-protonj2": {
- "name": "Java ProtonJ2",
- "shim_path": "shims/java-protonj2/shim.sh",
- "jms_mode": True, # 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
# =============================================================================
@@ -230,12 +171,6 @@ def test_queue():
return f"qit.test.jms.{suffix}"
[email protected]
-def project_root():
- """Get project root directory."""
- return Path(__file__).parent.parent
-
-
# =============================================================================
# Shim Runners
# =============================================================================
@@ -252,84 +187,27 @@ def run_sender(
properties: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Run sender shim for any client."""
- client_info = CLIENT_INFO[client]
- shim_path = project_root / client_info["shim_path"]
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
- if client == "jms":
- # JMS sender (native JMS format)
+ if shim.shim_type == "jms":
cmd = [
- str(shim_path),
+ str(shim.shim_dir / "shim.sh"), "send",
"--broker", broker_url,
"--queue", queue,
"--type", jms_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", amqp_type,
- "--count", str(len(messages)),
- "--data", json.dumps(messages),
- ]
- if client_info["jms_mode"]:
- cmd.append("--jms-mode")
- elif client == "javascript-rhea":
- # JavaScript sender with JMS emulation
- cmd = [
- "node", str(shim_path),
- "send",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--type", amqp_type,
- "--count", str(len(messages)),
- "--data", json.dumps(messages),
- ]
- if client_info["jms_mode"]:
- cmd.append("--jms-mode")
- elif client == "cpp-proton":
- # C++ sender with JMS emulation
- cmd = [
- str(shim_path),
- "send",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--type", amqp_type,
- "--count", str(len(messages)),
- "--data", json.dumps(messages),
- ]
- if client_info["jms_mode"]:
- cmd.append("--jms-mode")
- elif client == "dotnet-proton":
- # .NET sender with JMS emulation
+ else:
cmd = [
- str(shim_path),
- "send",
- "--broker", f"amqp://{broker_url}",
+ str(shim.shim_dir / "shim.sh"), "send",
+ "--broker", broker,
"--queue", queue,
"--type", amqp_type,
"--count", str(len(messages)),
"--data", json.dumps(messages),
+ "--jms-mode",
]
- if client_info["jms_mode"]:
- cmd.append("--jms-mode")
- elif client == "java-protonj2":
- # Java ProtonJ2 sender with JMS emulation
- cmd = [
- str(shim_path),
- "send",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--type", amqp_type,
- "--data", json.dumps(messages),
- ]
- if client_info["jms_mode"]:
- cmd.append("--jms-mode")
- else:
- pytest.skip(f"Sender for {client} not yet implemented")
if headers:
cmd.extend(["--headers", json.dumps(headers)])
@@ -339,7 +217,7 @@ def run_sender(
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}")
+ pytest.fail(f"{shim.name} sender failed: {result.stderr}")
return json.loads(result.stdout)
@@ -353,74 +231,20 @@ def run_receiver(
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"]
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
- 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),
- ]
- elif client == "javascript-rhea":
- # JavaScript receiver (automatically detects JMS annotation)
- cmd = [
- "node", str(shim_path),
- "receive",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--count", str(count),
- "--timeout", str(timeout),
- ]
- elif client == "cpp-proton":
- # C++ receiver (automatically detects JMS annotation)
- cmd = [
- str(shim_path),
- "receive",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--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),
- ]
- elif client == "java-protonj2":
- # Java ProtonJ2 receiver (automatically detects JMS annotation)
- cmd = [
- str(shim_path),
- "receive",
- "--broker", f"amqp://{broker_url}",
- "--queue", queue,
- "--count", str(count),
- "--timeout", str(timeout),
- ]
- else:
- pytest.skip(f"Receiver for {client} not yet implemented")
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "receive",
+ "--broker", broker,
+ "--queue", queue,
+ "--count", str(count),
+ "--timeout", str(timeout),
+ ]
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}")
+ pytest.fail(f"{shim.name} receiver failed: {result.stderr}")
return json.loads(result.stdout)
@@ -495,22 +319,17 @@ def test_jms_textmessage_interop(
This validates that each AMQP client can correctly send JMS-annotated
messages to, and receive JMS messages from, the native JMS client.
"""
- # Prepare message
- if sender_client == "jms":
- # JMS expects type="text" for TextMessage
+ sender_shim = DISCOVERED_SHIMS[sender_client]
+ if sender_shim.shim_type == "jms":
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)
@@ -525,7 +344,8 @@ def test_jms_bytesmessage_interop(
project_root: Path,
):
"""Test JMS BytesMessage interoperability using star configuration."""
- if sender_client == "jms":
+ sender_shim = DISCOVERED_SHIMS[sender_client]
+ if sender_shim.shim_type == "jms":
messages = [{"index": 0, "type": "bytes", "value": bytes_value}]
else:
messages = [{"index": 0, "type": "binary", "value": bytes_value}]
@@ -550,10 +370,11 @@ def test_jms_message_interop(
project_root: Path,
):
"""Test JMS Message (empty/no body) interoperability using star
configuration."""
- if sender_client == "javascript-rhea" and receiver_client == "jms":
+ if sender_client == "javascript-rhea" and
DISCOVERED_SHIMS[receiver_client].shim_type == "jms":
pytest.xfail("Rhea sends AmqpValue(null) for empty body, JMS maps this
to TextMessage")
- if sender_client == "jms":
+ sender_shim = DISCOVERED_SHIMS[sender_client]
+ if sender_shim.shim_type == "jms":
messages = [{"index": 0, "type": "none", "value": None}]
else:
messages = [{"index": 0, "type": "null", "value": None}]
@@ -672,7 +493,8 @@ def compare_headers(
def _header_test_message(sender_client: str) -> list[dict[str, Any]]:
"""Create a single TextMessage for header tests."""
- if sender_client == "jms":
+ sender_shim = DISCOVERED_SHIMS[sender_client]
+ if sender_shim.shim_type == "jms":
return [{"index": 0, "type": "text", "value": "header-test"}]
return [{"index": 0, "type": "string", "value": "header-test"}]
diff --git a/tests/test_large_content.py b/tests/test_large_content.py
index f05b929..20dfcfc 100644
--- a/tests/test_large_content.py
+++ b/tests/test_large_content.py
@@ -29,16 +29,8 @@ Phase 4c: Multi-frame-size tests — same payloads through
brokers with
Test Pairs:
- JMS star (11 pairs): JMS always on at least one side
- AMQP N×N (25 pairs): all 5 AMQP clients against each other
-
-Phase 4 default: 72 tests (1MB binary + string × 36 pairs)
-Phase 4 extended: 72 tests (10MB binary + string × 36 pairs)
-Phase 4b default: 122 tests (list × 36 + array × 25, sub/super-frame)
-Phase 4b extended: 122 tests (map × 36 + described × 25, sub/super-frame)
-Phase 4c default: 200 tests (binary + string + list + array × 25 pairs × 2
frame sizes)
-Phase 4c extended: 200 tests (map + described × 25 pairs × 2 frame sizes)
"""
-import itertools
import json
import os
import subprocess
@@ -47,72 +39,7 @@ from typing import Any
import pytest
-
-# =============================================================================
-# Client Configurations
-# =============================================================================
-
-JMS_CLIENT = "jms"
-
-AMQP_CLIENTS = [
- "python-proton",
- "javascript-rhea",
- "cpp-proton",
- "dotnet-proton",
- "java-protonj2",
-]
-
-STAR_PAIRS = (
- [pytest.param(JMS_CLIENT, c, id=f"jms->{c}") for c in AMQP_CLIENTS]
- + [pytest.param(c, JMS_CLIENT, id=f"{c}->jms") for c in AMQP_CLIENTS]
- + [pytest.param(JMS_CLIENT, JMS_CLIENT, id="jms->jms")]
-)
-
-AMQP_PAIRS = [
- pytest.param(s, r, id=f"{s}->{r}")
- for s, r in itertools.product(AMQP_CLIENTS, repeat=2)
-]
-
-ALL_PAIRS = STAR_PAIRS + AMQP_PAIRS
-
-CLIENT_INFO = {
- "python-proton": {
- "name": "Python Proton",
- "send_cmd": lambda path: ["python3", str(path /
"shims/python-proton/shim.py"), "send"],
- "recv_cmd": lambda path: ["python3", str(path /
"shims/python-proton/shim.py"), "receive"],
- "broker_prefix": "amqp://",
- },
- "javascript-rhea": {
- "name": "JavaScript Rhea",
- "send_cmd": lambda path: ["node", str(path /
"shims/javascript-rhea/shim.js"), "send"],
- "recv_cmd": lambda path: ["node", str(path /
"shims/javascript-rhea/shim.js"), "receive"],
- "broker_prefix": "amqp://",
- },
- "cpp-proton": {
- "name": "C++ Proton",
- "send_cmd": lambda path: [str(path /
"shims/cpp-proton/build/qit-shim-cpp"), "send"],
- "recv_cmd": lambda path: [str(path /
"shims/cpp-proton/build/qit-shim-cpp"), "receive"],
- "broker_prefix": "amqp://",
- },
- "dotnet-proton": {
- "name": ".NET Proton",
- "send_cmd": lambda path: [str(path / "shims/dotnet-proton/shim.sh"),
"send"],
- "recv_cmd": lambda path: [str(path / "shims/dotnet-proton/shim.sh"),
"receive"],
- "broker_prefix": "amqp://",
- },
- "java-protonj2": {
- "name": "Java ProtonJ2",
- "send_cmd": lambda path: [str(path / "shims/java-protonj2/shim.sh"),
"send"],
- "recv_cmd": lambda path: [str(path / "shims/java-protonj2/shim.sh"),
"receive"],
- "broker_prefix": "amqp://",
- },
- "jms": {
- "name": "Qpid JMS Client",
- "send_cmd": lambda path: [str(path / "shims/java-qpid-jms/sender.sh")],
- "recv_cmd": lambda path: [str(path /
"shims/java-qpid-jms/receiver.sh")],
- "broker_prefix": "",
- },
-}
+from shim_registry import ALL_PAIRS, AMQP_PAIRS, DISCOVERED_SHIMS, STAR_PAIRS
# Content type mapping for JMS sender which uses its own type names
JMS_CONTENT_TYPE = {
@@ -196,11 +123,6 @@ def test_queue():
return f"qit.test.large.{suffix}"
[email protected]
-def project_root():
- return Path(__file__).parent.parent
-
-
# =============================================================================
# Shim Runners
# =============================================================================
@@ -216,31 +138,23 @@ def run_large_sender(
jms_mode: bool = False,
timeout: int = 60,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
-
- if client == "jms":
- cmd = info["send_cmd"](project_root) + [
- "--broker", broker_url,
- "--queue", queue,
- "--large-content", content_type,
- "--size", str(size),
- "--seed", str(seed),
- ]
- else:
- cmd = info["send_cmd"](project_root) + [
- "--broker", broker,
- "--queue", queue,
- "--large-content", content_type,
- "--size", str(size),
- "--seed", str(seed),
- ]
- if jms_mode:
- cmd.append("--jms-mode")
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
+
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "send",
+ "--broker", broker,
+ "--queue", queue,
+ "--large-content", content_type,
+ "--size", str(size),
+ "--seed", str(seed),
+ ]
+ if jms_mode:
+ cmd.append("--jms-mode")
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout)
if result.returncode != 0:
- pytest.fail(f"{info['name']} sender failed: {result.stderr}")
+ pytest.fail(f"{shim.name} sender failed: {result.stderr}")
return json.loads(result.stdout)
@@ -255,32 +169,23 @@ def run_large_receiver(
project_root: Path,
timeout: int = 60,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
-
- if client == "jms":
- cmd = info["recv_cmd"](project_root) + [
- "--broker", broker_url,
- "--queue", queue,
- "--large-content", content_type,
- "--size", str(size),
- "--seed", str(seed),
- "--timeout", str(timeout),
- ]
- else:
- cmd = info["recv_cmd"](project_root) + [
- "--broker", broker,
- "--queue", queue,
- "--large-content", content_type,
- "--size", str(size),
- "--seed", str(seed),
- "--timeout", str(timeout),
- ]
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
+
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "receive",
+ "--broker", broker,
+ "--queue", queue,
+ "--large-content", content_type,
+ "--size", str(size),
+ "--seed", str(seed),
+ "--timeout", str(timeout),
+ ]
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout + 10)
if result.returncode != 0:
pytest.fail(
- f"{info['name']} receiver failed (rc={result.returncode}):
{result.stderr}\n"
+ f"{shim.name} receiver failed (rc={result.returncode}):
{result.stderr}\n"
f"stdout: {result.stdout}"
)
@@ -288,7 +193,9 @@ def run_large_receiver(
def _needs_jms_mode(sender: str, receiver: str) -> bool:
- return sender != "jms" and (sender == "jms" or receiver == "jms")
+ sender_shim = DISCOVERED_SHIMS[sender]
+ receiver_shim = DISCOVERED_SHIMS[receiver]
+ return sender_shim.shim_type == "amqp" and receiver_shim.shim_type == "jms"
# =============================================================================
@@ -446,33 +353,24 @@ def run_collection_sender(
jms_mode: bool = False,
timeout: int = 60,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
-
- if client == "jms":
- cmd = info["send_cmd"](project_root) + [
- "--broker", broker_url,
- "--queue", queue,
- "--large-content", content_type,
- "--elements", str(elements),
- "--element-size", str(element_size),
- "--seed", str(seed),
- ]
- else:
- cmd = info["send_cmd"](project_root) + [
- "--broker", broker,
- "--queue", queue,
- "--large-content", content_type,
- "--elements", str(elements),
- "--element-size", str(element_size),
- "--seed", str(seed),
- ]
- if jms_mode:
- cmd.append("--jms-mode")
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
+
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "send",
+ "--broker", broker,
+ "--queue", queue,
+ "--large-content", content_type,
+ "--elements", str(elements),
+ "--element-size", str(element_size),
+ "--seed", str(seed),
+ ]
+ if jms_mode:
+ cmd.append("--jms-mode")
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout)
if result.returncode != 0:
- pytest.fail(f"{info['name']} sender failed: {result.stderr}")
+ pytest.fail(f"{shim.name} sender failed: {result.stderr}")
return json.loads(result.stdout)
@@ -487,34 +385,24 @@ def run_collection_receiver(
project_root: Path,
timeout: int = 60,
) -> dict[str, Any]:
- info = CLIENT_INFO[client]
- broker = info["broker_prefix"] + broker_url
-
- if client == "jms":
- cmd = info["recv_cmd"](project_root) + [
- "--broker", broker_url,
- "--queue", queue,
- "--large-content", content_type,
- "--elements", str(elements),
- "--element-size", str(element_size),
- "--seed", str(seed),
- "--timeout", str(timeout),
- ]
- else:
- cmd = info["recv_cmd"](project_root) + [
- "--broker", broker,
- "--queue", queue,
- "--large-content", content_type,
- "--elements", str(elements),
- "--element-size", str(element_size),
- "--seed", str(seed),
- "--timeout", str(timeout),
- ]
+ shim = DISCOVERED_SHIMS[client]
+ broker = shim.broker_prefix + broker_url
+
+ cmd = [
+ str(shim.shim_dir / "shim.sh"), "receive",
+ "--broker", broker,
+ "--queue", queue,
+ "--large-content", content_type,
+ "--elements", str(elements),
+ "--element-size", str(element_size),
+ "--seed", str(seed),
+ "--timeout", str(timeout),
+ ]
result = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout + 10)
if result.returncode != 0:
pytest.fail(
- f"{info['name']} receiver failed (rc={result.returncode}):
{result.stderr}\n"
+ f"{shim.name} receiver failed (rc={result.returncode}):
{result.stderr}\n"
f"stdout: {result.stdout}"
)
return json.loads(result.stdout)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]