blob: 7b4d3bd79697acedfe9286591f20dca82aa14ca6 [file]
#
# 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.
#
"""
QIT CLI - Main entry point.
Command-line interface for running AMQP interoperability tests.
"""
import sys
from pathlib import Path
import click
from qit import __version__
@click.group()
@click.version_option(version=__version__, prog_name="qit")
def cli() -> None:
"""QIT - AMQP Interoperability Test Suite."""
pass
@cli.command()
@click.option(
"--build-shims",
is_flag=True,
help="Build native shims (C++, Java, etc.)",
)
def setup(build_shims: bool) -> None:
"""Set up QIT environment and dependencies."""
click.echo("Setting up QIT environment...")
# Check Python version
if sys.version_info < (3, 11):
click.echo("❌ Python 3.11+ required", err=True)
sys.exit(1)
click.echo("✓ Python version OK")
# Check qpid-proton
try:
import proton
click.echo(f"✓ qpid-proton found (version {proton.VERSION})")
except ImportError:
click.echo("❌ qpid-proton not found. Install with: pip install python-qpid-proton", err=True)
sys.exit(1)
# Check Docker
import subprocess
try:
subprocess.run(["docker", "--version"], check=True, capture_output=True)
click.echo("✓ Docker found")
except (subprocess.CalledProcessError, FileNotFoundError):
click.echo("⚠ Docker not found (required for broker management)", err=True)
if build_shims:
click.echo("\nBuilding native shims...")
click.echo(" [TODO] C++ shim build")
click.echo(" [TODO] Java shim build")
click.echo(" [TODO] JavaScript shim build")
click.echo(" [TODO] .NET shim build")
click.echo("\n✓ Setup complete!")
click.echo("\nNext steps:")
click.echo(" 1. Start broker: docker compose -f docker/compose.yaml up -d")
click.echo(" 2. Run tests: qit test amqp-types")
@cli.group()
def test() -> None:
"""Run interoperability tests."""
pass
@test.command(name="amqp-types")
@click.option(
"--sender",
multiple=True,
help="Sender shim(s) to test (default: all)",
)
@click.option(
"--receiver",
multiple=True,
help="Receiver shim(s) to test (default: all)",
)
@click.option(
"--type",
"amqp_types",
multiple=True,
help="AMQP type(s) to test (default: all)",
)
@click.option(
"--broker",
default="amqp://localhost:5672",
help="Broker URL",
)
@click.option(
"--mode",
type=click.Choice(["broker", "direct"]),
default="broker",
help="Test mode (broker or direct peer-to-peer)",
)
@click.option(
"--verbose",
"-v",
is_flag=True,
help="Verbose output",
)
@click.option(
"--junit-xml",
type=click.Path(),
help="Generate JUnit XML report (for CI/CD integration)",
)
@click.option(
"--extended",
is_flag=True,
help="Include extended tier test values for complex types",
)
@click.option(
"--strict",
is_flag=True,
help="Treat known failures (xfail) as real failures",
)
@click.option(
"--workers",
"-j",
type=int,
default=1,
help="Number of parallel test workers (default: 1 = sequential)",
)
def test_amqp_types(
sender: tuple[str, ...],
receiver: tuple[str, ...],
amqp_types: tuple[str, ...],
broker: str,
mode: str,
verbose: bool,
junit_xml: str | None,
extended: bool,
strict: bool,
workers: int,
) -> None:
"""Test AMQP primitive and complex types interoperability."""
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)
project_root = Path(__file__).parent.parent.parent.parent
discovered = discover_shims(project_root / "shims")
available_shims = {}
for key, info in discovered.items():
if info.shim_type == "jms":
continue
available_shims[key] = Shim(
ShimConfig(
name=key,
language=key.split("-")[0],
client=info.name,
executable=info.shim_dir / "shim.sh",
)
)
if not available_shims:
click.echo("❌ No shims found!", err=True)
click.echo(f" Expected shims in: {shim_dir}", err=True)
sys.exit(1)
click.echo(f"Found {len(available_shims)} shim(s): {', '.join(available_shims.keys())}")
# Filter shims if specified
sender_shims = list(sender) if sender else list(available_shims.keys())
receiver_shims = list(receiver) if receiver else list(available_shims.keys())
# Get AMQP types to test (primitives + complex)
all_types = {**AmqpPrimitiveTypes.get_all_types(), **AmqpComplexTypes.get_all_types(include_extended=extended)}
if amqp_types:
test_types = {k: all_types[k]["values"] for k in amqp_types if k in all_types}
else:
test_types = {k: v["values"] for k, v in all_types.items()}
click.echo(f"Testing {len(test_types)} type(s)")
click.echo()
# Set up broker if needed
broker_manager = None
if mode == "broker":
compose_file = project_root / "docker" / "compose.yaml"
if not compose_file.exists():
click.echo(f"❌ Compose file not found: {compose_file}", err=True)
sys.exit(1)
broker_config = BrokerConfig(
name="artemis",
type="artemis",
url=broker,
compose_file=compose_file,
)
broker_manager = BrokerManager(broker_config)
# Check if broker is running (don't auto-start for now)
click.echo("Note: Ensure broker is running:")
click.echo(f" docker compose -f {compose_file} up -d")
click.echo()
# Run tests
orchestrator = Orchestrator(
shims=available_shims,
broker=broker_manager,
)
results = orchestrator.run_test_matrix(
amqp_types=test_types,
sender_shims=sender_shims,
receiver_shims=receiver_shims,
workers=workers,
)
# Print report
click.echo()
report = orchestrator.generate_report(results)
click.echo(report)
# Generate JUnit XML if requested
if junit_xml:
orchestrator.generate_junit_xml(results, junit_xml, strict=strict)
click.echo(f"\n✓ JUnit XML report written to: {junit_xml}")
# Exit with error if any tests failed
has_failures = any(not r.success for r in results)
has_xfails = any(r.success and r.xfail_diffs for r in results)
if has_failures or (strict and has_xfails):
sys.exit(1)
@cli.command()
def broker() -> None:
"""Manage test broker."""
click.echo("Broker management commands:")
click.echo(" Start: docker compose -f docker/compose.yaml up -d")
click.echo(" Stop: docker compose -f docker/compose.yaml down")
click.echo(" Logs: docker compose -f docker/compose.yaml logs -f")
click.echo(" Status: docker compose -f docker/compose.yaml ps")
if __name__ == "__main__":
cli()