Skip to content

Commit

Permalink
Add FlakyTestRunner, Fix #9
Browse files Browse the repository at this point in the history
  • Loading branch information
minborg committed Jan 3, 2022
1 parent a183427 commit 78fcf13
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package net.openhft.chronicle.testframework;

import net.openhft.chronicle.testframework.internal.VanillaFlakyTestRunner;
import org.jetbrains.annotations.NotNull;

public final class FlakyTestRunner {

// Suppresses default constructor, ensuring non-instantiability.
private FlakyTestRunner() {
}

/**
* Runs the provided {@code action} trying at most 2 times.
*
* @param action non-null action to perform
* @param <X> exception type
* @throws X if an underlying exception is thrown despite retrying the specified number of times
*/
public static <X extends Throwable> void run(@NotNull final RunnableThrows<X> action) throws X {
new VanillaFlakyTestRunner().run(action, 2);
}

/**
* Runs the provided {@code action} trying at most 2 times if the provided {@code flakyOnTHisArch} is true,
* otherwise just runs the provided {@code action} once.
*
* @param flakyOnThisArch indicating if the provided action is
* @param action non-null action to perform
* @param <X> exception type
* @throws X if an underlying exception is thrown despite retrying the specified number of times
*/
public static <X extends Throwable> void run(final boolean flakyOnThisArch,
@NotNull final RunnableThrows<X> action) throws X {
new VanillaFlakyTestRunner().run(action, flakyOnThisArch ? 2 : 1);
}

/**
* Runs the provided {@code action} trying at most the provided {@code maxIteration} times if the
* provided {@code flakyOnTHisArch} is true, otherwise just runs the provided {@code action} once.
*
* @param flakyOnThisArch indicating if the provided action is
* @param action non-null action to perform
* @param <X> exception type
* @throws X if an underlying exception is thrown despite retrying the specified number of times
*/
public static <X extends Throwable> void run(final boolean flakyOnThisArch,
@NotNull final RunnableThrows<X> action,
final int maxIterations) throws X {
new VanillaFlakyTestRunner().run(action, flakyOnThisArch ? maxIterations : 1);
}

@FunctionalInterface
public interface RunnableThrows<T extends Throwable> {

/**
* Performs an action.
*
* @throws T if an underlying exception is thrown
*/
void run() throws T;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package net.openhft.chronicle.testframework.internal;

import net.openhft.chronicle.testframework.FlakyTestRunner;

import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.Objects.requireNonNull;

public final class VanillaFlakyTestRunner {

private final AtomicBoolean inRun = new AtomicBoolean();

public <X extends Throwable> void run(final FlakyTestRunner.RunnableThrows<X> action,
final int maxIterations) throws X {
requireNonNull(action);
if (maxIterations <= 0) {
throw new IllegalArgumentException("maxIterations is non positive " + maxIterations);
}
if (!inRun.compareAndSet(false, true))
throw new AssertionError("Can't run nested");
try {
for (int i = 0; i < maxIterations; i++) {
try {
action.run();
if (i > 0) {
System.out.println("Flaky test threw an error " + i + " run(s), but passed on run " + (i + 1));
}
break;
} catch (Throwable t) {
if (i == (maxIterations - 1)) {
throw t;
}
System.err.println("Rerunning failing test run " + (i + 2));
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException ignore) {
// do nothing
}
}
}
} finally {
inRun.set(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package net.openhft.chronicle.testframework.internal;

import net.openhft.chronicle.testframework.FlakyTestRunner.RunnableThrows;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;

class VanillaFlakyTestRunnerTest {

@Test
void runZero() {
RunnableThrows<IllegalStateException> action = new MyAction(0);
assertThrows(IllegalArgumentException.class, () ->
new VanillaFlakyTestRunner().run(action, 0)
);
}

@Test
void run() {
RunnableThrows<IllegalStateException> action = new MyAction(0);
new VanillaFlakyTestRunner().run(action, 1);
}

@Test
void runTry2() {
RunnableThrows<IllegalStateException> action = new MyAction(1);
new VanillaFlakyTestRunner().run(action, 2);
}

@Test
void runTry3() {
RunnableThrows<IllegalStateException> action = new MyAction(2);
assertThrows(IllegalStateException.class, () ->
new VanillaFlakyTestRunner().run(action, 2)
);
}

private static final class MyAction implements RunnableThrows<IllegalStateException> {

private int countDown;

public MyAction(int countDown) {
this.countDown = countDown;
}

@Override
public void run() throws IllegalStateException {
if (countDown <= 0)
return;
throw new IllegalStateException("" + countDown--);
}
}

}

0 comments on commit 78fcf13

Please sign in to comment.