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

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

commit 9c9c44a445be8be1e1b613fda48dd3ef185ccdc8
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 15:02:16 2026 +0100

    chore: camel-jbang - the run options form can turn on chaos testing 
(--chaos)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../pages/camel-jbang-tui-getting-started.adoc     |  4 +-
 .../ROOT/pages/camel-jbang-tui-observe.adoc        |  3 +-
 .../jbang/core/commands/tui/RunOptionsForm.java    | 47 +++++++++++---
 .../core/commands/tui/RunOptionsFormTest.java      | 73 +++++++++++++++++++---
 4 files changed, 109 insertions(+), 18 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-getting-started.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-getting-started.adoc
index e331ceca0ded..91e204bb826c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-getting-started.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-getting-started.adoc
@@ -44,8 +44,8 @@ Before launching, a run options form lets you choose the 
*runtime*: Camel Main (
 Spring Boot, Quarkus, or JBang. This makes it easy to try any example on all 
three runtimes without
 changing a single line of code. The first three run the example in a separate 
JVM that only
 contains the dependencies of the example (like a production deployment), while 
JBang runs it
-in-process in the Camel CLI JVM, which starts faster but has the CLI on the 
classpath as well. You can also set the integration name, toggle dev mode, and
-add extra dependencies. When the port the app will listen on (the one you set, 
else 8080) is taken already, the form
+in-process in the Camel CLI JVM, which starts faster but has the CLI on the 
classpath as well. You can also set the integration name, toggle dev mode,
+turn on chaos testing (`--chaos`, to inject faults with camel-chaos; not with 
profile prod), and add extra dependencies. When the port the app will listen on 
(the one you set, else 8080) is taken already, the form
 says by whom, so a second app with an HTTP server does not fail to start.
 
 The example starts running in the background. The TUI auto-selects it as soon 
as it appears.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
index f62c46e406fb..bbf7ec3e0a3f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
@@ -311,7 +311,8 @@ Beyond the core tabs described above, the TUI includes 
several specialized scree
 
 === Chaos
 
-With xref:next@components:others:chaos.adoc[camel-chaos] (`camel run 
--chaos`), the Chaos tab (under More >
+With xref:next@components:others:chaos.adoc[camel-chaos] (`camel run --chaos`, 
or *Chaos testing* in the run options
+form of the TUI), the Chaos tab (under More >
 Observability) shows the faults that the chaos rules inject into the running 
integration, and what they do:
 
 * The verdict of the report (PASS or FAIL with the reason), the messages lost, 
the expectations (met, unmet, pending)
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index b562236e6a57..1424843b0f5f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -57,7 +57,10 @@ class RunOptionsForm {
     private static final int ROW_STUB = 11;
     private static final int ROW_OTEL_AGENT = 12;
     private static final int ROW_JFR = 13;
-    private static final int ROW_COUNT = 14;
+    private static final int ROW_CHAOS = 14;
+    private static final int ROW_COUNT = 15;
+    /** The last row of the options page: Down and Tab go on from it to the 
properties page. */
+    private static final int ROW_LAST = ROW_CHAOS;
     /** Height of the options page: a row per option plus the border; the 
properties page is aligned to it. */
     private static final int PAGE1_HEIGHT = ROW_COUNT + 2;
 
@@ -117,6 +120,7 @@ class RunOptionsForm {
     private boolean otelAgent;
     private int otelExportTarget; // 0=TUI, 1=Jaeger
     private boolean jfrEnabled;
+    private boolean chaos;
     private boolean webConsole;
 
     private String exampleTitle;
@@ -160,6 +164,7 @@ class RunOptionsForm {
         otelAgent = false;
         otelExportTarget = 0;
         jfrEnabled = false;
+        chaos = false;
         webConsole = false;
         selectedRow = ROW_NAME;
         page = PAGE_OPTIONS;
@@ -211,6 +216,15 @@ class RunOptionsForm {
         return portWarning;
     }
 
+    /** Chaos is asked for, and the profile is not prod (where camel-chaos 
stays off). */
+    boolean isChaosEnabled() {
+        return chaos && !isProdProfile();
+    }
+
+    private boolean isProdProfile() {
+        return "prod".equals(PROFILE_VALUES[profileMode]);
+    }
+
     boolean isJaegerExport() {
         return otelAgent && otelExportTarget == 1;
     }
@@ -319,6 +333,10 @@ class RunOptionsForm {
         if (jfrEnabled) {
             args.add("--jfr");
         }
+        // camel-chaos stays off with profile prod, so the form does not ask 
for it then
+        if (isChaosEnabled()) {
+            args.add("--chaos");
+        }
         if (webConsole) {
             args.add("--console");
         }
@@ -346,7 +364,7 @@ class RunOptionsForm {
             return true;
         }
         if (ke.isDown()) {
-            if (selectedRow == ROW_JFR && hasProperties()) {
+            if (selectedRow == ROW_LAST && hasProperties()) {
                 page = PAGE_PROPERTIES;
                 selectedProperty = 0;
             } else {
@@ -355,7 +373,7 @@ class RunOptionsForm {
             return true;
         }
         if (ke.isFocusNext()) {
-            if (selectedRow == ROW_JFR && hasProperties()) {
+            if (selectedRow == ROW_LAST && hasProperties()) {
                 page = PAGE_PROPERTIES;
                 selectedProperty = 0;
             } else {
@@ -425,6 +443,10 @@ class RunOptionsForm {
                 case ROW_STUB -> stubMode = !stubMode;
                 case ROW_OTEL_AGENT -> otelAgent = !otelAgent;
                 case ROW_JFR -> jfrEnabled = !jfrEnabled;
+                case ROW_CHAOS -> {
+                    if (!isProdProfile())
+                        chaos = !chaos;
+                }
                 case ROW_CONSOLE -> webConsole = !webConsole;
             }
             return true;
@@ -457,7 +479,7 @@ class RunOptionsForm {
             editingKey = false;
             if (selectedProperty == 0) {
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_JFR;
+                selectedRow = ROW_LAST;
             } else {
                 selectedProperty--;
             }
@@ -487,7 +509,7 @@ class RunOptionsForm {
             } else if (selectedProperty == 0) {
                 editingKey = false;
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_JFR;
+                selectedRow = ROW_LAST;
             } else {
                 editingKey = false;
                 selectedProperty--;
@@ -503,7 +525,7 @@ class RunOptionsForm {
                     return true;
                 }
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_JFR;
+                selectedRow = ROW_LAST;
                 editingKey = false;
                 return true;
             }
@@ -518,7 +540,7 @@ class RunOptionsForm {
                     }
                     if (properties.isEmpty()) {
                         page = PAGE_OPTIONS;
-                        selectedRow = ROW_JFR;
+                        selectedRow = ROW_LAST;
                     }
                     return true;
                 }
@@ -648,6 +670,17 @@ class RunOptionsForm {
         rowY++;
 
         renderCheckbox(frame, innerX, rowY, innerW, "Java Flight Recorder 
(JFR)", jfrEnabled, selectedRow == ROW_JFR);
+        rowY++;
+
+        if (isProdProfile()) {
+            frame.renderWidget(Paragraph.from(Line.from(
+                    Span.styled(" [ ] Chaos testing (not available with 
profile prod)",
+                            selectedRow == ROW_CHAOS ? 
Style.EMPTY.dim().reversed() : Style.EMPTY.dim()))),
+                    new Rect(innerX, rowY, innerW, 1));
+        } else {
+            renderCheckbox(frame, innerX, rowY, innerW, "Chaos testing (inject 
faults with camel-chaos)", chaos,
+                    selectedRow == ROW_CHAOS);
+        }
 
         if (warning != null) {
             rowY++;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsFormTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsFormTest.java
index ee254414a8d1..09859c2f32cc 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsFormTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsFormTest.java
@@ -30,9 +30,9 @@ import org.junit.jupiter.api.Test;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * Drives {@link RunOptionsForm} through key events to verify the JFR checkbox 
row toggles {@code --jfr} into
- * {@link RunOptionsForm#buildArgs()}, and that inserting that row did not 
break navigation past the other checkbox rows
- * or the down-wrap back to row 0.
+ * Drives {@link RunOptionsForm} through key events to verify the JFR and 
chaos checkbox rows toggle {@code --jfr} and
+ * {@code --chaos} into {@link RunOptionsForm#buildArgs()}, and that inserting 
those rows did not break navigation past
+ * the other checkbox rows or the down-wrap back to row 0.
  */
 class RunOptionsFormTest {
 
@@ -80,11 +80,11 @@ class RunOptionsFormTest {
     }
 
     @Test
-    void downNavigationWrapsFromJfrRowBackToNameRow() {
+    void downNavigationWrapsFromChaosRowBackToNameRow() {
         RunOptionsForm form = openForm();
 
-        // 14 rows total (0=Name .. 13=JFR); 14 downs from row 0 wraps back to 
row 0 (Name).
-        for (int i = 0; i < 14; i++) {
+        // 15 rows total (0=Name .. 13=JFR, 14=Chaos); 15 downs from row 0 
wraps back to row 0 (Name).
+        for (int i = 0; i < 15; i++) {
             form.handleKeyEvent(key(KeyCode.DOWN));
         }
         form.handleKeyEvent(KeyEvent.ofChar('x'));
@@ -107,6 +107,63 @@ class RunOptionsFormTest {
         assertThat(args).doesNotContain("--jfr");
     }
 
+    @Test
+    void chaosOffByDefaultOmitsFlag() {
+        RunOptionsForm form = openForm();
+
+        assertThat(form.buildArgs()).doesNotContain("--chaos");
+        assertThat(form.isChaosEnabled()).isFalse();
+    }
+
+    @Test
+    void togglingChaosRowAddsChaosFlag() {
+        RunOptionsForm form = openForm();
+
+        // up from Name wraps to the last row, Chaos
+        form.handleKeyEvent(key(KeyCode.UP));
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+
+        
assertThat(form.buildArgs()).contains("--chaos").doesNotContain("--jfr");
+        assertThat(form.isChaosEnabled()).isTrue();
+    }
+
+    @Test
+    void chaosNotPassedWithProfileProd() {
+        RunOptionsForm form = openForm();
+
+        form.handleKeyEvent(key(KeyCode.UP));
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+        // Profile is row 2: cycle it from dev to prod
+        form.handleKeyEvent(key(KeyCode.DOWN));
+        form.handleKeyEvent(key(KeyCode.DOWN));
+        form.handleKeyEvent(key(KeyCode.DOWN));
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+
+        
assertThat(form.buildArgs()).contains("--profile=prod").doesNotContain("--chaos");
+        assertThat(renderPopupRows(form)).anyMatch(row -> row.contains("Chaos 
testing (not available with profile prod)"));
+    }
+
+    @Test
+    void chaosRowCannotBeToggledWithProfileProd() {
+        RunOptionsForm form = openForm();
+
+        // Profile is row 2: cycle it from dev to prod, then go up to the 
Chaos row and try to tick it
+        form.handleKeyEvent(key(KeyCode.DOWN));
+        form.handleKeyEvent(key(KeyCode.DOWN));
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+        for (int i = 0; i < 3; i++) {
+            form.handleKeyEvent(key(KeyCode.UP));
+        }
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+        // back to dev: the box was not ticked while prod was selected
+        for (int i = 0; i < 3; i++) {
+            form.handleKeyEvent(key(KeyCode.DOWN));
+        }
+        form.handleKeyEvent(KeyEvent.ofChar(' '));
+
+        
assertThat(form.buildArgs()).contains("--profile=dev").doesNotContain("--chaos");
+    }
+
     @Test
     void optionsPageHasNoBlankRows() {
         RunOptionsForm form = openForm();
@@ -115,8 +172,8 @@ class RunOptionsFormTest {
 
         List<String> inner = renderPopupRows(form);
 
-        // Name .. JFR: one row each, nothing left empty below the last 
checkbox
-        assertThat(inner).hasSize(14 + 
(form.portWarning(MonotonicClock.millis()) != null ? 1 : 0));
+        // Name .. Chaos: one row each, nothing left empty below the last 
checkbox
+        assertThat(inner).hasSize(15 + 
(form.portWarning(MonotonicClock.millis()) != null ? 1 : 0));
         assertThat(inner).noneMatch(String::isBlank);
     }
 

Reply via email to