/**
   * Unit test for the voting protocol for a singleton quorum.
   *
   * <p>FIXME For some reason this unit test occasionally takes much longer to run than would
   * otherwise be expected (up to a few seconds versus a small fraction of a second). You can see
   * this in the timestamps of the logger.
   *
   * <p>The test terminates when the fixture tears down the AbstractQuorum's internal watcher action
   * service, which has a hung action. If the WatcherActionService is also single-threaded, then
   * this could clearly lead to a deadlock since there would be no thread available to handle new
   * events.
   *
   * <p>It is awaiting the quorumBreak condition in AbstractQuorumActor.clearToken(). This issue may
   * be that we have two distinct signals for quorumBreak versus quorumMeet which need to be
   * combined and then the various methods modified to also test the condition variable. [I've made
   * that change.]
   *
   * <p>It seems likely that either a concurrent watcherActionService -or- a finite timeout would
   * get the unit tests to pass. However, only the former would work around a deadlock due to a
   * stuck Condition.
   *
   * <p>Look again at what Condition is getting stuck and at the stress test in {@link
   * #main(String[])} for causes. [nhang=17, nerr=0, nrun=1000]. There are several different causes,
   * each of which clearly reflects a different ordering of the events.
   *
   * <p>It maybe that we see the problem with a singleton quorum because there are no other sources
   * of events to kick the quorum into motion again once it fails to join under the initial impetus.
   *
   * <p>The problem appears to stem from withdrawing the cast vote before the quorum meets.
   */
  public void test_voting()
      throws InterruptedException, AsynchronousQuorumCloseException, TimeoutException {

    /*
     * When true, this test also exercises the awaitQuorum() and
     * awaitBreak() methods that accept a timeout, but only for the case in
     * which the condition should be true on entry. There is another unit
     * test in this class that verifies that the TimeoutException is
     * correctly thrown if the condition does not become true within the
     * timeout.
     */
    final boolean awaitMeetsAndBreaks = true;

    final Quorum<?, ?> quorum = quorums[0];
    final MockQuorumMember<?> client = clients[0];
    final QuorumActor<?, ?> actor = actors[0];
    final UUID serviceId = client.getServiceId();

    final long lastCommitTime1 = 0L;

    final long lastCommitTime2 = 2L;

    // Verify that no consensus has been achieved yet.
    assertEquals(-1L, clients[0].lastConsensusValue);

    // add as member service.
    actor.memberAdd();
    fixture.awaitDeque();

    assertTrue(clients[0].isMember());

    // join the pipeline.
    actor.pipelineAdd();
    fixture.awaitDeque();

    // Verify that timestamps must be non-negative.
    try {
      actor.castVote(-1L);
      fail("Expected " + IllegalArgumentException.class);
    } catch (IllegalArgumentException ex) {
      if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex);
    }

    // Should not be any votes.
    assertEquals(0, quorum.getVotes().size());

    // Cast a vote.
    actor.castVote(lastCommitTime1);
    fixture.awaitDeque();

    // Should be just one vote.
    assertEquals(1, quorum.getVotes().size());

    // Verify the consensus was updated
    assertEquals(lastCommitTime1, client.lastConsensusValue);

    if (awaitMeetsAndBreaks)
      assertEquals(Quorum.NO_QUORUM + 1, quorum.awaitQuorum(100, TimeUnit.MILLISECONDS));

    if (awaitMeetsAndBreaks) {
      actor.withdrawVote();
      quorum.awaitBreak();
      /*
       * FIXME I have modified castVote() to automatically add the service
       * to the pipeline. Otherwise we must do this explicitly after a
       * quorum break. Maybe the better approach to take is to have each
       * service self-report its lastCommitTime and have Quorum#start(M)
       * automatically strive towards a service join while
       * Quorum#terminate() causes leaves and then halts processing for
       * the service.  Alternatively, each client could set its target
       * state in MEMBER, PIPELINE, JOIN.  That would allow clients to
       * manage resynchronization, which they need to do.
       */
      //            actor.pipelineAdd();
    }

    // Cast another vote.
    actor.castVote(lastCommitTime2);
    fixture.awaitDeque();

    // Should be just one vote since a service can only vote for one
    // lastCommitTime at a time.
    if (quorum.getVotes().size() != 1) {
      assertEquals(quorum.getVotes().toString(), 1, quorum.getVotes().size());
    }

    // Verify the consensus was updated again.
    assertEquals(lastCommitTime2, client.lastConsensusValue);

    if (awaitMeetsAndBreaks)
      assertEquals(Quorum.NO_QUORUM + 2, quorum.awaitQuorum(100, TimeUnit.MILLISECONDS));

    // Remove as a member.
    actor.memberRemove();
    fixture.awaitDeque();

    assertFalse(clients[0].isMember());

    // The service vote was also removed.
    assertEquals(0, quorum.getVotes().size());

    if (awaitMeetsAndBreaks) quorum.awaitBreak(100, TimeUnit.MILLISECONDS);
  }