/**
   * Starting from a node 1, traverse the graph. Node 1 discovers two extra nodes, 1 and 2, of which
   * only 2 should be called.
   *
   * <p>Ensures that any state maintained during traversal is cleared.
   */
  @Test
  public void traverseFrom_node() throws IllegalAccessException {
    traverser.addNodeVisitor(visitor1);

    // initially, node 1 will be queued
    traverser.enqueueNodes(colEq(Arrays.<Object>asList(1)));
    expectLastCall();

    traverser.hasNext();
    expectLastCall().andReturn(true);
    traverser.next();
    expectLastCall().andReturn(1);

    StringBuilder traversalState = new StringBuilder();
    visitor1.visitNode(1, traverser, traversalState);
    expectLastCall()
        .andAnswer(new NodeVisitorAction(traverser, Arrays.<Object>asList(1, 2), "James", true));

    // the second node 1 should not be enqueued
    traverser.enqueueNodes(colEq(Arrays.<Object>asList(2)));
    expectLastCall();

    traverser.hasNext();
    expectLastCall().andReturn(true);
    traverser.next();
    expectLastCall().andReturn(2);

    // the visitor should *not* be called again for node 1
    visitor1.visitNode(2, traverser, traversalState);
    expectLastCall().andAnswer(new NodeVisitorAction(traverser, null, " Bond", true));

    // all nodes have been visited
    traverser.hasNext();
    expectLastCall().andReturn(false);

    replay(traverser, visitor1);

    // none of the visitors returned "false", so the traversal should succeed
    assertTrue(traverser.traverseFrom(1, traversalState));

    verify(traverser, visitor1);

    // check that the state object was correctly updated
    assertEquals("James Bond", traversalState.toString());

    // check that any internal state maintained during the cloning has been cleaned up
    assertTrue(ReflectionUtils.<Set<?>>getValue(traverser, "visitedOrQueuedNodes").isEmpty());
  }
  /**
   * Starting from nodes 1, 1 and 2, traverse the graph. Node 1 will be called twice because it is
   * specified with the starting nodes, and the second visitor is not called for node 2 because the
   * first one aborts the traversal.
   *
   * <p>This also tests that the visitors are called in the expected order, and ensures that any
   * state maintained during traversal is cleared.
   */
  @Test
  public void traverseFrom_nodes() throws IllegalAccessException {
    traverser.addNodeVisitor(visitor1);
    traverser.addNodeVisitor(visitor2);

    // initially, nodes 1, 1 and 2 will be queued
    traverser.enqueueNodes(colEq(Arrays.<Object>asList(1, 1, 2)));
    expectLastCall();

    StringBuilder traversalState = new StringBuilder();

    /*
     * The visitors will also be called for the *second* 1 (even though it is equal
     * to the previous one), because it was specified with the initial objects.
     */
    traverser.hasNext();
    expectLastCall().andReturn(true).times(2);
    traverser.next();
    expectLastCall().andReturn(1).times(2);

    visitor1.visitNode(1, traverser, traversalState);
    expectLastCall().andAnswer(new NodeVisitorAction(traverser, null, null, true)).times(2);

    visitor2.visitNode(1, traverser, traversalState);
    expectLastCall().andAnswer(new NodeVisitorAction(traverser, null, null, true)).times(2);

    // graph traversal is aborted by visitor1, so no further call to visitor2 is expected
    traverser.hasNext();
    expectLastCall().andReturn(true);
    traverser.next();
    expectLastCall().andReturn(2);

    visitor1.visitNode(2, traverser, traversalState);
    expectLastCall().andAnswer(new NodeVisitorAction(traverser, null, null, false));
    replay(traverser, visitor1, visitor2);

    //  of the visitors returned "false", so the traversal should have failed
    assertFalse(traverser.traverseFrom(Arrays.<Object>asList(1, 1, 2), traversalState));

    verify(traverser, visitor1, visitor2);

    // check that the state object was not updated
    assertEquals("", traversalState.toString());

    // check that any internal state maintained during the cloning has been cleaned up
    assertTrue(ReflectionUtils.<Set<?>>getValue(traverser, "visitedOrQueuedNodes").isEmpty());
  }