Skip to content

Commit

Permalink
Add ManagedScheduledExecutor scheduled asynch tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleAure committed Mar 18, 2024
1 parent f1546c3 commit 9ab2bac
Show file tree
Hide file tree
Showing 6 changed files with 542 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import ee.jakarta.tck.concurrent.framework.junit.anno.Assertion;
import ee.jakarta.tck.concurrent.framework.junit.anno.Challenge;
import ee.jakarta.tck.concurrent.framework.junit.anno.Common.PACKAGE;
import ee.jakarta.tck.concurrent.framework.junit.anno.Debug;
import ee.jakarta.tck.concurrent.framework.junit.anno.Full;
import ee.jakarta.tck.concurrent.framework.junit.anno.TestName;
import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionBean;
Expand All @@ -41,7 +40,6 @@
import jakarta.enterprise.concurrent.spi.ThreadContextProvider;

@Full
@Debug //TODO remove after testing
@RunAsClient // Requires client testing due to multiple servlets and annotation configuration
public class ManagedExecutorDefinitionFullTests extends TestClient {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import ee.jakarta.tck.concurrent.framework.junit.anno.Assertion;
import ee.jakarta.tck.concurrent.framework.junit.anno.Challenge;
import ee.jakarta.tck.concurrent.framework.junit.anno.Common;
import ee.jakarta.tck.concurrent.framework.junit.anno.Debug;
import ee.jakarta.tck.concurrent.framework.junit.anno.Common.PACKAGE;
import ee.jakarta.tck.concurrent.framework.junit.anno.TestName;
import ee.jakarta.tck.concurrent.framework.junit.anno.Web;
Expand All @@ -40,7 +39,6 @@
import jakarta.enterprise.concurrent.spi.ThreadContextProvider;;

@Web
@Debug //TODO remove after testing
@RunAsClient // Requires client testing due to multiple servlets and annotation configuration
@Common({ PACKAGE.CONTEXT, PACKAGE.CONTEXT_PROVIDERS })
public class ManagedExecutorDefinitionWebTests extends TestClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import ee.jakarta.tck.concurrent.framework.junit.anno.Assertion;
import ee.jakarta.tck.concurrent.framework.junit.anno.Challenge;
import ee.jakarta.tck.concurrent.framework.junit.anno.Common.PACKAGE;
import ee.jakarta.tck.concurrent.framework.junit.anno.Debug;
import ee.jakarta.tck.concurrent.framework.junit.anno.Full;
import ee.jakarta.tck.concurrent.framework.junit.anno.TestName;
import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionBean;
Expand All @@ -40,6 +41,7 @@
import jakarta.enterprise.concurrent.spi.ThreadContextProvider;

@Full
@Debug //TODO remove after testing
@RunAsClient // Requires client testing due to annotation configuration
public class ManagedScheduledExecutorDefinitionFullTests extends TestClient {

Expand Down Expand Up @@ -176,4 +178,47 @@ public void testScheduleWithCronTrigger() {
public void testScheduleWithZonedTrigger() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods are completed when future is completed.")
public void testScheduledAsynchCompletedFuture() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods are completed when a non-null result is returned.")
public void testScheduledAsynchCompletedResult() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods are completed when an exception is thrown.")
public void testScheduledAsynchCompletedExceptionally() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure overlapping scheduled asynchronous methods are skipped.")
public void testScheduledAsynchOverlapSkipping() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods ignore the max-async configuration."
+ " Ensure scheduled asynchronous methods honor cleared context configuration")
public void testScheduledAsynchIgnoresMaxAsync() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods choose closest execution time when multiple schedules are provided."
+ " Ensure scheduled asynchronous methods honor propogated context configuration")
public void testScheduledAsynchWithMultipleSchedules() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods are not executed when an invalid JNDI name is provided.")
public void testScheduledAsynchWithInvalidJNDIName() {
runTest(baseURL, testname);
}

@Assertion(id = "GIT:439", strategy = "Ensure scheduled asynchronous methods with void return type stop execution via completable future or exception.")
public void testScheduledAsynchVoidReturn() {
runTest(baseURL, testname);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,33 @@
package ee.jakarta.tck.concurrent.spec.ManagedScheduledExecutorService.resourcedef;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import javax.naming.InitialContext;
Expand All @@ -42,7 +51,9 @@
import ee.jakarta.tck.concurrent.common.context.IntContext;
import ee.jakarta.tck.concurrent.common.context.StringContext;
import ee.jakarta.tck.concurrent.framework.TestServlet;
import ee.jakarta.tck.concurrent.framework.junit.extensions.Wait;
import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionServlet;
import ee.jakarta.tck.concurrent.spec.ManagedScheduledExecutorService.resourcedef.ReqBean.RETURN;
import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.ContextService;
import jakarta.enterprise.concurrent.CronTrigger;
Expand Down Expand Up @@ -605,4 +616,252 @@ public ZoneId getZoneId() {
}

}

public void testScheduledAsynchCompletedFuture() throws Throwable {
AtomicInteger counter = new AtomicInteger();

// Method returns an incomplete future - stopping schedule because non-null value is returned
try {
CompletableFuture<Integer> future = reqBean.scheduledEvery5seconds(1, RETURN.INCOMPLETE, counter);
assertThrows(TimeoutException.class, () -> { // Slow assertion
future.get(10, TimeUnit.SECONDS);
});

assertFalse(future.isCancelled());
assertFalse(future.isCompletedExceptionally());
assertFalse(future.isDone());
assertEquals(1, counter.get(), "Schedule should have executed exactly once.");

future.cancel(false); // Cleanup resources
} finally {
counter.set(0);
}


// Caller completes future before scheduled asynch is completed - stopping schedule because future was cancelled
try {
CompletableFuture<Integer> future = reqBean.scheduledEvery5seconds(1, RETURN.NULL, counter);

assertFalse(future.isCancelled());
assertFalse(future.isCompletedExceptionally());
assertFalse(future.isDone());

int countBeforeCancel = counter.get();
future.cancel(false);
assertThrows(CancellationException.class, () -> {
future.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS);
});
int countAfterCancel = counter.get();

assertTrue((countAfterCancel - countBeforeCancel) <= 1, "Schedule should not have executed more than once after cancel was called.");
} finally {
counter.set(0);
}
}


public void testScheduledAsynchCompletedResult() throws Throwable {
AtomicInteger counter = new AtomicInteger();

// Method returns an expected result - stopping schedule because non-null value is returned
try {
int expected = 3;
CompletableFuture<Integer> future = reqBean.scheduledEvery5seconds(expected, RETURN.COMPLETE_RESULT, counter);

int result = future.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS); // Slow assertion

assertFalse(future.isCancelled());
assertFalse(future.isCompletedExceptionally());
assertTrue(future.isDone());
assertEquals(expected, result);
} finally {
counter.set(0);
}

}

/**
* Ensure completion of scheduled asynch after completing exceptionally
*/
public void testScheduledAsynchCompletedExceptionally() {
AtomicInteger counter = new AtomicInteger();

// Method invokes completeExceptionally - stopping schedule because non-null value is returned
try {
String expected = "testScheduledAsynchCompletedExceptionally-1";
CompletableFuture<Integer> future = reqBean.scheduledEvery5seconds(1, RETURN.COMPLETE_EXCEPTIONALLY.withMessage(expected), counter);

ExecutionException cause = assertThrows(ExecutionException.class, () -> {
future.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS);
});

assertFalse(future.isCancelled());
assertTrue(future.isCompletedExceptionally());
assertTrue(future.isDone());
assertTrue(cause.getMessage().contains(expected));
assertEquals(1, counter.get(), "Schedule should have executed exactly once.");

} finally {
counter.set(0);
}

// Method throws exception, platform invokes completeExceptionally - stopping schedule because future is completed
try {
String expected = "testScheduledAsynchCompletedExceptionally-2";
CompletableFuture<Integer> future = reqBean.scheduledEvery5seconds(1, RETURN.THROW_EXCEPTION.withMessage(expected), counter);

ExecutionException cause = assertThrows(ExecutionException.class, () -> {
future.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS);
});

assertFalse(future.isCancelled());
assertTrue(future.isCompletedExceptionally());
assertTrue(future.isDone());
assertTrue(cause.getMessage().contains(expected));
assertEquals(1, counter.get());
} finally {
counter.set(0);
}
}


public void testScheduledAsynchOverlapSkipping() throws Throwable {
AtomicInteger counter = new AtomicInteger();

try {
int expected = 3;
CompletableFuture<Integer> future = reqBean.scheduledEvery3SecondsTakes5Seconds(expected, counter);

// If scheduled async tasks are not skipped while overlapping this will fail
assertThrows(TimeoutException.class, () -> { // Slow assertion
future.get(expected * 3, TimeUnit.SECONDS);
});

int result = future.get(expected * 5, TimeUnit.SECONDS);
assertEquals(expected, result);

} finally {
counter.set(0);
}
}

public void testScheduledAsynchIgnoresMaxAsync() throws Throwable {
ManagedScheduledExecutorService executor = InitialContext.doLookup("java:module/concurrent/ScheduledExecutorB");

BlockingQueue<Integer> results = new LinkedBlockingQueue<Integer>();
CountDownLatch blocker = new CountDownLatch(1);

Runnable task = () -> {
results.add(IntContext.get());
try {
blocker.await(MAX_WAIT_SECONDS * 5, TimeUnit.SECONDS);
} catch (InterruptedException x) {
throw new CompletionException(x);
}
};

AtomicInteger counter = new AtomicInteger();

try {
IntContext.set(22); //Context should be cleared

executor.runAsync(task);
executor.runAsync(task);
executor.runAsync(task);
executor.runAsync(task);
executor.runAsync(task);
CompletableFuture<Integer> future = reqBean.scheduledEvery3Seconds(1, counter);


assertEquals(Integer.valueOf(0), results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must be able to run one async task.");

assertEquals(Integer.valueOf(0), results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must be able to run two async tasks.");

assertEquals(Integer.valueOf(0), results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must be able to run three async tasks.");

assertEquals(Integer.valueOf(0), results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must be able to run four async tasks.");

assertEquals(null, results.poll(1, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must not run 5 async tasks concurrently.");

assertEquals(Integer.valueOf(0), future.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS),
"ManagedScheduledExecutorService with maxAsync=4 must be able to run scheduled async methods concurrently.");
} finally {
IntContext.set(0);
counter.set(0);
blocker.countDown();
}
}

public void testScheduledAsynchWithMultipleSchedules() throws Throwable {
AtomicInteger counter = new AtomicInteger();

try {
String expected = "testScheduledAsynchWithMultipleSchedules";
StringContext.set(expected);

CompletableFuture<String> future = reqBean.scheduledEvery3SecondsAnd1Minute(5, counter);

String result = future.get(1, TimeUnit.MINUTES);
assertEquals(expected, result);

} finally {
StringContext.set(null);
counter.set(0);
}
}

public void testScheduledAsynchWithInvalidJNDIName() {
assertThrows(RejectedExecutionException.class, () -> {
reqBean.scheduledInvalidExecutor();
});
}

public void testScheduledAsynchVoidReturn() {
AtomicInteger counter = new AtomicInteger();

// Test future.complete(null);
try {
int expected = 3;
reqBean.scheduledEvery3SecondsVoidReturn(expected, RETURN.COMPLETE_RESULT, counter);
assertTimeoutPreemptively(Duration.ofSeconds(MAX_WAIT_SECONDS), () -> {
for (; expected != counter.get(); Wait.sleep(Duration.ofSeconds(3))) {
//empty
}
});
} finally {
counter.set(0);
}

// Test future.completeExceptionally();
try {
int expected = 3;
reqBean.scheduledEvery3SecondsVoidReturn(expected, RETURN.COMPLETE_EXCEPTIONALLY, counter);
assertTimeoutPreemptively(Duration.ofSeconds(MAX_WAIT_SECONDS), () -> {
for (; expected != counter.get(); Wait.sleep(Duration.ofSeconds(3))) {
//empty
}
});
} finally {
counter.set(0);
}

// Test method throws exception
try {
int expected = 3;
reqBean.scheduledEvery3SecondsVoidReturn(expected, RETURN.THROW_EXCEPTION, counter);
assertTimeoutPreemptively(Duration.ofSeconds(MAX_WAIT_SECONDS), () -> {
for (; expected != counter.get(); Wait.sleep(Duration.ofSeconds(3))) {
//empty
}
});
} finally {
counter.set(0);
}
}

}
Loading

0 comments on commit 9ab2bac

Please sign in to comment.