All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.grinder.testutility.CallRecorder Maven / Gradle / Ivy

// Copyright (C) 2004 - 2012 Philip Aston
// All rights reserved.
//
// This file is part of The Grinder software distribution. Refer to
// the file LICENSE which is part of The Grinder distribution for
// licensing details. The Grinder distribution is available on the
// Internet at http://grinder.sourceforge.net/
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package net.grinder.testutility;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import junit.framework.Assert;
import junit.framework.AssertionFailedError;


/**
 *  Utility class used to record and assert method invocations.
 *
 * @author    Philip Aston
 */
public class CallRecorder extends Assert implements CallAssertions {

  private static ThreadRecording s_threadRecording = new ThreadRecording();

    private final LinkedList m_callDataList =
    new LinkedList();
  private List m_methodFilters = new ArrayList();
  private boolean m_ignoreCallOrder = false;

  /**
   *  Reset the call data.
   */
  public final void resetCallHistory() {
    synchronized (m_callDataList) {
      m_callDataList.clear();
      m_callDataList.notifyAll();
    }
  }

  /**
   * Wait until we're called. Fail if we take more than timeout
   * milliseconds.
   *
   * @param timeout
   *          Maximum time in milliseconds to wait for.
   */
  public void waitUntilCalled(int timeout) {
    final long expires = System.currentTimeMillis() + timeout;

    synchronized (m_callDataList) {
      while (m_callDataList.size() == 0) {
        try {
          m_callDataList.wait(timeout);
        }
        catch (InterruptedException e) {
          throw new RuntimeException(e);
        }

        if (System.currentTimeMillis() > expires) {
          fail("Timed out waiting to be called after " + timeout + " ms");
        }
      }
    }
  }

  public String getCallHistory() {
    final StringBuffer result = new StringBuffer();

    try {
      s_threadRecording.disable();

      synchronized (m_callDataList) {
        for (CallData callData : m_callDataList) {
          result.append(callData);
          result.append("\n");
        }
      }
    }
    finally {
      s_threadRecording.enable();
    }

    return result.toString();
  }

  /**
   *  Check that no methods have been called.
   */
  public final void assertNoMoreCalls() {
    synchronized (m_callDataList) {
      assertEquals("Call history:\n" + getCallHistory(),
                   0, m_callDataList.size());
    }
  }

  public void setIgnoreCallOrder(boolean b) {
    m_ignoreCallOrder = b;
  }

  public void setIgnoreObjectMethods() {
    m_methodFilters.add(
      new MethodFilter() {
        public boolean matches(Method m) {
          return m.getDeclaringClass() == Object.class;
        }
      });
  }

  public void setIgnoreMethod(final String methodName) {
    m_methodFilters.add(
      new MethodFilter() {
        public boolean matches(Method m) {
          return m.getName().equals(methodName);
        }
      });
  }

  public final void record(CallData callData) {
    if (s_threadRecording.isEnabled()) {
      for (MethodFilter filter : m_methodFilters) {
        if (filter.matches(callData.getMethod())) {
          return;
        }
      }

      synchronized (m_callDataList) {
        m_callDataList.add(callData);
        m_callDataList.notifyAll();
      }
    }
  }

  public final CallData assertSuccess(final String methodName,
                                      final Object... parameters) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertSuccess(methodName, parameters);
      }
    }
    .run();
  }

  public final CallData assertSuccess(final String methodName,
                                      final Class... parameterTypes) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertSuccess(methodName, parameterTypes);
      }
    }
    .run();
  }

  public final CallData assertException(final String methodName,
                                        final Throwable throwable,
                                        final Object... parameters) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertException(methodName, throwable, parameters);
      }
    }
    .run();
  }

  public final CallData assertException(final String methodName,
                                        final Throwable throwable,
                                        final Class... parameterTypes) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertException(methodName, throwable, parameterTypes);
      }
    }
    .run();
  }

  public final CallData assertException(final String methodName,
                                        final Class throwableType,
                                        final Object... parameters) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertException(methodName, throwableType, parameters);
      }
    }
    .run();
  }

  public final CallData assertException(final String methodName,
                                        final Class throwableType,
                                        final Class... parameterTypes) {
    return new AssertMatchingCallTemplate() {
      public void test(CallData callData) {
        callData.assertException(methodName, throwableType, parameterTypes);
      }
    }
    .run();
  }

  private abstract class AssertMatchingCallTemplate {
    public final CallData run() {
      try {
        s_threadRecording.disable();

        if (m_ignoreCallOrder) {
          synchronized (m_callDataList) {
            // Check the earliest call first.
            final Iterator iterator = m_callDataList.iterator();

            while (iterator.hasNext()) {
              try {
                final CallData callData = iterator.next();

                test(callData);
                iterator.remove();
                m_callDataList.notifyAll();

                return callData;
              }
              catch (AssertionFailedError e) {
              }
            }
          }

          fail("No matching call");
          return null; // Not reached.
        }
        else {
          // Check the earliest call.
          synchronized (m_callDataList) {
            try {
              final CallData callData = m_callDataList.removeFirst();
              m_callDataList.notifyAll();
              test(callData);
              return callData;
            }
            catch (NoSuchElementException e) {
              fail("No more calls");
              return null; // Not reached.
            }
          }
        }
      }
      finally {
        s_threadRecording.enable();
      }
    }

    public abstract void test(CallData callData);
  }

  /**
   * Used to disable all CallRecorder recording for the calling thread so we
   * don't record side effects of CallRecorder processing. This also prevents
   * some ConcurrentModificationExceptions.
   */
  private static final class ThreadRecording {

    private final ThreadLocal m_threadLocal =
      new ThreadLocal();

    public void disable() {
      m_threadLocal.set(this);
    }

    public void enable() {
      m_threadLocal.set(null);
    }

    public boolean isEnabled() {
      return m_threadLocal.get() == null;
    }
  }

  public interface MethodFilter {
    boolean matches(Method m);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy