blob: 50e1aecd471062d8fc980609d2741badbd58ef05 [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.tinkerpop.gremlin.groovy.engine;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.tinkerpop.gremlin.TestHelper;
import org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin;
import org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin;
import org.apache.tinkerpop.gremlin.structure.io.Storage;
import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin;
import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException;
import org.javatuples.Pair;
import org.junit.Test;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.SimpleBindings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.number.OrderingComparison.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class GremlinExecutorTest {
public static Map<String, String> PATHS = new HashMap<>();
private final BasicThreadFactory testingThreadFactory = new BasicThreadFactory.Builder().namingPattern("test-gremlin-executor-%d").build();
/**
* Temporary "useless" plugin definition to force GremlinExecutor to use GremlinScriptEngineManager - will be
* removed when the old functionality of ScriptEngines is removed.
*/
private final Map<String, Map<String,Object>> triggerPlugin = new HashMap<String, Map<String,Object>>() {{
put(ImportGremlinPlugin.class.getName(), new HashMap<String,Object>() {{
put("classImports", Collections.singletonList("java.lang.Math"));
}});
}};
private final Map<String, Map<String,Object>> scriptFilePlugin = new HashMap<String, Map<String,Object>>() {{
put(ScriptFileGremlinPlugin.class.getName(), new HashMap<String,Object>() {{
put("files", Collections.singletonList(PATHS.get("GremlinExecutorInit.groovy")));
}});
}};
static {
try {
final List<String> groovyScriptResources = Collections.singletonList("GremlinExecutorInit.groovy");
for (final String fileName : groovyScriptResources) {
PATHS.put(fileName,
Storage.toPath(TestHelper.generateTempFileFromResource(GremlinExecutorTest.class, fileName, "")));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void shouldRaiseExceptionInWithResultOfLifeCycle() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final GremlinExecutor.LifeCycle lc = GremlinExecutor.LifeCycle.build()
.withResult(r -> {
throw new RuntimeException("no worky");
}).create();
final AtomicBoolean exceptionRaised = new AtomicBoolean(false);
final CompletableFuture<Object> future = gremlinExecutor.eval("1+1", "gremlin-groovy", new SimpleBindings(), lc);
future.handle((r, t) -> {
exceptionRaised.set(t != null && t instanceof RuntimeException && t.getMessage().equals("no worky"));
return null;
}).get();
assertThat(exceptionRaised.get(), is(true));
gremlinExecutor.close();
}
@Test
public void shouldEvalScript() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
assertEquals(2, gremlinExecutor.eval("1+1").get());
gremlinExecutor.close();
}
@Test
public void shouldCompileScript() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final CompiledScript script = gremlinExecutor.compile("1+1").get();
assertEquals(2, script.eval());
gremlinExecutor.close();
}
@Test
public void shouldEvalSuccessfulAssertionScript() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
assertNull(gremlinExecutor.eval("assert 1==1").get());
gremlinExecutor.close();
}
@Test
public void shouldEvalFailingAssertionScript() throws Exception {
try (GremlinExecutor gremlinExecutor = GremlinExecutor.build().create()) {
gremlinExecutor.eval("assert 1==0").get();
fail("Should have thrown an exception");
} catch (Exception ex) {
assertThat(ex.getCause(), instanceOf(AssertionError.class));
}
}
@Test
public void shouldEvalMultipleScripts() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
assertEquals(2, gremlinExecutor.eval("1+1").get());
assertEquals(3, gremlinExecutor.eval("1+2").get());
assertEquals(4, gremlinExecutor.eval("1+3").get());
assertEquals(5, gremlinExecutor.eval("1+4").get());
assertEquals(6, gremlinExecutor.eval("1+5").get());
assertEquals(7, gremlinExecutor.eval("1+6").get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithBindings() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final Bindings b = new SimpleBindings();
b.put("x", 1);
assertEquals(2, gremlinExecutor.eval("1+x", b).get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithMapBindings() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final Map<String,Object> b = new HashMap<>();
b.put("x", 1);
assertEquals(2, gremlinExecutor.eval("1+x", b).get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithMapBindingsAndLanguage() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final Map<String,Object> b = new HashMap<>();
b.put("x", 1);
assertEquals(2, gremlinExecutor.eval("1+x", "gremlin-groovy", b).get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithMapBindingsAndLanguageThenTransform() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final Map<String,Object> b = new HashMap<>();
b.put("x", 1);
assertEquals(4, gremlinExecutor.eval("1+x", "gremlin-groovy", b, r -> (int) r * 2).get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithMapBindingsAndLanguageThenConsume() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final Map<String,Object> b = new HashMap<>();
b.put("x", 1);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicInteger result = new AtomicInteger(0);
assertEquals(2, gremlinExecutor.eval("1+x", "gremlin-groovy", b, r -> {
result.set((int) r * 2);
latch.countDown();
}).get());
latch.await();
assertEquals(4, result.get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithGlobalBindings() throws Exception {
final Bindings b = new SimpleBindings();
b.put("x", 1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(b).create();
assertEquals(2, gremlinExecutor.eval("1+x").get());
gremlinExecutor.close();
}
@Test
public void shouldGetGlobalBindings() throws Exception {
final Bindings b = new SimpleBindings();
final Object bound = new Object();
b.put("x", bound);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().
addPlugins("gremlin-groovy", triggerPlugin).
globalBindings(b).create();
assertEquals(bound, gremlinExecutor.getScriptEngineManager().getBindings().get("x"));
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithGlobalAndLocalBindings() throws Exception {
final Bindings g = new SimpleBindings();
g.put("x", 1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(g).create();
final Bindings b = new SimpleBindings();
b.put("y", 1);
assertEquals(2, gremlinExecutor.eval("y+x", b).get());
gremlinExecutor.close();
}
@Test
public void shouldEvalScriptWithLocalOverridingGlobalBindings() throws Exception {
final Bindings g = new SimpleBindings();
g.put("x", 1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().globalBindings(g).create();
final Bindings b = new SimpleBindings();
b.put("x", 10);
assertEquals(11, gremlinExecutor.eval("x+1", b).get());
gremlinExecutor.close();
}
@Test
public void shouldTimeoutSleepingScript() throws Exception {
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final CountDownLatch timeOutCount = new CountDownLatch(1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(250)
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeOutCount.countDown()).create();
try {
gremlinExecutor.eval("Thread.sleep(1000);10").get();
fail("This script should have timed out with an exception");
} catch (Exception ex) {
assertEquals(TimeoutException.class, ex.getCause().getClass());
}
assertTrue(timeOutCount.await(2000, TimeUnit.MILLISECONDS));
assertFalse(successCalled.get());
assertFalse(failureCalled.get());
assertEquals(0, timeOutCount.getCount());
gremlinExecutor.close();
}
@Test
public void shouldTimeoutSleepingEval() throws Exception {
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final CountDownLatch timeOutCount = new CountDownLatch(1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(250)
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeOutCount.countDown()).create();
try {
gremlinExecutor.eval("Thread.sleep(1000);10").get();
fail("This script should have timed out with an exception");
} catch (Exception ex) {
assertEquals(TimeoutException.class, ex.getCause().getClass());
}
assertTrue(timeOutCount.await(2000, TimeUnit.MILLISECONDS));
assertFalse(successCalled.get());
assertFalse(failureCalled.get());
assertEquals(0, timeOutCount.getCount());
gremlinExecutor.close();
}
@Test
public void shouldTimeoutSleepingScriptViaOverrideOnLifeCycle() throws Exception {
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final CountDownLatch timeOutCount = new CountDownLatch(1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(10000)
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeOutCount.countDown()).create();
try {
final GremlinExecutor.LifeCycle lifeCycle = GremlinExecutor.LifeCycle.build()
.evaluationTimeoutOverride(250L).create();
gremlinExecutor.eval("Thread.sleep(1000);10", "gremlin-groovy", new SimpleBindings(), lifeCycle).get();
fail("This script should have timed out with an exception");
} catch (Exception ex) {
assertEquals(TimeoutException.class, ex.getCause().getClass());
}
assertTrue(timeOutCount.await(2000, TimeUnit.MILLISECONDS));
assertFalse(successCalled.get());
assertFalse(failureCalled.get());
assertEquals(0, timeOutCount.getCount());
gremlinExecutor.close();
}
@Test
public void shouldTimeoutSleepingEvalViaOverrideOnLifeCycle() throws Exception {
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final CountDownLatch timeOutCount = new CountDownLatch(1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(10000)
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeOutCount.countDown()).create();
try {
final GremlinExecutor.LifeCycle lifeCycle = GremlinExecutor.LifeCycle.build()
.evaluationTimeoutOverride(250L).create();
gremlinExecutor.eval("Thread.sleep(1000);10", "gremlin-groovy", new SimpleBindings(), lifeCycle).get();
fail("This script should have timed out with an exception");
} catch (Exception ex) {
assertEquals(TimeoutException.class, ex.getCause().getClass());
}
assertTrue(timeOutCount.await(2000, TimeUnit.MILLISECONDS));
assertFalse(successCalled.get());
assertFalse(failureCalled.get());
assertEquals(0, timeOutCount.getCount());
gremlinExecutor.close();
}
@Test
public void shouldOverrideBeforeEval() throws Exception {
final AtomicInteger called = new AtomicInteger(0);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().beforeEval(b -> called.set(1)).create();
assertEquals(2, gremlinExecutor.eval("1+1", null, new SimpleBindings(),
GremlinExecutor.LifeCycle.build().beforeEval(b -> called.set(200)).create()).get());
// need to wait long enough for the callback to register
Thread.sleep(500);
assertEquals(200, called.get());
}
@Test
public void shouldOverrideAfterSuccess() throws Exception {
final AtomicInteger called = new AtomicInteger(0);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().afterSuccess(b -> called.set(1)).create();
assertEquals(2, gremlinExecutor.eval("1+1", null, new SimpleBindings(),
GremlinExecutor.LifeCycle.build().afterSuccess(b -> called.set(200)).create()).get());
// need to wait long enough for the callback to register
Thread.sleep(500);
assertEquals(200, called.get());
}
@Test
public void shouldOverrideAfterFailure() throws Exception {
final AtomicInteger called = new AtomicInteger(0);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().afterFailure((b,t) -> called.set(1)).create();
try {
gremlinExecutor.eval("10/0", null, new SimpleBindings(),
GremlinExecutor.LifeCycle.build().afterFailure((b,t) -> called.set(200)).create()).get();
fail("Should have failed with division by zero");
} catch (Exception ignored) {
}
// need to wait long enough for the callback to register
Thread.sleep(500);
assertEquals(200, called.get());
}
@Test
public void shouldCallFail() throws Exception {
final AtomicBoolean timeoutCalled = new AtomicBoolean(false);
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeoutCalled.set(true)).create();
try {
gremlinExecutor.eval("10/0").get();
fail();
} catch (Exception ignored) { }
// need to wait long enough for the callback to register
Thread.sleep(500);
assertFalse(timeoutCalled.get());
assertFalse(successCalled.get());
assertTrue(failureCalled.get());
gremlinExecutor.close();
}
@Test
public void shouldCallSuccess() throws Exception {
final AtomicBoolean timeoutCalled = new AtomicBoolean(false);
final AtomicBoolean successCalled = new AtomicBoolean(false);
final AtomicBoolean failureCalled = new AtomicBoolean(false);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.afterFailure((b, e) -> failureCalled.set(true))
.afterSuccess((b) -> successCalled.set(true))
.afterTimeout((b) -> timeoutCalled.set(true)).create();
assertEquals(2, gremlinExecutor.eval("1+1").get());
// need to wait long enough for the callback to register
Thread.sleep(500);
assertFalse(timeoutCalled.get());
assertTrue(successCalled.get());
assertFalse(failureCalled.get());
gremlinExecutor.close();
}
@Test
public void shouldCancelTimeoutOnSuccessfulScript() throws Exception {
final long evaluationTimeout = 5_000;
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(evaluationTimeout)
.create();
final long now = System.currentTimeMillis();
assertEquals(2, gremlinExecutor.eval("1+1").get());
gremlinExecutor.close();
assertTrue(System.currentTimeMillis() - now < evaluationTimeout);
}
@Test
public void shouldCancelTimeoutOnSuccessfulEval() throws Exception {
final long evaluationTimeout = 5_000;
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.evaluationTimeout(evaluationTimeout)
.create();
final long now = System.currentTimeMillis();
assertEquals(2, gremlinExecutor.eval("1+1").get());
gremlinExecutor.close();
assertTrue(System.currentTimeMillis() - now < evaluationTimeout);
}
@Test
public void shouldEvalInMultipleThreads() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final CyclicBarrier barrier = new CyclicBarrier(2);
final AtomicInteger i1 = new AtomicInteger(0);
final AtomicBoolean b1 = new AtomicBoolean(false);
final Thread t1 = new Thread(() -> {
try {
barrier.await();
i1.set((Integer) gremlinExecutor.eval("1+1").get());
} catch (Exception ex) {
b1.set(true);
}
});
final AtomicInteger i2 = new AtomicInteger(0);
final AtomicBoolean b2 = new AtomicBoolean(false);
final Thread t2 = new Thread(() -> {
try {
barrier.await();
i2.set((Integer) gremlinExecutor.eval("1+1").get());
} catch (Exception ex) {
b2.set(true);
}
});
t1.start();
t2.start();
t1.join();
t2.join();
assertEquals(2, i1.get());
assertEquals(2, i2.get());
assertFalse(b1.get());
assertFalse(b2.get());
gremlinExecutor.close();
}
@Test
public void shouldNotExhaustThreads() throws Exception {
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2, testingThreadFactory);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.executorService(executorService)
.scheduledExecutorService(executorService).create();
final AtomicInteger count = new AtomicInteger(0);
assertTrue(IntStream.range(0, 1000).mapToObj(i -> gremlinExecutor.eval("1+1")).allMatch(f -> {
try {
return (Integer) f.get() == 2;
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
count.incrementAndGet();
}
}));
assertEquals(1000, count.intValue());
executorService.shutdown();
executorService.awaitTermination(30000, TimeUnit.MILLISECONDS);
}
@Test
public void shouldInitializeWithScript() throws Exception {
final Map<String, Map<String,Object>> config = new HashMap<>();
final Map<String, Object> scriptPluginConfig = new HashMap<>();
scriptPluginConfig.put("files", Collections.singletonList(PATHS.get("GremlinExecutorInit.groovy")));
config.put(ScriptFileGremlinPlugin.class.getName(), scriptPluginConfig);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.addPlugins("gremlin-groovy", config)
.create();
assertEquals(2, gremlinExecutor.eval("add(1,1)").get());
gremlinExecutor.close();
}
@Test
public void shouldInitializeWithScriptAndMakeGlobalBinding() throws Exception {
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.addPlugins("gremlin-groovy", scriptFilePlugin)
.create();
assertEquals(2, gremlinExecutor.eval("add(1,1)").get());
assertThat(gremlinExecutor.getScriptEngineManager().getBindings().keySet(), contains("name"));
assertThat(gremlinExecutor.getScriptEngineManager().getBindings().keySet(), not(hasItem("someSet")));
assertEquals("stephen", gremlinExecutor.getScriptEngineManager().getBindings().get("name"));
gremlinExecutor.close();
}
@Test
public void shouldContinueToEvalScriptsEvenWithTimedInterrupt() throws Exception {
final Map<String, Map<String,Object>> config = new HashMap<>();
final Map<String, Object> scriptPluginConfig = new HashMap<>();
scriptPluginConfig.put("files", Collections.singletonList(PATHS.get("GremlinExecutorInit.groovy")));
config.put(ScriptFileGremlinPlugin.class.getName(), scriptPluginConfig);
final Map<String, Object> groovyPluginConfig = new HashMap<>();
groovyPluginConfig.put("timedInterrupt", 250);
config.put(GroovyCompilerGremlinPlugin.class.getName(), groovyPluginConfig);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.addPlugins("gremlin-groovy", config)
.create();
for (int ix = 0; ix < 5; ix++) {
try {
// this script takes significantly longer than the interruptionTimeout
gremlinExecutor.eval("s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 10000) {}").get();
fail("This should have timed out");
} catch (Exception se) {
assertEquals(TimedInterruptTimeoutException.class, se.getCause().getClass());
}
// this script takes significantly less than the interruptionTimeout
assertEquals("test", gremlinExecutor.eval("s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 20) {};'test'").get());
}
gremlinExecutor.close();
}
@Test
public void shouldInterruptWhile() throws Exception {
final Map<String, Map<String,Object>> config = new HashMap<>();
final Map<String, Object> scriptPluginConfig = new HashMap<>();
scriptPluginConfig.put("files", Collections.singletonList(PATHS.get("GremlinExecutorInit.groovy")));
config.put(ScriptFileGremlinPlugin.class.getName(), scriptPluginConfig);
final Map<String, Object> groovyPluginConfig = new HashMap<>();
groovyPluginConfig.put("enableThreadInterrupt", true);
config.put(GroovyCompilerGremlinPlugin.class.getName(), groovyPluginConfig);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.addPlugins("gremlin-groovy", config)
.create();
final AtomicBoolean asserted = new AtomicBoolean(false);
final Thread t = new Thread(() -> {
try {
gremlinExecutor.eval("s = System.currentTimeMillis();\nwhile((System.currentTimeMillis() - s) < 10000) {}").get();
} catch (Exception se) {
asserted.set(se instanceof InterruptedException);
}
});
t.start();
Thread.sleep(100);
t.interrupt();
while(t.isAlive()) {}
assertTrue(asserted.get());
}
@Test
public void shouldNotShutdownExecutorServicesSuppliedToGremlinExecutor() throws Exception {
final ScheduledExecutorService service = Executors.newScheduledThreadPool(4, testingThreadFactory);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.executorService(service)
.scheduledExecutorService(service).create();
gremlinExecutor.close();
assertFalse(service.isShutdown());
service.shutdown();
service.awaitTermination(30000, TimeUnit.MILLISECONDS);
}
@Test
public void shouldGetExecutorService() throws Exception {
final ScheduledExecutorService service = Executors.newScheduledThreadPool(4, testingThreadFactory);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.executorService(service)
.scheduledExecutorService(service).create();
assertSame(service, gremlinExecutor.getExecutorService());
gremlinExecutor.close();
}
@Test
public void shouldGetScheduledExecutorService() throws Exception {
final ScheduledExecutorService service = Executors.newScheduledThreadPool(4, testingThreadFactory);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.executorService(service)
.scheduledExecutorService(service).create();
assertSame(service, gremlinExecutor.getScheduledExecutorService());
gremlinExecutor.close();
}
@Test
public void shouldAllowVariableReuseAcrossThreads() throws Exception {
final ExecutorService service = Executors.newFixedThreadPool(8, testingThreadFactory);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build().create();
final AtomicBoolean failed = new AtomicBoolean(false);
final int max = 512;
final List<Pair<Integer, List<Integer>>> futures = Collections.synchronizedList(new ArrayList<>(max));
IntStream.range(0, max).forEach(i -> {
final int yValue = i * 2;
final Bindings b = new SimpleBindings();
b.put("x", i);
b.put("y", yValue);
final int zValue = i * -1;
final String script = "z=" + zValue + ";[x,y,z]";
try {
service.submit(() -> {
try {
final List<Integer> result = (List<Integer>) gremlinExecutor.eval(script, b).get();
futures.add(Pair.with(i, result));
} catch (Exception ex) {
failed.set(true);
}
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
service.shutdown();
assertThat(service.awaitTermination(60000, TimeUnit.MILLISECONDS), is(true));
// likely a concurrency exception if it occurs - and if it does then we've messed up because that's what this
// test is partially designed to protected against.
assertThat(failed.get(), is(false));
assertEquals(max, futures.size());
futures.forEach(t -> {
assertEquals(t.getValue0(), t.getValue1().get(0));
assertEquals(t.getValue0() * 2, t.getValue1().get(1).intValue());
assertEquals(t.getValue0() * -1, t.getValue1().get(2).intValue());
});
}
@Test
public void shouldAllowConcurrentModificationOfGlobals() throws Exception {
// this test simulates a scenario that likely shouldn't happen - where globals are modified by multiple
// threads. globals are created in a synchronized fashion typically but it's possible that someone
// could do something like this and this test validate that concurrency exceptions don't occur as a
// result
final ExecutorService service = Executors.newFixedThreadPool(8, testingThreadFactory);
final Bindings globals = new SimpleBindings();
globals.put("g", -1);
final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
.addPlugins("gremlin-groovy", triggerPlugin)
.globalBindings(globals).create();
final AtomicBoolean failed = new AtomicBoolean(false);
final int max = 512;
final List<Pair<Integer, List<Integer>>> futures = Collections.synchronizedList(new ArrayList<>(max));
IntStream.range(0, max).forEach(i -> {
final int yValue = i * 2;
final Bindings b = new SimpleBindings();
b.put("x", i);
b.put("y", yValue);
final int zValue = i * -1;
final String script = "z=" + zValue + ";[x,y,z,g]";
try {
service.submit(() -> {
try {
// modify the global in a separate thread
gremlinExecutor.getScriptEngineManager().put("g", i);
gremlinExecutor.getScriptEngineManager().put(Integer.toString(i), i);
gremlinExecutor.getScriptEngineManager().getBindings().keySet().stream().filter(s -> i % 2 == 0 && !s.equals("g")).findFirst().ifPresent(globals::remove);
final List<Integer> result = (List<Integer>) gremlinExecutor.eval(script, b).get();
futures.add(Pair.with(i, result));
} catch (Exception ex) {
failed.set(true);
}
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
service.shutdown();
assertThat(service.awaitTermination(60000, TimeUnit.MILLISECONDS), is(true));
// likely a concurrency exception if it occurs - and if it does then we've messed up because that's what this
// test is partially designed to protected against.
assertThat(failed.get(), is(false));
assertEquals(max, futures.size());
futures.forEach(t -> {
assertEquals(t.getValue0(), t.getValue1().get(0));
assertEquals(t.getValue0() * 2, t.getValue1().get(1).intValue());
assertEquals(t.getValue0() * -1, t.getValue1().get(2).intValue());
assertThat(t.getValue1().get(3).intValue(), greaterThan(-1));
});
}
}