Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

testkit

Broker fixtures for the acceptance suite. Every milestone is verified against a real broker in a container — there are no mocked brokers in this workspace, by rule — and this crate is what makes that affordable.

publish = false.

Module map

FileLinesWhat
config.rs616BrokerConfig, Security, SaslMechanism, SaslUser
image.rs291the container image and its environment
kafka.rs233single_broker, cluster, and the _with variants
harness.rs173the Cluster trait and ExternalCluster
error.rs58fixture errors

The usual shapes

use testkit::{BrokerConfig, SaslMechanism, Security};

async fn example() -> testkit::Result<()> {
// The common case.
let broker = testkit::single_broker().await?;
let addr = broker.bootstrap_csv();

// Three nodes, for anything replica-aware.
let cluster = testkit::cluster(3).await?;

// Or configured.
let sasl = testkit::single_broker_with(
    BrokerConfig::new()
        .with_security(Security::SaslPlaintext)
        .with_mechanism(SaslMechanism::Plain)
        .with_user("alice", "alice-pw"),
)
.await?;
Ok(())
}

apache/kafka:4.3.1 in KRaft mode.

Tests take &dyn Cluster, never a concrete type

This is the crate's most important design decision and it is not abstraction for its own sake.

kaas-lib is the natural conformance harness for the kaas broker, and that only works if the acceptance suite can be pointed at something other than apache/kafka:4.3.1 without touching a single test. A hardcoded image in the fixtures would quietly foreclose that — not with an error, but by making the alternative expensive enough that nobody does it.

use std::fmt;
trait Cluster: fmt::Debug + Send + Sync {
    /// Bootstrap addresses reachable from the *test process* — for container
    /// fixtures the host-mapped port, not the in-container one.
    fn bootstrap(&self) -> &[String];
    fn nodes(&self) -> usize;
    // …plus exec, for driving the Kafka shell tools inside a node
}

ExternalCluster is the other half of the bargain: it makes "point the suite at a broker that is already running" a supported mode today, so the seam gets exercised rather than rotting into a trait nobody has implemented twice.

exec matters more than it looks

exec(node, argv) runs a command inside a fixture node, and several fixtures are generated by the Kafka shell tools rather than by a Rust client.

The group-kinds fixture is the reason. rdkafka cannot create a KIP-932 share group — librdkafka has no share-group support at all — so no Rust client in the workspace can generate that fixture. The apache/kafka image already ships kafka-console-consumer.sh and kafka-console-share-consumer.sh, and driving those through exec reaches every group kind with zero build dependencies.

The config builder exists from the start

single_broker_with(BrokerConfig) was built before anything needed it, because three later milestones do: SASL_PLAINTEXT/PLAIN and SASL_SSL/SCRAM-SHA-512 brokers, an authorizer-enabled broker for ACLs, and aggressive compaction settings for the backward-scan test.

The same argument applies to cluster(3). Retrofitting multi-broker fixtures after the fact is painful, and leader spread, log dirs and reassignment are not observable on one broker.

It paid off again for OAUTHBEARER, which arrived long after: a mechanism, one JAAS entry naming OAuthBearerUnsecuredValidatorCallbackHandler, and unsecured_jws to mint tokens. That handler is what makes the fixture possible at all — Kafka ships it so a broker can validate OAUTHBEARER with no identity provider anywhere, which takes the issuer out of the picture without taking the broker out of it. Note also that OAUTHBEARER is the first mechanism with no users: the principal is the token's sub claim, so validate() now asks whether any enabled mechanism needs credentials rather than assuming SASL means users.

Start reading at harness.rs — it is short, and its module docs state the conformance-harness argument in full.