activemq-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cshan...@apache.org
Subject [1/3] activemq git commit: https://issues.apache.org/jira/browse/AMQ-6305
Date Tue, 05 Jul 2016 19:55:41 GMT
Repository: activemq
Updated Branches:
  refs/heads/activemq-5.13.x fda982dcc -> 53c9d7ecb


https://issues.apache.org/jira/browse/AMQ-6305

Add test and supporting enhancements to the JUnit module to allow for
easy repetition of tests that don't always fail reliably
(cherry picked from commit 6ae169e2755257b1e8e5068473bdb3156160790d)


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/508c12d9
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/508c12d9
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/508c12d9

Branch: refs/heads/activemq-5.13.x
Commit: 508c12d9487ad653f05806be7a492e4bd870522b
Parents: fda982d
Author: Timothy Bish <tabish121@gmail.com>
Authored: Thu May 26 18:02:59 2016 -0400
Committer: Christopher L. Shannon (cshannon) <christopher.l.shannon@gmail.com>
Committed: Tue Jul 5 19:54:54 2016 +0000

----------------------------------------------------------------------
 activemq-amqp/pom.xml                           |   5 +
 .../transport/amqp/protocol/AmqpSender.java     |  24 ++-
 .../transport/amqp/JMSClientContext.java        |   2 +-
 .../transport/amqp/JMSQueueBrowserTest.java     |  85 +++++++++++
 .../src/test/resources/log4j.properties         |  10 +-
 .../activemq/junit/ActiveMQTestRunner.java      | 116 +++++++++++++++
 .../java/org/apache/activemq/junit/Repeat.java  |  34 +++++
 .../org/apache/activemq/junit/RepeatRule.java   |  38 +++++
 .../apache/activemq/junit/RepeatStatement.java  | 147 +++++++++++++++++++
 9 files changed, 455 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-amqp/pom.xml
----------------------------------------------------------------------
diff --git a/activemq-amqp/pom.xml b/activemq-amqp/pom.xml
index 0e41e58..1ee7277 100644
--- a/activemq-amqp/pom.xml
+++ b/activemq-amqp/pom.xml
@@ -104,6 +104,11 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.activemq.tooling</groupId>
+      <artifactId>activemq-junit</artifactId>
+      <scope>test</scope>
+     </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/protocol/AmqpSender.java
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/protocol/AmqpSender.java
b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/protocol/AmqpSender.java
index e0e7276..8cf7033 100644
--- a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/protocol/AmqpSender.java
+++ b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/protocol/AmqpSender.java
@@ -154,8 +154,11 @@ public class AmqpSender extends AmqpAbstractLink<Sender> {
     public void flow() throws Exception {
         int updatedCredit = getEndpoint().getCredit();
 
-        LOG.trace("Flow: drain={} credit={}, remoteCredit={}",
-                  getEndpoint().getDrain(), getEndpoint().getCredit(), getEndpoint().getRemoteCredit());
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("Flow: currentCredit={}, draining={}, drain={} credit={}, remoteCredit={},
queued={}",
+                      currentCredit, draining, getEndpoint().getDrain(),
+                      getEndpoint().getCredit(), getEndpoint().getRemoteCredit(), getEndpoint().getQueued());
+        }
 
         if (getEndpoint().getDrain() && (updatedCredit != currentCredit || !draining))
{
             currentCredit = updatedCredit >= 0 ? updatedCredit : 0;
@@ -166,6 +169,9 @@ public class AmqpSender extends AmqpAbstractLink<Sender> {
             control.setConsumerId(getConsumerId());
             control.setDestination(getDestination());
             control.setPrefetch(0);
+
+            LOG.trace("Flow: Pull case -> consumer control with prefetch (0)");
+
             sendToActiveMQ(control, null);
 
             // Now request dispatch of the drain amount, we request immediate
@@ -177,6 +183,9 @@ public class AmqpSender extends AmqpAbstractLink<Sender> {
             pullRequest.setTimeout(-1);
             pullRequest.setAlwaysSignalDone(true);
             pullRequest.setQuantity(currentCredit);
+
+            LOG.trace("Pull case -> consumer pull request quantity = {}", currentCredit);
+
             sendToActiveMQ(pullRequest, null);
         } else if (updatedCredit != currentCredit) {
             currentCredit = updatedCredit >= 0 ? updatedCredit : 0;
@@ -184,7 +193,12 @@ public class AmqpSender extends AmqpAbstractLink<Sender> {
             control.setConsumerId(getConsumerId());
             control.setDestination(getDestination());
             control.setPrefetch(currentCredit);
+
+            LOG.trace("Flow: update -> consumer control with prefetch (0)");
+
             sendToActiveMQ(control, null);
+        } else {
+            LOG.trace("Flow: no credit change -> no broker updates needed");
         }
     }
 
@@ -403,6 +417,12 @@ public class AmqpSender extends AmqpAbstractLink<Sender> {
                     draining = false;
                     currentCredit = 0;
                 } else {
+                    if (LOG.isTraceEnabled()) {
+                        LOG.trace("Sender:[{}] msgId={} currentCredit={}, draining={}, drain={}
credit={}, remoteCredit={}, queued={}",
+                                  getEndpoint().getName(), jms.getJMSMessageID(), currentCredit,
draining, getEndpoint().getDrain(),
+                                  getEndpoint().getCredit(), getEndpoint().getRemoteCredit(),
getEndpoint().getQueued());
+                    }
+
                     jms.setRedeliveryCounter(md.getRedeliveryCounter());
                     jms.setReadOnlyBody(true);
                     final EncodedMessage amqp = outboundTransformer.transform(jms);

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
index 08ebbb6..574e9f0 100644
--- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
@@ -182,7 +182,7 @@ public class JMSClientContext {
 
         factory.setUsername(username);
         factory.setPassword(password);
-        factory.setAlwaysSyncSend(syncPublish);
+        factory.setForceSyncSend(syncPublish);
         factory.setTopicPrefix("topic://");
         factory.setQueuePrefix("queue://");
 

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSQueueBrowserTest.java
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSQueueBrowserTest.java
b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSQueueBrowserTest.java
new file mode 100644
index 0000000..dfcc108
--- /dev/null
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSQueueBrowserTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.transport.amqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Enumeration;
+
+import javax.jms.Message;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.Session;
+
+import org.apache.activemq.broker.jmx.QueueViewMBean;
+import org.apache.activemq.junit.ActiveMQTestRunner;
+import org.apache.activemq.junit.Repeat;
+import org.apache.qpid.jms.JmsConnectionFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests for various QueueBrowser scenarios with an AMQP JMS client.
+ */
+@RunWith(ActiveMQTestRunner.class)
+public class JMSQueueBrowserTest extends JMSClientTestSupport {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(JMSClientTest.class);
+
+    @Test(timeout = 60000)
+    @Repeat(repetitions = 1)
+    public void testBrowseAllInQueueZeroPrefetch() throws Exception {
+
+        final int MSG_COUNT = 5;
+
+        JmsConnectionFactory cf = new JmsConnectionFactory(getBrokerURI() + "?jms.prefetchPolicy.all=0");
+        connection = cf.createConnection();
+        connection.start();
+
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        assertNotNull(session);
+        Queue queue = session.createQueue(getDestinationName());
+        sendMessages(name.getMethodName(), MSG_COUNT, false);
+
+        QueueViewMBean proxy = getProxyToQueue(getDestinationName());
+        assertEquals(MSG_COUNT, proxy.getQueueSize());
+
+        QueueBrowser browser = session.createBrowser(queue);
+        assertNotNull(browser);
+        Enumeration<?> enumeration = browser.getEnumeration();
+        int count = 0;
+        while (count < MSG_COUNT && enumeration.hasMoreElements()) {
+            Message msg = (Message) enumeration.nextElement();
+            assertNotNull(msg);
+            LOG.debug("Recv: {}", msg);
+            count++;
+        }
+
+        LOG.debug("Received all expected message, checking that hasMoreElements returns false");
+        assertFalse(enumeration.hasMoreElements());
+        assertEquals(5, count);
+    }
+
+    @Override
+    protected boolean isUseOpenWireConnector() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-amqp/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/activemq-amqp/src/test/resources/log4j.properties b/activemq-amqp/src/test/resources/log4j.properties
index 4111e16..be0efab 100755
--- a/activemq-amqp/src/test/resources/log4j.properties
+++ b/activemq-amqp/src/test/resources/log4j.properties
@@ -20,21 +20,25 @@
 #
 log4j.rootLogger=WARN, console, file
 log4j.logger.org.apache.activemq=INFO
-log4j.logger.org.apache.activemq.broker.scheduler=TRACE
 log4j.logger.org.apache.activemq.transport.amqp=DEBUG
 log4j.logger.org.apache.activemq.transport.amqp.FRAMES=INFO
 log4j.logger.org.fusesource=INFO
+
+# Configure various level of detail for Qpid JMS logs.
 log4j.logger.org.apache.qpid.jms=INFO
+log4j.logger.org.apache.qpid.jms.provider=INFO
+log4j.logger.org.apache.qpid.jms.provider.amqp=INFO
+log4j.logger.org.apache.qpid.jms.provider.amqp.FRAMES=INFO
 
 # Console will only display warnnings
 log4j.appender.console=org.apache.log4j.ConsoleAppender
 log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%d | %-5p | %t | %m%n
+log4j.appender.console.layout.ConversionPattern=%d [%-15.15t] - %-5p %-25.30c{1} - %m%n
 log4j.appender.console.threshold=TRACE
 
 # File appender will contain all info messages
 log4j.appender.file=org.apache.log4j.FileAppender
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d | %-5p | %m | %c | %t%n
+log4j.appender.file.layout.ConversionPattern=%d [%-15.15t] - %-5p %-30.30c{1} - %m%n
 log4j.appender.file.file=target/test.log
 log4j.appender.file.append=true

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/ActiveMQTestRunner.java
----------------------------------------------------------------------
diff --git a/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/ActiveMQTestRunner.java
b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/ActiveMQTestRunner.java
new file mode 100644
index 0000000..ab793ff
--- /dev/null
+++ b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/ActiveMQTestRunner.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.junit;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.junit.internal.runners.statements.FailOnTimeout;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Custom JUnit test runner for customizing JUnit tests run in ActiveMQ.
+ */
+public class ActiveMQTestRunner extends BlockJUnit4ClassRunner {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQTestRunner.class);
+
+    public ActiveMQTestRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+    }
+
+    @Override
+    protected Statement methodBlock(final FrameworkMethod method) {
+        Statement statement = super.methodBlock(method);
+
+        // Check for repeats needed
+        statement = withPotentialRepeat(method, statement);
+
+        return statement;
+    }
+
+    /**
+     * Perform the same logic as
+     * {@link BlockJUnit4ClassRunner#withPotentialTimeout(FrameworkMethod, Object, Statement)}
+     * but with additional support for changing the coded timeout with an extended value.
+     *
+     * @return either a {@link FailOnTimeout}, or the supplied {@link Statement} as appropriate.
+     */
+    @SuppressWarnings("deprecation")
+    @Override
+    protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance,
Statement next) {
+        long testTimeout = getOriginalTimeout(frameworkMethod);
+
+        if (testTimeout > 0) {
+            String multiplierString = System.getProperty("org.apache.activemq.junit.testTimeoutMultiplier");
+            double multiplier = 0.0;
+
+            try {
+                multiplier = Double.parseDouble(multiplierString);
+            } catch (NullPointerException npe) {
+            } catch (NumberFormatException nfe) {
+                LOG.warn("Ignoring testTimeoutMultiplier not set to a valid value: " + multiplierString);
+            }
+
+            if (multiplier > 0.0) {
+                LOG.info("Test timeout multiple {} applied to test timeout {}ms: new timeout
= {}",
+                    multiplier, testTimeout, (long) (testTimeout * multiplier));
+                testTimeout = (long) (testTimeout * multiplier);
+            }
+
+            next = FailOnTimeout.builder().
+                withTimeout(testTimeout, TimeUnit.MILLISECONDS).build(next);
+        } else {
+            next = super.withPotentialTimeout(frameworkMethod, testInstance, next);
+        }
+
+        return next;
+    }
+
+    /**
+     * Check for the presence of a {@link Repeat} annotation and return a {@link RepeatStatement}
+     * to handle executing the test repeated or the original value if not repeating.
+     *
+     * @return either a {@link RepeatStatement}, or the supplied {@link Statement} as appropriate.
+     */
+    protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Statement next)
{
+
+        Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class);
+
+        if (repeatAnnotation != null) {
+            next = RepeatStatement.builder().build(repeatAnnotation, next);
+        }
+
+        return next;
+    }
+
+    /**
+     * Retrieve the original JUnit {@code timeout} from the {@link Test @Test}
+     * annotation on the incoming {@linkplain FrameworkMethod test method}.
+     *
+     * @return the timeout, or {@code 0} if none was specified
+     */
+    protected long getOriginalTimeout(FrameworkMethod frameworkMethod) {
+        Test test = frameworkMethod.getAnnotation(Test.class);
+        return (test != null && test.timeout() > 0 ? test.timeout() : 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/Repeat.java
----------------------------------------------------------------------
diff --git a/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/Repeat.java
b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/Repeat.java
new file mode 100644
index 0000000..e702071
--- /dev/null
+++ b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/Repeat.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.junit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A Custom Test annotation used to repeat a troublesome test multiple
+ * times when attempting to reproduce an intermittent failure.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ java.lang.annotation.ElementType.METHOD })
+public @interface Repeat {
+
+    int repetitions() default 1;
+
+    boolean untilFailure() default false;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatRule.java
----------------------------------------------------------------------
diff --git a/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatRule.java
b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatRule.java
new file mode 100644
index 0000000..6c897f7
--- /dev/null
+++ b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatRule.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.junit;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Test rule used to allow a test to have the Repeat annotation applied.
+ */
+public class RepeatRule implements TestRule {
+
+    @Override
+    public Statement apply(Statement statement, Description description) {
+        Repeat repeat = description.getAnnotation(Repeat.class);
+
+        if (repeat != null) {
+            statement = RepeatStatement.builder().build(repeat, statement);
+        }
+
+        return statement;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/508c12d9/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatStatement.java
----------------------------------------------------------------------
diff --git a/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatStatement.java
b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatStatement.java
new file mode 100644
index 0000000..e7a414a
--- /dev/null
+++ b/activemq-tooling/activemq-junit/src/main/java/org/apache/activemq/junit/RepeatStatement.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.junit;
+
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class RepeatStatement extends Statement {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RepeatStatement.class);
+
+    private final int repetitions;
+    private final boolean untilFailure;
+    private final Statement statement;
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public RepeatStatement(int times, boolean untilFailure, Statement statement) {
+        this.repetitions = times;
+        this.untilFailure = untilFailure;
+        this.statement = statement;
+    }
+
+    protected RepeatStatement(Builder builder, Statement next) {
+        this.repetitions = builder.getRepetitions();
+        this.untilFailure = builder.isUntilFailure();
+        this.statement = next;
+    }
+
+    @Override
+    public void evaluate() throws Throwable {
+        for (int i = 0; i < repetitions && !untilFailure; i++) {
+            if (untilFailure) {
+                LOG.info("Running test iteration: {}.", i + 1);
+            } else {
+                LOG.info("Running test iteration: {} of configured repetitions: {}", i +
1, repetitions);
+            }
+            statement.evaluate();
+        }
+    }
+
+    /**
+     * Builder for {@link Repeat}.
+     */
+    public static class Builder {
+        private int repetitions = 1;
+        private boolean untilFailure = false;
+
+        protected Builder() {}
+
+        /**
+         * Specifies the number of times to run the test.
+         *
+         * @param repetitions
+         *      The number of times to run the test.
+         *
+         * @return {@code this} for method chaining.
+         */
+        public Builder withRepetitions(int repetitions) {
+            if (repetitions <= 0) {
+                throw new IllegalArgumentException("repetitions must be greater than zero");
+            }
+
+            this.repetitions = repetitions;
+            return this;
+        }
+
+        /**
+         * Specifies the number of times to run the test.
+         *
+         * @param untilFailure
+         *      true if the test should run until a failure occurs.
+         *
+         * @return {@code this} for method chaining.
+         */
+        public Builder withRunUntilFailure(boolean untilFailure) {
+            this.untilFailure = untilFailure;
+            return this;
+        }
+
+        protected int getRepetitions() {
+            return repetitions;
+        }
+
+        protected boolean isUntilFailure()  {
+            return untilFailure;
+        }
+
+        /**
+         * Builds a {@link RepeatStatement} instance using the values in this builder.
+         *
+         * @param next
+         *      The statement instance to wrap with the newly create repeat statement.
+         *
+         * @return a new {@link RepeatStatement} that wraps the given {@link Statement}.
+         */
+        public RepeatStatement build(Statement next) {
+            if (next == null) {
+                throw new NullPointerException("statement cannot be null");
+            }
+
+            return new RepeatStatement(this, next);
+        }
+
+        /**
+         * Builds a {@link RepeatStatement} instance using the values in this builder.
+         *
+         * @param annotation
+         *      The {@link Repeat} annotation that triggered this statement being created.
+         * @param next
+         *      The statement instance to wrap with the newly create repeat statement.
+         *
+         * @return a new {@link RepeatStatement} that wraps the given {@link Statement}.
+         */
+        public RepeatStatement build(Repeat annotation, Statement next) {
+            if (next == null) {
+                throw new NullPointerException("statement cannot be null");
+            }
+
+            if (annotation == null) {
+                throw new NullPointerException("annotation cannot be null");
+            }
+
+            withRepetitions(annotation.repetitions());
+            withRunUntilFailure(annotation.untilFailure());
+
+            return new RepeatStatement(this, next);
+        }
+    }
+}
\ No newline at end of file


Mime
View raw message