Property-based tests for LavinMQ, built on QuickCheck.
The crate provides Arbitrary generators for AMQP entities, arguments and
routing setups, and for MQTT 3.1.1 topics, sessions and malformed packets,
plus a suite of tests that runs them against a real broker.
Where routing logic is involved, the tests compare what LavinMQ does with
a small pure-Rust model of what it should do. The suite has found several
LavinMQ bugs. Each is written up in lavinmq-quirks.md.
| Area | Generators | What the broker tests check |
|---|---|---|
| Names | QueueName, RoutingKey, TopicRoutingKey |
Publish/consume round trips via the default, direct and topic exchanges |
| Queue arguments | Every LavinMQ x-* queue argument, alone and combined per queue type (classic, priority, stream) |
Valid arguments are accepted. Streams reject arguments they don't support |
| Message properties | Every BasicProperties field, plus a combined set |
Publishing with any of them succeeds |
| Routing graphs | Fanout exchanges, exchange-to-exchange bindings, dead-lettering, alternate exchanges, all acyclic | Per-queue delivery counts match routing::simulate |
| Alternate exchanges | Alternate-exchange loops, an alternate exchange that doesn't exist, argument vs. policy | Loops end, unroutable mandatory messages are returned, the argument wins over a policy |
| Consistent-hash exchange | ring/jump, x-hash-on, weighted bind/unbind sequences |
The same key always lands on the same queue |
| Stream offsets | x-stream-offset as first, next, integers (every AMQP int width, negative = last N), timestamps |
Delivered messages match the offset model |
| Delayed-message exchange | Both declare styles, x-delay values that can't be read as a delay, over-long names |
Such x-delay values mean "deliver now". Over-long names fail cleanly (ignored test, see below) |
| Consumer priority | x-priority values, valid and invalid |
In-range integers are accepted, everything else gets 406 |
| Headers exchange | Exchange and binding x-match, messages without headers, values of mixed types |
Routing matches headers::matches. Invalid x-match gets 406 |
| MQTT routing | Topic/filter pairs (+, #, empty levels, $ topics, non-ASCII), QoS 0–2 |
Delivery matches mqtt::topic::lavinmq_matches. Granted and delivered QoS match mqtt::qos |
| MQTT retain, will, sessions | Retained publish/clear sequences, wills, clean/persistent reconnects | New subscriptions get the latest retained message. Wills only fire on an unclean close. Queued messages and Session Present follow mqtt::session |
| MQTT malformed packets | Bad topic names and filters, ill-formed UTF-8, QoS 3, bad SUBSCRIBE flags, bad protocol levels | Sent over a raw socket. The broker closes the connection, or refuses with CONNACK 0x01 / SUBACK 0x80 |
queue-arguments.md is a reference for the arguments
LavinMQ accepts.
-
Rust (edition 2024, so a recent stable toolchain).
-
A LavinMQ broker on
localhost:5672(AMQP) andlocalhost:1883(MQTT), with the management API onlocalhost:15672and the defaultguest:guestuser:docker run -d --rm -p 5672:5672 -p 15672:15672 -p 1883:1883 cloudamqp/lavinmq
These addresses are hard-coded.
cargo test # whole suite (~45 s)
cargo test headers # only tests whose name contains "headers"
cargo test -- --ignored # only the known-bug tests
QUICKCHECK_TESTS=20 cargo test # fewer cases per propertyTests run in parallel against one broker. Each test function gets its own
vhost, qc-<test name>, which is deleted and recreated the first time the
test uses it. That stops one test's policies or queue names from affecting
another. MQTT tests reach their vhost through the username
(qc-<test name>:guest). The qc-* vhosts stay on the broker after a run.
Tests that don't need a broker (the routing, stream-offset and headers-matching models, and checks on what the generators produce) run as ordinary unit tests in the same suite.
Tests marked #[ignore] describe what LavinMQ should do for a bug that's
still open. They fail until the bug is fixed:
| Test | Upstream issue |
|---|---|
delayed_exchange_long_name_is_clean_error |
cloudamqp/lavinmq#2297 |
consistent_hash_survives_policy_change |
cloudamqp/lavinmq#2300 |
topic_trailing_empty_word_routes |
cloudamqp/lavinmq#2310 |
mqtt_hash_matches_parent_level |
cloudamqp/lavinmq#2312 |
mqtt_wildcards_skip_dollar_topics |
cloudamqp/lavinmq#2313 |
mqtt_qos2_publish_gets_pubrec |
cloudamqp/lavinmq#2314 |
mqtt_qos0_publish_is_delivered_at_qos0 |
cloudamqp/lavinmq#2315 |
mqtt_retained_non_ascii_topics |
cloudamqp/lavinmq#2316 |
internal_exchange_redeclare |
cloudamqp/lavinmq#2319 |
Two headers-exchange inconsistencies are filed as #2301 and #2302. Until they're resolved, the headers model mirrors LavinMQ's current behaviour.
Generators follow the newtype pattern. Each wraps a value that's valid for
the broker, and arguments can add themselves to a FieldTable:
use lavinmq_quickcheck::QueueName;
use lavinmq_quickcheck::arguments::MaxLength;
use lapin::types::FieldTable;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn declare_with_max_length(name: QueueName, max_len: MaxLength) -> bool {
let mut args = FieldTable::default();
max_len.insert_into(&mut args);
// declare `name.0` with `args` against your broker…
true
}.github/workflows/release.yml compiles the
whole suite into one static (musl) executable. It smoke-tests that binary
against LavinMQ and, on v* tags, publishes it to a GitHub Release.
Another project's CI can download and run it with no Rust toolchain:
./lavinmq-quickcheck-tests-x86_64-linuxdocs/lavinmq-ci.md has ready-made jobs for LavinMQ's
GitHub Actions setup. To build the binary locally you also need a musl C
compiler (musl-gcc, from musl-tools) for ring.
src/
names.rs queue names, routing keys
arguments.rs x-* queue arguments
combined.rs combined arguments per queue type
properties.rs BasicProperties fields
routing.rs routing-graph generator + simulate()
alternate.rs alternate-exchange edge cases
consistent_hash.rs consistent-hash exchange setups
stream_offset.rs x-stream-offset values + offset model
delayed.rs delayed-message exchange setups
consumer_priority.rs x-priority values
headers.rs headers exchange setups + matching model
mqtt/ MQTT 3.1.1: topics + matching, QoS, retain, sessions,
malformed input; rumqttc and raw-socket tests
tests.rs the suite that runs against the broker
lavinmq-quirks.md LavinMQ behaviour found by these tests
queue-arguments.md reference for LavinMQ queue/consumer arguments
docs/lavinmq-ci.md running the suite in LavinMQ's CI
See AGENTS.md for conventions when adding generators or tests.