All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tinkerpop.gremlin.process.computer.GraphComputerTest Maven / Gradle / Ivy

There is a newer version: 3.7.3
Show newest version
/*
 * 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.process.computer;

import org.apache.commons.configuration.Configuration;
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.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.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.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.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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Future;

import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.GRATEFUL;
import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
@ExceptionCoverage(exceptionClass = GraphComputer.Exceptions.class, methods = {
        "providedKeyIsNotAMemoryComputeKey",
        "computerHasNoVertexProgramNorMapReducers",
        "computerHasAlreadyBeenSubmittedAVertexProgram",
        "providedKeyIsNotAnElementComputeKey",
        "incidentAndAdjacentElementsCanNotBeAccessedInMapReduce",
        "adjacentVertexLabelsCanNotBeRead",
        "adjacentVertexPropertiesCanNotBeReadOrUpdated",
        "adjacentVertexEdgesAndVerticesCanNotBeReadOrUpdated",
        "resultGraphPersistCombinationNotSupported",
        "vertexPropertiesCanNotBeUpdatedInMapReduce",
        "computerRequiresMoreWorkersThanSupported",
        "vertexFilterAccessesIncidentEdges",
        "edgeFilterAccessesAdjacentVertices",
        "graphFilterNotSupported"
})
@ExceptionCoverage(exceptionClass = Memory.Exceptions.class, methods = {
        "memoryKeyCanNotBeEmpty",
        "memoryKeyCanNotBeNull",
        "memoryValueCanNotBeNull",
        "memoryIsCurrentlyImmutable",
        "memoryDoesNotExist",
        "memorySetOnlyDuringVertexProgramSetUpAndTerminate",
        "memoryAddOnlyDuringVertexProgramExecute",
        "adjacentVertexEdgesAndVerticesCanNotBeReadOrUpdated"
})
@ExceptionCoverage(exceptionClass = Graph.Exceptions.class, methods = {
        "graphDoesNotSupportProvidedGraphComputer"
})
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class GraphComputerTest extends AbstractGremlinProcessTest {

    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveStandardStringRepresentation() {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        assertEquals(StringFactory.graphComputerString(computer), computer.toString());
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowWithNoVertexProgramNorMapReducers() throws Exception {
        try {
            graphProvider.getGraphComputer(graph).submit().get();
            fail("Should throw an IllegalStateException when there is no vertex program nor map reducers");
        } catch (Exception ex) {
            validateException(GraphComputer.Exceptions.computerHasNoVertexProgramNorMapReducers(), ex);
        }
    }


    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowBadGraphComputers() {
        try {
            graph.compute(BadGraphComputer.class);
            fail("Providing a bad graph computer class should fail");
        } catch (Exception ex) {
            validateException(Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(BadGraphComputer.class), ex);
        }
        if (!new BadGraphComputer().features().supportsGraphFilter()) {
            try {
                new BadGraphComputer().vertices(__.hasLabel("software"));
                fail("Should throw an unsupported operation exception");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.graphFilterNotSupported().getMessage(), e.getMessage());
            }
            try {
                new BadGraphComputer().edges(__.bothE());
                fail("Should throw an unsupported operation exception");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.graphFilterNotSupported().getMessage(), e.getMessage());
            }
        } else {
            fail("Should not support graph filter: " + BadGraphComputer.class);
        }
    }

    public static class BadGraphComputer implements GraphComputer {

        @Override
        public GraphComputer result(final ResultGraph resultGraph) {
            return null;
        }

        @Override
        public GraphComputer persist(final Persist persist) {
            return null;
        }

        @Override
        public GraphComputer program(final VertexProgram vertexProgram) {
            return null;
        }

        @Override
        public GraphComputer mapReduce(final MapReduce mapReduce) {
            return null;
        }

        @Override
        public GraphComputer workers(final int workers) {
            return null;
        }

        @Override
        public GraphComputer vertices(final Traversal vertexFilter) {
            throw GraphComputer.Exceptions.graphFilterNotSupported();
        }

        @Override
        public GraphComputer edges(final Traversal edgeFilter) {
            throw GraphComputer.Exceptions.graphFilterNotSupported();
        }

        @Override
        public GraphComputer configure(final String key, final Object value) {
            return null;
        }

        @Override
        public Future submit() {
            return null;
        }

        public GraphComputer.Features features() {
            return new Features() {
                @Override
                public boolean supportsGraphFilter() {
                    return false;
                }
            };
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveImmutableComputeResultMemory() throws Exception {
        final ComputerResult results = graphProvider.getGraphComputer(graph).program(new VertexProgramB()).submit().get();

        try {
            results.memory().set("set", "test");
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().add("incr", 1);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().add("and", true);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }

        try {
            results.memory().add("or", false);
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryIsCurrentlyImmutable(), ex);
        }
    }

    public static class VertexProgramB extends StaticVertexProgram {
        @Override
        public void setup(final Memory memory) {
            assertEquals(0, memory.getIteration());
            assertTrue(memory.isInitialIteration());
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            assertEquals(0, memory.getIteration());
            assertTrue(memory.isInitialIteration());
        }

        @Override
        public boolean terminate(final Memory memory) {
            assertEquals(0, memory.getIteration());
            assertTrue(memory.isInitialIteration());
            return true;
        }

        @Override
        public Set getMemoryComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    MemoryComputeKey.of("set", Operator.assign, true, false),
                    MemoryComputeKey.of("incr", Operator.sum, true, false),
                    MemoryComputeKey.of("and", Operator.and, true, false),
                    MemoryComputeKey.of("or", Operator.or, true, false)));
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.ORIGINAL;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowNullMemoryKeys() throws Exception {
        try {
            graphProvider.getGraphComputer(graph).program(new VertexProgramC()).submit().get();
            fail("Providing null memory key should fail");
        } catch (Exception ex) {
            // validateException(Memory.Exceptions.memoryKeyCanNotBeNull(), ex);
        }
    }

    public static class VertexProgramC extends StaticVertexProgram {
        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {

        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getMemoryComputeKeys() {
            return Collections.singleton(MemoryComputeKey.of(null, Operator.or, true, false));
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.ORIGINAL;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowEmptyMemoryKeys() throws Exception {
        try {
            graphProvider.getGraphComputer(graph).program(new VertexProgramD()).submit().get();
            fail("Providing empty memory key should fail");
        } catch (Exception ex) {
            validateException(Memory.Exceptions.memoryKeyCanNotBeEmpty(), ex);
        }
    }

    public static class VertexProgramD extends StaticVertexProgram {
        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {

        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getMemoryComputeKeys() {
            return Collections.singleton(MemoryComputeKey.of("", Operator.or, true, false));
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.ORIGINAL;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    ////////////////////////////////////////////

    ////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldHandleUndeclaredMemoryKeysCorrectly() throws Exception {
        graphProvider.getGraphComputer(graph).program(new VertexProgramE()).submit().get();
    }

    public static class VertexProgramE extends StaticVertexProgram {
        @Override
        public void setup(final Memory memory) {
            try {
                memory.get("a");
                fail("The memory key does not exist and should fail");
            } catch (Exception e) {
                validateException(Memory.Exceptions.memoryDoesNotExist("a"), e);
            }
            try {
                memory.set("a", true);
                fail("Setting a memory key that wasn't declared should fail");
            } catch (Exception e) {
                validateException(GraphComputer.Exceptions.providedKeyIsNotAMemoryComputeKey("a"), e);
            }
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            try {
                memory.get("a");
                fail("The memory key does not exist and should fail");
            } catch (Exception e) {
                validateException(Memory.Exceptions.memoryDoesNotExist("a"), e);
            }
            try {
                memory.add("a", true);
                fail("Setting a memory key that wasn't declared should fail");
            } catch (Exception e) {
                validateException(GraphComputer.Exceptions.providedKeyIsNotAMemoryComputeKey("a"), e);
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            try {
                memory.get("a");
                fail("The memory key does not exist and should fail");
            } catch (Exception e) {
                validateException(Memory.Exceptions.memoryDoesNotExist("a"), e);
            }
            try {
                memory.set("a", true);
                // fail("Setting a memory key that wasn't declared should fail");
            } catch (Exception e) {
                validateException(GraphComputer.Exceptions.providedKeyIsNotAMemoryComputeKey("a"), e);
            }
            return true;
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.ORIGINAL;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    ////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldNotAllowTheSameComputerToExecutedTwice() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph).program(new VertexProgramA());
        computer.submit().get(); // this should work as its the first run of the graph computer

        try {
            computer.submit(); // this should fail as the computer has already been executed
            fail("Using the same graph computer to compute again should not be possible");
        } catch (IllegalStateException e) {

        } catch (Exception e) {
            fail("Should yield an illegal state exception for graph computer being executed twice");
        }

        // test no rerun of graph computer
        try {
            computer.submit(); // this should fail as the computer has already been executed even through new program submitted
            fail("Using the same graph computer to compute again should not be possible");
        } catch (IllegalStateException e) {

        } catch (Exception e) {
            fail("Should yield an illegal state exception for graph computer being executed twice");
        }
    }

    public static class VertexProgramA extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {

        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.ORIGINAL;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldHaveConsistentMemoryVertexPropertiesAndExceptions() throws Exception {
        ComputerResult results = graphProvider.getGraphComputer(graph).program(new VertexProgramF()).submit().get();
        assertEquals(1, results.memory().getIteration());
        assertEquals(2, results.memory().asMap().size());
        assertEquals(2, results.memory().keys().size());
        assertTrue(results.memory().keys().contains("a"));
        assertTrue(results.memory().keys().contains("b"));
        assertTrue(results.memory().getRuntime() >= 0);

        assertEquals(12, results.memory().get("a").intValue());   // 2 iterations
        assertEquals(28, results.memory().get("b").intValue());
        try {
            results.memory().get("BAD");
            fail("Should throw an IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals(Memory.Exceptions.memoryDoesNotExist("BAD").getMessage(), e.getMessage());
        }
        assertEquals(Long.valueOf(0), results.graph().traversal().V().count().next()); // persist new/nothing.

        results.graph().traversal().V().forEachRemaining(v -> {
            assertTrue(v.property("nameLengthCounter").isPresent());
            assertEquals(Integer.valueOf(v.value("name").length() * 2), Integer.valueOf(v.value("nameLengthCounter")));
        });
    }

    public static class VertexProgramF extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            try {
                vertex.property(VertexProperty.Cardinality.single, "blah", "blah");
                fail("Should throw an IllegalArgumentException");
            } catch (final IllegalArgumentException e) {
                assertEquals(GraphComputer.Exceptions.providedKeyIsNotAnElementComputeKey("blah").getMessage(), e.getMessage());
            } catch (final Exception e) {
                fail("Should throw an IllegalArgumentException: " + e);
            }

            memory.add("a", 1);
            if (memory.isInitialIteration()) {
                vertex.property(VertexProperty.Cardinality.single, "nameLengthCounter", vertex.value("name").length());
                memory.add("b", vertex.value("name").length());
            } else {
                vertex.property(VertexProperty.Cardinality.single, "nameLengthCounter", vertex.value("name").length() + vertex.value("nameLengthCounter"));
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            return memory.getIteration() == 1;
        }

        @Override
        public Set getVertexComputeKeys() {
            return Collections.singleton(VertexComputeKey.of("nameLengthCounter", false));
        }

        @Override
        public Set getMemoryComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    MemoryComputeKey.of("a", Operator.sum, true, false),
                    MemoryComputeKey.of("b", Operator.sum, true, false)));
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldAndOrIncrCorrectlyThroughSubStages() throws Exception {
        ComputerResult results = graphProvider.getGraphComputer(graph).program(new VertexProgramG()).submit().get();
        assertEquals(2, results.memory().getIteration());
        assertEquals(6, results.memory().asMap().size());
        assertEquals(6, results.memory().keys().size());
        assertTrue(results.memory().keys().contains("a"));
        assertTrue(results.memory().keys().contains("b"));
        assertTrue(results.memory().keys().contains("c"));
        assertTrue(results.memory().keys().contains("d"));
        assertTrue(results.memory().keys().contains("e"));
        assertTrue(results.memory().keys().contains("f"));

        assertEquals(Long.valueOf(18), results.memory().get("a"));
        assertEquals(Long.valueOf(0), results.memory().get("b"));
        assertFalse(results.memory().get("c"));
        assertTrue(results.memory().get("d"));
        assertTrue(results.memory().get("e"));
        assertEquals(3, results.memory().get("f").intValue());
    }

    public static class VertexProgramG extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {
            memory.set("a", 0l);
            memory.set("b", 0l);
            memory.set("c", true);
            memory.set("d", false);
            memory.set("e", true);
            memory.set("f", memory.getIteration());
            try {
                memory.add("a", 0l);
                fail("Should only allow Memory.set() during VertexProgram.setup()");
            } catch (final Exception e) {
                validateException(Memory.Exceptions.memoryAddOnlyDuringVertexProgramExecute("a"), e);
            }
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            // test current step values
            assertEquals(Long.valueOf(6 * memory.getIteration()), memory.get("a"));
            assertEquals(Long.valueOf(0), memory.get("b"));
            if (memory.isInitialIteration()) {
                assertTrue(memory.get("c"));
                assertFalse(memory.get("d"));
            } else {
                assertFalse(memory.get("c"));
                assertTrue(memory.get("d"));
            }
            assertTrue(memory.get("e"));
            assertEquals(memory.getIteration(), memory.get("f").intValue());

            // update current step values
            memory.add("a", 1l);
            memory.add("b", 1l);
            memory.add("c", false);
            memory.add("d", true);
            memory.add("e", false);
            memory.add("f", memory.getIteration() + 1);

            // test current step values, should be the same as previous prior to update
            assertEquals(Long.valueOf(6 * memory.getIteration()), memory.get("a"));
            assertEquals(Long.valueOf(0), memory.get("b"));
            if (memory.isInitialIteration()) {
                assertTrue(memory.get("c"));
                assertFalse(memory.get("d"));
            } else {
                assertFalse(memory.get("c"));
                assertTrue(memory.get("d"));
            }
            assertTrue(memory.get("e"));
            assertEquals(memory.getIteration(), memory.get("f").intValue());
            try {
                memory.set("a", 0l);
                fail("Should only allow Memory.add() during VertexProgram.execute()");
            } catch (final Exception e) {
                validateException(Memory.Exceptions.memorySetOnlyDuringVertexProgramSetUpAndTerminate("a"), e);
            }
        }

        @Override
        public boolean terminate(Memory memory) {
            assertEquals(Long.valueOf(6 * (memory.getIteration() + 1)), memory.get("a"));
            assertEquals(Long.valueOf(6), memory.get("b"));
            assertFalse(memory.get("c"));
            assertTrue(memory.get("d"));
            assertFalse(memory.get("e"));
            assertEquals(memory.getIteration() + 1, memory.get("f").intValue());
            memory.set("b", 0l);
            memory.set("e", true);
            try {
                memory.add("a", 0l);
                fail("Should only allow Memory.set() during VertexProgram.terminate()");
            } catch (final Exception e) {
                validateException(Memory.Exceptions.memoryAddOnlyDuringVertexProgramExecute("a"), e);
            }
            return memory.getIteration() > 1;
        }

        @Override
        public Set getMemoryComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    MemoryComputeKey.of("a", Operator.sum, true, false),
                    MemoryComputeKey.of("b", Operator.sum, true, false),
                    MemoryComputeKey.of("c", Operator.and, true, false),
                    MemoryComputeKey.of("d", Operator.or, true, false),
                    MemoryComputeKey.of("e", Operator.and, true, false),
                    MemoryComputeKey.of("f", Operator.assign, true, false)));
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldAllowMapReduceWithNoVertexProgram() throws Exception {
        final ComputerResult results = graphProvider.getGraphComputer(graph).mapReduce(new MapReduceA()).submit().get();
        assertEquals(123, results.memory().get("ageSum").intValue());
    }

    private static class MapReduceA extends StaticMapReduce {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.MAP) || stage.equals(Stage.REDUCE);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            vertex.property("age").ifPresent(emitter::emit);
        }

        @Override
        public void reduce(NullObject key, Iterator values, ReduceEmitter emitter) {
            int sum = 0;
            while (values.hasNext()) {
                sum = sum + values.next();
            }
            emitter.emit(sum);
        }

        @Override
        public Integer generateFinalResult(Iterator> keyValues) {
            return keyValues.next().getValue();
        }

        @Override
        public String getMemoryKey() {
            return "ageSum";
        }
    }
    /////////////////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportMultipleMapReduceJobs() throws Exception {
        final ComputerResult results = graphProvider.getGraphComputer(graph)
                .program(new VertexProgramH())
                .mapReduce(new MapReduceH1())
                .mapReduce(new MapReduceH2()).submit().get();
        assertEquals(60, results.memory().get("a").intValue());
        assertEquals(1, results.memory().get("b").intValue());
    }

    public static class VertexProgramH extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(Vertex vertex, Messenger messenger, Memory memory) {
            vertex.property(VertexProperty.Cardinality.single, "counter", memory.isInitialIteration() ? 1 : vertex.value("counter") + 1);
        }

        @Override
        public boolean terminate(final Memory memory) {
            return memory.getIteration() > 8;
        }

        @Override
        public Set getVertexComputeKeys() {
            return Collections.singleton(VertexComputeKey.of("counter", false));
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }

    private static class MapReduceH1 extends StaticMapReduce {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.MAP) || stage.equals(Stage.REDUCE);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            vertex.property("counter").ifPresent(emitter::emit);
        }

        @Override
        public void reduce(final NullObject key, final Iterator values, final ReduceEmitter emitter) {
            int sum = 0;
            while (values.hasNext()) {
                sum = sum + values.next();
            }
            emitter.emit(sum);
        }

        @Override
        public Integer generateFinalResult(final Iterator> keyValues) {
            return keyValues.next().getValue();
        }

        @Override
        public String getMemoryKey() {
            return "a";
        }
    }

    private static class MapReduceH2 extends StaticMapReduce {

        @Override
        public boolean doStage(final Stage stage) {
            return true;
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            vertex.property("age").ifPresent(age -> emitter.emit(age, age));
        }

        @Override
        public void combine(Integer key, Iterator values, ReduceEmitter emitter) {
            values.forEachRemaining(i -> emitter.emit(i, 1));
        }

        @Override
        public void reduce(Integer key, Iterator values, ReduceEmitter emitter) {
            values.forEachRemaining(i -> emitter.emit(i, 1));
        }

        @Override
        public Integer generateFinalResult(Iterator> keyValues) {
            return keyValues.next().getValue();
        }

        @Override
        public String getMemoryKey() {
            return "b";
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldSortReduceOutput() throws Exception {
        final ComputerResult results = graphProvider.getGraphComputer(graph).mapReduce(new MapReduceB()).submit().get();
        final List nameLengths = results.memory().get("nameLengths");
        assertEquals(6, nameLengths.size());
        for (int i = 1; i < nameLengths.size(); i++) {
            assertTrue(nameLengths.get(i) <= nameLengths.get(i - 1));
        }
    }

    public static class MapReduceB extends StaticMapReduce> {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.REDUCE) || stage.equals(Stage.MAP);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            emitter.emit(vertex.value("name").length(), vertex.value("name").length());
        }

        @Override
        public void reduce(Integer key, Iterator values, ReduceEmitter emitter) {
            values.forEachRemaining(id -> emitter.emit(id, id));
        }

        @Override
        public Optional> getReduceKeySort() {
            return Optional.of(Comparator.reverseOrder());
        }

        @Override
        public String getMemoryKey() {
            return "nameLengths";
        }

        @Override
        public List generateFinalResult(final Iterator> keyValues) {
            final List list = new ArrayList<>();
            keyValues.forEachRemaining(nameLength -> list.add(nameLength.getKey()));
            return list;
        }
    }

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldSortMapOutput() throws Exception {
        final ComputerResult results = graphProvider.getGraphComputer(graph).mapReduce(new MapReduceBB()).submit().get();
        final List nameLengths = results.memory().get("nameLengths");
        assertEquals(6, nameLengths.size());
        for (int i = 1; i < nameLengths.size(); i++) {
            assertTrue(nameLengths.get(i) <= nameLengths.get(i - 1));
        }
    }

    public static class MapReduceBB extends StaticMapReduce> {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.MAP);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            emitter.emit(vertex.value("name").length(), vertex.value("name").length());
        }

        @Override
        public Optional> getMapKeySort() {
            return Optional.of(Comparator.reverseOrder());
        }

        @Override
        public String getMemoryKey() {
            return "nameLengths";
        }

        @Override
        public List generateFinalResult(final Iterator> keyValues) {
            final List list = new ArrayList<>();
            keyValues.forEachRemaining(nameLength -> list.add(nameLength.getKey()));
            return list;
        }
    }


    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldOnlyAllowReadingVertexPropertiesInMapReduce() throws Exception {
        graphProvider.getGraphComputer(graph).mapReduce(new MapReduceC()).submit().get();
    }

    public static class MapReduceC extends StaticMapReduce {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.MAP);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            try {
                vertex.edges(Direction.OUT);
                fail("Edges should not be accessible in MapReduce.map()");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.incidentAndAdjacentElementsCanNotBeAccessedInMapReduce().getMessage(), e.getMessage());
            }
            try {
                vertex.edges(Direction.IN);
                fail("Edges should not be accessible in MapReduce.map()");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.incidentAndAdjacentElementsCanNotBeAccessedInMapReduce().getMessage(), e.getMessage());
            }
            try {
                vertex.edges(Direction.BOTH);
                fail("Edges should not be accessible in MapReduce.map()");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.incidentAndAdjacentElementsCanNotBeAccessedInMapReduce().getMessage(), e.getMessage());
            }
            ////
            try {
                vertex.property("name", "bob");
                fail("Vertex properties should be immutable in MapReduce.map()");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.vertexPropertiesCanNotBeUpdatedInMapReduce().getMessage(), e.getMessage());
            }
            try {
                vertex.property("name").property("test", 1);
                fail("Vertex properties should be immutable in MapReduce.map()");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.vertexPropertiesCanNotBeUpdatedInMapReduce().getMessage(), e.getMessage());
            }

        }

        @Override
        public String getMemoryKey() {
            return "nothing";
        }

        @Override
        public MapReduce.NullObject generateFinalResult(final Iterator> keyValues) {
            return MapReduce.NullObject.instance();
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldOnlyAllowIDAccessOfAdjacentVertices() throws Exception {
        graphProvider.getGraphComputer(graph).program(new VertexProgramI()).submit().get();
    }

    public static class VertexProgramI extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(Vertex vertex, Messenger messenger, Memory memory) {
            vertex.vertices(Direction.OUT).forEachRemaining(Vertex::id);
            vertex.vertices(Direction.IN).forEachRemaining(Vertex::id);
            vertex.vertices(Direction.BOTH).forEachRemaining(Vertex::id);
            if (vertex.vertices(Direction.OUT).hasNext()) {
                try {
                    vertex.vertices(Direction.OUT).forEachRemaining(Vertex::label);
                    fail("Adjacent vertex labels should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead().getMessage(), e.getMessage());
                }
            }
            if (vertex.vertices(Direction.IN).hasNext()) {
                try {
                    vertex.vertices(Direction.IN).forEachRemaining(Vertex::label);
                    fail("Adjacent vertex labels should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead().getMessage(), e.getMessage());
                }
            }
            if (vertex.vertices(Direction.BOTH).hasNext()) {
                try {
                    vertex.vertices(Direction.BOTH).forEachRemaining(Vertex::label);
                    fail("Adjacent vertex labels should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexLabelsCanNotBeRead().getMessage(), e.getMessage());
                }
            }
            ////////////////////
            if (vertex.vertices(Direction.OUT).hasNext()) {
                try {
                    vertex.vertices(Direction.OUT).forEachRemaining(v -> v.property("name"));
                    fail("Adjacent vertex properties should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexPropertiesCanNotBeReadOrUpdated().getMessage(), e.getMessage());
                }
            }
            if (vertex.vertices(Direction.IN).hasNext()) {
                try {
                    vertex.vertices(Direction.IN).forEachRemaining(v -> v.property("name"));
                    fail("Adjacent vertex properties should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexPropertiesCanNotBeReadOrUpdated().getMessage(), e.getMessage());
                }
            }
            if (vertex.vertices(Direction.BOTH).hasNext()) {
                try {
                    vertex.vertices(Direction.BOTH).forEachRemaining(v -> v.property("name"));
                    fail("Adjacent vertex properties should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexPropertiesCanNotBeReadOrUpdated().getMessage(), e.getMessage());
                }
            }
            ////////////////////
            if (vertex.vertices(Direction.BOTH).hasNext()) {
                try {
                    vertex.vertices(Direction.BOTH).forEachRemaining(v -> v.edges(Direction.BOTH));
                    fail("Adjacent vertex edges should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexEdgesAndVerticesCanNotBeReadOrUpdated().getMessage(), e.getMessage());
                }
            }
            if (vertex.vertices(Direction.BOTH).hasNext()) {
                try {
                    vertex.vertices(Direction.BOTH).forEachRemaining(v -> v.vertices(Direction.BOTH));
                    fail("Adjacent vertex vertices should not be accessible in VertexProgram.execute()");
                } catch (UnsupportedOperationException e) {
                    assertEquals(GraphComputer.Exceptions.adjacentVertexEdgesAndVerticesCanNotBeReadOrUpdated().getMessage(), e.getMessage());
                }
            }

        }

        @Override
        public boolean terminate(final Memory memory) {
            return memory.getIteration() > 1;
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }
    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith(MODERN)
    public void shouldStartAndEndWorkersForVertexProgramAndMapReduce() throws Exception {
        MapReduceI.WORKER_START.clear();
        MapReduceI.WORKER_END.clear();
        assertEquals(3, graphProvider.getGraphComputer(graph).program(new VertexProgramJ()).mapReduce(new MapReduceI()).submit().get().memory().get("a").intValue());
        if (MapReduceI.WORKER_START.size() == 2) {
            assertEquals(2, MapReduceI.WORKER_START.size());
            assertTrue(MapReduceI.WORKER_START.contains(MapReduce.Stage.MAP) && MapReduceI.WORKER_START.contains(MapReduce.Stage.REDUCE));
        } else {
            assertEquals(3, MapReduceI.WORKER_START.size());
            assertTrue(MapReduceI.WORKER_START.contains(MapReduce.Stage.MAP) && MapReduceI.WORKER_START.contains(MapReduce.Stage.COMBINE) && MapReduceI.WORKER_START.contains(MapReduce.Stage.REDUCE));
        }
        if (MapReduceI.WORKER_END.size() == 2) {
            assertEquals(2, MapReduceI.WORKER_END.size());
            assertTrue(MapReduceI.WORKER_END.contains(MapReduce.Stage.MAP) && MapReduceI.WORKER_END.contains(MapReduce.Stage.REDUCE));
        } else {
            assertEquals(3, MapReduceI.WORKER_END.size());
            assertTrue(MapReduceI.WORKER_END.contains(MapReduce.Stage.MAP) && MapReduceI.WORKER_END.contains(MapReduce.Stage.COMBINE) && MapReduceI.WORKER_END.contains(MapReduce.Stage.REDUCE));
        }
    }

    public static class VertexProgramJ extends StaticVertexProgram {


        @Override
        public void setup(final Memory memory) {
            memory.set("test", memory.getIteration());
        }

        @Override
        public void workerIterationStart(final Memory memory) {
            assertEquals(memory.getIteration() * 6, memory.get("test").intValue());
            try {
                memory.add("test", memory.getIteration());
                fail("Should throw an immutable memory exception");
            } catch (IllegalStateException e) {
                assertEquals(Memory.Exceptions.memoryIsCurrentlyImmutable().getMessage(), e.getMessage());
            }
        }

        @Override
        public void execute(Vertex vertex, Messenger messenger, Memory memory) {
            assertEquals(memory.getIteration() * 6, memory.get("test").intValue());
            memory.add("test", 1);
        }

        @Override
        public boolean terminate(final Memory memory) {
            return memory.getIteration() > 3;
        }

        @Override
        public void workerIterationEnd(final Memory memory) {
            assertEquals(memory.getIteration() * 6, memory.get("test").intValue());
            try {
                memory.set("test", memory.getIteration());
                fail("Should throw an immutable memory exception");
            } catch (IllegalStateException e) {
                assertEquals(Memory.Exceptions.memoryIsCurrentlyImmutable().getMessage(), e.getMessage());
            }
        }

        @Override
        public Set getMemoryComputeKeys() {
            return Collections.singleton(MemoryComputeKey.of("test", Operator.sum, true, false));
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }
    }

    private static class MapReduceI extends StaticMapReduce {

        private static final Set WORKER_START = new ConcurrentSkipListSet<>();
        private static final Set WORKER_END = new ConcurrentSkipListSet<>();

        @Override
        public boolean doStage(final Stage stage) {
            return true;
        }

        @Override
        public void workerStart(final Stage stage) {
            WORKER_START.add(stage);
            if (!stage.equals(Stage.MAP))
                assertFalse(WORKER_END.isEmpty());
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            emitter.emit(1);
            assertEquals(1, WORKER_START.size());
            assertTrue(WORKER_START.contains(Stage.MAP));
        }

        @Override
        public void combine(final NullObject key, final Iterator values, final ReduceEmitter emitter) {
            emitter.emit(2);
            assertEquals(2, WORKER_START.size());
            assertTrue(WORKER_START.contains(Stage.MAP) && WORKER_START.contains(Stage.COMBINE));
            assertFalse(WORKER_END.isEmpty());
        }

        @Override
        public void reduce(final NullObject key, final Iterator values, final ReduceEmitter emitter) {
            emitter.emit(3);
            if (WORKER_START.size() == 2) {
                assertEquals(2, WORKER_START.size());
                assertTrue(WORKER_START.contains(Stage.MAP) && WORKER_START.contains(Stage.REDUCE));
            } else {
                assertEquals(3, WORKER_START.size());
                assertTrue(WORKER_START.contains(Stage.MAP) && WORKER_START.contains(Stage.COMBINE) && WORKER_START.contains(Stage.REDUCE));
            }
            assertFalse(WORKER_END.isEmpty());
        }

        @Override
        public void workerEnd(final Stage stage) {
            assertFalse(WORKER_START.isEmpty());
            if (!stage.equals(Stage.MAP))
                assertFalse(WORKER_END.isEmpty());
            WORKER_END.add(stage);
        }

        @Override
        public Integer generateFinalResult(final Iterator> keyValues) {
            assertEquals(3, keyValues.next().getValue().intValue());
            return 3;
        }

        @Override
        public String getMemoryKey() {
            return "a";
        }
    }

    /////////////////////////////////////////////

    /////////////////////////////////////////////
    @Test
    @LoadGraphWith
    public void shouldSupportPersistResultGraphPairsStatedInFeatures() throws Exception {
        for (final GraphComputer.ResultGraph resultGraph : GraphComputer.ResultGraph.values()) {
            for (final GraphComputer.Persist persist : GraphComputer.Persist.values()) {
                final GraphComputer computer = graphProvider.getGraphComputer(graph);
                if (computer.features().supportsResultGraphPersistCombination(resultGraph, persist)) {
                    computer.program(new VertexProgramK()).result(resultGraph).persist(persist).submit().get();
                } else {
                    try {
                        computer.program(new VertexProgramK()).result(resultGraph).persist(persist).submit().get();
                        fail("The GraphComputer " + computer + " states that it does support the following resultGraph/persist pair: " + resultGraph + ":" + persist);
                    } catch (final IllegalArgumentException e) {
                        assertEquals(GraphComputer.Exceptions.resultGraphPersistCombinationNotSupported(resultGraph, persist).getMessage(), e.getMessage());
                    }
                }
            }
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphNewWithPersistNothing() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.NEW, GraphComputer.Persist.NOTHING)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.NEW).persist(GraphComputer.Persist.NOTHING).submit().get();
            assertEquals(Long.valueOf(0l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(0l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(0l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(0l), result.graph().traversal().E().values().count().next());
            assertEquals(0, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(12l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(0, graph.traversal().V().values("money").sum().next());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphNewWithPersistVertexProperties() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.NEW, GraphComputer.Persist.VERTEX_PROPERTIES)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.NEW).persist(GraphComputer.Persist.VERTEX_PROPERTIES).submit().get();
            assertEquals(Long.valueOf(6l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(0l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(18l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(0l), result.graph().traversal().E().values().count().next());
            assertEquals(28l, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(12l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(0, graph.traversal().V().values("money").sum().next());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphNewWithPersistEdges() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.NEW, GraphComputer.Persist.EDGES)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.NEW).persist(GraphComputer.Persist.EDGES).submit().get();
            assertEquals(Long.valueOf(6l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(18l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().values().count().next());
            assertEquals(28l, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(12l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(0, graph.traversal().V().values("money").sum().next());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphOriginalWithPersistNothing() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.ORIGINAL, GraphComputer.Persist.NOTHING)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.ORIGINAL).persist(GraphComputer.Persist.NOTHING).submit().get();
            assertEquals(Long.valueOf(6l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(12l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().values().count().next());
            assertEquals(0, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(12l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(0, graph.traversal().V().values("money").sum().next());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphOriginalWithPersistVertexProperties() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.ORIGINAL, GraphComputer.Persist.VERTEX_PROPERTIES)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.ORIGINAL).persist(GraphComputer.Persist.VERTEX_PROPERTIES).submit().get();
            assertEquals(Long.valueOf(6l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(18l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().values().count().next());
            assertEquals(28l, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(18l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(28l, graph.traversal().V().values("money").sum().next());
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldProcessResultGraphOriginalWithPersistEdges() throws Exception {
        final GraphComputer computer = graphProvider.getGraphComputer(graph);
        if (computer.features().supportsResultGraphPersistCombination(GraphComputer.ResultGraph.ORIGINAL, GraphComputer.Persist.EDGES)) {
            final ComputerResult result = computer.program(new VertexProgramK()).result(GraphComputer.ResultGraph.ORIGINAL).persist(GraphComputer.Persist.EDGES).submit().get();
            assertEquals(Long.valueOf(6l), result.graph().traversal().V().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().count().next());
            assertEquals(Long.valueOf(18l), result.graph().traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), result.graph().traversal().E().values().count().next());
            assertEquals(28l, result.graph().traversal().V().values("money").sum().next());
            ///
            assertEquals(Long.valueOf(6l), graph.traversal().V().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().count().next());
            assertEquals(Long.valueOf(18l), graph.traversal().V().values().count().next());
            assertEquals(Long.valueOf(6l), graph.traversal().E().values().count().next());
            assertEquals(28l, graph.traversal().V().values("money").sum().next());
        }
    }

    public static class VertexProgramK extends StaticVertexProgram {


        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            vertex.property("money", vertex.value("name").length());
        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getVertexComputeKeys() {
            return Collections.singleton(VertexComputeKey.of("money", false));
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.EDGES;
        }
    }

    /////////////////////////////////////////////

    @Test
    @LoadGraphWith(GRATEFUL)
    public void shouldSupportWorkerCount() throws Exception {
        int maxWorkers = graphProvider.getGraphComputer(graph).features().getMaxWorkers();
        if (maxWorkers != Integer.MAX_VALUE) {
            for (int i = maxWorkers + 1; i < maxWorkers + 10; i++) {
                try {
                    graphProvider.getGraphComputer(graph).program(new VertexProgramL()).workers(i).submit().get();
                    fail("Should throw a GraphComputer.Exceptions.computerRequiresMoreWorkersThanSupported() exception");
                } catch (final IllegalArgumentException e) {
                    assertTrue(e.getMessage().contains("computer requires more workers"));
                }
            }
        }
        if (maxWorkers > 25) maxWorkers = 25;
        for (int i = 1; i <= maxWorkers; i++) {
            ComputerResult result = graphProvider.getGraphComputer(graph).program(new VertexProgramL()).workers(i).submit().get();
            assertEquals(Integer.valueOf(i).longValue(), (long) result.memory().get("workerCount"));
        }
    }

    public static class VertexProgramL implements VertexProgram {

        boolean announced = false;

        @Override
        public void setup(final Memory memory) {
            memory.set("workerCount", 0l);
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            try {
                Thread.sleep(1);
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!this.announced) {
                memory.add("workerCount", 1l);
                this.announced = true;
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getMemoryComputeKeys() {
            return Collections.singleton(MemoryComputeKey.of("workerCount", Operator.sum, true, false));
        }

        /*public void workerIterationStart(final Memory memory) {
            assertEquals(0l, (long) memory.get("workerCount"));
        }

        public void workerIterationEnd(final Memory memory) {
            assertEquals(1l, (long) memory.get("workerCount"));
        }*/

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }

        @Override
        @SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
        public VertexProgramL clone() {
            return new VertexProgramL();
        }

        @Override
        public void storeState(final Configuration configuration) {
            VertexProgram.super.storeState(configuration);
        }

    }

    /////////////////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportGraphFilter() throws Exception {
        // if the graph computer does not support graph filter, then make sure its exception handling is correct
        if (!graphProvider.getGraphComputer(graph).features().supportsGraphFilter()) {
            try {
                graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software"));
                fail("Should throw an unsupported operation exception");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.graphFilterNotSupported().getMessage(), e.getMessage());
            }
            try {
                graphProvider.getGraphComputer(graph).edges(__.outE().limit(10));
                fail("Should throw an unsupported operation exception");
            } catch (final UnsupportedOperationException e) {
                assertEquals(GraphComputer.Exceptions.graphFilterNotSupported().getMessage(), e.getMessage());
            }
            return;
        }
        /// VERTEX PROGRAM
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software")).program(new VertexProgramM(VertexProgramM.SOFTWARE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).program(new VertexProgramM(VertexProgramM.PEOPLE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.bothE("knows")).program(new VertexProgramM(VertexProgramM.KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.bothE("knows")).program(new VertexProgramM(VertexProgramM.PEOPLE_KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.bothE("knows").has("weight", P.gt(0.5f))).program(new VertexProgramM(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.bothE().limit(0)).program(new VertexProgramM(VertexProgramM.VERTICES_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.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();

        /// VERTEX PROGRAM + MAP REDUCE
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software")).program(new VertexProgramM(VertexProgramM.SOFTWARE_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.SOFTWARE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).program(new VertexProgramM(VertexProgramM.PEOPLE_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.bothE("knows")).program(new VertexProgramM(VertexProgramM.KNOWS_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.bothE("knows")).program(new VertexProgramM(VertexProgramM.PEOPLE_KNOWS_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.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(__.bothE().limit(0)).program(new VertexProgramM(VertexProgramM.VERTICES_ONLY)).mapReduce(new MapReduceJ(VertexProgramM.VERTICES_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.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();

        /// MAP REDUCE ONLY
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("software")).mapReduce(new MapReduceJ(VertexProgramM.SOFTWARE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.bothE("knows")).mapReduce(new MapReduceJ(VertexProgramM.KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.bothE("knows")).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_KNOWS_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).vertices(__.hasLabel("person")).edges(__.bothE("knows").has("weight", P.gt(0.5f))).mapReduce(new MapReduceJ(VertexProgramM.PEOPLE_KNOWS_WELL_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.bothE().limit(0)).mapReduce(new MapReduceJ(VertexProgramM.VERTICES_ONLY)).submit().get();
        graphProvider.getGraphComputer(graph).edges(__.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();

        // EXCEPTION HANDLING
        try {
            graphProvider.getGraphComputer(graph).vertices(__.out());
            fail();
        } catch (final IllegalArgumentException e) {
            assertEquals(e.getMessage(), GraphComputer.Exceptions.vertexFilterAccessesIncidentEdges(__.out()).getMessage());
        }
        try {
            graphProvider.getGraphComputer(graph).edges(__.out().outE());
            fail();
        } catch (final IllegalArgumentException e) {
            assertEquals(e.getMessage(), GraphComputer.Exceptions.edgeFilterAccessesAdjacentVertices(__.out().outE()).getMessage());
        }
    }

    public static class VertexProgramM implements VertexProgram {

        public static final String SOFTWARE_ONLY = "softwareOnly";
        public static final String PEOPLE_ONLY = "peopleOnly";
        public static final String KNOWS_ONLY = "knowsOnly";
        public static final String PEOPLE_KNOWS_ONLY = "peopleKnowsOnly";
        public static final String PEOPLE_KNOWS_WELL_ONLY = "peopleKnowsWellOnly";
        public static final String VERTICES_ONLY = "verticesOnly";
        public static final String ONE_OUT_EDGE_ONLY = "oneOutEdgeOnly";
        public static final String OUT_EDGES_ONLY = "outEdgesOnly";

        private String state;

        public VertexProgramM() {

        }

        public VertexProgramM(final String state) {
            this.state = state;
        }

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            switch (this.state) {
                case SOFTWARE_ONLY: {
                    assertEquals("software", vertex.label());
                    assertFalse(vertex.edges(Direction.OUT).hasNext());
                    assertTrue(vertex.edges(Direction.IN).hasNext());
                    assertTrue(vertex.edges(Direction.IN, "created").hasNext());
                    assertFalse(vertex.edges(Direction.IN, "knows").hasNext());
                    break;
                }
                case PEOPLE_ONLY: {
                    assertEquals("person", vertex.label());
                    assertFalse(vertex.edges(Direction.IN, "created").hasNext());
                    assertTrue(IteratorUtils.count(vertex.edges(Direction.BOTH)) > 0);
                    break;
                }
                case KNOWS_ONLY: {
                    assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "created")));
                    if (vertex.value("name").equals("marko"))
                        assertEquals(2, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                    else if (vertex.value("name").equals("vadas"))
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                    else if (vertex.value("name").equals("josh"))
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                    else {
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    }
                    break;
                }
                case PEOPLE_KNOWS_ONLY: {
                    assertEquals("person", vertex.label());
                    assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "created")));
                    if (vertex.value("name").equals("marko"))
                        assertEquals(2, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                    else if (vertex.value("name").equals("vadas"))
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                    else if (vertex.value("name").equals("josh"))
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                    else {
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    }
                    break;
                }
                case PEOPLE_KNOWS_WELL_ONLY: {
                    assertEquals("person", vertex.label());
                    assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "created")));
                    if (vertex.value("name").equals("marko")) {
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                        assertEquals(1.0, vertex.edges(Direction.OUT, "knows").next().value("weight"), 0.001);
                    } else if (vertex.value("name").equals("vadas"))
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                    else if (vertex.value("name").equals("josh")) {
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.IN, "knows")));
                        assertEquals(1.0, vertex.edges(Direction.IN, "knows").next().value("weight"), 0.001);
                    } else {
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH, "knows")));
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    }
                    break;
                }
                case VERTICES_ONLY: {
                    assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    break;
                }
                case ONE_OUT_EDGE_ONLY: {
                    if (vertex.label().equals("software") || vertex.value("name").equals("vadas"))
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    else {
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.OUT)));
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.IN)));
                        assertEquals(1, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    }
                    break;
                }
                case OUT_EDGES_ONLY: {
                    if (vertex.label().equals("software") || vertex.value("name").equals("vadas"))
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    else {
                        assertTrue(IteratorUtils.count(vertex.edges(Direction.OUT)) > 0);
                        assertEquals(0, IteratorUtils.count(vertex.edges(Direction.IN)));
                        assertEquals(IteratorUtils.count(vertex.edges(Direction.OUT)), IteratorUtils.count(vertex.edges(Direction.BOTH)));
                    }
                    break;
                }
                default:
                    throw new IllegalStateException("This is an illegal state for this test case: " + this.state);
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            return true;
        }

        @Override
        public Set getMessageScopes(Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.NOTHING;
        }

        @Override
        @SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
        public VertexProgramM clone() {
            return new VertexProgramM(this.state);
        }

        @Override
        public void loadState(final Graph graph, final Configuration configuration) {
            this.state = configuration.getString("state");
        }

        @Override
        public void storeState(final Configuration configuration) {
            configuration.setProperty("state", this.state);
            VertexProgram.super.storeState(configuration);
        }

    }

    private static class MapReduceJ implements MapReduce {

        private String state;

        public MapReduceJ() {
        }

        public MapReduceJ(final String state) {
            this.state = state;
        }

        @Override
        public void loadState(final Graph graph, final Configuration configuration) {
            this.state = configuration.getString("state");
        }

        @Override
        public void storeState(final Configuration configuration) {
            configuration.setProperty("state", this.state);
            MapReduce.super.storeState(configuration);
        }

        @Override
        @SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
        public MapReduceJ clone() {
            return new MapReduceJ(this.state);
        }

        @Override
        public boolean doStage(final Stage stage) {
            return true;
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            emitter.emit(1);
            switch (this.state) {
                case VertexProgramM.SOFTWARE_ONLY: {
                    assertEquals("software", vertex.label());
                    break;
                }
                case VertexProgramM.PEOPLE_ONLY: {
                    assertEquals("person", vertex.label());
                    break;
                }
                case VertexProgramM.KNOWS_ONLY: {
                    assertTrue(vertex.label().equals("person") || vertex.label().equals("software"));
                    break;
                }
                case VertexProgramM.PEOPLE_KNOWS_ONLY: {
                    assertEquals("person", vertex.label());
                    break;
                }
                case VertexProgramM.PEOPLE_KNOWS_WELL_ONLY: {
                    assertEquals("person", vertex.label());
                    break;
                }
                case VertexProgramM.VERTICES_ONLY: {
                    assertTrue(vertex.label().equals("person") || vertex.label().equals("software"));
                    break;
                }
                case VertexProgramM.ONE_OUT_EDGE_ONLY: {
                    assertTrue(vertex.label().equals("person") || vertex.label().equals("software"));
                    break;
                }
                case VertexProgramM.OUT_EDGES_ONLY: {
                    assertTrue(vertex.label().equals("person") || vertex.label().equals("software"));
                    break;
                }
                default:
                    throw new IllegalStateException("This is an illegal state for this test case: " + this.state);
            }
        }

        @Override
        public void combine(final NullObject key, final Iterator values, final ReduceEmitter emitter) {
            this.reduce(key, values, emitter);
        }

        @Override
        public void reduce(final NullObject key, final Iterator values, final ReduceEmitter emitter) {
            int count = 0;
            while (values.hasNext()) {
                count = count + values.next();
            }
            emitter.emit(count);
        }

        @Override
        public Integer generateFinalResult(final Iterator> keyValues) {
            int counter = keyValues.next().getValue();
            assertFalse(keyValues.hasNext());

            switch (this.state) {
                case VertexProgramM.SOFTWARE_ONLY: {
                    assertEquals(2, counter);
                    break;
                }
                case VertexProgramM.PEOPLE_ONLY: {
                    assertEquals(4, counter);
                    break;
                }
                case VertexProgramM.KNOWS_ONLY: {
                    assertEquals(6, counter);
                    break;
                }
                case VertexProgramM.PEOPLE_KNOWS_ONLY: {
                    assertEquals(4, counter);
                    break;
                }
                case VertexProgramM.PEOPLE_KNOWS_WELL_ONLY: {
                    assertEquals(4, counter);
                    break;
                }
                case VertexProgramM.VERTICES_ONLY: {
                    assertEquals(6, counter);
                    break;
                }
                case VertexProgramM.ONE_OUT_EDGE_ONLY: {
                    assertEquals(6, counter);
                    break;
                }
                case VertexProgramM.OUT_EDGES_ONLY: {
                    assertEquals(6, counter);
                    break;
                }
                default:
                    throw new IllegalStateException("This is an illegal state for this test case: " + this.state);
            }
            return counter;
        }

        @Override
        public String getMemoryKey() {
            return "a";
        }
    }

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportJobChaining() throws Exception {
        final ComputerResult result1 = graphProvider.getGraphComputer(graph)
                .program(PageRankVertexProgram.build().iterations(5).create(graph)).persist(GraphComputer.Persist.EDGES).result(GraphComputer.ResultGraph.NEW).submit().get();
        final Graph graph1 = result1.graph();
        final Memory memory1 = result1.memory();
        assertEquals(5, memory1.getIteration());
        assertEquals(6, graph1.traversal().V().count().next().intValue());
        assertEquals(6, graph1.traversal().E().count().next().intValue());
        assertEquals(6, graph1.traversal().V().values(PageRankVertexProgram.PAGE_RANK).count().next().intValue());
        assertEquals(18, graph1.traversal().V().values().count().next().intValue());
        //
        final ComputerResult result2 = graph1.compute(graphProvider.getGraphComputer(graph1).getClass())
                .program(PeerPressureVertexProgram.build().maxIterations(4).create(graph1)).persist(GraphComputer.Persist.EDGES).result(GraphComputer.ResultGraph.NEW).submit().get();
        final Graph graph2 = result2.graph();
        final Memory memory2 = result2.memory();
        assertTrue(memory2.getIteration() <= 4);
        assertEquals(6, graph2.traversal().V().count().next().intValue());
        assertEquals(6, graph2.traversal().E().count().next().intValue());
        assertEquals(6, graph2.traversal().V().values(PeerPressureVertexProgram.CLUSTER).count().next().intValue());
        assertEquals(6, graph2.traversal().V().values(PageRankVertexProgram.PAGE_RANK).count().next().intValue());
        assertEquals(24, graph2.traversal().V().values().count().next().intValue());
        //
        final ComputerResult result3 = graph2.compute(graphProvider.getGraphComputer(graph2).getClass())
                .program(TraversalVertexProgram.build().traversal(g.V().groupCount("m").by(__.values(PageRankVertexProgram.PAGE_RANK).count()).label().asAdmin()).create(graph2)).persist(GraphComputer.Persist.EDGES).result(GraphComputer.ResultGraph.NEW).submit().get();
        final Graph graph3 = result3.graph();
        final Memory memory3 = result3.memory();
        assertTrue(memory3.keys().contains("m"));
        assertTrue(memory3.keys().contains(TraversalVertexProgram.HALTED_TRAVERSERS));
        assertEquals(1, memory3.>get("m").size());
        assertEquals(6, memory3.>get("m").get(1l).intValue());
        List> traversers = IteratorUtils.list(memory3.get(TraversalVertexProgram.HALTED_TRAVERSERS).iterator());
        assertEquals(6l, traversers.stream().map(Traverser::bulk).reduce((a, b) -> a + b).get().longValue());
        assertEquals(4l, traversers.stream().filter(s -> s.get().equals("person")).map(Traverser::bulk).reduce((a, b) -> a + b).get().longValue());
        assertEquals(2l, traversers.stream().filter(s -> s.get().equals("software")).map(Traverser::bulk).reduce((a, b) -> a + b).get().longValue());
        assertEquals(6, graph3.traversal().V().count().next().intValue());
        assertEquals(6, graph3.traversal().E().count().next().intValue());
        assertEquals(6, graph3.traversal().V().values(TraversalVertexProgram.HALTED_TRAVERSERS).count().next().intValue());
        assertEquals(6, graph3.traversal().V().values(PeerPressureVertexProgram.CLUSTER).count().next().intValue());
        assertEquals(6, graph3.traversal().V().values(PageRankVertexProgram.PAGE_RANK).count().next().intValue());
        assertEquals(30, graph3.traversal().V().values().count().next().intValue());

        // TODO: add a test the shows DAG behavior -- splitting another TraversalVertexProgram off of the PeerPressureVertexProgram job.
    }

    ///////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportPreExistingComputeKeys() throws Exception {
        final ComputerResult result = graphProvider.getGraphComputer(graph).program(new VertexProgramN()).submit().get();
        result.graph().vertices().forEachRemaining(vertex -> {
            if (vertex.label().equals("person")) {
                if (vertex.value("name").equals("marko"))
                    assertEquals(32, vertex.value("age").intValue());
                else if (vertex.value("name").equals("peter"))
                    assertEquals(38, vertex.value("age").intValue());
                else if (vertex.value("name").equals("vadas"))
                    assertEquals(30, vertex.value("age").intValue());
                else if (vertex.value("name").equals("josh"))
                    assertEquals(35, vertex.value("age").intValue());
                else
                    throw new IllegalStateException("This vertex should not have been accessed: " + vertex);
            }
        });
    }

    private static class VertexProgramN extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {

        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            if (vertex.label().equals("person"))
                vertex.property(VertexProperty.Cardinality.single, "age", vertex.value("age") + 1);
        }

        @Override
        public boolean terminate(final Memory memory) {
            return memory.getIteration() > 1;
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public Set getVertexComputeKeys() {
            return Collections.singleton(VertexComputeKey.of("age", false));
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.VERTEX_PROPERTIES;
        }
    }

    ///////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportTransientKeys() throws Exception {
        final ComputerResult result = graphProvider.getGraphComputer(graph).program(new VertexProgramO()).mapReduce(new MapReduceK()).submit().get();
        result.graph().vertices().forEachRemaining(vertex -> {
            assertFalse(vertex.property("v1").isPresent());
            assertFalse(vertex.property("v2").isPresent());
            assertTrue(vertex.property("v3").isPresent());
            assertEquals("shouldExist", vertex.value("v3"));
            assertTrue(vertex.property("name").isPresent());
            if (vertex.label().equals("software"))
                assertTrue(vertex.property("lang").isPresent());
            else
                assertTrue(vertex.property("age").isPresent());
            assertEquals(3, IteratorUtils.count(vertex.properties()));
            assertEquals(0, IteratorUtils.count(vertex.properties("v1")));
            assertEquals(0, IteratorUtils.count(vertex.properties("v2")));
            assertEquals(1, IteratorUtils.count(vertex.properties("v3")));
            assertEquals(1, IteratorUtils.count(vertex.properties("name")));
        });
        assertEquals(6l, result.graph().traversal().V().properties("name").count().next().longValue());
        assertEquals(0l, result.graph().traversal().V().properties("v1").count().next().longValue());
        assertEquals(0l, result.graph().traversal().V().properties("v2").count().next().longValue());
        assertEquals(6l, result.graph().traversal().V().properties("v3").count().next().longValue());
        assertEquals(6l, result.graph().traversal().V().values("name").dedup().count().next().longValue());
        assertEquals(1l, result.graph().traversal().V().values("v3").dedup().count().next().longValue());
        assertEquals("shouldExist", result.graph().traversal().V().values("v3").dedup().next());
        ///
        assertFalse(result.memory().exists("m1"));
        assertFalse(result.memory().exists("m2"));
        assertTrue(result.memory().exists("m3"));
        assertEquals(24l, result.memory().get("m3").longValue());
        assertEquals(2, result.memory().keys().size());  // mapReduceK
    }

    private static class VertexProgramO extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {
            assertFalse(memory.exists("m1"));
            assertFalse(memory.exists("m2"));
            assertFalse(memory.exists("m3"));
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            if (memory.isInitialIteration()) {
                assertFalse(vertex.property("v1").isPresent());
                assertFalse(vertex.property("v2").isPresent());
                assertFalse(vertex.property("v3").isPresent());
                vertex.property("v1", "shouldNotExist");
                vertex.property("v2", "shouldNotExist");
                vertex.property("v3", "shouldExist");
                assertTrue(vertex.property("v1").isPresent());
                assertTrue(vertex.property("v2").isPresent());
                assertTrue(vertex.property("v3").isPresent());
                assertEquals("shouldNotExist", vertex.value("v1"));
                assertEquals("shouldNotExist", vertex.value("v2"));
                assertEquals("shouldExist", vertex.value("v3"));
                //
                assertFalse(memory.exists("m1"));
                assertFalse(memory.exists("m2"));
                assertFalse(memory.exists("m3"));
                memory.add("m1", false);
                memory.add("m2", true);
                memory.add("m3", 2l);
                // should still not exist as this pulls from the master memory
                assertFalse(memory.exists("m1"));
                assertFalse(memory.exists("m2"));
                assertFalse(memory.exists("m3"));

            } else {
                assertTrue(vertex.property("v1").isPresent());
                assertTrue(vertex.property("v2").isPresent());
                assertTrue(vertex.property("v3").isPresent());
                assertEquals("shouldNotExist", vertex.value("v1"));
                assertEquals("shouldNotExist", vertex.value("v2"));
                assertEquals("shouldExist", vertex.value("v3"));
                //
                assertTrue(memory.exists("m1"));
                assertTrue(memory.exists("m2"));
                assertTrue(memory.exists("m3"));
                assertFalse(memory.get("m1"));
                assertTrue(memory.get("m2"));
                assertEquals(12l, memory.get("m3").longValue());
                memory.add("m1", true);
                memory.add("m2", true);
                memory.add("m3", 2l);
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            assertTrue(memory.exists("m1"));
            assertTrue(memory.exists("m2"));
            assertTrue(memory.exists("m3"));
            if (memory.isInitialIteration()) {
                assertFalse(memory.get("m1"));
                assertTrue(memory.get("m2"));
                assertEquals(12l, memory.get("m3").longValue());
                return false;
            } else {
                assertTrue(memory.get("m1"));
                assertTrue(memory.get("m2"));
                assertEquals(24l, memory.get("m3").longValue());
                return true;
            }
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public Set getMemoryComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    MemoryComputeKey.of("m1", Operator.or, true, true),
                    MemoryComputeKey.of("m2", Operator.and, true, true),
                    MemoryComputeKey.of("m3", Operator.sum, true, false)));
        }

        @Override
        public Set getVertexComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    VertexComputeKey.of("v1", true),
                    VertexComputeKey.of("v2", true),
                    VertexComputeKey.of("v3", false)));
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.VERTEX_PROPERTIES;
        }
    }

    public static class MapReduceK extends StaticMapReduce {

        @Override
        public boolean doStage(final Stage stage) {
            return stage.equals(Stage.MAP);
        }

        @Override
        public void map(final Vertex vertex, final MapEmitter emitter) {
            assertFalse(vertex.property("v1").isPresent());
            assertFalse(vertex.property("v2").isPresent());
            assertTrue(vertex.property("v3").isPresent());
            assertTrue(vertex.property("name").isPresent());
            assertEquals(3, IteratorUtils.count(vertex.properties()));
            assertEquals(3, IteratorUtils.count(vertex.values()));
        }

        @Override
        public String getMemoryKey() {
            return "mapReduceK";
        }

        @Override
        public Object generateFinalResult(final Iterator keyValues) {
            return "anObject";
        }
    }

    ///////////////////////////////////

    @Test
    @LoadGraphWith(MODERN)
    public void shouldSupportBroadcastKeys() throws Exception {
        final ComputerResult result = graphProvider.getGraphComputer(graph).program(new VertexProgramP()).submit().get();
        assertTrue(result.memory().exists("m1"));
        assertFalse(result.memory().exists("m2"));
        assertFalse(result.memory().exists("m3"));
        assertTrue(result.memory().exists("m4"));
        assertTrue(result.memory().get("m1"));
        assertEquals(-18, result.memory().get("m4").intValue());
        assertEquals(2, result.memory().keys().size());
    }

    private static class VertexProgramP extends StaticVertexProgram {

        @Override
        public void setup(final Memory memory) {
            assertFalse(memory.exists("m1"));  // or
            assertFalse(memory.exists("m2"));  // and
            assertFalse(memory.exists("m3"));  // long
            assertFalse(memory.exists("m4"));  // int
            memory.set("m1", false);
            memory.set("m2", true);
            memory.set("m3", 0l);
            memory.set("m4", 0);
        }

        @Override
        public void execute(final Vertex vertex, final Messenger messenger, final Memory memory) {
            if (memory.isInitialIteration()) {
                assertFalse(memory.exists("m1"));
                assertTrue(memory.exists("m2"));
                assertTrue(memory.get("m2"));
                assertFalse(memory.exists("m3"));
                assertTrue(memory.exists("m4"));
                assertEquals(0, memory.get("m4").intValue());
                memory.add("m1", false);
                memory.add("m2", true);
                memory.add("m3", 1l);
                memory.add("m4", -1);
            } else {
                assertFalse(memory.exists("m1")); // no broadcast
                assertTrue(memory.exists("m2"));
                assertFalse(memory.exists("m3")); // no broadcast
                assertTrue(memory.exists("m4"));
                try {
                    assertFalse(memory.get("m1"));
                    fail();
                } catch (final Exception e) {
                    validateException(Memory.Exceptions.memoryDoesNotExist("m1"), e);
                }
                assertTrue(memory.get("m2"));
                try {
                    assertEquals(6l, memory.get("m3").longValue());
                    fail();
                } catch (final Exception e) {
                    validateException(Memory.Exceptions.memoryDoesNotExist("m3"), e);
                }
                assertEquals(-6l, memory.get("m4").intValue());
                ///
                memory.add("m1", true);
                memory.add("m2", true);
                memory.add("m3", 2l);
                memory.add("m4", -2);
            }
        }

        @Override
        public boolean terminate(final Memory memory) {
            assertTrue(memory.exists("m1"));
            assertTrue(memory.exists("m2"));
            assertTrue(memory.exists("m3"));
            assertTrue(memory.exists("m4"));
            if (memory.isInitialIteration()) {
                assertFalse(memory.get("m1"));
                assertTrue(memory.get("m2"));
                assertEquals(6l, memory.get("m3").longValue());
                assertEquals(-6, memory.get("m4").intValue());
                return false;
            } else {
                assertTrue(memory.get("m1"));
                assertTrue(memory.get("m2"));
                assertEquals(18l, memory.get("m3").longValue());
                assertEquals(-18, memory.get("m4").intValue());
                return true;
            }
        }

        @Override
        public Set getMessageScopes(final Memory memory) {
            return Collections.emptySet();
        }

        @Override
        public Set getMemoryComputeKeys() {
            return new HashSet<>(Arrays.asList(
                    MemoryComputeKey.of("m1", Operator.or, false, false),
                    MemoryComputeKey.of("m2", Operator.and, true, true),
                    MemoryComputeKey.of("m3", Operator.sum, false, true),
                    MemoryComputeKey.of("m4", Operator.sum, true, false)));
        }

        @Override
        public GraphComputer.ResultGraph getPreferredResultGraph() {
            return GraphComputer.ResultGraph.NEW;
        }

        @Override
        public GraphComputer.Persist getPreferredPersist() {
            return GraphComputer.Persist.VERTEX_PROPERTIES;
        }
    }
}