private void setupTestState(boolean isLogSegmented, long segmentCapacity, boolean addTtlUpdates, boolean addUndeletes)
      throws IOException, StoreException {
    Offset expectedStartOffset = new Offset(log.getFirstSegment().getName(), log.getFirstSegment().getStartOffset());
    assertEquals("Start Offset of index not as expected", expectedStartOffset, index.getStartOffset());
    assertEquals("End Offset of index not as expected", log.getEndOffset(), index.getCurrentEndOffset());
    // advance time by a second in order to be able to add expired keys and to avoid keys that are expired from
    // being picked for delete.
    advanceTime(Time.MsPerSec);
    assertEquals("Incorrect log segment count", 0, index.getLogSegmentCount());
    long expectedUsedCapacity;
    if (!isLogSegmented) {
      // log is filled about ~50%.
      addCuratedIndexEntriesToLogSegment(segmentCapacity / 2, 1, addTtlUpdates);
      expectedUsedCapacity = segmentCapacity / 2;
      assertEquals("Used capacity reported not as expected", expectedUsedCapacity, index.getLogUsedCapacity());
    } else {
      // first log segment is filled to capacity.
      addCuratedIndexEntriesToLogSegment(segmentCapacity, 1, addTtlUpdates);
      assertEquals("Used capacity reported not as expected", segmentCapacity, index.getLogUsedCapacity());

      // second log segment is filled but has some space at the end (free space has to be less than the lesser of the
      // standard delete and put record sizes so that the next write causes a roll over of log segments).
      addCuratedIndexEntriesToLogSegment(segmentCapacity - (CuratedLogIndexState.DELETE_RECORD_SIZE - 1), 2,
          addTtlUpdates);
      assertEquals("Used capacity reported not as expected",
          2 * segmentCapacity - (CuratedLogIndexState.DELETE_RECORD_SIZE - 1), index.getLogUsedCapacity());

      // third log segment is partially filled and is left as the "active" segment
      // First Index Segment
      // 1 PUT entry
      addPutEntries(1, CuratedLogIndexState.PUT_RECORD_SIZE, Utils.Infinite_Time);
      assertEquals("Incorrect log segment count", 3, index.getLogSegmentCount());
      // DELETE for a key in the first log segment
      LogSegment firstSegment = log.getFirstSegment();
      MockId idToDelete = getIdToDeleteFromLogSegment(firstSegment, false);
      addDeleteEntry(idToDelete);
      // DELETE for a key in the second segment
      LogSegment secondSegment = log.getNextSegment(firstSegment);
      idToDelete = getIdToDeleteFromLogSegment(secondSegment, false);
      addDeleteEntry(idToDelete);
      // 1 DELETE for the PUT in the same segment
      idToDelete = getIdToDeleteFromIndexSegment(referenceIndex.lastKey(), false);
      addDeleteEntry(idToDelete);
      if (addTtlUpdates) {
        // 1 TTL update for a key in first log segment
        MockId idToUpdate = getIdToTtlUpdateFromLogSegment(firstSegment);
        makePermanent(idToUpdate, false);

        // Second Index Segment
        // 1 TTL update for a key in the second log segment
        idToUpdate = getIdToTtlUpdateFromLogSegment(secondSegment);
        makePermanent(idToUpdate, false);
        // 1 TTL update for a key in first log segment
        idToUpdate = getIdToTtlUpdateFromLogSegment(firstSegment);
        makePermanent(idToUpdate, false);
        // 1 DELETE for the key above
        addDeleteEntry(idToUpdate);
        // 1 TTL update for a key in second log segment
        idToUpdate = getIdToTtlUpdateFromLogSegment(secondSegment);
        makePermanent(idToUpdate, false);
        // 1 DELETE for the key above
        addDeleteEntry(idToUpdate);

        // Third Index Segment
        // 1 DELETE for a key that's already ttl updated in the first log segment
        idToDelete = getIdToDeleteFromLogSegment(firstSegment, true);
        addDeleteEntry(idToDelete);
        // 1 DELETE for a key that's already ttl updated in the first second segment
        idToDelete = getIdToDeleteFromLogSegment(secondSegment, true);
        addDeleteEntry(idToDelete);
      }
      if (addUndeletes) {
        addCuratedUndeleteToLogSegment();
      }
      // 1 PUT entry that spans the rest of the data in the segment (upto a third of the segment size)
      long size = segmentCapacity / 3 - index.getCurrentEndOffset().getOffset();
      addPutEntries(1, size, Utils.Infinite_Time);

      expectedUsedCapacity = 2 * segmentCapacity + segmentCapacity / 3;
      assertEquals("Used capacity reported not as expected", expectedUsedCapacity, index.getLogUsedCapacity());

      // fourth and fifth log segment are free.
    }
    // make sure all indexes are written to disk and mapped as required (forcing IndexPersistor to run).
    log.flush();
    reloadIndex(true, false);
    verifyState(isLogSegmented);
    assertEquals("Start Offset of index not as expected", expectedStartOffset, index.getStartOffset());
    assertEquals("End Offset of index not as expected", log.getEndOffset(), index.getCurrentEndOffset());
    assertEquals("Used capacity reported not as expected", expectedUsedCapacity, index.getLogUsedCapacity());
    assertFalse("Expected nonempty index", index.isEmpty());
  }
Sets up some state in order to make sure all cases are represented and the tests don't need to do any setup
individually. For understanding the created index, please read the source code which is annotated with comments.
<p/>
Also tests critical functionality of {@link PersistentIndex} and behaviour of the {@link Journal} in the index.
Also verifies that the state in {@link #referenceIndex} matches the state in the real index.
@param isLogSegmented {@code true} if segmented. {@code false} otherwise.
@param segmentCapacity the intended capacity of each segment
@param addTtlUpdates if {@code true}, adds entries that update TTL.
@param addUndeletes if {@code true}, adds undelete entries.
@throws IOException
@throws StoreException
ambry-store/src/test/java/com/github/ambry/store/CuratedLogIndexState.java