This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch tui-chaos-explore-start
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f4e4645d6ad2983a2041aeb1c40e0746a1176d09
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 17:38:13 2026 +0100

    chore: camel-jbang - let there be chaos: polish from trying it on the chaos 
example
    
    The time left is shared by the rules left (the last one ran past the 
deadline), a stall or lifecycle rule gets within=
    instead of until=, the reason of a refused rule shows, the dead letter 
channel on its own is not picked (a pair is),
    the selection follows the running experiment, and the waiting dialogs show 
Esc once.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../jbang/core/commands/tui/ChaosExploreRun.java   | 32 +++++++++++++++++++---
 .../jbang/core/commands/tui/ChaosExploreTest.java  |  9 +++---
 2 files changed, 33 insertions(+), 8 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreRun.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreRun.java
index 53cc8b7708a2..9a8e39801c47 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreRun.java
@@ -43,6 +43,8 @@ final class ChaosExploreRun {
     private static final Logger LOG = 
LoggerFactory.getLogger(ChaosExploreRun.class);
     private static final Pattern UNTIL = Pattern.compile("\\buntil=\\S+");
     private static final Pattern WITHIN = Pattern.compile("\\bwithin=\\S+");
+    // the faults that act on the route once, for their duration, and take no 
until=
+    private static final Pattern ONCE = 
Pattern.compile("\\s(stall|lifecycle)\\b");
     // the order of the verdicts in the summary
     private static final List<String> VERDICTS = List.of("PASS", "FAIL", 
"INCONCLUSIVE", "REFUSED");
     // left over at the deadline, for the last experiment to end before 
camel-chaos ends it
@@ -63,6 +65,10 @@ final class ChaosExploreRun {
     private volatile boolean running;
     private volatile boolean stopped;
     private volatile String outcome;
+    // why the first rule the integration refused was refused
+    private volatile String refusal;
+    private volatile long started;
+    private volatile String budget;
     private final Map<String, Integer> verdicts = new TreeMap<>(
             (a, b) -> Integer.compare(order(a), order(b)) != 0 ? 
Integer.compare(order(a), order(b)) : a.compareTo(b));
 
@@ -87,10 +93,14 @@ final class ChaosExploreRun {
         this.pid = pid;
         this.picks = List.copyOf(picks);
         this.share = budgetMillis / Math.max(1, picks.size());
-        this.deadline = MonotonicClock.millis() + budgetMillis;
+        this.started = MonotonicClock.millis();
+        this.ruleStarted = started;
+        this.deadline = started + budgetMillis;
+        this.budget = budget;
         this.current = 0;
         this.stopped = false;
         this.outcome = null;
+        this.refusal = null;
         synchronized (verdicts) {
             verdicts.clear();
         }
@@ -146,6 +156,9 @@ final class ChaosExploreRun {
                     // the integration refused it, such as a rule that is not 
valid there: the next one
                     LOG.debug("Chaos experiment {} refused: {}", pick.rule(), 
e.getMessage());
                     verdict = "REFUSED";
+                    if (refusal == null) {
+                        refusal = "rule " + (i + 1) + " refused: " + 
e.getMessage();
+                    }
                 }
                 count(verdict);
             }
@@ -160,8 +173,12 @@ final class ChaosExploreRun {
                 LOG.debug("Chaos exploration finished already: {}", 
e.getMessage());
             }
             int tried = tried();
-            outcome = (stopped ? "Stopped after " : "Done: ") + tried + (tried 
== 1 ? " rule" : " rules") + " tried"
-                      + (tried == 0 ? "" : ", " + verdictCounts());
+            String counts = tried + (tried == 1 ? " rule" : " rules") + " 
tried" + (tried == 0 ? "" : ", " + verdictCounts());
+            outcome = (stopped
+                    ? "Stopped after " + counts
+                    : "Done after " + 
ChaosTab.shortTime(MonotonicClock.millis() - started) + " of " + budget + ": "
+                      + counts)
+                      + (refusal != null ? " (" + refusal + ")" : "");
             running = false;
         }
     }
@@ -220,11 +237,18 @@ final class ChaosExploreRun {
 
     /**
      * The rules for their share of the budget: each ends when its share is up 
({@code until=}), and an expectation that
-     * waits longer for its messages ({@code within=}) waits that long at most.
+     * waits longer for its messages ({@code within=}) waits that long at 
most. A fault that acts once for its duration
+     * (stall, lifecycle) takes no until: its expectation is decided when the 
share is up instead.
      */
     static String forShare(String rules, long share) {
         String seconds = Math.max(1, share / 1000) + "s";
         return Arrays.stream(rules.split(";")).map(String::strip).filter(r -> 
!r.isEmpty()).map(r -> {
+            if (ONCE.matcher(r).find()) {
+                if (WITHIN.matcher(r).find()) {
+                    return WITHIN.matcher(r).replaceAll("within=" + seconds);
+                }
+                return r.contains(" expect=") ? r + " within=" + seconds : r;
+            }
             String rule = UNTIL.matcher(r).find() ? 
UNTIL.matcher(r).replaceAll("until=" + seconds) : r + " until=" + seconds;
             return WITHIN.matcher(rule).replaceAll("within=" + seconds);
         }).collect(Collectors.joining("; "));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java
index 474ecb9f617b..dc2c1e5414f5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreTest.java
@@ -151,8 +151,9 @@ class ChaosExploreTest {
         assertThat(ChaosExploreRun.forShare(
                 "route=shipping error rate=0.2 until=2m 
expect=orders->shipping:lost<=0 within=3m", 150_000))
                 .isEqualTo("route=shipping error rate=0.2 until=150s 
expect=orders->shipping:lost<=0 within=150s");
-        assertThat(ChaosExploreRun.forShare("route=payment stall duration=20s 
expect=payment:failed<=0", 60_000))
-                .isEqualTo("route=payment stall duration=20s 
expect=payment:failed<=0 until=60s");
+        // a stall acts once, for its duration: no until, and its expectation 
is decided when the share is up
+        assertThat(ChaosExploreRun.forShare("route=payment stall duration=20s 
expect=orders:failed<=0", 60_000))
+                .isEqualTo("route=payment stall duration=20s 
expect=orders:failed<=0 within=60s");
         // two faults at once: both end with the share
         assertThat(ChaosExploreRun.forShare("endpoint=a error until=1m; 
endpoint=b error at=dlq until=1m", 30_000))
                 .isEqualTo("endpoint=a error until=30s; endpoint=b error 
at=dlq until=30s");
@@ -188,8 +189,8 @@ class ChaosExploreTest {
         assertThat(integration.hypotheses).containsExactly(
                 "orders: Returns an error (HTTP status)", "orders: The route 
after a hand-off fails",
                 "orders: An outage while the dead letter channel fails");
-        assertThat(explorer.progress("1234")).isEqualTo("Done: 3 rules tried, 
3 FAIL");
-        assertThat(TuiTestHelper.renderToString(tab, 160, 40)).contains("Done: 
3 rules tried, 3 FAIL");
+        assertThat(explorer.progress("1234")).matches("Done after \\d+ms of 
10m: 3 rules tried, 3 FAIL");
+        assertThat(TuiTestHelper.renderToString(tab, 160, 40)).contains("of 
10m: 3 rules tried, 3 FAIL");
     }
 
     @Test

Reply via email to