This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-26788-chaos-explore-save
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/CAMEL-26788-chaos-explore-save
by this push:
new 9ee5438231bb CAMEL-26788: camel-jbang - an experiment waits at least
the within= of its expectations, so they can decide (found in a recorded run)
9ee5438231bb is described below
commit 9ee5438231bb27da0fac87c73c00f772bb7d8609
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 18:35:12 2026 +0100
CAMEL-26788: camel-jbang - an experiment waits at least the within= of its
expectations, so they can decide (found in a recorded run)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel-jbang-cmd-chaos-experiment.adoc | 2 +-
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../action/CamelChaosExperimentAction.java | 3 ++-
.../dsl/jbang/core/commands/ai/ToolRegistry.java | 2 +-
.../dsl/jbang/core/common/ChaosExperimentRun.java | 23 ++++++++++++++++++++++
.../jbang/core/common/ChaosExperimentRunTest.java | 18 ++++++++++++++++-
.../src/test/resources/golden/cli-help.txt | 3 ++-
.../dsl/jbang/core/commands/mcp/RuntimeTools.java | 2 +-
8 files changed, 48 insertions(+), 7 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
index f058c4f411d6..a8793619685d 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
@@ -29,7 +29,7 @@ camel cmd chaos-experiment [options]
| `--repeat` | How many messages to send in all (at most 10000) | | int
| `--rule` | A rule in its one-line text form, as an entry of
camel.chaos.rules; can be given more than once, and one of them needs an
expectation (expect=) | | List
| `--timeout` | How long to wait for the integration to answer, in millis or
as a duration such as 30s | 10000 | long
-| `--wait` | The longest wait for the verdict after the messages were sent (at
most 10m) | 2m | String
+| `--wait` | The longest wait for the verdict after the messages were sent,
and at least the within= of the expectations (at most 10m) | 2m | String
| `-h,--help` | Display the help and sub-commands | | boolean
|===
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index a98548c7bc7f..f05a290e35fb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
{ "name": "ask", "fullName": "ask", "description": "Ask a question about a
running Camel application using AI", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names":
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY,
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType":
"java.lang.String", "type": "string" }, { "names": "--api-type", "description":
"API type: 'ollama', 'openai', 'anthropic', or 'watsonx'", "javaType": [...]
{ "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind
source and sink Kamelets as a new Camel integration", "deprecated": true,
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options":
[ { "names": "--error-handler", "description": "Add error handler
(none|log|sink:<endpoint>). Sink endpoints are expected in the format
\"[[apigroup\/]version:]kind:[namespace\/]name\", plain Camel URIs or Kamelet
name.", "javaType": "java.lang.String", "type": " [...]
{ "name": "catalog", "fullName": "catalog", "description": "List artifacts
from Camel Catalog", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [
{ "names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"component", "fullName": "catalog component", "description": "List components
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
- { "name": "cmd", "fullName": "cmd", "description": "Performs commands in
the running Camel integrations, such as start\/stop route, or change logging
levels.", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"browse", "fullName": "cmd browse", "description": "Browse pending messages on
endpoints [...]
+ { "name": "cmd", "fullName": "cmd", "description": "Performs commands in
the running Camel integrations, such as start\/stop route, or change logging
levels.", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"browse", "fullName": "cmd browse", "description": "Browse pending messages on
endpoints [...]
{ "name": "completion", "fullName": "completion", "description": "Generate
completion script for bash\/zsh", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "config", "fullName": "config", "description": "Get and set user
configuration values", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get",
"fullName": "config get", "description": "Display user configuration value",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
{ "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "How [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
index 5986e63a1526..f3ea315b53c7 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
@@ -96,7 +96,8 @@ public class CamelChaosExperimentAction extends
CamelChaosAction {
String interval;
@CommandLine.Option(names = { "--wait" }, defaultValue = "2m",
- description = "The longest wait for the verdict after
the messages were sent (at most 10m)")
+ description = "The longest wait for the verdict after
the messages were sent, and at least the"
+ + " within= of the expectations (at most
10m)")
String wait;
@CommandLine.Option(names = { "--note" },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index d0e2caaa5ff3..60ce44ddcabb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -983,7 +983,7 @@ public final class ToolRegistry {
"The pause between two messages in millis, or a range
such as 100..500 (at most 60000)", false)
.param("wait", "string",
"The longest wait for the verdict after the messages
were sent, such as 90s (default 2m, at most"
- + " 10m)",
+ + " 10m); it is at least the within=
of the expectations",
false)
.param("note", "string", "What to note with the experiment in
the journal", false)
.param("name", "string", CHAOS_NAME, false)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
index dbaeab9254b7..2951bfd76a25 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
@@ -19,6 +19,8 @@ package org.apache.camel.dsl.jbang.core.common;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.camel.util.TimeUtils;
import org.apache.camel.util.json.JsonObject;
@@ -140,6 +142,11 @@ public final class ChaosExperimentRun {
throw new IllegalArgumentException(why);
}
}
+ // an expectation such as failed<=0 is decided when its within= is up:
a shorter wait leaves it undecided
+ long within = longestWithin(rules);
+ if (within + POLL * 2 > wait) {
+ wait = Math.min(within + POLL * 2, MAX_WAIT);
+ }
JsonObject begun = integration.chaos("experiment-begin", root -> {
if (hypothesis != null && !hypothesis.isBlank()) {
root.put("hypothesis", hypothesis);
@@ -190,6 +197,22 @@ public final class ChaosExperimentRun {
return answer;
}
+ /** The longest within= of the rules, in millis, or 0 (an expectation
without it has the time of its rule). */
+ static long longestWithin(String rules) {
+ long answer = 0;
+ Matcher m = WITHIN.matcher(rules);
+ while (m.find()) {
+ try {
+ answer = Math.max(answer,
TimeUtils.toMilliSeconds(m.group(1)));
+ } catch (RuntimeException e) {
+ // not a time: camel-chaos refuses the rule with the reason
+ }
+ }
+ return answer;
+ }
+
+ private static final Pattern WITHIN =
Pattern.compile("(?:^|\\s)within=(\\S+)");
+
/** Sends the traffic: the summary of the send, or why it could not be
sent. */
private static JsonObject traffic(Integration integration, Feed feed) {
JsonObject answer;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
index d9c994f8fd55..a6bf3a60dafd 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
@@ -154,7 +154,9 @@ class ChaosExperimentRunTest {
integration.shows("PENDING", true);
integration.ended = experiment("INCONCLUSIVE", false);
- JsonObject answer = ChaosExperimentRun.run(integration, null, RULE,
null, 5_000, null, clock);
+ // an expectation without within= has the time of its rule, which the
wait does not know
+ JsonObject answer = ChaosExperimentRun.run(integration, null,
"route=refunds error expect=refunds:failed<=0", null,
+ 5_000, null, clock);
assertThat(answer.getString("verdict")).isEqualTo("INCONCLUSIVE");
assertThat(answer.getLong("waited")).isEqualTo(5_000);
@@ -239,6 +241,20 @@ class ChaosExperimentRunTest {
.containsExactly("r1: endpoint=file:/parked matches no
endpoint of the context now");
}
+ @Test
+ void theWaitIsAtLeastTheWithinOfTheExpectations() throws Exception {
+ integration.shows("PENDING", true);
+ integration.ended = experiment("PASS", false);
+
+ // RULE has within=30s: a wait of 5s would end it before failed<=0 is
decided
+ JsonObject answer = ChaosExperimentRun.run(integration, null, RULE,
null, 5_000, null, clock);
+
+ assertThat(answer.getLong("waited")).isEqualTo(32_000);
+ assertThat(ChaosExperimentRun.longestWithin("route=a error expect=a:1
within=1m; route=b drop expect=b:1 within=90s"))
+ .isEqualTo(90_000);
+ assertThat(ChaosExperimentRun.longestWithin("route=a error")).isZero();
+ }
+
@Test
void theWait() {
assertThat(ChaosExperimentRun.waitMillis(null)).isEqualTo(ChaosExperimentRun.DEFAULT_WAIT);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 94d33a3c2fd4..76f9cefccd26 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -786,7 +786,8 @@ rules, send its traffic, wait for the verdict, and end it
range such as 100..500 to draw each pause at
random (at most 60s)
--wait=<wait> The longest wait for the verdict after the messages
- were sent (at most 10m)
+ were sent, and at least the within= of the
+ expectations (at most 10m)
Default: 2m
--note=<note> What to note with the experiment when it ends
--end End the open experiment (with --note) instead of
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index fb76a9d01cc6..813e34815deb 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -1314,7 +1314,7 @@ public class RuntimeTools {
@ToolArg(description = "The pause between two messages in millis,
or a range such as 100..500",
required = false) String interval,
@ToolArg(description = "The longest wait for the verdict after the
messages were sent, such as 90s"
- + " (default 2m, at most 10m)",
+ + " (default 2m, at most 10m); it is at
least the within= of the expectations",
required = false) String wait,
@ToolArg(description = "What to note with the experiment in the
journal", required = false) String note) {
requireArg("rule", rule);