private void doComprehensiveTest(short version, boolean includeSmallKeys, boolean includeLargeKeys)
      throws IOException, StoreException {
    LogSegmentName[] logSegmentNames =
        {LogSegmentName.generateFirstSegmentName(false), StoreTestUtils.getRandomLogSegmentName(null)};
    int valueSize;
    switch (version) {
      case PersistentIndex.VERSION_0:
        valueSize = IndexValue.INDEX_VALUE_SIZE_IN_BYTES_V0;
        break;
      case PersistentIndex.VERSION_1:
      case PersistentIndex.VERSION_2:
        valueSize = IndexValue.INDEX_VALUE_SIZE_IN_BYTES_V1_V2;
        break;
      case PersistentIndex.VERSION_3:
      case PersistentIndex.VERSION_4:
        valueSize = IndexValue.INDEX_VALUE_SIZE_IN_BYTES_V3_V4;
        break;
      default:
        fail("Unknown PersistentIndex formatVersion");
        valueSize = -1;
    }
    for (LogSegmentName logSegmentName : logSegmentNames) {
      long writeStartOffset = Utils.getRandomLong(TestUtils.RANDOM, 1000);
      Offset startOffset = new Offset(logSegmentName, writeStartOffset);
      NavigableMap<MockId, NavigableSet<IndexValue>> referenceIndex = new TreeMap<>();
      // advance time so that last modified time for VERSION_1 has different last modified times for different index
      // segments
      time.sleep(10 * Time.MsPerSec);
      PersistentIndex.cleanupIndexSegmentFilesForLogSegment(tempDir.getAbsolutePath(), logSegmentName);
      IndexSegment indexSegment = generateIndexSegment(startOffset, STORE_KEY_FACTORY);
      StoreKey resetKey = null;
      PersistentIndex.IndexEntryType resetKeyType = null;
      short resetKeyLifeVersion = StoreFindToken.UNINITIALIZED_RESET_KEY_VERSION;
      verifyIndexSegmentDetails(indexSegment, startOffset, 0, 0, false, startOffset.getOffset(), time.milliseconds(),
          null, null, resetKeyLifeVersion);
      int numItems = 10;
      int numSmallKeys = 0;
      int numLargeKeys = 0;
      List<Long> offsets = new ArrayList<>();
      offsets.add(writeStartOffset);
      for (int i = 0; i < numItems - 1; i++) {
        // size has to be > 0 (no record is 0 sized)
        long size = Utils.getRandomLong(TestUtils.RANDOM, 1000) + 1;
        offsets.add(writeStartOffset + size);
        writeStartOffset += size;
        if (i % 3 == 1 && includeSmallKeys) {
          numSmallKeys++;
        }
        if (i % 3 == 2 && includeLargeKeys) {
          numLargeKeys++;
        }
      }
      long lastEntrySize = Utils.getRandomLong(TestUtils.RANDOM, 1000) + 1;
      long endOffset = offsets.get(offsets.size() - 1) + lastEntrySize;
      List<IndexEntry> newEntries =
          addPutEntries(offsets, lastEntrySize, indexSegment, referenceIndex, includeSmallKeys, includeLargeKeys);
      if (version != PersistentIndex.VERSION_0) {
        resetKey = newEntries.get(0).getKey();
        resetKeyType = PersistentIndex.IndexEntryType.PUT;
        resetKeyLifeVersion = newEntries.get(0).getValue().getLifeVersion();
      }
      int expectedSizeWritten =
          SMALLER_KEY_SIZE * numSmallKeys + LARGER_KEY_SIZE * numLargeKeys + KEY_SIZE * (numItems - numSmallKeys
              - numLargeKeys) + numItems * valueSize;
      verifyAllForIndexSegmentFromFile(referenceIndex, indexSegment, startOffset, numItems, expectedSizeWritten, false,
          endOffset, time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);

      int extraIdsToTtlUpdate = 5;
      Set<MockId> idsToTtlUpdate = getIdsToTtlUpdate(referenceIndex, extraIdsToTtlUpdate);
      addTtlUpdateEntries(idsToTtlUpdate, indexSegment, referenceIndex);
      endOffset += idsToTtlUpdate.size() * TTL_UPDATE_FILE_SPAN_SIZE;
      numItems += idsToTtlUpdate.size();
      for (MockId id : idsToTtlUpdate) {
        expectedSizeWritten += valueSize + id.sizeInBytes();
      }
      verifyAllForIndexSegmentFromFile(referenceIndex, indexSegment, startOffset, numItems, expectedSizeWritten, false,
          endOffset, time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);
      indexSegment.writeIndexSegmentToFile(indexSegment.getEndOffset());
      verifyReadFromFile(referenceIndex, indexSegment.getFile(), startOffset, numItems, expectedSizeWritten, endOffset,
          time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);

      int extraIdsToDelete = 5;
      Set<MockId> idsToDelete = getIdsToDelete(referenceIndex, extraIdsToDelete);
      addDeleteEntries(idsToDelete, indexSegment, referenceIndex);
      endOffset += idsToDelete.size() * DELETE_FILE_SPAN_SIZE;
      numItems += idsToDelete.size();
      for (MockId id : idsToDelete) {
        expectedSizeWritten += valueSize + id.sizeInBytes();
      }
      verifyAllForIndexSegmentFromFile(referenceIndex, indexSegment, startOffset, numItems, expectedSizeWritten, false,
          endOffset, time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);
      indexSegment.writeIndexSegmentToFile(indexSegment.getEndOffset());
      verifyReadFromFile(referenceIndex, indexSegment.getFile(), startOffset, numItems, expectedSizeWritten, endOffset,
          time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);

      // all combinations
      offsets = new ArrayList<>();
      for (int i = 0; i < 3; i++) {
        offsets.add(endOffset);
        endOffset += Utils.getRandomLong(TestUtils.RANDOM, 1000) + 1;
        numItems++;
        expectedSizeWritten += valueSize + KEY_SIZE;
      }
      List<IndexEntry> puts =
          addPutEntries(offsets, endOffset - offsets.get(offsets.size() - 1), indexSegment, referenceIndex, false,
              false);
      // the second entry has only a TTL update
      addTtlUpdateEntries(Collections.singleton((MockId) puts.get(1).getKey()), indexSegment, referenceIndex);
      // the third entry has a TTL update and a delete
      addTtlUpdateEntries(Collections.singleton((MockId) puts.get(2).getKey()), indexSegment, referenceIndex);
      addDeleteEntries(Collections.singleton((MockId) puts.get(2).getKey()), indexSegment, referenceIndex);
      // an ID that is only TTL updated
      addTtlUpdateEntries(Collections.singleton(generateIds(referenceIndex, 1).get(0)), indexSegment, referenceIndex);
      // an ID that is TTL updated and deleted
      MockId idToTtlUpdateAndDelete = generateIds(referenceIndex, 1).get(0);
      addTtlUpdateEntries(Collections.singleton(idToTtlUpdateAndDelete), indexSegment, referenceIndex);
      addDeleteEntries(Collections.singleton(idToTtlUpdateAndDelete), indexSegment, referenceIndex);
      // an ID that is only deleted
      addDeleteEntries(Collections.singleton(generateIds(referenceIndex, 1).get(0)), indexSegment, referenceIndex);
      endOffset += 4 * TTL_UPDATE_FILE_SPAN_SIZE + 3 * DELETE_FILE_SPAN_SIZE;
      numItems += 7;
      expectedSizeWritten += 7 * (KEY_SIZE + valueSize);
      verifyAllForIndexSegmentFromFile(referenceIndex, indexSegment, startOffset, numItems, expectedSizeWritten, false,
          endOffset, time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);
      indexSegment.writeIndexSegmentToFile(indexSegment.getEndOffset());
      verifyReadFromFile(referenceIndex, indexSegment.getFile(), startOffset, numItems, expectedSizeWritten, endOffset,
          time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);

      // verify that flipping StoreConfig.storeIndexMemStateName does not break anything
      IndexMemState saved = config.storeIndexMemState;
      for (IndexMemState state : IndexMemState.values()) {
        if (!state.equals(saved)) {
          setIndexMemState(state);
          verifyReadFromFile(referenceIndex, indexSegment.getFile(), startOffset, numItems, expectedSizeWritten,
              endOffset, time.milliseconds(), resetKey, resetKeyType, resetKeyLifeVersion);
        }
      }
      setIndexMemState(saved);
    }
  }
Comprehensive tests for {@link IndexSegment}.
1. Creates a segment and checks the getters to make sure they return the right values
2. Adds some put entries with random sizes, checks getters again and exercises {@link IndexSegment#find(StoreKey)}
and {@link IndexSegment#getEntriesSince(StoreKey, FindEntriesCondition, List, AtomicLong, boolean)}.
3. Adds some delete entries (deletes some existing put entries and creates deletes for puts not in this segment)
and does the same checks as #2.
4. Writes index to a file and loads it sealed and not sealed and does all the checks in #2 once again along with
checking that journal entries are populated correctly.
@throws IOException
@throws StoreException
ambry-store/src/test/java/com/github/ambry/store/IndexSegmentTest.java