private List<MockId> createLogSegmentWithDuplicateOfUndelete() throws Exception {
    refreshState(false, false, false);
    int numPuts = (int) ((state.log.getSegmentCapacity() - LogSegment.HEADER_SIZE) / (PUT_RECORD_SIZE));
    List<IndexEntry> entries = state.addPutEntries(numPuts, PUT_RECORD_SIZE, Utils.Infinite_Time);
    // We will construct log such as
    // First log segment will be all PUTs, and some of them will be compacted by adding DELETEs in the end
    // Second log segment will contain certain records, such that the records will be compacted and fitted in a single
    // compacted segment with first log segment. And certain records will be inserted to make sure it meets some corner
    // cases, which wil be commented below.
    assertEquals(1, state.index.getLogSegmentCount());

    int numRecordToCompact = 0;
    MockId pu = (MockId) state.addPutEntries(1, PUT_RECORD_SIZE, Utils.Infinite_Time).get(0).getKey();
    state.makePermanent(pu, false);
    state.addDeleteEntry(pu);
    state.addUndeleteEntry(pu);
    numRecordToCompact += 3;
    // LifeVersion in source index > candidate.
    // Here we have a P0, T0, D0 and U1. These records will be compacted to P0, T1, U1. When it recovers, the U1 will be in
    // source index, and P0, T0, U1 will be tested alreadyExists against U1. (D0 is not valid).

    MockId pd = (MockId) state.addPutEntries(1, PUT_RECORD_SIZE, Utils.Infinite_Time).get(0).getKey();
    state.makePermanent(pd, false);
    state.addDeleteEntry(pd);
    numRecordToCompact += 1;
    // LifeVersion in source index = candidate. Delete is final
    // Here we have a P0, T0 and D0. These records will be compacted to D0. When it recovers, the D0 will be in the source
    // index and P0 and T0 will be tested alreadyExists against D0.

    MockId pt = (MockId) state.addPutEntries(1, PUT_RECORD_SIZE, Utils.Infinite_Time).get(0).getKey();
    state.makePermanent(pt, false);
    numRecordToCompact += 2;
    // LifeVersion in source index = candidate. TtlUpdate is final
    // Here we have a P0, T0. These records will be both copied over. When it recovers, the T0 will be in the source
    // index and P0 and T0 will be tested alreadyExists against T0.

    // LifeVersion in source index < candidate is too common that it's already been tested in other test functions.
    writeDataToMeetRequiredSegmentCount(2, null);

    assertTrue(numRecordToCompact < numPuts);
    for (int i = 0; i < numRecordToCompact; i++) {
      state.addDeleteEntry((MockId) entries.get(i).getKey());
    }
    writeDataToMeetRequiredSegmentCount(4, null);
    return Arrays.asList(pu, pd, pt);
  }
Tests the case where close/crash creates a situation where duplicates, includeing undeletes, will be detected during
the search for copy candidates in the tgtIndex
@throws Exception
ambry-store/src/test/java/com/github/ambry/store/BlobStoreCompactorTest.java