@Test
  public void
      shouldProvideIndexStatisticsWhenIndexIsBuiltViaPopulationAndConcurrentAdditionsAndDeletions()
          throws Exception {
    // given some initial data
    long[] nodes = repeatCreateNamedPeopleFor(NAMES.length * CREATION_MULTIPLIER);
    int initialNodes = nodes.length;

    // when populating while creating
    IndexDescriptor index = createIndex("Person", "name");
    UpdatesTracker updatesTracker = executeCreationsAndDeletions(nodes, index, CREATION_MULTIPLIER);
    awaitOnline(index);

    // then
    int seenWhilePopulating =
        initialNodes
            + updatesTracker.createdDuringPopulation()
            - updatesTracker.deletedDuringPopulation();
    double expectedSelectivity = UNIQUE_NAMES / (seenWhilePopulating);
    assertCorrectIndexSelectivity(expectedSelectivity, indexSelectivity(index));
    assertCorrectIndexSize(seenWhilePopulating, indexSize(index));
    int expectedIndexUpdates =
        updatesTracker.deletedAfterPopulation() + updatesTracker.createdAfterPopulation();
    assertCorrectIndexUpdates(expectedIndexUpdates, indexUpdates(index));
  }
  @Test
  public void shouldWorkWhileHavingHeavyConcurrentUpdates() throws Exception {
    // given some initial data
    final long[] nodes = repeatCreateNamedPeopleFor(NAMES.length * CREATION_MULTIPLIER);
    int initialNodes = nodes.length;
    int threads = 5;
    ExecutorService executorService = Executors.newFixedThreadPool(threads);

    // when populating while creating
    final IndexDescriptor index = createIndex("Person", "name");

    final Collection<Callable<UpdatesTracker>> jobs = new ArrayList<>(threads);
    for (int i = 0; i < threads; i++) {
      jobs.add(
          new Callable<UpdatesTracker>() {
            @Override
            public UpdatesTracker call() throws Exception {
              return executeCreationsDeletionsAndUpdates(nodes, index, CREATION_MULTIPLIER);
            }
          });
    }

    List<Future<UpdatesTracker>> futures = executorService.invokeAll(jobs);
    // sum result into empty result
    UpdatesTracker result = new UpdatesTracker();
    result.notifyPopulationCompleted();
    for (Future<UpdatesTracker> future : futures) {
      result.add(future.get());
    }
    awaitOnline(index);

    executorService.awaitTermination(1, TimeUnit.SECONDS);
    executorService.shutdown();

    // then
    int tolerance = MISSED_UPDATES_TOLERANCE * threads;
    double doubleTolerance = DOUBLE_ERROR_TOLERANCE * threads;
    int seenWhilePopulating =
        initialNodes + result.createdDuringPopulation() - result.deletedDuringPopulation();
    double expectedSelectivity = UNIQUE_NAMES / (seenWhilePopulating);
    assertCorrectIndexSelectivity(expectedSelectivity, indexSelectivity(index), doubleTolerance);
    assertCorrectIndexSize(
        "Tracker had " + result, seenWhilePopulating, indexSize(index), tolerance);
    int expectedIndexUpdates = result.deletedAfterPopulation() + result.createdAfterPopulation();
    assertCorrectIndexUpdates(
        "Tracker had " + result, expectedIndexUpdates, indexUpdates(index), tolerance);
  }
  private UpdatesTracker internalExecuteCreationsDeletionsAndUpdates(
      long[] nodes,
      IndexDescriptor index,
      int numberOfCreations,
      boolean allowDeletions,
      boolean allowUpdates)
      throws KernelException {
    Random random = ThreadLocalRandom.current();
    UpdatesTracker updatesTracker = new UpdatesTracker();
    int offset = 0;
    while (updatesTracker.created() < numberOfCreations) {
      int created = createNamedPeople(nodes, offset);
      offset += created;
      updatesTracker.increaseCreated(created);

      // check index online
      if (!updatesTracker.isPopulationCompleted() && indexOnlineMonitor.isIndexOnline(index)) {
        updatesTracker.notifyPopulationCompleted();
      }

      // delete if allowed
      if (allowDeletions && updatesTracker.created() % 5 == 0) {
        long nodeId = nodes[random.nextInt(nodes.length)];
        try {
          deleteNode(nodeId);
          updatesTracker.increaseDeleted(1);
        } catch (EntityNotFoundException ex) {
          // ignore
        }

        // check again index online
        if (!updatesTracker.isPopulationCompleted() && indexOnlineMonitor.isIndexOnline(index)) {
          updatesTracker.notifyPopulationCompleted();
        }
      }

      // update if allowed
      if (allowUpdates && updatesTracker.created() % 5 == 0) {
        int randomIndex = random.nextInt(nodes.length);
        try {
          changeName(nodes[randomIndex], "name", NAMES[randomIndex % NAMES.length]);
        } catch (EntityNotFoundException ex) {
          // ignore
        }

        // check again index online
        if (!updatesTracker.isPopulationCompleted() && indexOnlineMonitor.isIndexOnline(index)) {
          updatesTracker.notifyPopulationCompleted();
        }
      }
    }
    // make sure population complete has been notified
    updatesTracker.notifyPopulationCompleted();
    return updatesTracker;
  }