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]

Reply via email to