@Test
  public void testStateTransition()
      throws InterruptedException, ExecutionException, TimeoutException {
    InMemoryZKServer zkServer = InMemoryZKServer.builder().build();
    zkServer.startAndWait();

    try {
      final String namespace =
          Joiner.on('/').join("/twill", RunIds.generate(), "runnables", "Runner1");

      final ZKClientService zkClient =
          ZKClientService.Builder.of(zkServer.getConnectionStr()).build();
      zkClient.startAndWait();
      zkClient.create(namespace, null, CreateMode.PERSISTENT).get();

      try {
        JsonObject content = new JsonObject();
        content.addProperty("containerId", "container-123");
        content.addProperty("host", "localhost");

        RunId runId = RunIds.generate();
        final Semaphore semaphore = new Semaphore(0);
        ZKServiceDecorator service =
            new ZKServiceDecorator(
                ZKClients.namespace(zkClient, namespace),
                runId,
                Suppliers.ofInstance(content),
                new AbstractIdleService() {
                  @Override
                  protected void startUp() throws Exception {
                    Preconditions.checkArgument(
                        semaphore.tryAcquire(5, TimeUnit.SECONDS), "Fail to start");
                  }

                  @Override
                  protected void shutDown() throws Exception {
                    Preconditions.checkArgument(
                        semaphore.tryAcquire(5, TimeUnit.SECONDS), "Fail to stop");
                  }
                });

        final String runnablePath = namespace + "/" + runId.getId();
        final AtomicReference<String> stateMatch = new AtomicReference<String>("STARTING");
        watchDataChange(zkClient, runnablePath + "/state", semaphore, stateMatch);
        Assert.assertEquals(Service.State.RUNNING, service.start().get(5, TimeUnit.SECONDS));

        stateMatch.set("STOPPING");
        Assert.assertEquals(Service.State.TERMINATED, service.stop().get(5, TimeUnit.SECONDS));

      } finally {
        zkClient.stopAndWait();
      }
    } finally {
      zkServer.stopAndWait();
    }
  }
 private static ZKClient getContainerZKClient(
     ZKClient zkClient, RunId appRunId, String runnableName) {
   return ZKClients.namespace(zkClient, String.format("/%s/runnables/%s", appRunId, runnableName));
 }
 /** Returns a {@link ZKClient} that namespaced under the given run id. */
 private static ZKClient getAppRunZKClient(ZKClient zkClient, RunId appRunId) {
   return ZKClients.namespace(zkClient, String.format("/%s", appRunId));
 }