/*
* Copyright 2012 Jason Miller
*
* Licensed 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 jj.script;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import static org.mockito.BDDMockito.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jj.execution.TaskRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* @author jason
*
*/
@RunWith(MockitoJUnitRunner.class)
public class ContinuationPendingCacheTest {
// just trying to make sure that the concurrency is done correctly
private static final int STRESS_LEVEL = Runtime.getRuntime().availableProcessors(); // total number of threads to use for the test
private static final int STRIDE = 400;
private static final int KEY_COUNT = STRESS_LEVEL * STRIDE;
private static final class HelperTask extends ScriptTask<ScriptEnvironment<?>> {
/** it will never run so nulls are cool!
*/
protected HelperTask(PendingKey pendingKey) {
super("", null);
this.pendingKey = pendingKey;
}
@Override
protected void begin() throws Exception {
// never gonna run
}
protected Object result() {
return result;
}
}
private @Mock TaskRunner taskRunner;
private @InjectMocks ContinuationPendingCache cache;
@Test
public void testRemoval() throws Throwable {
// given
PendingKey key = new PendingKey(cache);
// when
cache.storeForContinuation(new HelperTask(key));
cache.removePendingTasks(Collections.singletonList(key));
// then
boolean failed = false;
try {
cache.resume(key, "");
} catch (AssertionError ae) {
failed = true;
}
assertTrue(failed);
}
@Test
public void testStoreAndResume() throws Throwable {
//System.out.println(KEY_COUNT + " keys on " + STRESS_LEVEL + " threads");
assertThat(KEY_COUNT % STRESS_LEVEL, is(0));
HelperTask[] tasks = new HelperTask[KEY_COUNT];
PendingKey[] pendingKeys = new PendingKey[KEY_COUNT];
for (int i = 0; i < KEY_COUNT; ++i) {
pendingKeys[i] = new PendingKey(cache);
tasks[i] = new HelperTask(pendingKeys[i]);
}
final ArrayBlockingQueue<Throwable> throwables = new ArrayBlockingQueue<>(STRESS_LEVEL);
final CountDownLatch latch = new CountDownLatch(STRESS_LEVEL);
final int stride = STRIDE; // 200 / 10 = 20;
for (int i = 0; i < STRESS_LEVEL; ++i) {
final int start = i * stride; // 0 * 20 = 0, 1 * 20 = 20;
new Thread(() -> {
try {
for (int j = start; j < start + stride; ++j) {
cache.storeForContinuation(tasks[j]);
Thread.yield(); // get em all good and mixed up
}
latch.countDown();
} catch (Throwable t) {
throwables.add(t);
}
}).start();
}
assumeTrue(latch.await(2, TimeUnit.SECONDS));
if (!throwables.isEmpty()) {
throw throwables.poll();
}
final CountDownLatch latch2 = new CountDownLatch(STRESS_LEVEL);
for (int i = 0; i < STRESS_LEVEL; ++i) {
final int start = i * stride; // 0 * 20 = 0, 1 * 20 = 20;
new Thread(() -> {
try {
for (int j = start; j < start + stride; ++j) {
final Object result = new Object();
cache.resume(pendingKeys[j], result);
assertThat(tasks[j].result(), is(sameInstance(result)));
verify(taskRunner).execute(tasks[j]);
}
latch2.countDown();
} catch (Throwable t) {
throwables.add(t);
}
}).start();
}
assumeTrue(latch2.await(2, TimeUnit.SECONDS));
if (!throwables.isEmpty()) {
throw throwables.poll();
}
final CountDownLatch latch3 = new CountDownLatch(STRESS_LEVEL);
for (int i = 0; i < STRESS_LEVEL; ++i) {
final int start = i * stride; // 0 * 20 = 0, 1 * 20 = 20;
new Thread(() -> {
try {
for (int j = start; j < start + stride; ++j) {
assertResume(pendingKeys[j]);
}
latch3.countDown();
} catch (Throwable t) {
t.printStackTrace();
}
}).start();
}
assumeTrue(latch3.await(2, TimeUnit.SECONDS));
if (!throwables.isEmpty()) {
throw throwables.poll();
}
}
private void assertResume(PendingKey pendingKey) {
boolean asserted = false;
try {
cache.resume(pendingKey, ""); // should throw!
} catch (AssertionError ae) {
asserted = true;
}
assertTrue(asserted);
}
}