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

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

commit bfef819070d3910a52b0b546d0de64a1966a1584
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Nov 11 10:46:52 2026 +0100

    chore: camel-chaos - Times out counts as tried once it ran, and let there 
be chaos remembers the budget and the picks
    
    A rule that fails after a wait (after=) is a timeout: kind slow, as the 
suggestions offer it, not errors.
    The Chaos screen (L) opens with the budget and the rules picked the last 
time, while the TUI runs.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |  6 ++--
 components/camel-chaos/src/main/docs/chaos.adoc    |  6 ++--
 .../apache/camel/component/chaos/ChaosAdvisor.java |  3 +-
 .../camel/component/chaos/ChaosTriedTest.java      |  3 ++
 .../dsl/jbang/core/commands/tui/ChaosControl.java  | 13 +++++++--
 .../core/commands/tui/ChaosExploreDialog.java      | 32 ++++++++++++++++++++++
 .../jbang/core/commands/tui/ChaosExploreTest.java  | 30 ++++++++++++++++++++
 7 files changed, 84 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index 3cf17107f73d..2a245ccca8a8 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -324,9 +324,9 @@ $ camel get chaos --report
   shipping  dead letter channel                    not tried yet
 ----
 
-* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a 
send is dropped), `corrupt`, `duplicate`,
-`consumer` (the polls, or the entry of the route), `lifecycle` (stall and 
lifecycle) and `dead letter channel`
-(`at=dlq`). A rule has one kind, from its fault and where it acts.
+* The kinds are `errors` (a send or a step fails), `slow` (a delay, or an 
error that waits first: a timeout), `lost`
+(a send is dropped), `corrupt`, `duplicate`, `consumer` (the polls, or the 
entry of the route), `lifecycle` (stall and
+lifecycle) and `dead letter channel` (`at=dlq`). A rule has one kind, from its 
fault and where it acts.
 * A kind is tried in a route when a rule of that kind fired in it: a rule 
counts its faults per route, so a rule that
 reaches several routes is tried in each. A rule that expects no message lost 
between two routes
 (`expect=orders->shipping:lost<=0`) also tries `lost` for the first route, as 
the hand-off suggestion fails the next
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index 3cf17107f73d..2a245ccca8a8 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -324,9 +324,9 @@ $ camel get chaos --report
   shipping  dead letter channel                    not tried yet
 ----
 
-* The kinds are `errors` (a send or a step fails), `slow` (a delay), `lost` (a 
send is dropped), `corrupt`, `duplicate`,
-`consumer` (the polls, or the entry of the route), `lifecycle` (stall and 
lifecycle) and `dead letter channel`
-(`at=dlq`). A rule has one kind, from its fault and where it acts.
+* The kinds are `errors` (a send or a step fails), `slow` (a delay, or an 
error that waits first: a timeout), `lost`
+(a send is dropped), `corrupt`, `duplicate`, `consumer` (the polls, or the 
entry of the route), `lifecycle` (stall and
+lifecycle) and `dead letter channel` (`at=dlq`). A rule has one kind, from its 
fault and where it acts.
 * A kind is tried in a route when a rule of that kind fired in it: a rule 
counts its faults per route, so a rule that
 reaches several routes is tried in each. A rule that expects no message lost 
between two routes
 (`expect=orders->shipping:lost<=0`) also tries `lost` for the first route, as 
the hand-off suggestion fails the next
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
index 4f65002691ee..6c5db1c6c0a5 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
@@ -179,7 +179,8 @@ final class ChaosAdvisor {
             case DROP -> consumer ? Kind.CONSUMER : Kind.LOST;
             case CORRUPT -> Kind.CORRUPT;
             case DUPLICATE -> Kind.DUPLICATE;
-            default -> consumer ? Kind.CONSUMER : Kind.ERRORS;
+            // an error that waits first (after=) is a call that times out, 
which the suggestions offer as slow
+            default -> consumer ? Kind.CONSUMER : rule.after > 0 ? Kind.SLOW : 
Kind.ERRORS;
         };
     }
 
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
index 92bd4826ff56..2db55f43e568 100644
--- 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosTriedTest.java
@@ -168,6 +168,9 @@ class ChaosTriedTest extends ChaosTestSupport {
     void theKindOfARule() {
         assertThat(kind("endpoint=x 
error")).isEqualTo(ChaosAdvisor.Kind.ERRORS);
         assertThat(kind("endpoint=x delay 
duration=1s")).isEqualTo(ChaosAdvisor.Kind.SLOW);
+        // Times out: the suggestion of an HTTP call is slow, so trying it 
marks it tried
+        assertThat(kind("endpoint=x error after=5s 
exception=java.net.SocketTimeoutException"))
+                .isEqualTo(ChaosAdvisor.Kind.SLOW);
         assertThat(kind("endpoint=x drop")).isEqualTo(ChaosAdvisor.Kind.LOST);
         assertThat(kind("endpoint=x drop 
at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER);
         assertThat(kind("endpoint=x error 
at=poll")).isEqualTo(ChaosAdvisor.Kind.CONSUMER);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
index 93d290500f57..be7e1eb6def5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
@@ -23,6 +23,7 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -96,6 +97,8 @@ final class ChaosControl {
     // the exploration started here (L), and whether one was just started, for 
the screen to show it
     private final ChaosExploreRun explorer;
     private boolean exploreStarted;
+    // the budget and the picks of the dialog per integration, as it was 
closed last, while the TUI runs
+    private final Map<String, ChaosExploreDialog.Picks> lastPicks = new 
ConcurrentHashMap<>();
     // set by the background task that sends the action, read when the screen 
is drawn
     private volatile Step step = Step.NONE;
     private Command command;
@@ -404,7 +407,9 @@ final class ChaosControl {
                         : reply.getString("error"));
                 return;
             }
-            exploreDialog = new ChaosExploreDialog(of, reply);
+            ChaosExploreDialog dialog = new ChaosExploreDialog(of, reply);
+            dialog.restore(lastPicks.get(of));
+            exploreDialog = dialog;
         };
         if (sender != null) {
             task.run();
@@ -422,8 +427,12 @@ final class ChaosControl {
             return true;
         }
         switch (dialog.handleKey(ke)) {
-            case CLOSED -> close();
+            case CLOSED -> {
+                lastPicks.put(dialog.name(), dialog.picks());
+                close();
+            }
             case START -> {
+                lastPicks.put(dialog.name(), dialog.picks());
                 explorer.start(pid, dialog.picked(), dialog.budget(), 
dialog.budgetMillis());
                 exploreStarted = true;
                 close();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java
index 6d634cc4044b..bfa08c75fb5c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosExploreDialog.java
@@ -95,6 +95,10 @@ final class ChaosExploreDialog {
         }
     }
 
+    /** The budget and the rules picked, to open the dialog with them again. */
+    record Picks(String budget, Set<String> rules) {
+    }
+
     /** The budgets to pick from: an exploration by hand is short, the long 
ones are for camel cmd chaos-explore. */
     static final List<String> BUDGETS = List.of("2m", "5m", "10m", "15m", 
"30m");
     // the shortest time a rule is tried, so its faults fire and the routes 
have messages
@@ -175,6 +179,34 @@ final class ChaosExploreDialog {
         return items;
     }
 
+    String name() {
+        return name;
+    }
+
+    /** The budget and the rules picked now. */
+    Picks picks() {
+        return new Picks(budget(), 
picked().stream().map(Item::rule).collect(Collectors.toSet()));
+    }
+
+    /**
+     * Opens with the budget and the picks of last time, to try them again 
(after a change to the routes): the rules of
+     * last time that the integration still suggests, else the ones picked by 
itself.
+     */
+    void restore(Picks last) {
+        if (last == null) {
+            return;
+        }
+        int index = BUDGETS.indexOf(last.budget());
+        if (index >= 0) {
+            budget = index;
+        }
+        if (items.stream().anyMatch(item -> 
last.rules().contains(item.rule()))) {
+            for (int i = 0; i < items.size(); i++) {
+                picked.set(i, last.rules().contains(items.get(i).rule()));
+            }
+        }
+    }
+
     /** The picked rules, in the order of the list. */
     List<Item> picked() {
         List<Item> answer = new ArrayList<>();
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 8fa1af8c414c..f52700e8436f 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
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -150,6 +151,35 @@ class ChaosExploreTest {
                 .isEqualTo(ChaosExploreDialog.Outcome.NONE);
     }
 
+    @Test
+    void theDialogOpensWithTheBudgetAndThePicksOfLastTime() {
+        key('L');
+        // the first rule unpicked, refunds picked, and 5 minutes
+        key(' ');
+        press(KeyCode.DOWN);
+        press(KeyCode.DOWN);
+        key(' ');
+        press(KeyCode.LEFT);
+        press(KeyCode.ESCAPE);
+        assertThat(tab.isTextInputActive()).isFalse();
+
+        key('L');
+        assertThat(TuiTestHelper.renderToString(tab, 160, 40)).contains("5m", 
"3 rules, 1m40s each",
+                "[ ] Returns an error (HTTP status)", "[x] The route after a 
hand-off fails", "[x] The route fails",
+                "[x] An outage while the dead letter channel fails");
+    }
+
+    @Test
+    void thePicksOfLastTimeThatAreNoLongerSuggestedAreNot() throws Exception {
+        ChaosExploreDialog dialog = dialog();
+        dialog.restore(new ChaosExploreDialog.Picks("30m", Set.of("route=gone 
error")));
+        assertThat(dialog.budget()).isEqualTo("30m");
+        // picked by itself, as none of last time is suggested
+        assertThat(dialog.picked()).extracting(ChaosExploreDialog.Item::title)
+                .containsExactly("Returns an error (HTTP status)", "The route 
after a hand-off fails",
+                        "An outage while the dead letter channel fails");
+    }
+
     @Test
     void eachRuleRunsForItsShare() {
         assertThat(ChaosExploreRun.forShare(

Reply via email to