package com.github.mustachejava;
import org.junit.Test;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import static com.github.mustachejavabenchmarks.BenchmarkTest.skip;
import static junit.framework.Assert.assertEquals;
/**
* Inspired by an unconfirmed bug report.
*/
public class ConcurrencyTest {
static Random r = new SecureRandom();
private static class TestObject {
final int a;
final int b;
final int c;
int aa() throws InterruptedException {
Thread.sleep(r.nextInt(10));
return a;
}
int bb() throws InterruptedException {
Thread.sleep(r.nextInt(10));
return b;
}
int cc() throws InterruptedException {
Thread.sleep(r.nextInt(10));
return c;
}
Callable<Integer> calla() throws InterruptedException {
return () -> {
Thread.sleep(r.nextInt(10));
return a;
};
}
Callable<Integer> callb() throws InterruptedException {
return () -> {
Thread.sleep(r.nextInt(10));
return b;
};
}
Callable<Integer> callc() throws InterruptedException {
return () -> {
Thread.sleep(r.nextInt(10));
return c;
};
}
private TestObject(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
}
// Alternate render
static String render(TestObject to) {
return to.a + ":" + to.b + ":" + to.c;
}
@Test
public void testConcurrentExecution() throws InterruptedException {
if (skip()) return;
String template = "{{aa}}:{{bb}}:{{cc}}";
final Mustache test = new DefaultMustacheFactory().compile(new StringReader(template), "test");
ExecutorService es = Executors.newCachedThreadPool();
final AtomicInteger total = render(test, es);
assertEquals(0, total.intValue());
}
private AtomicInteger render(Mustache test, ExecutorService es) throws InterruptedException {
final AtomicInteger total = new AtomicInteger();
final Semaphore semaphore = new Semaphore(100);
for (int i = 0; i < 100000; i++) {
semaphore.acquire();
es.submit(() -> {
try {
TestObject testObject = new TestObject(r.nextInt(), r.nextInt(), r.nextInt());
StringWriter sw = new StringWriter();
test.execute(sw, testObject).close();
if (!render(testObject).equals(sw.toString())) {
total.incrementAndGet();
}
} catch (IOException e) {
// Can't fail
e.printStackTrace();
System.exit(1);
} finally {
semaphore.release();
}
});
}
// Wait for them all to complete
semaphore.acquire(100);
return total;
}
@Test
public void testConcurrentExecutionWithConcurrentTemplate() throws InterruptedException {
if (skip()) return;
String template = "{{calla}}:{{callb}}:{{callc}}";
ExecutorService es = Executors.newCachedThreadPool();
DefaultMustacheFactory dmf = new DefaultMustacheFactory();
dmf.setExecutorService(es);
final Mustache test = dmf.compile(new StringReader(template), "test");
final AtomicInteger total = render(test, es);
assertEquals(0, total.intValue());
}
}