From 941409dcb92303dda36ac33990984ac598ea736d Mon Sep 17 00:00:00 2001 From: Timon Borter Date: Tue, 29 Oct 2024 16:03:44 +0100 Subject: [PATCH] fix: add timeout to iterating action containers --- .../AbstractIteratingContainerBuilder.java | 52 +++--- .../AbstractIteratingActionContainer.java | 167 ++++++++++++------ .../container/RepeatOnErrorUntilTrue.java | 66 ++++--- .../AbstractIteratingActionContainerTest.java | 134 ++++++++++++++ .../container/RepeatOnErrorUntilTrueTest.java | 91 +++++++--- ...ractIteratingTestContainerFactoryBean.java | 10 ++ .../AbstractIterationTestActionParser.java | 22 ++- .../xml/RepeatOnErrorUntilTrueParser.java | 2 +- .../schema/citrus-testcase.xsd | 7 +- .../config/xml/IterateParserTest.java | 42 +++-- .../xml/RepeatOnErrorUntilTrueParserTest.java | 53 +++--- .../config/xml/RepeatUntilTrueParserTest.java | 36 ++-- .../config/xml/IterateParserTest-context.xml | 29 +-- ...peatOnErrorUntilTrueParserTest-context.xml | 33 ++-- .../xml/RepeatUntilTrueParserTest-context.xml | 29 +-- .../KafkaEndpointIT_selectiveMessage.xml | 2 +- .../KafkaEndpointIT_singleMessage.xml | 2 +- 17 files changed, 547 insertions(+), 230 deletions(-) create mode 100644 core/citrus-base/src/test/java/org/citrusframework/container/AbstractIteratingActionContainerTest.java diff --git a/core/citrus-base/src/main/java/org/citrusframework/AbstractIteratingContainerBuilder.java b/core/citrus-base/src/main/java/org/citrusframework/AbstractIteratingContainerBuilder.java index 1e8e468eba..1ad3be01b2 100644 --- a/core/citrus-base/src/main/java/org/citrusframework/AbstractIteratingContainerBuilder.java +++ b/core/citrus-base/src/main/java/org/citrusframework/AbstractIteratingContainerBuilder.java @@ -19,18 +19,19 @@ import org.citrusframework.container.AbstractIteratingActionContainer; import org.citrusframework.container.IteratingConditionExpression; +import java.time.Duration; + public abstract class AbstractIteratingContainerBuilder> extends AbstractTestContainerBuilder { protected String condition; protected IteratingConditionExpression conditionExpression; protected String indexName = "i"; + protected Duration timeout; protected int index; protected int start = 1; /** * Adds a condition to this iterate container. - * @param condition - * @return */ public S condition(String condition) { this.condition = condition; @@ -39,8 +40,6 @@ public S condition(String condition) { /** * Adds a condition expression to this iterate container. - * @param condition - * @return */ public S condition(IteratingConditionExpression condition) { this.conditionExpression = condition; @@ -49,35 +48,26 @@ public S condition(IteratingConditionExpression condition) { /** * Sets the index variable name. - * @param name - * @return */ public S index(String name) { this.indexName = name; return self; } + public S timeout(Duration timeout) { + this.timeout = timeout; + return self; + } + /** * Sets the index start value. - * @param index - * @return */ public S startsWith(int index) { this.start = index; return self; } - @Override - public T build() { - if (condition == null && conditionExpression == null) { - conditionExpression = (index, context) -> index > 10; - } - - return super.build(); - } - /** - * Gets the condition. * @return the condition */ public String getCondition() { @@ -85,23 +75,27 @@ public String getCondition() { } /** - * Gets the condition. - * @return the conditionExpression + * @return the condition expression */ public IteratingConditionExpression getConditionExpression() { return conditionExpression; } /** - * Gets the indexName. - * @return the indexName + * @return the index name */ public String getIndexName() { return indexName; } /** - * Gets the index. + * @return the timeout duration + */ + public Duration getTimeout() { + return timeout; + } + + /** * @return the index */ public int getIndex() { @@ -109,10 +103,18 @@ public int getIndex() { } /** - * Gets the start index. - * @return + * @return the start index */ public int getStart() { return start; } + + @Override + public T build() { + if (condition == null && conditionExpression == null) { + conditionExpression = (index, context) -> index > 10; + } + + return super.build(); + } } diff --git a/core/citrus-base/src/main/java/org/citrusframework/container/AbstractIteratingActionContainer.java b/core/citrus-base/src/main/java/org/citrusframework/container/AbstractIteratingActionContainer.java index d3d2cb8e81..0cdacbe8b3 100644 --- a/core/citrus-base/src/main/java/org/citrusframework/container/AbstractIteratingActionContainer.java +++ b/core/citrus-base/src/main/java/org/citrusframework/container/AbstractIteratingActionContainer.java @@ -20,63 +20,161 @@ import org.citrusframework.TestActionBuilder; import org.citrusframework.context.TestContext; import org.citrusframework.context.TestContextFactory; +import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.exceptions.ValidationException; import org.citrusframework.util.BooleanExpressionParser; import org.citrusframework.validation.matcher.ValidationMatcherUtils; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static java.lang.Thread.currentThread; +import static java.util.Objects.nonNull; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + public abstract class AbstractIteratingActionContainer extends AbstractActionContainer { - /** Boolean expression string */ + + /** + * Boolean expression string + */ protected final String condition; - /** Optional condition expression evaluates to true or false */ + /** + * Optional condition expression evaluates to true or false + */ protected final IteratingConditionExpression conditionExpression; - /** Name of index variable */ + /** + * Looping index + */ + protected int index; + + /** + * Name of index variable + */ protected final String indexName; - /** Cache start index for further container executions - e.g. in loop */ + /** + * Cache start index for further container executions - e.g. in loop + */ protected final int start; - /** Looping index */ - protected int index; + /** + * The maximum duration this iteration can take until it reaches a timeout. + */ + private final Duration timeout; public AbstractIteratingActionContainer(String name, AbstractIteratingContainerBuilder builder) { super(name, builder); this.condition = builder.getCondition(); this.conditionExpression = builder.getConditionExpression(); - this.indexName = builder.getIndexName(); this.index = builder.getIndex(); + this.indexName = builder.getIndexName(); this.start = builder.getStart(); + this.timeout = builder.getTimeout(); } @Override public final void doExecute(TestContext context) { index = start; - executeIteration(context); + + if (nonNull(timeout) && timeout.toMillis() > 0) { + executeIterationWithTimeout(context); + } else { + executeIteration(context); + } + } + + private void executeIterationWithTimeout(TestContext context) { + var executor = newSingleThreadExecutor(); + + try { + var future = executor.submit(() -> executeIteration(context)); + future.get(timeout.toMillis(), MILLISECONDS); + } catch (ExecutionException | TimeoutException e) { + throw new CitrusRuntimeException("Iteration reached timeout!", e); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw new RuntimeException(e); + } finally { + executor.shutdown(); + } + } + + @Override + public boolean isDone(TestContext context) { + return super.isDone(context) || !checkCondition(context); + } + + /** + * @return the condition + */ + public String getCondition() { + return condition; + } + + /** + * @return the condition expression + */ + public IteratingConditionExpression getConditionExpression() { + return conditionExpression; + } + + /** + * @return the index + */ + public int getIndex() { + return index; + } + + /** + * @return the index name + */ + public String getIndexName() { + return indexName; + } + + /** + * @return the start index + */ + public int getStart() { + return start; + } + + /** + * The maximum duration this iteration can take until it reaches a timeout. + */ + public Duration getTimeout() { + return timeout; } /** * Execute embedded actions in loop. - * @param context TestContext holding variable information. + * + * @param context Test context holding variable information. */ protected abstract void executeIteration(TestContext context); /** * Executes the nested test actions. - * @param context + * + * @param context Test context holding variable information. */ protected void executeActions(TestContext context) { context.setVariable(indexName, String.valueOf(index)); - for (TestActionBuilder actionBuilder: actions) { + for (TestActionBuilder actionBuilder : actions) { executeAction(actionBuilder.build(), context); } } /** * Check aborting condition. - * @return + * + * @return whether the conditioning has been satisfied. */ protected boolean checkCondition(TestContext context) { if (conditionExpression != null) { @@ -104,49 +202,4 @@ protected boolean checkCondition(TestContext context) { return BooleanExpressionParser.evaluate(conditionString); } - - @Override - public boolean isDone(TestContext context) { - return super.isDone(context) || !checkCondition(context); - } - - /** - * Gets the condition. - * @return the condition - */ - public String getCondition() { - return condition; - } - - /** - * Gets the condition. - * @return the conditionExpression - */ - public IteratingConditionExpression getConditionExpression() { - return conditionExpression; - } - - /** - * Gets the indexName. - * @return the indexName - */ - public String getIndexName() { - return indexName; - } - - /** - * Gets the index. - * @return the index - */ - public int getIndex() { - return index; - } - - /** - * Gets the start index. - * @return - */ - public int getStart() { - return start; - } } diff --git a/core/citrus-base/src/main/java/org/citrusframework/container/RepeatOnErrorUntilTrue.java b/core/citrus-base/src/main/java/org/citrusframework/container/RepeatOnErrorUntilTrue.java index 1f9a34e065..dea167e97f 100644 --- a/core/citrus-base/src/main/java/org/citrusframework/container/RepeatOnErrorUntilTrue.java +++ b/core/citrus-base/src/main/java/org/citrusframework/container/RepeatOnErrorUntilTrue.java @@ -16,29 +16,34 @@ package org.citrusframework.container; +import jakarta.annotation.Nullable; +import org.apache.commons.lang3.time.StopWatch; import org.citrusframework.AbstractIteratingContainerBuilder; import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.ActionTimeoutException; import org.citrusframework.exceptions.CitrusRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.Duration; + +import static java.util.Objects.nonNull; + /** * Looping test container iterating the nested test actions in case an error occurred in one * of them. Iteration continues until a aborting condition evaluates to true. - * + *

* Number of iterations is kept in a index variable. The nested test actions can access this variable * as normal test variable. - * + *

* Between the iterations container can sleep automatically a given amount of time. - * */ public class RepeatOnErrorUntilTrue extends AbstractIteratingActionContainer { - /** Auto sleep in milliseconds */ - private final Long autoSleep; - /** Logger */ private static final Logger logger = LoggerFactory.getLogger(RepeatOnErrorUntilTrue.class); + private final Duration autoSleep; + /** * Default constructor. */ @@ -52,7 +57,7 @@ public RepeatOnErrorUntilTrue(Builder builder) { public void executeIteration(TestContext context) { CitrusRuntimeException exception = null; - while(!checkCondition(context)) { + while (!checkCondition(context)) { try { exception = null; executeActions(context); @@ -60,8 +65,7 @@ public void executeIteration(TestContext context) { } catch (CitrusRuntimeException e) { exception = e; - logger.info("Caught exception of type " + e.getClass().getName() + " '" + - e.getMessage() + "' - performing retry #" + index); + logger.info("Caught exception of type {} '{}' - performing retry #{}", e.getClass().getName(), e.getMessage(), index); doAutoSleep(); index++; @@ -69,7 +73,7 @@ public void executeIteration(TestContext context) { } if (exception != null) { - logger.info("All retries failed - raising exception " + exception.getClass().getName()); + logger.info("All retries failed - raising exception {}", exception.getClass().getName()); throw exception; } } @@ -78,24 +82,35 @@ public void executeIteration(TestContext context) { * Sleep amount of time in between iterations. */ private void doAutoSleep() { - if (autoSleep > 0) { - logger.info("Sleeping " + autoSleep + " milliseconds"); + if (autoSleep.toMillis() > 0) { + logger.info("Sleeping {}", autoSleep); try { - Thread.sleep(autoSleep); + Thread.sleep(autoSleep.toMillis()); } catch (InterruptedException e) { logger.error("Error during doc generation", e); + Thread.currentThread().interrupt(); } - logger.info("Returning after " + autoSleep + " milliseconds"); + logger.info("Returning after {}", autoSleep); } } /** - * Gets the autoSleep. + * Gets the duration this action sleeps in milliseconds. + * * @return the autoSleep + * @deprecated use {@link RepeatOnErrorUntilTrue#getAutoSleepDuration()} instead */ + @Deprecated(forRemoval = true) public Long getAutoSleep() { + return autoSleep.toMillis(); + } + + /** + * @return the duration this action sleeps in between retries + */ + public Duration getAutoSleepDuration() { return autoSleep; } @@ -104,11 +119,10 @@ public Long getAutoSleep() { */ public static class Builder extends AbstractIteratingContainerBuilder { - private Long autoSleep = 1000L; + private Duration autoSleep = Duration.ofMillis(1_000L); /** * Fluent API action building entry method used in Java DSL. - * @return */ public static Builder repeatOnError() { return new Builder(); @@ -116,8 +130,6 @@ public static Builder repeatOnError() { /** * Adds a condition to this iterate container. - * @param condition - * @return */ public Builder until(String condition) { condition(condition); @@ -126,8 +138,6 @@ public Builder until(String condition) { /** * Adds a condition expression to this iterate container. - * @param condition - * @return */ public Builder until(IteratingConditionExpression condition) { condition(condition); @@ -136,11 +146,19 @@ public Builder until(IteratingConditionExpression condition) { /** * Sets the auto sleep time in between repeats in milliseconds. - * @param autoSleepInMillis - * @return + * + * @deprecated use {@link Builder#autoSleep(Duration)} instead */ public Builder autoSleep(long autoSleepInMillis) { - this.autoSleep = autoSleepInMillis; + this.autoSleep = Duration.ofMillis(autoSleepInMillis); + return this; + } + + /** + * Sets the sleep interval between retries of this action. + */ + public Builder autoSleep(Duration autoSleep) { + this.autoSleep = autoSleep; return this; } diff --git a/core/citrus-base/src/test/java/org/citrusframework/container/AbstractIteratingActionContainerTest.java b/core/citrus-base/src/test/java/org/citrusframework/container/AbstractIteratingActionContainerTest.java new file mode 100644 index 0000000000..54688468fa --- /dev/null +++ b/core/citrus-base/src/test/java/org/citrusframework/container/AbstractIteratingActionContainerTest.java @@ -0,0 +1,134 @@ +package org.citrusframework.container; + +import static java.lang.Thread.currentThread; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.citrusframework.container.AbstractIteratingActionContainerTest.Fixture.FixtureBuilder.fixture; +import static org.mockito.MockitoAnnotations.openMocks; + +import java.time.Duration; +import org.apache.commons.lang3.time.StopWatch; +import org.citrusframework.AbstractIteratingContainerBuilder; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.mockito.Mock; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class AbstractIteratingActionContainerTest { + + public static final Duration ONE_SECOND = Duration.ofSeconds(1); + + @Mock + private TestContext testContext; + + private AutoCloseable mockitoContext; + + private static StopWatch getStartedStopWatch() { + var stopWatch = new StopWatch(); + stopWatch.start(); + return stopWatch; + } + + @DataProvider + public static Fixture[] synchronousIterations() { + return new Fixture[]{ + fixture(ONE_SECOND, null).doBuild(), + fixture(ONE_SECOND, Duration.ofMillis(0)).doBuild(), + fixture(ONE_SECOND, Duration.ofMillis(-1)).doBuild() + }; + } + + @BeforeMethod + public void beforeMethodSetup() { + mockitoContext = openMocks(this); + } + + @AfterMethod + public void afterMethodTeardown() throws Exception { + mockitoContext.close(); + } + + @Test(dataProvider = "synchronousIterations") + public void synchronousIteration(Fixture iteratingActionContainer) { + var stopWatch = getStartedStopWatch(); + + iteratingActionContainer.doExecute(testContext); + + assertThat(stopWatch.getDuration()) + .isGreaterThan(ONE_SECOND); + } + + @Test + public void asynchronousIteration_doesExecuteWithTimeout() { + var timeout = Duration.ofSeconds(2); + var iteratingActionContainer = fixture(ONE_SECOND, timeout).doBuild(); + + var stopWatch = getStartedStopWatch(); + + iteratingActionContainer.doExecute(testContext); + + assertThat(stopWatch.getDuration()) + .isGreaterThan(ONE_SECOND) + .isLessThan(timeout); + } + + @Test + public void asynchronousIteration_throwsTimeoutException_whenSleepTimeGreaterThanTimeout() { + var sleepTime = Duration.ofSeconds(2); + var iteratingActionContainer = fixture(sleepTime, ONE_SECOND).doBuild(); + + var stopWatch = getStartedStopWatch(); + + assertThatThrownBy(() -> iteratingActionContainer.doExecute(testContext)) + .isInstanceOf(CitrusRuntimeException.class) + .hasMessage("Iteration reached timeout!"); + + assertThat(stopWatch.getDuration()) + .isGreaterThan(ONE_SECOND) + .isLessThan(sleepTime); + } + + public static class Fixture extends AbstractIteratingActionContainer { + + private final Duration sleepTime; + + private Fixture(Duration sleepTime, FixtureBuilder builder) { + super("AbstractIteratingActionContainerTest", builder); + this.sleepTime = sleepTime; + } + + @Override + protected void executeIteration(TestContext context) { + try { + Thread.sleep(sleepTime.toMillis()); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw new IllegalArgumentException(e); + } + } + + static class FixtureBuilder extends + AbstractIteratingContainerBuilder { + + private final Duration sleepTime; + + public FixtureBuilder(Duration sleepTime) { + super(); + this.sleepTime = sleepTime; + } + + static FixtureBuilder fixture(Duration sleepTime, Duration timeout) { + return new FixtureBuilder(sleepTime) + .timeout(timeout); + } + + @Override + protected Fixture doBuild() { + return new Fixture(sleepTime, this); + } + } + } +} diff --git a/core/citrus-base/src/test/java/org/citrusframework/container/RepeatOnErrorUntilTrueTest.java b/core/citrus-base/src/test/java/org/citrusframework/container/RepeatOnErrorUntilTrueTest.java index 83ac658eda..9cb6d80336 100644 --- a/core/citrus-base/src/test/java/org/citrusframework/container/RepeatOnErrorUntilTrueTest.java +++ b/core/citrus-base/src/test/java/org/citrusframework/container/RepeatOnErrorUntilTrueTest.java @@ -20,64 +20,113 @@ import org.citrusframework.UnitTestSupport; import org.citrusframework.actions.FailAction; import org.citrusframework.exceptions.CitrusRuntimeException; -import org.mockito.Mockito; +import org.mockito.Mock; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.mockito.Mockito.reset; +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.openMocks; public class RepeatOnErrorUntilTrueTest extends UnitTestSupport { - private TestAction action = Mockito.mock(TestAction.class); + @Mock + private TestAction action; + + private AutoCloseable openedMocks; + + @BeforeMethod + public void beforeMethodSetup() { + openedMocks = openMocks(this); + } + + @AfterMethod + public void afterMethodTearDown() throws Exception { + openedMocks.close(); + } + + @Test + public void buildWithLongApi() { + var autoSleepMillis = 5L; + + RepeatOnErrorUntilTrue repeat = new RepeatOnErrorUntilTrue.Builder() + .autoSleep(autoSleepMillis) + .build(); + + assertThat(repeat) + .satisfies( + r -> assertThat(r.getAutoSleep()).isEqualTo(autoSleepMillis), + r -> assertThat(r.getAutoSleepDuration()).isEqualTo(Duration.ofMillis(autoSleepMillis)) + ); + } + + @Test + public void buildWithDurationApi() { + var autoSleepMillis = 120_000L; + var autoSleepDuration = Duration.ofMinutes(2); + + RepeatOnErrorUntilTrue repeat = new RepeatOnErrorUntilTrue.Builder() + .autoSleep(autoSleepDuration) + .build(); + + assertThat(repeat) + .satisfies( + r -> assertThat(r.getAutoSleep()).isEqualTo(autoSleepMillis), + r -> assertThat(r.getAutoSleepDuration()).isEqualTo(autoSleepDuration) + ); + } + + @DataProvider + public Object[][] expressionProvider() { + return new Object[][]{ + new Object[]{"i = 5"}, + new Object[]{"@greaterThan(4)@"} + }; + } @Test(dataProvider = "expressionProvider") public void testSuccessOnFirstIteration(String expression) { - reset(action); - RepeatOnErrorUntilTrue repeat = new RepeatOnErrorUntilTrue.Builder() .condition(expression) .index("i") .actions(() -> action) .build(); + repeat.execute(context); - verify(action).execute(context); - } - @DataProvider - public Object[][] expressionProvider() { - return new Object[][] { - new Object[] {"i = 5"}, - new Object[] {"@greaterThan(4)@"} - }; + verify(action).execute(context); } - @Test(expectedExceptions=CitrusRuntimeException.class) + @Test(expectedExceptions = CitrusRuntimeException.class) public void testRepeatOnErrorNoSuccess() { - reset(action); - RepeatOnErrorUntilTrue repeat = new RepeatOnErrorUntilTrue.Builder() .condition("i = 5") .index("i") - .autoSleep(0L) + .autoSleep(Duration.ofMillis(0)) .actions(() -> action, new FailAction.Builder()) .build(); + repeat.execute(context); + verify(action, times(4)).execute(context); } - @Test(expectedExceptions=CitrusRuntimeException.class) + @Test(expectedExceptions = CitrusRuntimeException.class) public void testRepeatOnErrorNoSuccessConditionExpression() { - reset(action); - RepeatOnErrorUntilTrue repeat = new RepeatOnErrorUntilTrue.Builder() .condition((index, context) -> index == 5) .index("i") - .autoSleep(0L) + .autoSleep(Duration.ofMillis(0)) .actions(() -> action, new FailAction.Builder()) .build(); + repeat.execute(context); + verify(action, times(4)).execute(context); } } diff --git a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIteratingTestContainerFactoryBean.java b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIteratingTestContainerFactoryBean.java index e7fc201ffe..a8d071702d 100644 --- a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIteratingTestContainerFactoryBean.java +++ b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIteratingTestContainerFactoryBean.java @@ -20,6 +20,8 @@ import org.citrusframework.container.AbstractIteratingActionContainer; import org.citrusframework.container.IteratingConditionExpression; +import java.time.Duration; + public abstract class AbstractIteratingTestContainerFactoryBean> extends AbstractTestContainerFactoryBean { /** @@ -53,4 +55,12 @@ public void setIndexName(String indexName) { public void setStart(int start) { getBuilder().startsWith(start); } + + /** + * Setter fot timeout duration. + * @param timeout the maximum duration this action will take before ending in a timeout + */ + public void setTimeout(Duration timeout) { + getBuilder().timeout(timeout); + } } diff --git a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIterationTestActionParser.java b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIterationTestActionParser.java index f0be3b9eb6..42726d5361 100644 --- a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIterationTestActionParser.java +++ b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/AbstractIterationTestActionParser.java @@ -22,23 +22,29 @@ import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; -import org.citrusframework.config.util.BeanDefinitionParserUtils; +import java.time.Duration; + +import static org.citrusframework.config.util.BeanDefinitionParserUtils.setPropertyValue; +import static org.citrusframework.util.StringUtils.hasText; /** - * Abstract parser implementation for all iterative container actions. Parser takes care of - * index name, aborting condition, index start value and description - * + * Abstract parser implementation for all iterative container actions. Parser takes care of index name, aborting + * condition, index start value and description */ -public abstract class AbstractIterationTestActionParser implements BeanDefinitionParser { +public abstract class AbstractIterationTestActionParser implements BeanDefinitionParser { @Override public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = parseComponent(element, parserContext); - DescriptionElementParser.doParse(element, builder); + MessageSelectorParser.doParse(element, builder); + + setPropertyValue(builder, element.getAttribute("condition"), "condition"); + setPropertyValue(builder, element.getAttribute("index"), "indexName"); - BeanDefinitionParserUtils.setPropertyValue(builder, element.getAttribute("index"), "indexName"); - BeanDefinitionParserUtils.setPropertyValue(builder, element.getAttribute("condition"), "condition"); + if (hasText(element.getAttribute("timeout"))) { + builder.addPropertyValue("timeout", Duration.parse(element.getAttribute("timeout"))); + } builder.addPropertyValue("name", element.getLocalName()); diff --git a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParser.java b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParser.java index 24adf579db..6ed7cd790e 100644 --- a/core/citrus-spring/src/main/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParser.java +++ b/core/citrus-spring/src/main/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParser.java @@ -46,7 +46,7 @@ public static class RepeatOnErrorUntilTrueFactoryBean extends AbstractIteratingT /** * Setter for auto sleep time (in milliseconds). - * @param autoSleep + * @param autoSleep the auto sleep time in between repeats in milliseconds */ public void setAutoSleep(Long autoSleep) { this.builder.autoSleep(autoSleep); diff --git a/core/citrus-spring/src/main/resources/org/citrusframework/schema/citrus-testcase.xsd b/core/citrus-spring/src/main/resources/org/citrusframework/schema/citrus-testcase.xsd index 73568ce943..6dce553562 100644 --- a/core/citrus-spring/src/main/resources/org/citrusframework/schema/citrus-testcase.xsd +++ b/core/citrus-spring/src/main/resources/org/citrusframework/schema/citrus-testcase.xsd @@ -649,6 +649,7 @@ + @@ -658,6 +659,7 @@ + @@ -665,9 +667,10 @@ - - + + + diff --git a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/IterateParserTest.java b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/IterateParserTest.java index 02d8f2a780..9aefd2dcd0 100644 --- a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/IterateParserTest.java +++ b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/IterateParserTest.java @@ -16,11 +16,14 @@ package org.citrusframework.config.xml; -import org.testng.Assert; -import org.testng.annotations.Test; - import org.citrusframework.container.Iterate; import org.citrusframework.testng.AbstractActionParserTest; +import org.testng.annotations.Test; + +import java.time.Duration; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; public class IterateParserTest extends AbstractActionParserTest { @@ -30,24 +33,27 @@ public void testActionParser() { assertActionClassAndName(Iterate.class, "iterate"); Iterate action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i lt 3"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getStep(), 1); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "i lt 3"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getStep(), 1); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "index lt= 2"); - Assert.assertEquals(action.getIndexName(), "index"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getStep(), 1); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "index lt= 2"); + assertEquals(action.getIndexName(), "index"); + assertEquals(action.getStart(), 1); + assertEquals(action.getStep(), 1); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i lt= 10"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 0); - Assert.assertEquals(action.getStep(), 5); - Assert.assertEquals(action.getActionCount(), 2); + assertEquals(action.getCondition(), "i lt= 10"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 0); + assertEquals(action.getStep(), 5); + assertEquals(action.getActionCount(), 2); + assertEquals(action.getTimeout(), Duration.ofSeconds(3)); } } diff --git a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest.java b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest.java index f414f9b97c..3b1aff8886 100644 --- a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest.java +++ b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest.java @@ -16,11 +16,14 @@ package org.citrusframework.config.xml; -import org.testng.Assert; -import org.testng.annotations.Test; - import org.citrusframework.container.RepeatOnErrorUntilTrue; import org.citrusframework.testng.AbstractActionParserTest; +import org.testng.annotations.Test; + +import java.time.Duration; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; public class RepeatOnErrorUntilTrueParserTest extends AbstractActionParserTest { @@ -30,31 +33,35 @@ public void testRepeatOnErrorParser() { assertActionClassAndName(RepeatOnErrorUntilTrue.class, "repeat-onerror-until-true"); RepeatOnErrorUntilTrue action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i gt 3"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getAutoSleep(), Long.valueOf(1000L)); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "i gt 3"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getAutoSleep(), Long.valueOf(1000L)); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "index gt= 2"); - Assert.assertEquals(action.getIndexName(), "index"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getAutoSleep(), Long.valueOf(1000L)); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "index gt= 2"); + assertEquals(action.getIndexName(), "index"); + assertEquals(action.getStart(), 1); + assertEquals(action.getAutoSleep(), Long.valueOf(1000L)); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i gt= 10"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getAutoSleep(), Long.valueOf(500L)); - Assert.assertEquals(action.getActionCount(), 2); + assertEquals(action.getCondition(), "i gt= 10"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getAutoSleep(), Long.valueOf(500L)); + assertEquals(action.getActionCount(), 2); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i gt= 5"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getAutoSleep(), Long.valueOf(250L)); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "i gt= 5"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getAutoSleep(), Long.valueOf(250L)); + assertEquals(action.getActionCount(), 1); + assertEquals(action.getTimeout(), Duration.ofSeconds(1)); } } diff --git a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatUntilTrueParserTest.java b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatUntilTrueParserTest.java index 78d3873f65..60f4db7167 100644 --- a/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatUntilTrueParserTest.java +++ b/core/citrus-spring/src/test/java/org/citrusframework/config/xml/RepeatUntilTrueParserTest.java @@ -16,11 +16,14 @@ package org.citrusframework.config.xml; -import org.testng.Assert; -import org.testng.annotations.Test; - import org.citrusframework.container.RepeatUntilTrue; import org.citrusframework.testng.AbstractActionParserTest; +import org.testng.annotations.Test; + +import java.time.Duration; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; public class RepeatUntilTrueParserTest extends AbstractActionParserTest { @@ -30,21 +33,24 @@ public void testActionParser() { assertActionClassAndName(RepeatUntilTrue.class, "repeat-until-true"); RepeatUntilTrue action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i lt 3"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "i lt 3"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "index lt= 2"); - Assert.assertEquals(action.getIndexName(), "index"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getActionCount(), 1); + assertEquals(action.getCondition(), "index lt= 2"); + assertEquals(action.getIndexName(), "index"); + assertEquals(action.getStart(), 1); + assertEquals(action.getActionCount(), 1); + assertNull(action.getTimeout()); action = getNextTestActionFromTest(); - Assert.assertEquals(action.getCondition(), "i lt= 10"); - Assert.assertEquals(action.getIndexName(), "i"); - Assert.assertEquals(action.getStart(), 1); - Assert.assertEquals(action.getActionCount(), 2); + assertEquals(action.getCondition(), "i lt= 10"); + assertEquals(action.getIndexName(), "i"); + assertEquals(action.getStart(), 1); + assertEquals(action.getActionCount(), 2); + assertEquals(action.getTimeout(), Duration.ofSeconds(2)); } } diff --git a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/IterateParserTest-context.xml b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/IterateParserTest-context.xml index 48109236f4..80b5588d16 100644 --- a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/IterateParserTest-context.xml +++ b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/IterateParserTest-context.xml @@ -1,24 +1,31 @@ - - Hello Citrus! + + Hello Citrus! + - + - Hello Citrus! + + Hello Citrus! + - - - Hello Citrus! - Hello You! + + + + Hello Citrus! + + + Hello You! + - diff --git a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest-context.xml b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest-context.xml index 62f8fdf3b8..3182560874 100644 --- a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest-context.xml +++ b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatOnErrorUntilTrueParserTest-context.xml @@ -1,28 +1,37 @@ - - Hello Citrus! + + Hello Citrus! + - + - Hello Citrus! + + Hello Citrus! + - + - Hello Citrus! - Hello You! + + Hello Citrus! + + + Hello You! + - - Hello Citrus! + + + Hello Citrus! + - diff --git a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatUntilTrueParserTest-context.xml b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatUntilTrueParserTest-context.xml index 5623f47bd2..6cb8ec61c4 100644 --- a/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatUntilTrueParserTest-context.xml +++ b/core/citrus-spring/src/test/resources/org/citrusframework/config/xml/RepeatUntilTrueParserTest-context.xml @@ -1,24 +1,31 @@ - - Hello Citrus! + + Hello Citrus! + - + - Hello Citrus! + + Hello Citrus! + - - - Hello Citrus! - Hello You! + + + + Hello Citrus! + + + Hello You! + - diff --git a/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_selectiveMessage.xml b/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_selectiveMessage.xml index 185e22727c..222ab71015 100644 --- a/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_selectiveMessage.xml +++ b/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_selectiveMessage.xml @@ -59,7 +59,7 @@ - + Receive Kafka request: Kafka broker -> Citrus diff --git a/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_singleMessage.xml b/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_singleMessage.xml index c7d5dd748c..5e09b2ec70 100644 --- a/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_singleMessage.xml +++ b/endpoints/citrus-kafka/src/test/resources/org/citrusframework/kafka/integration/KafkaEndpointIT_singleMessage.xml @@ -57,7 +57,7 @@ - + Receive Kafka request: Kafka broker -> Citrus