Allow VertexPrograms to declare their traverser requirements.
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/a0cbe2d2 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/a0cbe2d2 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/a0cbe2d2 Branch: refs/heads/TINKERPOP-1442-master Commit: a0cbe2d284a02cb69a0ef495372342b752dc88a4 Parents: 9004b4b Author: Daniel Kuppitz <daniel_kupp...@hotmail.com> Authored: Mon Sep 12 10:43:39 2016 +0200 Committer: Daniel Kuppitz <daniel_kupp...@hotmail.com> Committed: Thu Sep 15 12:34:28 2016 +0200 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 1 + .../gremlin/process/computer/VertexProgram.java | 11 + .../step/map/ProgramVertexProgramStep.java | 9 + .../process/computer/GraphComputerTest.java | 246 ++++++++++++++++++- .../decoration/TranslationStrategy.java | 10 +- 5 files changed, 270 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/a0cbe2d2/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 24ed601..b359f37 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -28,6 +28,7 @@ TinkerPop 3.2.3 (Release Date: NOT OFFICIALLY RELEASED YET) * Fixed a `JavaTranslator` bug where `Bytecode` instructions were being mutated during translation. * Added `Path` to Gremlin-Python with respective GraphSON 2.0 deserializer. +* VertexPrograms can now declare traverser requirements, e.g. to have access to the path when used with `.program()`. * Added missing `InetAddress` to GraphSON extension module. [[release-3-2-2]] http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/a0cbe2d2/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexProgram.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexProgram.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexProgram.java index 15243fa..1c8d0cb 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexProgram.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexProgram.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.computer; import org.apache.commons.configuration.Configuration; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -184,6 +185,16 @@ public interface VertexProgram<M> extends Cloneable { } /** + * The traverser requirements that are needed when this VP is used as part of a traversal. + * The default is an empty set. + * + * @return the traverser requirements + */ + public default Set<TraverserRequirement> getTraverserRequirements() { + return Collections.emptySet(); + } + + /** * When multiple workers on a single machine need VertexProgram instances, it is possible to use clone. * This will provide a speedier way of generating instances, over the {@link VertexProgram#storeState} and {@link VertexProgram#loadState} model. * The default implementation simply returns the object as it assumes that the VertexProgram instance is a stateless singleton. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/a0cbe2d2/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/step/map/ProgramVertexProgramStep.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/step/map/ProgramVertexProgramStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/step/map/ProgramVertexProgramStep.java index 31eb04b..49add72 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/step/map/ProgramVertexProgramStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/step/map/ProgramVertexProgramStep.java @@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.computer.Memory; import org.apache.tinkerpop.gremlin.process.computer.VertexProgram; import org.apache.tinkerpop.gremlin.process.computer.traversal.TraversalVertexProgram; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.PureTraversal; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.structure.Graph; @@ -32,6 +33,7 @@ import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import java.util.HashMap; import java.util.Map; +import java.util.Set; /** * @author Marko A. Rodriguez (http://markorodriguez.com) @@ -40,6 +42,7 @@ public final class ProgramVertexProgramStep extends VertexProgramStep { private final Map<String, Object> configuration; private final String toStringOfVertexProgram; + private final Set<TraverserRequirement> traverserRequirements; public ProgramVertexProgramStep(final Traversal.Admin traversal, final VertexProgram vertexProgram) { super(traversal); @@ -48,6 +51,7 @@ public final class ProgramVertexProgramStep extends VertexProgramStep { base.setDelimiterParsingDisabled(true); vertexProgram.storeState(base); this.toStringOfVertexProgram = vertexProgram.toString(); + this.traverserRequirements = vertexProgram.getTraverserRequirements(); } @Override @@ -62,6 +66,11 @@ public final class ProgramVertexProgramStep extends VertexProgramStep { } @Override + public Set<TraverserRequirement> getRequirements() { + return this.traverserRequirements; + } + + @Override public int hashCode() { return super.hashCode() ^ this.configuration.hashCode(); } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/a0cbe2d2/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/computer/GraphComputerTest.java ---------------------------------------------------------------------- diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/computer/GraphComputerTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/computer/GraphComputerTest.java index 761ae06..108550a 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/computer/GraphComputerTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/computer/GraphComputerTest.java @@ -18,34 +18,43 @@ */ package org.apache.tinkerpop.gremlin.process.computer; +import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationUtils; import org.apache.tinkerpop.gremlin.ExceptionCoverage; import org.apache.tinkerpop.gremlin.LoadGraphWith; import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest; import org.apache.tinkerpop.gremlin.process.computer.clustering.peerpressure.PeerPressureVertexProgram; import org.apache.tinkerpop.gremlin.process.computer.ranking.pagerank.PageRankVertexProgram; import org.apache.tinkerpop.gremlin.process.computer.traversal.TraversalVertexProgram; +import org.apache.tinkerpop.gremlin.process.computer.util.AbstractVertexProgramBuilder; import org.apache.tinkerpop.gremlin.process.computer.util.StaticMapReduce; import org.apache.tinkerpop.gremlin.process.computer.util.StaticVertexProgram; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.VerificationException; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import org.junit.Test; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -54,13 +63,17 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.GRATEFUL; import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN; +import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE; +import static org.apache.tinkerpop.gremlin.structure.T.id; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; /** * @author Marko A. Rodriguez (http://markorodriguez.com) @@ -94,6 +107,9 @@ import static org.junit.Assert.fail; @ExceptionCoverage(exceptionClass = Graph.Exceptions.class, methods = { "graphDoesNotSupportProvidedGraphComputer" }) +@ExceptionCoverage(exceptionClass = Path.Exceptions.class, methods = { + "shouldFailWithImproperTraverserRequirements" +}) @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public class GraphComputerTest extends AbstractGremlinProcessTest { @@ -1603,7 +1619,7 @@ public class GraphComputerTest extends AbstractGremlinProcessTest { graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.<Vertex>bothE("knows").has("weight", P.gt(0.5f))).program(new VertexProgramM(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>bothE().limit(0)).program(new VertexProgramM(VertexProgramM.VERTICES_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>outE().limit(1)).program(new VertexProgramM(VertexProgramM.ONE_OUT_EDGE_ONLY)).submit().get(); - graphProvider.getGraphComputer(graph).edges(__.outE()).program(new VertexProgramM(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); + graphProvider.getGraphComputer(graph).edges(outE()).program(new VertexProgramM(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); /// VERTEX PROGRAM + MAP REDUCE graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software")).program(new VertexProgramM(VertexProgramM.SOFTWARE_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.SOFTWARE_ONLY)).submit().get(); @@ -1613,7 +1629,7 @@ public class GraphComputerTest extends AbstractGremlinProcessTest { graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.<Vertex>bothE("knows").has("weight", P.gt(0.5f))).program(new VertexProgramM(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>bothE().limit(0)).program(new VertexProgramM(VertexProgramM.VERTICES_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.VERTICES_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>outE().limit(1)).program(new VertexProgramM(VertexProgramM.ONE_OUT_EDGE_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.ONE_OUT_EDGE_ONLY)).submit().get(); - graphProvider.getGraphComputer(graph).edges(__.outE()).program(new VertexProgramM(VertexProgramM.OUT_EDGES_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); + graphProvider.getGraphComputer(graph).edges(outE()).program(new VertexProgramM(VertexProgramM.OUT_EDGES_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); /// MAP REDUCE ONLY graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software")).mapReduce(new MapReduceJ(VertexProgramM.SOFTWARE_ONLY)).submit().get(); @@ -1623,7 +1639,7 @@ public class GraphComputerTest extends AbstractGremlinProcessTest { graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.<Vertex>bothE("knows").has("weight", P.gt(0.5f))).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>bothE().limit(0)).mapReduce(new MapReduceJ(VertexProgramM.VERTICES_ONLY)).submit().get(); graphProvider.getGraphComputer(graph).edges(__.<Vertex>outE().limit(1)).mapReduce(new MapReduceJ(VertexProgramM.ONE_OUT_EDGE_ONLY)).submit().get(); - graphProvider.getGraphComputer(graph).edges(__.outE()).mapReduce(new MapReduceJ(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); + graphProvider.getGraphComputer(graph).edges(outE()).mapReduce(new MapReduceJ(VertexProgramM.OUT_EDGES_ONLY)).submit().get(); // EXCEPTION HANDLING try { @@ -2327,4 +2343,226 @@ public class GraphComputerTest extends AbstractGremlinProcessTest { return GraphComputer.Persist.VERTEX_PROPERTIES; } } -} + + /////////////////////////////////// + + @Test + @LoadGraphWith(MODERN) + public void shouldSucceedWithProperTraverserRequirements() throws Exception { + + final AtomicInteger counter = new AtomicInteger(0); + final Map<String, Object> idsByName = new HashMap<>(); + final VertexProgramQ vp = VertexProgramQ.build().from("a").property("coworkers").create(); + + g.V().hasLabel("person").filter(outE("created")).valueMap(true, "name").forEachRemaining((Map map) -> + idsByName.put((String) ((List) map.get("name")).get(0), map.get(id))); + + try { + g.V().as("a").out("created").in("created").program(vp).dedup() + .valueMap("name", "coworkers").forEachRemaining((Map<String, Object> map) -> { + + final String name = (String) ((List) map.get("name")).get(0); + final Map<Object, Long> coworkers = (Map<Object, Long>) ((List) map.get("coworkers")).get(0); + assertTrue(idsByName.containsKey(name)); + assertEquals(2, coworkers.size()); + idsByName.keySet().stream().filter(cn -> !cn.equals(name)).forEach(cn -> { + final Object cid = idsByName.get(cn); + assertTrue(coworkers.containsKey(cid)); + assertEquals(1L, coworkers.get(cid).longValue()); + }); + counter.incrementAndGet(); + }); + + assertEquals(3, counter.intValue()); + } catch (VerificationException ex) { + assumeNoException(ex); + } + } + + @Test + @LoadGraphWith(MODERN) + public void shouldFailWithImproperTraverserRequirements() throws Exception { + + final AtomicInteger counter = new AtomicInteger(0); + final Map<String, Object> idsByName = new HashMap<>(); + final VertexProgramQ vp = VertexProgramQ.build().from("a").property("coworkers"). + useTraverserRequirements(false).create(); + + g.V().hasLabel("person").filter(outE("created")).valueMap(true, "name").forEachRemaining((Map map) -> + idsByName.put((String) ((List) map.get("name")).get(0), map.get(id))); + + try { + g.V().as("a").out("created").in("created").program(vp).dedup() + .valueMap("name", "coworkers").forEachRemaining((Map<String, Object> map) -> { + + final String name = (String) ((List) map.get("name")).get(0); + final Map coworkers = (Map) ((List) map.get("coworkers")).get(0); + assertTrue(idsByName.containsKey(name)); + assertTrue(coworkers.isEmpty()); + counter.incrementAndGet(); + }); + + assertEquals(3, counter.intValue()); + } catch (VerificationException ex) { + assumeNoException(ex); + } + } + + private static class VertexProgramQ implements VertexProgram<Object> { + + private static final String VERTEX_PROGRAM_Q_CFG_PREFIX = "gremlin.vertexProgramQ"; + private static final String MAP_KEY_CFG_KEY = VERTEX_PROGRAM_Q_CFG_PREFIX + ".source"; + private static final String PROPERTY_CFG_KEY = VERTEX_PROGRAM_Q_CFG_PREFIX + ".property"; + private static final String USE_TRAVERSER_REQUIREMENTS_CFG_KEY = VERTEX_PROGRAM_Q_CFG_PREFIX + ".useTraverserRequirements"; + + private final Set<VertexComputeKey> elementComputeKeys; + private Configuration configuration; + private String sourceKey; + private String propertyKey; + private Set<TraverserRequirement> traverserRequirements; + + private VertexProgramQ() { + elementComputeKeys = new HashSet<>(); + } + + @Override + public void storeState(final Configuration config) { + VertexProgram.super.storeState(config); + if (configuration != null) { + ConfigurationUtils.copy(configuration, config); + } + } + + @Override + public void loadState(final Graph graph, final Configuration config) { + configuration = new BaseConfiguration(); + if (config != null) { + ConfigurationUtils.copy(config, configuration); + } + sourceKey = configuration.getString(MAP_KEY_CFG_KEY); + propertyKey = configuration.getString(PROPERTY_CFG_KEY); + traverserRequirements = configuration.getBoolean(USE_TRAVERSER_REQUIREMENTS_CFG_KEY, true) + ? Collections.singleton(TraverserRequirement.LABELED_PATH) : Collections.emptySet(); + elementComputeKeys.add(VertexComputeKey.of(propertyKey, false)); + } + + @Override + public void setup(final Memory memory) { + } + + @Override + public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) { + final Property<TraverserSet> haltedTraversers = vertex.property(TraversalVertexProgram.HALTED_TRAVERSERS); + if (!haltedTraversers.isPresent()) return; + final Iterator iterator = haltedTraversers.value().iterator(); + if (iterator.hasNext()) { + List<Map.Entry<Object, Long>> list = new ArrayList<>(); + while (iterator.hasNext()) { + final Traverser t = (Traverser) iterator.next(); + try { + final Vertex source = (Vertex) t.path(sourceKey); + if (!source.id().equals(vertex.id())) { + final Map.Entry<Object, Long> entry = new AbstractMap.SimpleEntry<>(source.id(), t.bulk()); + list.add(entry); + } + assertFalse(traverserRequirements.isEmpty()); + } catch (Exception ex) { + assertTrue(traverserRequirements.isEmpty()); + validateException(Path.Exceptions.stepWithProvidedLabelDoesNotExist(sourceKey), ex); + } + } + final Map<Object, Number> map = new HashMap<>(list.size(), 1f); + for (Map.Entry<Object, Long> entry : list) map.put(entry.getKey(), entry.getValue()); + vertex.property(propertyKey, map); + } + } + + @Override + public boolean terminate(final Memory memory) { + return memory.isInitialIteration(); + } + + @Override + public Set<MessageScope> getMessageScopes(final Memory memory) { + return Collections.emptySet(); + } + + @Override + public Set<VertexComputeKey> getVertexComputeKeys() { + return elementComputeKeys; + } + + @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "CloneDoesntCallSuperClone"}) + @Override + public VertexProgram<Object> clone() { + return this; + } + + @Override + public GraphComputer.ResultGraph getPreferredResultGraph() { + return GraphComputer.ResultGraph.NEW; + } + + @Override + public GraphComputer.Persist getPreferredPersist() { + return GraphComputer.Persist.VERTEX_PROPERTIES; + } + + @Override + public Set<TraverserRequirement> getTraverserRequirements() { + return this.traverserRequirements; + } + + @Override + public Features getFeatures() { + return new Features() { + @Override + public boolean requiresVertexPropertyAddition() { + return true; + } + }; + } + + public static Builder build() { + return new Builder(); + } + + static class Builder extends AbstractVertexProgramBuilder<Builder> { + + private Builder() { + super(VertexProgramQ.class); + } + + @SuppressWarnings("unchecked") + @Override + public VertexProgramQ create(final Graph graph) { + if (graph != null) { + ConfigurationUtils.append(graph.configuration().subset(VERTEX_PROGRAM_Q_CFG_PREFIX), configuration); + } + return (VertexProgramQ) VertexProgram.createVertexProgram(graph, configuration); + } + + public VertexProgramQ create() { + return create(null); + } + + public Builder from(final String label) { + configuration.setProperty(MAP_KEY_CFG_KEY, label); + return this; + } + + public Builder property(final String name) { + configuration.setProperty(PROPERTY_CFG_KEY, name); + return this; + } + + /** + * This is only configurable for the purpose of testing. In a real-world VP this would be a bad pattern. + */ + public Builder useTraverserRequirements(final boolean value) { + configuration.setProperty(USE_TRAVERSER_REQUIREMENTS_CFG_KEY, value); + return this; + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/a0cbe2d2/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java ---------------------------------------------------------------------- diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java index 92c9483..6b06d60 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration; import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine; import org.apache.tinkerpop.gremlin.jsr223.SingleGremlinScriptEngineManager; +import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ProgramVertexProgramStep; import org.apache.tinkerpop.gremlin.process.remote.traversal.strategy.decoration.RemoteStrategy; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.Step; @@ -70,9 +71,12 @@ public final class TranslationStrategy extends AbstractTraversalStrategy<Travers return; // verifications to ensure unsupported steps do not exist in the traversal - if (Boolean.valueOf(System.getProperty("is.testing", "false")) && - (traversal.getBytecode().toString().contains("$") || traversal.getBytecode().toString().contains("HashSetSupplier"))) - throw new VerificationException("Test suite does not support lambdas", traversal); + if (Boolean.valueOf(System.getProperty("is.testing", "false"))) { + if (traversal.getBytecode().toString().contains("$") || traversal.getBytecode().toString().contains("HashSetSupplier")) + throw new VerificationException("Test suite does not support lambdas", traversal); + if (TraversalHelper.hasStepOfAssignableClassRecursively(ProgramVertexProgramStep.class, traversal)) + throw new VerificationException("Test suite does not support embedded vertex programs", traversal); + } final Traversal.Admin<?, ?> translatedTraversal; final Bytecode bytecode = Boolean.valueOf(System.getProperty("is.testing", "false")) ?