/**
   * Test that compaction doesnt add to index without having persisted entrylog first. This is
   * needed because compaction doesn't go through the journal. {@see
   * https://issues.apache.org/jira/browse/BOOKKEEPER-530} {@see
   * https://issues.apache.org/jira/browse/BOOKKEEPER-664}
   */
  @Test(timeout = 60000)
  public void testCompactionSafety() throws Exception {
    tearDown(); // I dont want the test infrastructure
    ServerConfiguration conf = TestBKConfiguration.newServerConfiguration();
    final Set<Long> ledgers = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
    ActiveLedgerManager manager = getActiveLedgerManager(ledgers);

    File tmpDir = createTempDir("compaction", "compactionSafety");
    File curDir = Bookie.getCurrentDirectory(tmpDir);
    Bookie.checkDirectoryStructure(curDir);
    conf.setLedgerDirNames(new String[] {tmpDir.toString()});

    conf.setEntryLogSizeLimit(EntryLogger.LOGFILE_HEADER_LENGTH + 3 * (4 + ENTRY_SIZE));
    conf.setGcWaitTime(100);
    conf.setMinorCompactionThreshold(0.7f);
    conf.setMajorCompactionThreshold(0.0f);
    conf.setMinorCompactionInterval(1);
    conf.setMajorCompactionInterval(10);
    conf.setPageLimit(1);

    CheckpointSource checkpointProgress =
        new CheckpointSource() {
          AtomicInteger idGen = new AtomicInteger(0);

          class MyCheckpoint implements Checkpoint {
            int id = idGen.incrementAndGet();

            @Override
            public int compareTo(Checkpoint o) {
              if (o == Checkpoint.MAX) {
                return -1;
              } else if (o == Checkpoint.MIN) {
                return 1;
              }
              return id - ((MyCheckpoint) o).id;
            }
          }

          @Override
          public Checkpoint newCheckpoint() {
            return new MyCheckpoint();
          }

          @Override
          public void checkpointComplete(Checkpoint checkpoint, boolean compact)
              throws IOException {}
        };

    final byte[] KEY = "foobar".getBytes();
    File log0 = new File(curDir, "0.log");
    File log1 = new File(curDir, "1.log");
    LedgerDirsManager dirs = new LedgerDirsManager(conf, conf.getLedgerDirs());
    assertFalse("Log 0 shouldnt exist", log0.exists());
    assertFalse("Log 1 shouldnt exist", log1.exists());
    InterleavedLedgerStorage storage =
        new InterleavedLedgerStorage(
            conf, manager, dirs, dirs, checkpointProgress, NullStatsLogger.INSTANCE);
    ledgers.add(1l);
    ledgers.add(2l);
    ledgers.add(3l);
    storage.setMasterKey(1, KEY);
    storage.setMasterKey(2, KEY);
    storage.setMasterKey(3, KEY);
    LOG.info("Write Ledger 1");
    storage.addEntry(genEntry(1, 1, ENTRY_SIZE));
    LOG.info("Write Ledger 2");
    storage.addEntry(genEntry(2, 1, ENTRY_SIZE));
    storage.addEntry(genEntry(2, 2, ENTRY_SIZE));
    LOG.info("Write ledger 3");
    storage.addEntry(genEntry(3, 2, ENTRY_SIZE));
    storage.flush();
    storage.shutdown();

    assertTrue("Log 0 should exist", log0.exists());
    assertTrue("Log 1 should exist", log1.exists());
    ledgers.remove(2l);
    ledgers.remove(3l);

    storage =
        new InterleavedLedgerStorage(
            conf, manager, dirs, dirs, checkpointProgress, NullStatsLogger.INSTANCE);
    storage.start();
    for (int i = 0; i < 10; i++) {
      if (!log0.exists() && !log1.exists()) {
        break;
      }
      Thread.sleep(1000);
      storage.entryLogger.flush(); // simulate sync thread
    }
    assertFalse("Log shouldnt exist", log0.exists());
    assertFalse("Log shouldnt exist", log1.exists());

    LOG.info("Write ledger 4");
    ledgers.add(4l);
    storage.setMasterKey(4, KEY);
    storage.addEntry(genEntry(4, 1, ENTRY_SIZE)); // force ledger 1 page to flush

    storage =
        new InterleavedLedgerStorage(
            conf, manager, dirs, dirs, checkpointProgress, NullStatsLogger.INSTANCE);
    storage.getEntry(1, 1); // entry should exist
  }