@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 manySameDiscoverable() throws Exception {
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    DiscoveryService discoveryService = entry.getKey();
    DiscoveryServiceClient discoveryServiceClient = entry.getValue();

    List<Cancellable> cancellables = Lists.newArrayList();

    cancellables.add(register(discoveryService, "manyDiscoverable", "localhost", 1));
    cancellables.add(register(discoveryService, "manyDiscoverable", "localhost", 2));
    cancellables.add(register(discoveryService, "manyDiscoverable", "localhost", 3));
    cancellables.add(register(discoveryService, "manyDiscoverable", "localhost", 4));
    cancellables.add(register(discoveryService, "manyDiscoverable", "localhost", 5));

    ServiceDiscovered serviceDiscovered = discoveryServiceClient.discover("manyDiscoverable");
    Assert.assertTrue(waitTillExpected(5, serviceDiscovered));

    for (int i = 0; i < 5; i++) {
      cancellables.get(i).cancel();
      Assert.assertTrue(waitTillExpected(4 - i, serviceDiscovered));
    }
  }
  @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());
  }
  @Test
  public void multiServiceDiscoverable() throws Exception {
    Map.Entry<DiscoveryService, DiscoveryServiceClient> entry = create();
    DiscoveryService discoveryService = entry.getKey();
    DiscoveryServiceClient discoveryServiceClient = entry.getValue();

    List<Cancellable> cancellables = Lists.newArrayList();

    cancellables.add(register(discoveryService, "service1", "localhost", 1));
    cancellables.add(register(discoveryService, "service1", "localhost", 2));
    cancellables.add(register(discoveryService, "service1", "localhost", 3));
    cancellables.add(register(discoveryService, "service1", "localhost", 4));
    cancellables.add(register(discoveryService, "service1", "localhost", 5));

    cancellables.add(register(discoveryService, "service2", "localhost", 1));
    cancellables.add(register(discoveryService, "service2", "localhost", 2));
    cancellables.add(register(discoveryService, "service2", "localhost", 3));

    cancellables.add(register(discoveryService, "service3", "localhost", 1));
    cancellables.add(register(discoveryService, "service3", "localhost", 2));

    ServiceDiscovered serviceDiscovered = discoveryServiceClient.discover("service1");
    Assert.assertTrue(waitTillExpected(5, serviceDiscovered));

    serviceDiscovered = discoveryServiceClient.discover("service2");
    Assert.assertTrue(waitTillExpected(3, serviceDiscovered));

    serviceDiscovered = discoveryServiceClient.discover("service3");
    Assert.assertTrue(waitTillExpected(2, serviceDiscovered));

    cancellables.add(register(discoveryService, "service3", "localhost", 3));
    Assert.assertTrue(waitTillExpected(3, serviceDiscovered)); // Shows live iterator.

    for (Cancellable cancellable : cancellables) {
      cancellable.cancel();
    }

    Assert.assertTrue(waitTillExpected(0, discoveryServiceClient.discover("service1")));
    Assert.assertTrue(waitTillExpected(0, discoveryServiceClient.discover("service2")));
    Assert.assertTrue(waitTillExpected(0, discoveryServiceClient.discover("service3")));
  }