Skip to content

Add next test client #13

@englishm-cloudflare

Description

@englishm-cloudflare

Overview

The interop test runner currently tests with a single client implementation (moq-rs). Adding more test clients dramatically increases the value of the interop matrix — instead of "does moq-rs work against each relay?", we get a true N×M matrix showing which implementations can talk to each other.

The framework is already designed for this. run-interop-tests.sh reads from implementations.json and automatically tests every registered client against every registered relay. Adding a new test client is a matter of:

  1. Building a test client that conforms to the interface spec
  2. Making its Docker image available
  3. Registering it in implementations.json

This issue walks through each step.

Step 1: Implement the Test Client Interface

Your test client needs to conform to the interface defined in the repo docs:

The key requirements:

Requirement Details
Input Accept RELAY_URL, TESTCASE, TLS_DISABLE_VERIFY as environment variables
Output TAP version 14 to stdout
Exit codes 0 = all pass, 1 = failures, 127 = unsupported
Docker Packaged as a Docker image

You don't need to implement every test case to start — even just setup-only is useful for the interop matrix. Use the TAP SKIP directive for tests you haven't implemented yet.

Reference implementation

See moq-test-client in the moq-rs repo for a complete example.

Step 2: Make the Docker Image Available

The test runner executes clients as Docker containers. You have three options for making your image available:

Option A: Pre-built image from a registry (preferred)

If you publish your test client image to a container registry (ghcr.io, Docker Hub, etc.), the CI workflow can just pull it. This is the fastest and simplest approach.

Your implementations.json entry would reference the registry image directly:

"client": {
  "docker": {
    "image": "ghcr.io/your-org/your-moq-test-client:latest",
    "notes": "Pre-built image from GitHub Container Registry"
  }
}

Option B: Create an adapter

If you publish a Docker image that mostly works but doesn't quite match the interop runner's conventions (e.g., different environment variable names), you can create a thin adapter that wraps your image. Adapters live in adapters/<your-impl>/ and are typically just a Dockerfile.

For example, if your image expects TARGET_URL instead of RELAY_URL:

FROM ghcr.io/your-org/moq-client:latest
ENTRYPOINT ["sh", "-c", "TARGET_URL=$RELAY_URL SKIP_TLS_VERIFY=$TLS_DISABLE_VERIFY exec /usr/local/bin/moq-test-client"]

See adapters/README.md for full documentation on the adapter pattern.

Option C: Define a source build

If you don't publish images at all, you can add a build script under builds/<your-impl>/ that compiles your implementation from source and produces a Docker image. See builds/moq-rs/ for an example and builds/README.md for the framework docs.

At minimum you need:

builds/<your-impl>/
├── build.sh          # Entry point (must support --target client)
├── Dockerfile.client # Docker build for the test client
└── config.json       # Source repo, default ref, targets

The build script should:

  • Clone or use a local checkout of your repo
  • Build a Docker image tagged as expected by your implementations.json entry
  • Support --target client to build only the client image
  • Support --ref <branch> for building specific versions

Which option should I pick?

Pre-built image (A) Adapter (B) Source build (C)
CI speed Fast (pull only) Fast (thin wrapper) Slow (compile from source)
Setup effort You maintain the image in your own CI Small Dockerfile in this repo Build scripts + Dockerfiles in this repo
Version control You control what version gets tested You control (wraps your image) Runner controls via git ref
Best for Images that match our conventions Images that need minor convention mapping No published image available

Option A is preferred when your image already matches the conventions. Option B is great when it's close but needs minor env var or path mapping. Option C is the fallback when no image exists.

Step 3: Register in implementations.json

Add or update your implementation's entry in implementations.json to include the client role. If your implementation is already registered as a relay, you just need to add the client field under roles.

Adding a client role to an existing implementation

If your implementation already has an entry (e.g., as a relay), add the client role:

{
  "your-impl": {
    "name": "Your Implementation",
    "organization": "Your Org",
    "repository": "https://github.com/your-org/your-impl",
    "draft_versions": ["draft-14"],
    "roles": {
      "relay": {
        ...existing relay config...
      },
      "client": {
        "docker": {
          "image": "ghcr.io/your-org/your-moq-test-client:latest"
        }
      }
    }
  }
}

Adding a new implementation

If your implementation isn't registered at all yet, see AGENTS.md or IMPLEMENTATIONS.md for the full schema. The key fields:

  • name: Display name
  • organization: Who maintains it
  • repository: Git repo URL
  • draft_versions: Array of supported MoQT draft versions (e.g., ["draft-14"])
  • roles.client.docker.image: The Docker image name

What Happens Next

Once your client is registered, the existing automation handles everything:

  1. run-interop-tests.sh automatically discovers your client and tests it against all registered relays
  2. The CI workflow builds/pulls your client image and includes it in the nightly test run
  3. generate-report.sh includes your client in the interop matrix on the results page

No changes to the test runner or report generator are needed.

CI considerations for multiple clients

The current CI workflow (interop-report.yml) hardcodes the moq-rs client build. When a second client is added, the workflow will need a small update. The simplest approach is sequential builds/pulls:

- name: Build/pull test clients
  run: |
    ./builds/moq-rs/build.sh --target client
    docker pull ghcr.io/your-org/your-moq-test-client:latest

If build times become a concern later, we can explore parallel matrix builds or other optimizations, but sequential is fine to start.

Checklist

  • Test client implements the interface spec (at minimum: setup-only test case, TAP output, Docker image)
  • Docker image is available (via registry, adapter, or source build)
  • implementations.json updated with client role
  • PR submitted — see the existing moq-rs entry as a reference

Questions?

Reach out to @englishm if you have questions or want to discuss your implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions