allthingssecurity opened a new pull request, #28067:
URL: https://github.com/apache/camel/pull/28067
CAMEL-25738: camel-test-dst - deterministic simulation testing for Camel
consumers (stage 1)
## What
A new test module, `camel-test-dst` (preview), and three regression tests
that use it. The real consumer of a component
runs under one seeded scheduler with virtual time on the test thread; only
the external system is faked. A test runs
thousands of seeds in seconds, and a failing seed replays with the same step
trace (same SHA-256). Nothing in it runs
in production and no Camel component or core class changes (outside tests,
the PR touches only poms, the catalog/docs registration and one line of
`MojoHelper`).
Stage 1 of CAMEL-25738 makes the harness trustworthy enough to write a fake
world for a component without running into
the harness. Following the direction on Zulip, this is DST only; the chaos
proposal comes later.
## Commits
The first four are the prototype (as on the `camel-test-dst-prototype`
branch). The fifth is Claus Ibsen's SQS world
(his CAMEL-25738 patch, kept as he wrote it). Then one commit per stage 1
item (the pitfalls subsection of item 7 is a
commit of its own), and one per follow-up (9 to 12):
0. **Seed scrambling.** `new Random(seed)` drew the same first values for
consecutive seeds (for seeds 1 to 1000 the
first `nextInt(2)` is always 1), so the configuration a world picked
first barely changed. The seed is scrambled
with SplitMix64; replay stays exact. `DstSeedScrambleTest`.
1. **Time read outside the instrumented package.** The rewrite now covers
`new Date()`, `Clock.system*()` and
`now()` / `now(ZoneId)` of the `java.time` types.
`@DstInstrument(alsoRewrite = ...)` lists core classes or packages
to rewrite too (`StopWatch`, `MonotonicClock`, ...), documented as a
stop-gap: Camel core is not changed here, a
time source in core needs a dev@ thread first. `DstInstrumentationTest`,
`DstAlsoRewriteTest`.
2. **Real sleeps on the loop thread.** `Thread.sleep` / `TimeUnit.sleep`
become virtual waits (`dst.block`). With
`alsoRewrite` this covers `RedeliveryPolicy.sleep` and the synchronous
redelivery loop, `ForegroundTask`, and the
wait loop of `DefaultShutdownStrategy` (a route can be stopped with an
exchange in flight). `DstSleepTest`,
`DstCoreWaitsTest`.
3. **Timed waits.** `DstTask.get(timeout)` and `awaitTermination` time out
in virtual time; `shutdown()` drops periodic
tasks as `ScheduledThreadPoolExecutor` does. `DstTimedWaitTest`.
4. **Waits on something that is not a DST task.**
`CompletableFuture.get/join`, `Future.get`, `CountDownLatch.await`,
`Semaphore.acquire`, `DelayQueue`/`BlockingQueue` `take/poll` in the code
under test run the loop until the wait can
end, or fail the seed at once with the call site. A watchdog fails a seed
whose loop thread waits in code that is not
rewritten (10 s real time), instead of the surefire timeout.
`DstBlockingWaitTest`.
5. **Strict mode**, the default: reaching an uncontrolled escape at runtime
fails the seed. `DstStrictModeTest`.
6. **Module layout.** The package of the test is its world and is not
rewritten. The worlds move to the tests of their
components (`...dst` packages) as passing regression tests with a test
scoped dependency on camel-test-dst; Claus's
SQS world moves in a commit of its own. The module keeps the harness only.
7. **The guide for writing a fake world** (for AI agents and humans), on the
module doc page `test-dst.adoc`, with
the SQS world as the worked example and a pointer in `AGENTS.md`. It
covers each point of CAMEL-25738: find where
the client is swapped; run the escape scan before writing anything; write
the fake from the service's documentation,
quoting the rule for each behaviour and marking assumptions; the
one-stack timing rule; let the seed pick the endpoint
configuration as well as the timing; seeded faults with a drain phase and
a record of which fault excuses which
violation; invariants from the component's contract (`Invariants`); the
self-check (fail on the code before a past
fix, pass on `main`, same SHA-256 on replay); triage before reporting. A
subsection "Pitfalls seen when agents wrote
worlds" lists what went wrong when agents wrote nine worlds with the
guide (fake timestamps at 0 instead of virtual
wall clock time, no unwind for a consumer that loops forever, a fake that
blocks under the consumer's lock, missing
fault-excuse records, old traces in `target/dst`, a test log that filled
the disk, a fixed endpoint configuration),
with matching checklist items.
8. **Registration.** `MojoHelper` lists the module under `camel-test`; the
catalog copies, the BOM entry, the docs nav
entry and the doc symlinks are committed. The module's surefire config
pins `camel.surefire.parallel=false`.
9. **One module doc.** `README.md` and `DEMO.md` of the module are removed
(they repeated the doc page); the run
commands, the limits and the table of the classes move to `test-dst.adoc`.
10. **`runFor` returns when two periodic tasks block each other.** A
blocking task moves the clock past the target of
`runFor`; two periodic tasks that block each other kept each other due,
and `runFor` never returned (100 of 459 seeds
of a Kinesis world ran into the step budget). `runFor` now returns once
the clock passed its target.
`DstSchedulerTest`.
11. **A truncated trace keeps its end.** The trace kept only its first 200
000 lines, so the end of a long run, where it
failed, was lost; it now keeps the first half and a rolling tail.
`DstSchedulerTest`.
12. **Parallel streams.** `parallelStream()` of the `java.util` collections
in the code under test ran on the common
fork-join pool and was not in the escape scan; it is rewritten to
`stream()` and listed by the scan.
`DstInstrumentationTest`.
## Results
Each world is now a regression test in its component, 1000 seeds by default,
2 to 3 ms per seed, strict mode.
| World | `main`, 5000 seeds | fix reverted locally, 5000 seeds |
|---|---|---|
| camel-azure-eventhubs checkpointing (CAMEL-25683, #27922) | 1 failing
(seed 3042, see below) | 2064 |
| camel-slack suspend mid-poll (CAMEL-25684, #27923) | 0 | 2652 |
| camel-aws2-sqs visibility extender (CAMEL-25700, #27985; world by Claus
Ibsen) | 0 | 1397, all with `maxMessagesPerPoll >= 2` and `concurrentConsumers
>= 2` |
`main` with `visibilityTimeout=1` only: 843 of 2000 seeds fail (CAMEL-25737,
still open; left out of the default set).
Event Hubs seed 3042 on `main`: an event completes in the same millisecond
in which the timer of its window ran;
`EventHubsCheckpointUpdaterTask.isExpired()` compares with `>`, so the
window is reused and the event is checkpointed
only with the next event of the partition (if the partition stays quiet, it
is delivered again after a restart). In
real time the window is narrow but not only that millisecond: the timer task
does not take the consumer's lock, so it
can also run between the `isExpired()` check and the count of the event. Low
severity; documented in the test, not
fixed in this PR. Replayed in two JVMs, it gives the same trace (SHA-256
`1137160c...`).
Other worlds written with the guide (not part of this PR, kept outside the
tree): fake worlds for the code before
CAMEL-25709 (aggregate), CAMEL-25679 (mongodb tailable), CAMEL-25712
(google-pubsub synchronous pull) and CAMEL-25711
(mongodb-gridfs) fail in 427, 3019, 1316 and 2578 of 5000 seeds and pass on
`main`. A fake DynamoDB Streams world
written from the AWS API reference found a new defect on `main`, CAMEL-25741
(aws2-ddbstream leaves a closed shard
before reading it to its end; 1253 of 3000 seeds), which has its own fix and
plain JUnit test.
## Not in this PR (later stages, documented on the doc page)
- **A time source in Camel core** (stage 1 item 1, the decision between a
hook in core and rewriting byte code): the
proposal is the option from the TIP on CAMEL-25738, a small static time
source in camel-util (`currentTimeMillis`,
`nanoTime`, `instant`, `sleep`) used only by the 25 to 30 call sites that
change behaviour (the waits and the
consumer timing), with byte code rewriting kept for the component under
test and pointed at it. It touches core, so
it goes to dev@ first with the call-site list and a JMH benchmark. Until
then `@DstInstrument(alsoRewrite)` is a
stop-gap; core still reads `System` directly.
- **The one-stack model**: a blocked task resumes only after the tasks that
blocked after it; a timed wait notices its
timeout only when the tasks above it return; a route stop cannot see a
synchronously blocked exchange below it finish
(the Slack world keeps its hand-made suspend sequence for that reason).
Stage 1 documents it; a fix may need virtual
threads with a custom scheduler.
- **Reactor and other third-party schedulers** (the Azure SDKs): outside the
harness; strict mode fails the seed when
one is reached, so the worlds fake the clients above them.
- **Several runs in parallel in one JVM**: the active run is a static field,
so it is one run per JVM at a time.
- Sharing the fault rules with the chaos proposal.
## Testing
```
./mvnw -Papache-snapshots test -pl
components/camel-test/camel-test-dst,components/camel-aws/camel-aws2-sqs,\
components/camel-azure/camel-azure-eventhubs,components/camel-slack
-Dtest='Dst*Test,*DstTest' \
-Dsurefire.failIfNoSpecifiedTests=false
```
43 harness self-tests pass, and the whole test suites of camel-aws2-sqs
(1077 tests), camel-azure-eventhubs (1036)
and camel-slack (1019) pass with the worlds in them (1000 seeds each), so
the rewriting of the component classes in
the same surefire JVM does not disturb the other tests (whole suites run
before items 9 to 12, which change only DST
runs; the three components do not call `parallelStream`). The counts in the
results table were re-run on this branch
(5000 seeds each, with and without the fixes), after the harness fixes 10 to
12 as well; the seed 3042 trace
hash is unchanged. `formatter:format impsort:sort` applied.
_Claude Code on behalf of allthingssecurity_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]