package com.tinkerpop.gremlin.groovy.jsr223; import com.tinkerpop.blueprints.Graph; import com.tinkerpop.blueprints.Index; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.blueprints.impls.tg.TinkerGraph; import com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory; import com.tinkerpop.pipes.util.Pipeline; import junit.framework.Assert; import junit.framework.TestCase; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptEngine; import javax.script.ScriptException; import java.nio.channels.Pipe; import java.util.*; import java.util.concurrent.CountDownLatch; /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ public class GremlinGroovyScriptEngineTest extends TestCase { public void testGremlinLoading() throws Exception { ScriptEngine engine = new GremlinGroovyScriptEngine(); List list = new ArrayList(); engine.put("g", TinkerGraphFactory.createTinkerGraph()); engine.put("list", list); assertEquals(list.size(), 0); engine.eval("g.v(1).outE.inV._.fill(list)"); assertEquals(list.size(), 3); } public void testImports() throws Exception { ScriptEngine engine = new GremlinGroovyScriptEngine(); engine.eval("Vertex.class.getName()"); engine.eval("new TinkerGraph()"); engine.eval("TinkerGraphFactory.createTinkerGraph().V.hasNot('age',null).has('age',T.gt,25).count()"); engine.eval("TinkerGraphFactory.createTinkerGraph().getVertex(1).getVertices(OUT)"); engine.eval("TinkerGraphFactory.createTinkerGraph().getVertex(1).getEdges(BOTH)"); engine.eval("TinkerGraphFactory.createTinkerGraph().getVertex(1).getEdges(IN)"); engine.eval("Direction.OUT.toString(); Direction.IN.toString(); Direction.BOTH.toString()"); engine.eval("SUCCESS.toString(); FAILURE.toString()"); engine.eval("TransactionalGraph.Conclusion.SUCCESS.toString(); TransactionalGraph.Conclusion.FAILURE.toString()"); } public void testBindings() throws Exception { final TinkerGraph g = TinkerGraphFactory.createTinkerGraph(); final ScriptEngine engine = new GremlinGroovyScriptEngine(); assertTrue(engine.eval("g = TinkerGraphFactory.createTinkerGraph()") instanceof TinkerGraph); assertTrue(engine.get("g") instanceof TinkerGraph); assertEquals(engine.eval("g.v(1)"), g.getVertex(1)); final Bindings bindings = engine.createBindings(); bindings.put("g", g); bindings.put("s", "marko"); bindings.put("f", 0.5f); bindings.put("i", 1); bindings.put("b", true); bindings.put("l", 100l); bindings.put("d", 1.55555d); assertEquals(g.getEdge(7), engine.eval("g.E.has('weight',f).next()", bindings)); assertEquals(g.getVertex(1), engine.eval("g.V.has('name',s).next()", bindings)); assertEquals(g.getVertex(1), engine.eval("g.V.sideEffect{it.bbb=it.name=='marko'}.iterate();g.V.has('bbb',b).next()", bindings)); assertEquals(g.getVertex(1), engine.eval("g.V.sideEffect{it.iii=it.name=='marko'?1:0}.iterate();g.V.has('iii',i).next()", bindings)); assertEquals(g.getVertex(1), engine.eval("g.V.sideEffect{it.lll=it.name=='marko'?100l:0l}.iterate();g.V.has('lll',l).next()", bindings)); assertEquals(g.getVertex(1), engine.eval("g.V.sideEffect{it.ddd=it.name=='marko'?1.55555d:0}.iterate();g.V.has('ddd',d).next()", bindings)); } public void testThreadSafetyOnEngine() throws Exception { final ScriptEngine engine = new GremlinGroovyScriptEngine(10); int runs = 500; final CountDownLatch latch = new CountDownLatch(runs); final List<String> names = Arrays.asList("marko", "peter", "josh", "vadas", "stephen", "pavel", "matthias"); final Random random = new Random(); for (int i = 0; i < runs; i++) { new Thread() { public void run() { String name = names.get(random.nextInt(names.size() - 1)); try { final Bindings bindings = engine.createBindings(); bindings.put("g", TinkerGraphFactory.createTinkerGraph()); bindings.put("name", name); final Object result = engine.eval("pipe = g.V('name',name); if(pipe.hasNext()) { pipe.out.count() } else { null }", bindings); if (name.equals("stephen") || name.equals("pavel") || name.equals("matthias")) assertNull(result); else assertNotNull(result); } catch (ScriptException e) { //System.out.println(e); assertFalse(true); } latch.countDown(); } }.start(); } latch.await(); } public void testThreadSafetyOnCompiledScript() throws Exception { final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(10); final CompiledScript script = engine.compile("pipe = g.V('name',name); if(pipe.hasNext()) { pipe.out.count() } else { null }"); int runs = 500; final CountDownLatch latch = new CountDownLatch(runs); final List<String> names = Arrays.asList("marko", "peter", "josh", "vadas", "stephen", "pavel", "matthias"); final Random random = new Random(); for (int i = 0; i < runs; i++) { new Thread() { public void run() { String name = names.get(random.nextInt(names.size() - 1)); try { final Bindings bindings = engine.createBindings(); bindings.put("g", TinkerGraphFactory.createTinkerGraph()); bindings.put("name", name); Object result = script.eval(bindings); if (name.equals("stephen") || name.equals("pavel") || name.equals("matthias")) assertNull(result); else assertNotNull(result); } catch (ScriptException e) { //System.out.println(e); assertFalse(true); } latch.countDown(); } }.start(); } latch.await(); } public void testEngineVsCompiledCosts() throws Exception { final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(); Bindings bindings = engine.createBindings(); bindings.put("g", TinkerGraphFactory.createTinkerGraph()); int runs = 1000; long totalTime = 0l; for (int i = 0; i < runs; i++) { long time = System.currentTimeMillis(); CompiledScript script = engine.compile("g.v(1).out().count()"); script.eval(bindings); totalTime += System.currentTimeMillis() - time; } System.out.println("Multi-compiled script runtime for " + runs + " runs: " + totalTime); totalTime = 0l; for (int i = 0; i < runs; i++) { long time = System.currentTimeMillis(); engine.eval("g.v(1).out().count()", bindings); totalTime += System.currentTimeMillis() - time; } System.out.println("Evaluated script runtime for " + runs + " runs: " + totalTime); totalTime = 0l; CompiledScript script = engine.compile("g.v(1).out().count()"); for (int i = 0; i < runs; i++) { long time = System.currentTimeMillis(); script.eval(bindings); totalTime += System.currentTimeMillis() - time; } System.out.println("Compiled script runtime for " + runs + " runs: " + totalTime); } /** * Tries to force the out of memory exceptions that commonly seem to happen with scriptengine. */ public void testBlowTheHeap() throws ScriptException { final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(); final Graph g = TinkerGraphFactory.createTinkerGraph(); final String[] gremlins = new String[]{ "g.v(xxx).out.toList()", "g.v(xxx).in.toList()", "g.v(xxx).out.loop(1){true}{true}.toList()", "g.v(xxx).groupCount.cap.next()" }; /******************START PARAMETERIZE GREMLIN*************** * parameterized gremlin doesn't blow the heap */ long parameterizedStartTime = System.currentTimeMillis(); System.out.println("Try to blow the heap with parameterized Gremlin."); try { for (int ix = 0; ix < 50001; ix++) { final Bindings bindings = engine.createBindings(); bindings.put("g", g); bindings.put("xxx", ((ix % 4) + 1)); Object x = engine.eval(gremlins[ix % 4], bindings); if (ix > 0 && ix % 5000 == 0) { System.out.println(String.format("%s scripts processed in %s (ms) - rate %s (ms/q).", ix, System.currentTimeMillis() - parameterizedStartTime, Double.valueOf(System.currentTimeMillis() - parameterizedStartTime) / Double.valueOf(ix))); } } } catch (OutOfMemoryError oome) { assertTrue(false); } /*************************DONE PARAMETERIZE GREMLIN*****************/ long notParameterizedStartTime = System.currentTimeMillis(); System.out.println("Try to blow the heap with non-parameterized Gremlin."); try { for (int ix = 0; ix < 15001; ix++) { final Bindings bindings = engine.createBindings(); bindings.put("g", g); engine.eval(String.format("g.v(%s)", ix), bindings); if (ix > 0 && ix % 5000 == 0) { System.out.println(String.format("%s scripts processed in %s (ms) - rate %s (ms/q).", ix, System.currentTimeMillis() - notParameterizedStartTime, Double.valueOf(System.currentTimeMillis() - notParameterizedStartTime) / Double.valueOf(ix))); } } } catch (OutOfMemoryError oome) { assertTrue(false); } } public void testFunctionsUsedInClosure() throws ScriptException { GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(); final Graph g = TinkerGraphFactory.createTinkerGraph(); final Bindings bindings = engine.createBindings(); bindings.put("g", g); // this works on its own when the function and the line that uses it is in one "script". this is the // current workaround assertEquals(g.getVertex(2), engine.eval("def isVadas(v){v.name=='vadas'};g.V.filter{isVadas(it)}.next()", bindings)); // let's reset this piece and make sure isVadas is not hanging around. engine = new GremlinGroovyScriptEngine(); // validate that isVadas throws an exception since it is not defined try { engine.eval("isVadas(g.v(2))", bindings); // fail the test if the above doesn't throw an exception fail(); } catch (Exception ex) { // this is good...we want this. it means isVadas isn't hanging about } // now...define the function separately on its own in one script engine.eval("def isVadas(v){v.name=='vadas'}", bindings); // make sure the function works on its own...no problem assertEquals(true, engine.eval("isVadas(g.v(2))", bindings)); // make sure the function works in a closure...this generates a StackOverflowError assertEquals(g.getVertex(2), engine.eval("g.V.filter{isVadas(it)}.next()", bindings)); } public void untestUsingClassesIsGood() throws ScriptException { GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(); final Graph g = TinkerGraphFactory.createTinkerGraph(); final Bindings bindings = engine.createBindings(); bindings.put("g", g); // works when it's all defined together assertEquals(true, engine.eval("class c { static def isVadas(v){v.name=='vadas'}};c.isVadas(g.v(2))", bindings)); // let's reset this piece and make sure isVadas is not hanging around. engine = new GremlinGroovyScriptEngine(); // validate that isVadas throws an exception since it is not defined try { engine.eval("c.isVadas(g.v(2))", bindings); // fail the test if the above doesn't throw an exception fail(); } catch (Exception ex) { // this is good...we want this. it means isVadas isn't hanging about } // now...define the class separately on its own in one script... // HERE'S and AWKWARD BIT......... // YOU HAVE TO END WITH: null; // ....OR ELSE YOU GET: // javax.script.ScriptException: javax.script.ScriptException: // org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack: No signature of method: c.main() // is applicable for argument types: ([Ljava.lang.String;) values: [[]] // WOULD BE NICE IF WE DIDN'T HAVE TO DO THAT engine.eval("class c { static def isVadas(v){v.name=='vadas'}};null;", bindings); // make sure the class works on its own...this generates: groovy.lang.MissingPropertyException: No such property: c for class: Script2 assertEquals(true, engine.eval("c.isVadas(g.v(2))", bindings)); } public void testGremlinScriptEngineWithScriptBaseClass() throws Exception { ScriptEngine engine = new GremlinGroovyScriptEngine(); List list = new ArrayList(); engine.put("g", TinkerGraphFactory.createTinkerGraph()); engine.put("list", list); assertEquals(list.size(), 0); engine.eval("g.V.fill(list)"); assertEquals(6, list.size()); list.clear(); TinkerGraph tinkerGraph = TinkerGraphFactory.createTinkerGraph(); Vertex marko = tinkerGraph.getVertex(1L); Assert.assertEquals("marko", marko.getProperty("name")); marko.setProperty("deleted", new Date()); engine = new GremlinGroovyScriptEngine("com.tinkerpop.gremlin.groovy.basescript.GremlinGroovyScriptBaseClassForTest"); engine.put("g", tinkerGraph); engine.put("list", list); assertEquals(list.size(), 0); String groovy = "g.V.fill(list)"; groovy = "useInterceptor( GremlinGroovyPipeline, com.tinkerpop.gremlin.groovy.basescript.GremlinGroovyPipelineInterceptor) {" + groovy + "}"; engine.eval(groovy); assertEquals(5, list.size()); } public void testGremlinScriptEngineWithUTF8Characters() throws Exception { ScriptEngine engine = new GremlinGroovyScriptEngine(); assertEquals("轉注", engine.eval("'轉注'")); } public void testUTF8Query() throws Exception { TinkerGraph graph = new TinkerGraph(); Index<Vertex> index = graph.createIndex("nodes", Vertex.class); Vertex nonUtf8 = graph.addVertex("1"); nonUtf8.setProperty("name", "marko"); nonUtf8.setProperty("age", 29); index.put("name", "marko", nonUtf8); Vertex utf8Name = graph.addVertex("2"); utf8Name.setProperty("name", "轉注"); utf8Name.setProperty("age", 32); index.put("name", "轉注", utf8Name); graph.addVertex(utf8Name); graph.addEdge("12", nonUtf8, utf8Name, "created").setProperty("weight", 0.2f); ScriptEngine engine = new GremlinGroovyScriptEngine(); engine.put("g", graph); Pipeline eval = (Pipeline) engine.eval("g.idx(\"nodes\")[['name' : 'marko']]"); assertEquals(nonUtf8, eval.next()); eval = (Pipeline) engine.eval("g.idx(\"nodes\")[['name' : '轉注']]"); assertEquals(utf8Name, eval.next()); } }