blob: 0e4e34bc2c5812e8446ec5fe89f7b0ca81a9e5f5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.jexl3;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.jexl3.internal.Script;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests around asynchronous script execution and interrupts.
*/
@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
public class ScriptCallableTest extends JexlTestCase {
//private Log logger = LogFactory.getLog(JexlEngine.class);
public ScriptCallableTest() {
super("ScriptCallableTest");
}
@Test
public void testFuture() throws Exception {
JexlScript e = JEXL.createScript("while(true);");
FutureTask<Object> future = new FutureTask<Object>(e.callable(null));
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(future);
Object t = 42;
try {
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
} finally {
executor.shutdown();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
}
@Test
public void testCallableCancel() throws Exception {
List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new MapContext();
ctxt.set("latch", latch);
JexlScript e = JEXL.createScript("latch.release(); while(true);");
final Script.Callable c = (Script.Callable) e.callable(ctxt);
Object t = 42;
Callable<Object> kc = new Callable<Object>() {
@Override
public Object call() throws Exception {
latch.acquire();
return c.cancel();
}
};
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<?> future = executor.submit(c);
Future<?> kfc = executor.submit(kc);
try {
Assert.assertTrue((Boolean) kfc.get());
t = future.get();
Assert.fail("should have been cancelled");
} catch (ExecutionException xexec) {
// ok, ignore
Assert.assertTrue(xexec.getCause() instanceof JexlException.Cancel);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(c.isCancelled());
Assert.assertTrue(lr == null || lr.isEmpty());
}
@Test
public void testCallableTimeout() throws Exception {
List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new MapContext();
ctxt.set("latch", latch);
JexlScript e = JEXL.createScript("latch.release(); while(true);");
Callable<Object> c = e.callable(ctxt);
Object t = 42;
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(c);
try {
latch.acquire();
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCallableClosure() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("function(t) {while(t);}");
Callable<Object> c = e.callable(null, Boolean.TRUE);
Object t = 42;
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(c);
try {
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
Assert.assertTrue(lr.isEmpty());
}
public static class TestContext extends MapContext implements JexlContext.NamespaceResolver {
@Override
public Object resolveNamespace(String name) {
return name == null ? this : null;
}
public int wait(int s) throws InterruptedException {
Thread.sleep(1000 * s);
return s;
}
public int waitInterrupt(int s) {
try {
Thread.sleep(1000 * s);
return s;
} catch (InterruptedException xint) {
Thread.currentThread().interrupt();
}
return -1;
}
public int runForever() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
return 1;
}
public int interrupt() throws InterruptedException {
Thread.currentThread().interrupt();
return 42;
}
public void sleep(long millis) throws InterruptedException {
try {
Thread.sleep(millis);
} catch (InterruptedException xint) {
throw xint;
}
}
public int hangs(Object t) {
return 1;
}
}
@Test
public void testNoWait() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(0)");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
try {
Future<?> future = executor.submit(c);
Object t = future.get(2, TimeUnit.SECONDS);
Assert.assertTrue(future.isDone());
Assert.assertEquals(0, t);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testWait() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(1)");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
try {
Future<?> future = executor.submit(c);
Object t = future.get(2, TimeUnit.SECONDS);
Assert.assertEquals(1, t);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWait() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(10)");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
try {
Future<?> future = executor.submit(c);
Object t = 42;
try {
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWaitInterrupt() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("waitInterrupt(42)");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(c);
Object t = 42;
try {
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelForever() throws Exception {
List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new TestContext();
ctxt.set("latch", latch);
JexlScript e = JEXL.createScript("latch.release(); runForever()");
Callable<Object> c = e.callable(ctxt);
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(c);
Object t = 42;
try {
latch.acquire();
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
future.cancel(true);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelLoopWait() throws Exception {
List<Runnable> lr = null;
JexlScript e = JEXL.createScript("while (true) { wait(10) }");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(c);
Object t = 42;
try {
t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
future.cancel(true);
} finally {
lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testInterruptVerboseStrict() throws Exception {
runInterrupt(new JexlBuilder().silent(false).strict(true).create());
}
@Test
public void testInterruptVerboseLenient() throws Exception {
runInterrupt(new JexlBuilder().silent(false).strict(false).create());
}
@Test
public void testInterruptSilentStrict() throws Exception {
runInterrupt(new JexlBuilder().silent(true).strict(true).create());
}
@Test
public void testInterruptSilentLenient() throws Exception {
runInterrupt(new JexlBuilder().silent(true).strict(false).create());
}
@Test
public void testInterruptCancellable() throws Exception {
runInterrupt(new JexlBuilder().silent(true).strict(true).cancellable(true).create());
}
/**
* Redundant test with previous ones but impervious to JEXL engine configuation.
* @param silent silent engine flag
* @param strict strict (aka not lenient) engine flag
* @throws Exception if there is a regression
*/
private void runInterrupt(JexlEngine jexl) throws Exception {
List<Runnable> lr = null;
ExecutorService exec = Executors.newFixedThreadPool(2);
try {
JexlContext ctxt = new TestContext();
// run an interrupt
JexlScript sint = jexl.createScript("interrupt(); return 42");
Object t = null;
Script.Callable c = (Script.Callable) sint.callable(ctxt);
try {
t = c.call();
if (c.isCancellable()) {
Assert.fail("should have thrown a Cancel");
}
} catch (JexlException.Cancel xjexl) {
if (!c.isCancellable()) {
Assert.fail("should not have thrown " + xjexl);
}
}
Assert.assertTrue(c.isCancelled());
Assert.assertNotEquals(42, t);
// self interrupt
Future<Object> f = null;
c = (Script.Callable) sint.callable(ctxt);
try {
f = exec.submit(c);
t = f.get();
if (c.isCancellable()) {
Assert.fail("should have thrown a Cancel");
}
} catch (ExecutionException xexec) {
if (!c.isCancellable()) {
Assert.fail("should not have thrown " + xexec);
}
}
Assert.assertTrue(c.isCancelled());
Assert.assertNotEquals(42, t);
// timeout a sleep
JexlScript ssleep = jexl.createScript("sleep(30000); return 42");
try {
f = exec.submit(ssleep.callable(ctxt));
t = f.get(100L, TimeUnit.MILLISECONDS);
Assert.fail("should timeout");
} catch (TimeoutException xtimeout) {
if (f != null) {
f.cancel(true);
}
}
Assert.assertNotEquals(42, t);
// cancel a sleep
try {
final Future<Object> fc = exec.submit(ssleep.callable(ctxt));
Runnable cancels = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200L);
} catch (Exception xignore) {
}
fc.cancel(true);
}
};
exec.submit(cancels);
t = f.get(100L, TimeUnit.MILLISECONDS);
Assert.fail("should be cancelled");
} catch (CancellationException xexec) {
// this is the expected result
}
// timeout a while(true)
JexlScript swhile = jexl.createScript("while(true); return 42");
try {
f = exec.submit(swhile.callable(ctxt));
t = f.get(100L, TimeUnit.MILLISECONDS);
Assert.fail("should timeout");
} catch (TimeoutException xtimeout) {
if (f != null) {
f.cancel(true);
}
}
Assert.assertNotEquals(42, t);
// cancel a while(true)
try {
final Future<Object> fc = exec.submit(swhile.callable(ctxt));
Runnable cancels = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200L);
} catch (Exception xignore) {
}
fc.cancel(true);
}
};
exec.submit(cancels);
t = fc.get();
Assert.fail("should be cancelled");
} catch (CancellationException xexec) {
// this is the expected result
}
Assert.assertNotEquals(42, t);
} finally {
lr = exec.shutdownNow();
}
Assert.assertTrue(lr.isEmpty());
}
@Test
public void testHangs() throws Exception {
JexlScript e = JEXL.createScript("hangs()");
Callable<Object> c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
try {
Future<?> future = executor.submit(c);
Object t = future.get(1, TimeUnit.SECONDS);
Assert.fail("hangs should not be solved");
} catch(ExecutionException xexec) {
Assert.assertTrue(xexec.getCause() instanceof JexlException.Method);
} finally {
executor.shutdown();
}
}
}