mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
yesterday 6477a7e28301b6d5d9746cf4bfb29ae4b748258d
opendj-server-legacy/src/test/java/org/opends/server/replication/ReplicationTestCase.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -57,6 +58,10 @@
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.loggers.ErrorLogPublisher;
import org.opends.server.loggers.ErrorLogger;
import org.opends.server.loggers.TextErrorLogPublisher;
import org.opends.server.loggers.TextWriter;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.internal.SearchRequest;
@@ -939,6 +944,227 @@
    return new ReplSessionSecurity(null, null, null, true);
  }
  /**
   * Runs the provided action and returns the records the error log received while it ran.
   * <p>
   * A publisher of its own is registered for the duration rather than reading the one the
   * test harness installs, whose contents span the whole test JVM.
   * <p>
   * It publishes every severity, so that a record a throttle kept out of the warnings is
   * captured too. That is server wide while the action runs, so the records of other
   * threads are captured as well and a caller has to pick out its own.
   *
   * @param action
   *          The action to run.
   * @return The error log records written while the action ran, in order.
   * @throws Exception
   *           Whatever the action throws.
   */
  protected static List<String> errorLogRecordsOf(final Callable<Void> action) throws Exception
  {
    return errorLogRecordsOf(unused -> action.call());
  }
  /**
   * Runs the provided action, which reads the error log records as they are written, and
   * returns the records the error log received while it ran.
   * <p>
   * The list handed to the action is the live one, so an action can wait for a server
   * thread to log something instead of waiting for a duration. It is synchronized, and
   * iterating it is not: a reader has to copy it, or hold its monitor.
   *
   * @param action
   *          The action to run, taking the records written so far.
   * @return The error log records written while the action ran, in order.
   * @throws Exception
   *           Whatever the action throws.
   */
  // The publisher is built raw and handed to a parameterized addLogPublisher: the
  // conversion is unchecked, and it is the one the test harness makes as well.
  @SuppressWarnings({ "rawtypes", "unchecked" })
  protected static List<String> errorLogRecordsOf(ErrorLogAction action) throws Exception
  {
    final List<String> records = Collections.synchronizedList(new ArrayList<String>());
    final ErrorLogPublisher capture =
        TextErrorLogPublisher.getToolStartupTextErrorPublisher(new TextWriter()
        {
          @Override
          public void writeRecord(String record)
          {
            records.add(record);
          }
          @Override
          public void flush()
          {
            // Nothing is buffered.
          }
          @Override
          public void shutdown()
          {
            // Nothing is buffered.
          }
          @Override
          public long getBytesWritten()
          {
            return 0;
          }
        });
    ErrorLogger.getInstance().addLogPublisher(capture);
    try
    {
      action.run(records);
    }
    finally
    {
      ErrorLogger.getInstance().removeLogPublisher(capture);
    }
    // Copied under the monitor: the publishers are iterated from a snapshot, so a thread
    // which is already inside it can still write a record after this one was removed, and
    // the caller must not have to synchronize to read what it got.
    return copyOf(records);
  }
  /**
   * Returns the one record of the provided error log which reports the provided message.
   * <p>
   * A record holds the severity it was published with next to the message, so this is how
   * a test reads the severity of a message it expects, rather than only its text.
   *
   * @param records
   *          The error log records to look in, as {@link #errorLogRecordsOf} returned them.
   * @param message
   *          The message the record is expected to report.
   * @return The record reporting the provided message.
   */
  protected static String recordOf(List<String> records, LocalizableMessage message)
  {
    String found = null;
    for (String record : records)
    {
      if (record.contains(message.toString()))
      {
        assertNull(found, "\"" + message + "\" should have been logged once, but the error log"
            + " holds it more than once: " + records);
        found = record;
      }
    }
    assertNotNull(found, "\"" + message + "\" should have been logged, but the error log holds: " + records);
    return found;
  }
  /**
   * Returns how many records of the provided error log hold the provided text.
   *
   * @param records
   *          The error log records to count in.
   * @param contained
   *          The text the counted records hold.
   * @return The number of records holding the provided text.
   */
  protected static int countRecordsOf(List<String> records, String contained)
  {
    int count = 0;
    synchronized (records)
    {
      for (String record : records)
      {
        if (record.contains(contained))
        {
          count++;
        }
      }
    }
    return count;
  }
  /**
   * Waits for a record holding the provided text to be written to the provided error log.
   * <p>
   * For what a server thread logs on its own schedule: the connect thread of a replication
   * server retries a peer every second, so what it reports is waited for rather than
   * expected to be there already.
   * <p>
   * Only the records which arrived since the previous poll are read: the capture publishes
   * every severity, which is every {@code logger.debug} of every thread of the server for
   * as long as it is installed, so rereading the whole list on each poll would cost the
   * square of what a slow wait captures.
   *
   * @param records
   *          The live error log records, as {@link ErrorLogAction} received them.
   * @param contained
   *          The text the awaited record holds.
   * @param timeoutMs
   *          How long to wait for it, in milliseconds.
   * @throws InterruptedException
   *           If the wait is interrupted.
   */
  protected static void waitForErrorLogRecord(List<String> records, String contained, long timeoutMs)
      throws InterruptedException
  {
    final long deadline = System.currentTimeMillis() + timeoutMs;
    int read = 0;
    while (true)
    {
      synchronized (records)
      {
        final int written = records.size();
        while (read < written)
        {
          if (records.get(read++).contains(contained))
          {
            return;
          }
        }
      }
      // Tested after the records are read and not after the sleep, so that what arrives
      // during the last sleep of the wait is still read: it arrived inside the timeout.
      if (System.currentTimeMillis() >= deadline)
      {
        break;
      }
      Thread.sleep(100);
    }
    fail("\"" + contained + "\" should have been logged within " + timeoutMs
        + " ms, but the error log received " + read + " records, the last of them: "
        + lastRecordsOf(records));
  }
  /** Copies the live error log records, which are synchronized but not safe to iterate. */
  private static List<String> copyOf(List<String> records)
  {
    synchronized (records)
    {
      return new ArrayList<>(records);
    }
  }
  /**
   * The tail of the live error log records, for a failure message: the capture holds every
   * severity, so the whole list is not something a report can carry.
   */
  private static List<String> lastRecordsOf(List<String> records)
  {
    final List<String> all = copyOf(records);
    return all.subList(Math.max(0, all.size() - 20), all.size());
  }
  /** An action which reads the error log records as they are written. */
  protected interface ErrorLogAction
  {
    /**
     * Runs the action.
     *
     * @param records
     *          The live error log records written since the action started.
     * @throws Exception
     *           Whatever the action throws.
     */
    void run(List<String> records) throws Exception;
  }
  protected void executeTask(Entry taskEntry, long maxWaitTimeInMillis) throws Exception
  {
    addTask(taskEntry, ResultCode.SUCCESS, null);