@Test
  public void testChangeListener() throws InterruptedException {
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    DiscoveryService discoveryService = entry.getKey();
    DiscoveryServiceClient discoveryServiceClient = entry.getValue();

    // Start discovery
    String serviceName = "listener_test";
    ServiceDiscovered serviceDiscovered = discoveryServiceClient.discover(serviceName);

    // Watch for changes.
    final BlockingQueue<List<Discoverable>> events = new ArrayBlockingQueue<List<Discoverable>>(10);
    serviceDiscovered.watchChanges(
        new ServiceDiscovered.ChangeListener() {
          @Override
          public void onChange(ServiceDiscovered serviceDiscovered) {
            events.add(ImmutableList.copyOf(serviceDiscovered));
          }
        },
        Threads.SAME_THREAD_EXECUTOR);

    // An empty list will be received first, as no endpoint has been registered.
    List<Discoverable> discoverables = events.poll(20, TimeUnit.SECONDS);
    Assert.assertNotNull(discoverables);
    Assert.assertTrue(discoverables.isEmpty());

    // Register a service
    Cancellable cancellable = register(discoveryService, serviceName, "localhost", 10000);

    discoverables = events.poll(20, TimeUnit.SECONDS);
    Assert.assertNotNull(discoverables);
    Assert.assertEquals(1, discoverables.size());

    // Register another service endpoint
    Cancellable cancellable2 = register(discoveryService, serviceName, "localhost", 10001);

    discoverables = events.poll(20, TimeUnit.SECONDS);
    Assert.assertNotNull(discoverables);
    Assert.assertEquals(2, discoverables.size());

    // Cancel both of them
    cancellable.cancel();
    cancellable2.cancel();

    // There could be more than one event triggered, but the last event should be an empty list.
    discoverables = events.poll(20, TimeUnit.SECONDS);
    Assert.assertNotNull(discoverables);
    if (!discoverables.isEmpty()) {
      discoverables = events.poll(20, TimeUnit.SECONDS);
    }

    Assert.assertTrue(discoverables.isEmpty());
  }
  @Test
  public void testCancelChangeListener() throws InterruptedException {
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    DiscoveryService discoveryService = entry.getKey();
    DiscoveryServiceClient discoveryServiceClient = entry.getValue();

    String serviceName = "cancel_listener";
    ServiceDiscovered serviceDiscovered = discoveryServiceClient.discover(serviceName);

    // An executor that delay execute a Runnable. It's for testing race because listener cancel and
    // discovery changes.
    Executor delayExecutor =
        new Executor() {
          @Override
          public void execute(final Runnable command) {
            Thread t =
                new Thread() {
                  @Override
                  public void run() {
                    try {
                      TimeUnit.SECONDS.sleep(2);
                      command.run();
                    } catch (InterruptedException e) {
                      throw Throwables.propagate(e);
                    }
                  }
                };
            t.start();
          }
        };

    final BlockingQueue<List<Discoverable>> events = new ArrayBlockingQueue<List<Discoverable>>(10);
    Cancellable cancelWatch =
        serviceDiscovered.watchChanges(
            new ServiceDiscovered.ChangeListener() {
              @Override
              public void onChange(ServiceDiscovered serviceDiscovered) {
                events.add(ImmutableList.copyOf(serviceDiscovered));
              }
            },
            delayExecutor);

    // Wait for the init event call
    Assert.assertNotNull(events.poll(3, TimeUnit.SECONDS));

    // Register a new service endpoint, wait a short while and then cancel the listener
    register(discoveryService, serviceName, "localhost", 1);
    TimeUnit.SECONDS.sleep(1);
    cancelWatch.cancel();

    // The change listener shouldn't get any event, since the invocation is delayed by the executor.
    Assert.assertNull(events.poll(3, TimeUnit.SECONDS));
  }
  @Test
  public void simpleDiscoverable() throws Exception {
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    DiscoveryService discoveryService = entry.getKey();
    DiscoveryServiceClient discoveryServiceClient = entry.getValue();

    // Register one service running on one host:port
    Cancellable cancellable = register(discoveryService, "foo", "localhost", 8090);

    // Discover that registered host:port.
    ServiceDiscovered serviceDiscovered = discoveryServiceClient.discover("foo");
    Assert.assertTrue(waitTillExpected(1, serviceDiscovered));

    Discoverable discoverable =
        new Discoverable() {
          @Override
          public String getName() {
            return "foo";
          }

          @Override
          public InetSocketAddress getSocketAddress() {
            return new InetSocketAddress("localhost", 8090);
          }
        };

    // Check it exists.
    Assert.assertTrue(serviceDiscovered.contains(discoverable));

    // Remove the service
    cancellable.cancel();

    // There should be no service.
    Assert.assertTrue(waitTillExpected(0, serviceDiscovered));

    Assert.assertFalse(serviceDiscovered.contains(discoverable));
  }
  @Test
  public void testIterator() throws InterruptedException {
    // This test is to verify TWILL-75
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    final DiscoveryService service = entry.getKey();
    DiscoveryServiceClient client = entry.getValue();

    final String serviceName = "iterator";
    ServiceDiscovered discovered = client.discover(serviceName);

    // Create a thread for performing registration.
    Thread t =
        new Thread() {
          @Override
          public void run() {
            service.register(
                new Discoverable() {
                  @Override
                  public String getName() {
                    return serviceName;
                  }

                  @Override
                  public InetSocketAddress getSocketAddress() {
                    return new InetSocketAddress(12345);
                  }
                });
          }
        };

    Iterator<Discoverable> iterator = discovered.iterator();
    t.start();
    t.join();

    // This would throw exception if there is race condition.
    Assert.assertFalse(iterator.hasNext());
  }
  protected boolean waitTillExpected(final int expected, ServiceDiscovered serviceDiscovered) {
    final CountDownLatch latch = new CountDownLatch(1);
    serviceDiscovered.watchChanges(
        new ServiceDiscovered.ChangeListener() {
          @Override
          public void onChange(ServiceDiscovered serviceDiscovered) {
            if (expected == Iterables.size(serviceDiscovered)) {
              latch.countDown();
            }
          }
        },
        Threads.SAME_THREAD_EXECUTOR);

    try {
      return latch.await(60, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw Throwables.propagate(e);
    }
  }