Repository navigation
test: add Kafka CLI smoke tests for the broker, topics, configs, produce, consume, offsets and restarts - #902
Conversation
solace-aross
left a comment
There was a problem hiding this comment.
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-failureis gone, so a crashed broker now stays exited, andcheck_container_runningreadsState.Runningand the exit code, then saves the broker's logs. That covers what the oldRestartCountcheck caught. - The CI change gives a leg that fails during setup a
FAILrow, andrun.shfails a leg whose nextest log has no parseable status line. Both close ways a leg could look green with no tests. smoke-tests.mdreads 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:
- At least one ignore reason looks stale.
timestamp_after_last_record_returns_minus_1intests/smoke/offsets.rsis 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. - Link the PRs, not only issues. Two reasons describe fixes that are already in flight: the
acks=0response (#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. .claude/rules/naming.mdis a workspace-wide naming policy (it applies to every*.rs,*.shandjustfile), 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.- 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.
6d883ce to
eeba60d
Compare
|
Thanks for going through this, @solace-aross! The results of the smoke tests are as follows:
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
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. Re-read d0e609a and 7dbb5f6 against my earlier comments.
Resolved:
- Timestamp and
maintenance_intervaltests 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.mdis 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:
- 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) andalter_partitions_keeps_records_in_place(topics.rs:116, CreatePartitions). acks_0_stores_every_recordis 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.- 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.
7dbb5f6 to
3394ecf
Compare
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>
09f3a9b to
0133a0b
Compare
solace-aross
left a comment
There was a problem hiding this comment.
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_recordruns 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>
solace-aross
left a comment
There was a problem hiding this comment.
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.rslines 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 fromunique_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>
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>
There was a problem hiding this comment.
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.
…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>
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: everyackssetting, 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 smokeruns (setup, toolchain) a FAIL row, so it still shows in the report.run.shalso fails the leg if no nextest status line parses, instead of showing a green leg with no tests..claude/rules/smoke-tests.mdsets 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-timestamplookup.Testing
just clippy-equivalent fornisshi-smoke-test(all features, and each engine feature alone),cargo fmt, the crate's unit tests, rustdoc with warnings denied, ShellCheck onrun.shand actionlint onci.ymlall pass.just smoke postgres,sqliteandmemoryon this tree, rebased on main: every test that isn't ignored passes, and the shared broker passes its checks. Local runs also run the ignored tests: postgres 55 of 64 pass, sqlite 58 of 67, memory 45 of 58, and every failure is an ignored test.maintenance_interval, timestamp-lookup andacks=0tests were ignored for bugs that Fail clearly on bad storage config, lake-without-registry, and unauthenticated clients #850, fix: ListOffsets by timestamp should answer offset -1, not 0, when no record matches #838 and Produce with acks=0 no longer gets an unsolicited response; failure closes the connection #845 fixed, and are now enabled.acks=0stays ignored on in-memory storage for Object-store ListOffsets(Latest) returns the newest batch's base offset + 1, not the high watermark #798, like the other produce tests.s3is unchanged: it still runs onlytopic_lifecycle.🤖 Generated with Claude Code