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
