Skip to content

test: add Kafka CLI smoke tests for the broker, topics, configs, produce, consume, offsets and restarts - #902

Merged
solace-aross merged 18 commits into
mainfrom
test/sol-155250-kafka-cli-checks
Oct 9, 2026
Merged

solace-aross merged 18 commits into
mainfrom
test/sol-155250-kafka-cli-checks

Conversation

@solace-wkourlas

@solace-wkourlas solace-wkourlas commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #862: review that one first. This PR adds the next set of smoke tests on top of its harness.

What this adds

Tests that run the Kafka CLI tools the way a user does, on every storage engine:

  • broker: ApiVersions names node 111, and the cluster id is the one the broker started with.
  • topics: describe, auto-create on produce, delete and re-create.
  • configs: describe, add and delete topic configs, and broker defaults.
  • produce: every acks setting, compression codecs, tombstones, max.message.bytes, idempotent and concurrent producers, LogAppendTime.
  • consume: reading from an offset, a group resuming where it stopped, a pattern subscription that picks up a topic created later.
  • offsets: lookups by time.
  • restart: topics, records, committed offsets, topic configs and SCRAM users survive a SIGTERM restart on PostgreSQL and SQLite, and an in-memory broker starts again empty.
  • storage_url: an unparsable or invalid storage URL stops the broker with an error that names it.

The harness now has one module per Kafka tool. Each command whose output a test reads has its own Output<Marker> type, so a reader can only be called on its own command's output. Brokers can restart on the same storage, and tests can start isolated brokers.

The CI change gives a smoke leg that fails before just smoke runs (setup, toolchain) a FAIL row, so it still shows in the report. run.sh also fails the leg if no nextest status line parses, instead of showing a green leg with no tests.

.claude/rules/smoke-tests.md sets the rules for writing a smoke test.

Ignored tests

A test that fails because of a broker bug is ignored with the bug in its reason, so CI stays green and the fix enables it. Every reason names the issue or PR that fixes it, e.g. #798 for the five produce tests that read the latest offset on in-memory storage, and #849 for the max-timestamp lookup.

Testing

🤖 Generated with Claude Code

@solace-aross solace-aross left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving comments, not a verdict yet. The design is good, and a few items are worth settling before this goes in.

What I checked and found sound:

  • Crash detection survived the harness rewrite. --restart=on-failure is gone, so a crashed broker now stays exited, and check_container_running reads State.Running and the exit code, then saves the broker's logs. That covers what the old RestartCount check caught.
  • The CI change gives a leg that fails during setup a FAIL row, and run.sh fails a leg whose nextest log has no parseable status line. Both close ways a leg could look green with no tests.
  • smoke-tests.md reads well. Checking only what a test's doc comment states is the right rule for a suite that reads human-oriented tool output.
  • Disclosure check: no Solace-internal references, roadmap or hiring language, ticket IDs or customer names in the title, body, commits, diff or the two new rules files.

Findings:

  1. At least one ignore reason looks stale. timestamp_after_last_record_returns_minus_1 in tests/smoke/offsets.rs is ignored with "the broker answers offset 0 when no record is at or after the timestamp". The CHANGELOG at this head already says ListOffsets by timestamp answers -1 in that case. I could not run it, so I do not know whether it holds on all three engines. Please un-ignore it and run it. If it fails on one engine, name that engine in the reason.
  2. Link the PRs, not only issues. Two reasons describe fixes that are already in flight: the acks=0 response (#845) and the ListOffsets version advertising (#849). Naming those PRs in the reasons means the fix un-ignores its own test. The remaining ignored tests need issue numbers before merge; you say they are coming, and I would hold the approval for them, because an ignored test with prose only is a bug with no owner.
  3. .claude/rules/naming.md is a workspace-wide naming policy (it applies to every *.rs, *.sh and justfile), added inside a smoke-test PR. It sets rules such as "spell words out" and "name a test for the behaviour it requires" that existing code does not follow. I would move it to its own PR so the team can agree on it separately. That is Andrea's and the maintainers' call, not a blocker from me.
  4. Order: this is stacked on #862, which still has an open change request from Peter. The diff here will shrink once #862 merges, and it is easier to review then.

Not reviewed in depth: the broker.rs harness rewrite, the kafka_cli/* modules and the eight new test files, about 4,000 of the diff lines. I did not run the suite (no Docker where I reviewed). Please post your local postgres, sqlite and memory results when they finish, and I will look again.

@solace-wkourlas

solace-wkourlas commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thanks for going through this, @solace-aross! The results of the smoke tests are as follows:

  • postgres: 54 of 63 passed. The 9 failures are all ignored tests.
  • sqlite: 57 of 66 passed. The 9 failures are all ignored tests.
  • memory: 44 of 57 passed. The 13 failures are all ignored tests.

These counts include the ignored tests, because they run locally too. The extra failures on memory are the four produce tests that are waiting for #798.

You were right about the timestamp test, which now passes on all three engines. While checking that, I found the two maintenance_interval tests in storage_url.rs were in the same situation after #850. The broker now stops on a bad interval, but it logs the error through tracing, and the harness only looked for an "Error:" line. The harness now checks every error line the broker prints. Both tests are un-ignored and pass on all three engines.

I also moved naming.md to its own PR, #903, so it can be discussed separately.

@solace-aross solace-aross left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up. Re-read d0e609a and 7dbb5f6 against my earlier comments.

Resolved:

  • Timestamp and maintenance_interval tests are un-ignored and the harness now checks every error line the broker prints. Local results on all three engines are in your comment.
  • naming.md is out of the diff (moved to #903, now merged to main).
  • Issue numbers are on the ignores for #798, #904, #905, #906 and #907.

Still open, and the reason I have not approved yet:

  1. Three ignored tests name no issue or PR: acks_0_stores_every_record (produce.rs:106), max_timestamp_lookup_fails_at_once_as_unsupported (offsets.rs:76, #849 is open) and alter_partitions_keeps_records_in_place (topics.rs:116, CreatePartitions).
  2. acks_0_stores_every_record is likely stale once rebased. #845 merged to main, and this branch does not contain 8543d06 yet. Un-ignore it after the rebase and check it passes on all three engines; if it does not, name the engine.
  3. This still sits on #862, which carries Peter's change request at 28fd07a, so the order of merging is #862 first.

Happy to approve once 1 and 2 are in. I have still not run the suite or read the broker.rs rewrite line by line.

@solace-wkourlas
solace-wkourlas force-pushed the test/sol-155250-kafka-cli-checks branch from 7dbb5f6 to 3394ecf Compare October 9, 2026 16:18
solace-wkourlas and others added 15 commits October 9, 2026 12:32
The new crate runs the real Kafka command-line tools, from an
apache/kafka image, against a broker on one storage engine:

    just smoke <postgres|sqlite|memory|s3>

run.sh starts the services the engine needs under its own compose
project, then smoke-broker starts the broker the tests share and runs
nextest against it. Tests talk to that broker, or launch one of their
own with Broker::isolated(), and use their own topic and group names so
they can run in parallel. Each Kafka CLI call and each docker command
has a time limit, so a hang fails the test that caused it and says
which command hung.

A run fails if a test fails, or if the shared broker exited, restarted,
panicked, or didn't exit with 0 on SIGTERM. It writes one
name,PASS|FAIL|SKIP row per test to target/smoke/results-<leg>.csv, the
format the compat suites' report reads, plus the broker's log.

The first tests cover a topic's life: create, list, describe, produce
with keys and headers, read offsets, consume through a group, describe
the group, and delete. describe_topic_configs is #[ignore]d because the
broker labels a topic's own configs as defaults, so kafka-topics hides
them. CI skips ignored tests and local runs still run them.

`just test` excludes the crate, since it needs Docker and a broker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
… grid

smoke ran the bats tests from a pinned example-java checkout. It now
runs `just smoke <storage>` from this repo: the Kafka CLI tools from
Kafka 3.9.2 and 4.3.1 against the packaged image on postgres, memory
and s3, on x86 and arm.

The packaged image crashes at startup on sqlite:// (#796), so the
sqlite legs build the broker from source until that is fixed. Only
they build the broker, so only they use the Rust cache.

Each leg uploads its results file and logs. The new smoke-report job
puts every leg's results in one pass/fail grid in the step summary,
using the compat suites' report query, which now shows SKIP rows as
skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
Add `just smoke` to the command list, say how the suite's tests share a
broker and how a test that fails because of an open bug is disabled,
and describe the new smoke job. Also correct the member crate count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
The smoke job runs the Kafka 3.9.2 and 4.3.1 CLI tools, so no job in CI
checked that an older Java client still negotiates API versions with the
broker and works against it.

smoke-oldest-client runs `just smoke <engine>` with apache/kafka:3.7.2 on
postgres, sqlite, memory and s3. It runs on every engine, because a 3.7
client calls some APIs that newer clients don't, such as Metadata for
`kafka-topics --describe`, and each engine implements those separately.
It runs on x86 only, because a client sends the same requests on any CPU.

smoke-report shows its legs in the same grid as smoke's, and ci-gate
requires it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…anoid

smoke-broker now parses `smoke-broker -- <command>...` with clap, like the
workspace's other command-line tools.

unique_name now appends 21 random characters from nanoid to the prefix,
instead of the process id, a counter and the time. The characters are
lowercase letters and digits only: `kafka-topics` prints a warning before
its output for a topic name that contains `_`, and the tests read the
first line of that output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
run.sh assigned two empty values with `services= ports=`, which ShellCheck
reads as a possible typo (SC1007). They are now `services='' ports=''`.

ShellCheck also reports that nothing calls cleanup (SC2329). The EXIT trap
calls it, but ShellCheck misses calls from a trap in a script that runs
`exit` (koalaman/shellcheck#2542), so a directive turns the check off for
that function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…uce, consume, offsets and restarts

Splits the KafkaCli harness into one module per tool, each with typed
readers of the tool's output, and adds broker restarts, isolated brokers
and storage URL settings to the harness.

A test that fails because of an open broker bug is ignored with the bug
in its reason, so CI stays green and the fix enables it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…port

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…E.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
The broker now stops on a bad maintenance_interval and logs the error
through tracing, so a test checks every error line the broker printed,
not only the last one. kafka-get-offsets prints no offset for a
partition when the broker answers -1, so the lookup after the last
record expects no offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
It's not settled whether the broker will support CreatePartitions, so the
suite doesn't keep an ignored test waiting for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
#845 stopped the broker answering a produce request sent with acks=0, so
the test runs again, except on in-memory storage, where it reads the
latest offset like the other produce tests waiting for #798. #849 stops
the broker advertising ListOffsets versions it doesn't support, which the
max-timestamp lookup is waiting for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…est leaves running

The results file kept the last status line for a test, so a later PASS
could hide a FAIL; a FAIL now stays. A leg where no test passed fails, and
nextest fails a run that selects no tests.

nextest kills a test that runs too long with SIGKILL, which skips Drop, so
a process broker the test launched kept running with its port held. Each
process broker now keeps its process id in its directory until the
harness stops it, and run.sh stops and reports any broker still listed
when the run ends. docker run now has a minute to start a broker
container, and run.sh pulls the image before the tests, so starting a
broker no longer takes most of a test's time limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
…che, and upload broker logs

A hung `just smoke` ran into the job's timeout, which cancelled the
steps that upload the results and logs; the step now stops after 20
minutes. Both smoke jobs use one cache key, so smoke-oldest-client
restores the cache the smoke job saves instead of building cold. The
smoke job also uploads the logs of the brokers that failed tests
launched, as smoke-oldest-client already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
@solace-wkourlas
solace-wkourlas force-pushed the test/sol-155250-kafka-cli-checks branch from 09f3a9b to 0133a0b Compare October 9, 2026 16:33
@solace-wkourlas
solace-wkourlas added this pull request to stack #917 October 9, 2026 16:38
solace-aross
solace-aross previously approved these changes Oct 9, 2026

@solace-aross solace-aross left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 0133a0b. My open points from the last round are in:

  • The CreatePartitions test is removed (b7177b2), so no ignore is left without an issue. #849 is now named on the max-timestamp test.
  • acks_0_stores_every_record runs again after #845 (bc193cb). It is ignored only on the memory engine, with #798 named, like the other produce tests waiting on that.
  • #862 is now the base, rebased onto current main, so the merge order is #862 first.

I read the new commits (fail-closed results, leftover-broker cleanup, the container start timeout, the 20 minute step timeout) and they look sound. I have not run the suite locally, so the sqlite, postgres and s3 legs in CI are what confirm the acks=0 test passes on those engines.

One thing to fix before merge: typos fails on unparseable in tests/smoke/storage_url.rs (lines 23 and 31, from the first commit). Renaming the two tests to unparsable_* clears it. CI was still running when I approved, so the smoke legs had not reported yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
Comment thread nisshi-smoke-test/tests/smoke/restart.rs Fixed
Comment thread nisshi-smoke-test/tests/smoke/restart.rs Fixed
solace-aross
solace-aross previously approved these changes Oct 9, 2026

@solace-aross solace-aross left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with notes. Re-approving after the push to 1268ced.

The typos note is fixed: both smoke tests are now unparsable_*, and typos passes. That commit is the only change since my approval at 0133a0b.

Notes:

  • CodeQL fails on this head with two critical alerts (rust/hard-coded-cryptographic-value) at nisshi-smoke-test/tests/smoke/restart.rs lines 143 and 180. Both are the literal "secret" used as the SCRAM password in test fixtures, so they look like false positives. Someone with security-events access needs to dismiss them as "used in tests", or the tests can generate the password (for example from unique_name), before the check goes green.
  • The smoke legs are skipped on this PR run, so acks=0 on the sqlite, postgres and s3 engines is not confirmed by CI yet. test (postgres:17), clippy, fmt and both compat legs pass.

CodeQL reported the hard-coded "secret" as a hard-coded cryptographic
value. Each test now gives its user a password made with unique_name,
like its user name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
Comment thread nisshi-smoke-test/tests/smoke/restart.rs Fixed
Comment thread nisshi-smoke-test/tests/smoke/restart.rs Fixed
CodeQL still reported the passwords made with unique_name as hard-coded,
because they contain its literal prefix and characters from its constant
alphabet. random_password writes a random u128 in decimal, so no constant
reaches the password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>

@solace-aross solace-aross left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at bc12723. The only change since my approval at 1268ced is how the restart tests get their SCRAM passwords. They now call a new random_password() helper, a random u128 in decimal, instead of the literal "secret". That removes the two CodeQL alerts at the source rather than dismissing them. The helper uses rand, which is already a workspace dependency, so Cargo.lock gains an edge and no new crate. The wrong-password test gets a different password, since a collision between two random u128 values is negligible. The unparsable_* rename is still in.

CodeQL and typos passed on the previous head. On this head clippy, Analyze (rust), both compat legs and third-party-license were still running when I approved. The sqlite, postgres and s3 smoke legs are skipped on this PR because the base is the stacked branch, so they first run once it lands on main.

@solace-aross
solace-aross added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit bf4611b Oct 9, 2026
27 checks passed
Base automatically changed from test/smoke-suite to main October 9, 2026 19:37
@solace-aross
solace-aross deleted the test/sol-155250-kafka-cli-checks branch October 9, 2026 19:37
IgorOffline pushed a commit to IgorOffline/tansu-io-main that referenced this pull request Oct 10, 2026
…te-records and SCRAM (nisshi-io#923)

Stacked on nisshi-io#902: review that one first.

## What changes, and why?

It adds smoke tests that run the Kafka CLI tools against Nisshi for the
broker's maintenance and authentication:

- **Retention** (PostgreSQL and SQLite): expired records deleted and
newer ones kept, the next offset after retention, a topic without
`cleanup.policy`, `retention.ms=-1`, a 30-day `retention.ms`,
`kafka-configs --add-config` and `--delete-config retention.ms`,
`retention.bytes`, offsets after retention empties a partition.
- **Compaction** (PostgreSQL and SQLite): the latest value per key, a
tombstone as the key's only record, and `compact,delete`.
- **`kafka-delete-records`** (every engine): the new low watermark, the
earliest offset, the latest offset.
- **SCRAM:** users created with `kafka-configs` and described with it;
login with SCRAM-SHA-256 and SCRAM-SHA-512, a wrong password, and a
client without credentials, all on PostgreSQL and SQLite, because a
broker with `--authentication` can't create its first user, so each test
restarts the broker. That restart duplicated three SCRAM tests in
`restart.rs`, which this PR moves into `scram.rs`.
- **SQLite `vacuum_into`:** a broker started on the snapshot, after its
own database has been deleted, has the topics and records.

Harness changes: `kafka-delete-records`, `kafka-configs --describe
--entity-type users`, a producer that sets old timestamps, a broker that
runs maintenance every 2 seconds, and a restart onto other storage that
can delete files first. `Broker::file_exists` now copies the file out
with `docker cp`. It used `docker exec … test`, and the broker image has
no `test` command, so it returned `false` for every file in a broker
container.

### Ignored tests

Each is waiting for:

- nisshi-io#918: a topic without `cleanup.policy` is never cleaned
- nisshi-io#919: `retention.ms=-1` deletes every record
- nisshi-io#920: a `retention.ms` above 2147483647 stops retention on PostgreSQL
(2 tests)
- nisshi-io#921: `retention.bytes` is ignored
- nisshi-io#922: `kafka-configs` can't describe users without
`DescribeClientQuotas`
- nisshi-io#864: offsets go back to 0 when retention empties a partition
- nisshi-io#904: `kafka-configs --delete-config` refuses a topic's own config
- PR nisshi-io#835, which fixes DeleteRecords: the three `delete_records` tests

## Upgrade impact

None. This changes only the smoke tests.

## How was this tested?

Local runs of `just smoke <engine>` with every test, the ignored ones
included, and the Kafka 3.9 tools:

| Engine | Tests run | Passed | Failed, all ignored |
|---|---|---|---|
| SQLite | 86 | 69 | 17 |
| Memory | 62 | 46 | 16 |
| PostgreSQL | 82 | 63 | 19 |

On each engine, the failed tests are exactly the tests ignored on that
engine, so CI, which skips them, passes. The failures include the
ignored tests from nisshi-io#902.

- [x] `cargo clippy -p nisshi-smoke-test --all-features --all-targets --
-D warnings`, and with `--features memory`
- [x] `cargo fmt --check`, and rustdoc with warnings denied
- [x] each commit builds and passes the crate's unit tests on its own
- [ ] S3 and the Kafka 3.7 and 4.3 tools: CI's smoke jobs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: William Kourlas <156007774+solace-wkourlas@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants