Skip to content

Commit

Permalink
Add more tests
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Pinčuk <alexander.v.pinchuk@gmail.com>
  • Loading branch information
avpinchuk committed Jul 25, 2023
1 parent 3761c2f commit f59a2f9
Showing 1 changed file with 249 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@

package com.sun.enterprise.resource.pool.datastructure;

import com.sun.appserv.connectors.internal.api.PoolingException;
import com.sun.enterprise.resource.ClientSecurityInfo;
import com.sun.enterprise.resource.ResourceHandle;
import com.sun.enterprise.resource.ResourceSpec;
import com.sun.enterprise.resource.allocator.ResourceAllocator;
import com.sun.enterprise.resource.pool.ResourceHandler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import org.easymock.IExpectationSetters;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.Timeout.ThreadMode;
import org.junit.jupiter.api.function.Executable;
Expand All @@ -42,8 +47,11 @@
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
Expand All @@ -55,17 +63,19 @@ public class RWLockDataStructureTest {

private static final int THREAD_COUNT = 500;

private static final int MAX_SIZE = TASK_COUNT;
private static final int RESOURCE_COUNT = TASK_COUNT;

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void raceConditions() throws Exception {
private volatile ResourceHandler handler;
private volatile ResourceAllocator allocator;

ResourceHandler handler = createNiceMock(ResourceHandler.class);
ResourceAllocator allocator = createNiceMock(ResourceAllocator.class);
@BeforeEach
public void createAndPopulateMocks() throws PoolingException {

List<Object> mocks = new ArrayList<>(MAX_SIZE);
for (int i = 0; i < MAX_SIZE; i++) {
ResourceHandler localHandler = createNiceMock(ResourceHandler.class);
ResourceAllocator localAllocator = createNiceMock(ResourceAllocator.class);

List<Object> mocks = new ArrayList<>(RESOURCE_COUNT);
for (int i = 0; i < RESOURCE_COUNT; i++) {
mocks.add(
// We use constructor to generate ResourceHandle mock
// because we depend on an internal state of this object.
Expand All @@ -76,25 +86,240 @@ public void raceConditions() throws Exception {
.createNiceMock());
}

IExpectationSetters<ResourceHandle> handlerExpectation = expect(handler.createResource(allocator));
IExpectationSetters<ResourceHandle> allocatorExpectation = expect(allocator.createResource());
IExpectationSetters<ResourceHandle> handlerExpectation = expect(localHandler.createResource(localAllocator));
IExpectationSetters<ResourceHandle> allocatorExpectation = expect(localAllocator.createResource());
for (Object resource : mocks) {
handlerExpectation.andReturn((ResourceHandle) resource);
allocatorExpectation.andReturn((ResourceHandle) resource);
}
mocks.add(handler);
mocks.add(allocator);
mocks.add(localHandler);
mocks.add(localAllocator);

replay(mocks.toArray());

RWLockDataStructure dataStructure = new RWLockDataStructure(null, MAX_SIZE, handler, null);
handler = localHandler;
allocator = localAllocator;
}

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void testAddResource() throws Exception {
int resourceCount = RESOURCE_COUNT / 2;
int taskCount = TASK_COUNT / 2;

DataStructure dataStructure = new RWLockDataStructure(null, resourceCount, handler, null);

List<Callable<Integer>> tasks = new ArrayList<>(taskCount);
for (int i = 0; i < taskCount; i++) {
tasks.add(() -> dataStructure.addResource(allocator, 1));
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

List<Future<Integer>> futures = threadPool.invokeAll(tasks);
assertAll(
() -> assertAll(futures.stream().map(f -> (Executable) f::get).collect(Collectors.toList())),
() -> assertThat(futures.stream().collect(Collectors.summingInt(this::getResult)), equalTo(taskCount)),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(taskCount)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(taskCount))
);

assertThat(dataStructure.addResource(allocator, 1), equalTo(0));

// Increase max pool size
dataStructure.setMaxSize(resourceCount + 100);
assertAll(
() -> assertThat("Add Resources", dataStructure.addResource(allocator, 100), equalTo(100)),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(resourceCount + 100)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(resourceCount + 100))
);

// Decrease max pool size
dataStructure.setMaxSize(resourceCount);
assertAll(
() -> assertThat("Add Resource", dataStructure.addResource(allocator, 1), equalTo(0)),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(resourceCount + 100)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(resourceCount + 100))
);

List<ResourceHandle> allResources = dataStructure.getAllResources();
assertThat("Resources Size", allResources, hasSize(dataStructure.getResourcesSize()));
for (ResourceHandle resource : allResources) {
assertThat(Collections.frequency(allResources, resource), equalTo(1));
}

threadPool.shutdownNow();
}

@Test
public void testAddResourceWithException() throws Exception {

handler = createNiceMock(ResourceHandler.class);
allocator = createNiceMock(ResourceAllocator.class);

ResourceHandle resource = createMockBuilder(ResourceHandle.class)
.withConstructor(Object.class, ResourceSpec.class, ResourceAllocator.class, ClientSecurityInfo.class)
.withArgs(null, null, null, null)
.createNiceMock();

expect(handler.createResource(allocator)).andThrow(new PoolingException());
expect(allocator.createResource()).andThrow(new PoolingException());
expect(handler.createResource(allocator)).andReturn(resource);
expect(allocator.createResource()).andReturn(resource);

replay(resource, handler, allocator);

DataStructure dataStructure = new RWLockDataStructure(null, 1, handler, null);

assertAll(
() -> assertThrows(PoolingException.class, () -> dataStructure.addResource(allocator, 1)),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(0)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(0))
);

assertAll(
() -> assertThat("Add Resource", dataStructure.addResource(allocator, 1), equalTo(1)),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(1)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(1))
);
}

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void testGetResource() throws Exception {

DataStructure dataStructure = new RWLockDataStructure(null, RESOURCE_COUNT, handler, null);

assertThat("Add Resources", dataStructure.addResource(allocator, RESOURCE_COUNT), equalTo(RESOURCE_COUNT));

List<Callable<ResourceHandle>> tasks = new ArrayList<>(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
tasks.add(dataStructure::getResource);
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

List<Future<ResourceHandle>> futures = threadPool.invokeAll(tasks);
assertAll(
() -> assertAll(futures.stream().map(f -> (Executable) f::get).toList()),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(RESOURCE_COUNT)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(0))
);

List<ResourceHandle> resources = futures.stream().map(this::getResult).collect(Collectors.toList());
assertThat(resources, hasSize(RESOURCE_COUNT));

List<ResourceHandle> allResources = dataStructure.getAllResources();
for (ResourceHandle resource : allResources) {
assertThat(Collections.frequency(allResources, resource), equalTo(1));
}

assertThat("Get Resource", dataStructure.getResource(), nullValue());

threadPool.shutdownNow();
}

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void testReturnResource() throws Exception {

DataStructure dataStructure = new RWLockDataStructure(null, RESOURCE_COUNT, handler, null);

assertThat("Add Resources", dataStructure.addResource(allocator, RESOURCE_COUNT), equalTo(RESOURCE_COUNT));
assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(RESOURCE_COUNT));

List<ResourceHandle> resources = new CopyOnWriteArrayList<>();
for (int i = 0; i < RESOURCE_COUNT; i++) {
resources.add(dataStructure.getResource());
}
assertAll(
() -> assertThat(resources, hasSize(RESOURCE_COUNT)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(0))
);

List<Callable<Void>> tasks = new ArrayList<>(TASK_COUNT);
for (ResourceHandle resource : resources) {
tasks.add(() -> {
dataStructure.returnResource(resource);
return null;
});
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

List<Future<Void>> futures = threadPool.invokeAll(tasks);
assertAll(
() -> assertAll(futures.stream().map(f -> (Executable) f::get).collect(Collectors.toList())),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(RESOURCE_COUNT)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(RESOURCE_COUNT))
);

threadPool.shutdownNow();
}

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void testRemoveResource() throws Exception {

DataStructure dataStructure = new RWLockDataStructure(null, RESOURCE_COUNT, handler, null);

assertThat("Add Resources", dataStructure.addResource(allocator, RESOURCE_COUNT), equalTo(RESOURCE_COUNT));

List<ResourceHandle> resources = new CopyOnWriteArrayList<>();
for (int i = 0; i < RESOURCE_COUNT; i++) {
resources.add(dataStructure.getResource());
}
assertThat(resources, hasSize(RESOURCE_COUNT));

for (int i = 0; i < MAX_SIZE; i++) {
List<Callable<Void>> tasks = new ArrayList<>(TASK_COUNT);
for (ResourceHandle resource : resources) {
tasks.add(() -> {
dataStructure.removeResource(resource);
return null;
});
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

List<Future<Void>> futures = threadPool.invokeAll(tasks);
assertAll(
() -> assertAll(futures.stream().map(f -> (Executable) f::get).collect(Collectors.toList())),
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(0)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(0)),
() -> assertThat("All Resources", dataStructure.getAllResources(), hasSize(0))
);

threadPool.shutdownNow();
}

@Test
public void testRemoveAll() throws PoolingException {

DataStructure dataStructure = new RWLockDataStructure(null, RESOURCE_COUNT, handler, null);

dataStructure.addResource(allocator, RESOURCE_COUNT);
assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(RESOURCE_COUNT));

dataStructure.removeAll();

assertAll(
() -> assertThat("Resources Size", dataStructure.getResourcesSize(), equalTo(0)),
() -> assertThat("Free List Size", dataStructure.getFreeListSize(), equalTo(0)),
() -> assertThat("Get Resource", dataStructure.getResource(), nullValue())
);
}

@RepeatedTest(20)
@Timeout(value = 10, threadMode = ThreadMode.SEPARATE_THREAD)
public void testRaceConditions() throws Exception {

RWLockDataStructure dataStructure = new RWLockDataStructure(null, RESOURCE_COUNT, handler, null);

for (int i = 0; i < RESOURCE_COUNT; i++) {
// requires handler.createResource(allocator)
dataStructure.addResource(allocator, 1);
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
List<Callable<ResourceHandle>> tasks = new ArrayList<>(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
tasks.add(() -> {
Expand All @@ -103,6 +328,9 @@ public void raceConditions() throws Exception {
return resource;
});
}

ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);

List<Future<ResourceHandle>> futures = threadPool.invokeAll(tasks);
// When executed without races, all returned ResourceHandles is not null
// and Resources List always empty. This is because we do pair getResource and
Expand All @@ -119,9 +347,13 @@ public void raceConditions() throws Exception {
threadPool.shutdownNow();
}

private boolean notNull(Future<?> future) {
private <T> boolean notNull(Future<T> future) {
return getResult(future) != null;
}

private <T> T getResult(Future<T> future) {
try {
return future.get() != null;
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
}
Expand Down

0 comments on commit f59a2f9

Please sign in to comment.