Package com.danhaywood.java.testsupport.coverage

Source Code of com.danhaywood.java.testsupport.coverage.PojoTester$TestException

/*
*  Copyright 2010-2013 Dan Haywood
*
*  Licensed under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package com.danhaywood.java.testsupport.coverage;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import junit.framework.AssertionFailedError;

public final class PojoTester {

  public static class FixtureDatumFactory<T> {
    private Class<T> type;
    private T[] fixtureData;
    private int index;
    public FixtureDatumFactory() {
    }
    public FixtureDatumFactory(Class<T> type) {
      this.type = type;
    }
    public FixtureDatumFactory(Class<T> type, T... fixtureData) {
      this(type);
      this.fixtureData = fixtureData;
      index = fixtureData.length - 1;
    }
    public Class<T> getType() { return type; }
    public T getNext() {
      index = (index + 1) % fixtureData.length;
      return fixtureData[index];
    }
  }

   public static PojoTester strict() {
        return new PojoTester(Mode.STRICT);
   }
   public static PojoTester relaxed() {
       return new PojoTester(Mode.RELAXED);
   }

  private final Map<Class<?>, FixtureDatumFactory<?>> fixtureDataByType = new HashMap<Class<?>, FixtureDatumFactory<?>>();
  private final AtomicInteger counter = new AtomicInteger();

  private enum Mode {
      STRICT,
      RELAXED
  }
 
  private Mode mode;
 
  private PojoTester(Mode mode) {
   
    this.mode = mode;
        FixtureDatumFactory<Boolean> booleanDatumFactory = new FixtureDatumFactory<Boolean>(Boolean.class) {
      public Boolean getNext() {
        return counter.getAndIncrement() == 0;
      }
    };
    fixtureDataByType.put(boolean.class, booleanDatumFactory);
    fixtureDataByType.put(Boolean.class, booleanDatumFactory);

   
    FixtureDatumFactory<Byte> byteDatumFactory = new FixtureDatumFactory<Byte>(Byte.class) {
      public Byte getNext() {
        return (byte) counter.getAndIncrement();
      }
    };
    fixtureDataByType.put(byte.class, byteDatumFactory);
    fixtureDataByType.put(Byte.class, byteDatumFactory);
   
   
    FixtureDatumFactory<Short> shortDatumFactory = new FixtureDatumFactory<Short>(Short.class) {
      public Short getNext() {
        return (short) counter.getAndIncrement();
      }
    };
    fixtureDataByType.put(short.class, shortDatumFactory);
    fixtureDataByType.put(Short.class, shortDatumFactory);
   
   
    FixtureDatumFactory<Character> charDatumFactory = new FixtureDatumFactory<Character>(Character.class) {
      public Character getNext() {
        return (char) counter.getAndIncrement();
      }
    };
    fixtureDataByType.put(char.class, charDatumFactory);
    fixtureDataByType.put(Character.class, charDatumFactory);
   
   
    FixtureDatumFactory<Integer> intDatumFactory = new FixtureDatumFactory<Integer>(Integer.class) {
      public Integer getNext() {
        return counter.getAndIncrement();
      }
    };
    fixtureDataByType.put(int.class, intDatumFactory);
    fixtureDataByType.put(Integer.class, intDatumFactory);
   
   
    FixtureDatumFactory<Long> longDatumFactory = new FixtureDatumFactory<Long>(Long.class) {
      public Long getNext() {
        return (long) counter.getAndIncrement();
      }
    };
    fixtureDataByType.put(long.class, longDatumFactory);
    fixtureDataByType.put(Long.class, longDatumFactory);
   
   
    FixtureDatumFactory<Float> floatDatumFactory = new FixtureDatumFactory<Float>(Float.class) {
      public Float getNext() {
        return new Float(counter.getAndIncrement());
      }
    };
    fixtureDataByType.put(float.class, floatDatumFactory);
    fixtureDataByType.put(Float.class, floatDatumFactory);
   
   
    FixtureDatumFactory<Double> doubleDatumFactory = new FixtureDatumFactory<Double>(Double.class) {
      public Double getNext() {
        return new Double(counter.getAndIncrement());
      }
    };
    fixtureDataByType.put(double.class, doubleDatumFactory);
    fixtureDataByType.put(Double.class, doubleDatumFactory);

    fixtureDataByType.put(String.class, new FixtureDatumFactory<String>(String.class) {
      public String getNext() {
        return "string" + counter.getAndIncrement();
      }
    });

    fixtureDataByType.put(BigDecimal.class, new FixtureDatumFactory<BigDecimal>(BigDecimal.class) {
      public BigDecimal getNext() {
        return new BigDecimal(counter.getAndIncrement());
      }
    });

    fixtureDataByType.put(BigInteger.class, new FixtureDatumFactory<BigInteger>(BigInteger.class) {
      public BigInteger getNext() {
        return BigInteger.valueOf(counter.getAndIncrement());
      }
    });

    fixtureDataByType.put(Date.class, new FixtureDatumFactory<Date>(Date.class) {
      public Date getNext() {
        return new Date(counter.getAndIncrement());
      }
    });
   
    fixtureDataByType.put(Timestamp.class, new FixtureDatumFactory<Timestamp>(Timestamp.class) {
      public Timestamp getNext() {
        return new Timestamp(counter.getAndIncrement());
      }
    });
   
    fixtureDataByType.put(Pattern.class, new FixtureDatumFactory<Pattern>(Pattern.class) {
      public Pattern getNext() {
        return Pattern.compile("p" + counter.getAndIncrement());
      }
    });
   
    fixtureDataByType.put(File.class, new FixtureDatumFactory<File>(File.class) {
      public File getNext() {
        return new File("file" + counter.getAndIncrement());
      }
    });

    FixtureDatumFactory<List<?>> listDatumFactory = new FixtureDatumFactory<List<?>>() {
      public List<?> getNext() {
        final List<String> list = new ArrayList<String>();
        list.add("element" + counter.getAndIncrement());
        list.add("element" + counter.getAndIncrement());
        list.add("element" + counter.getAndIncrement());
        return list;
      }
    };
    fixtureDataByType.put(Iterable.class, listDatumFactory);
    fixtureDataByType.put(Collection.class, listDatumFactory);
    fixtureDataByType.put(List.class, listDatumFactory);

    fixtureDataByType.put(Set.class, new FixtureDatumFactory<Set<?>>() {
      public Set<?> getNext() {
        final Set<String> list = new HashSet<String>();
        list.add("element" + counter.getAndIncrement());
        list.add("element" + counter.getAndIncrement());
        list.add("element" + counter.getAndIncrement());
        return list;
      }
    });
    fixtureDataByType.put(SortedSet.class, new FixtureDatumFactory<SortedSet<?>>() {
        public SortedSet<?> getNext() {
            final SortedSet<String> list = new TreeSet<String>();
            list.add("element" + counter.getAndIncrement());
            list.add("element" + counter.getAndIncrement());
            list.add("element" + counter.getAndIncrement());
            return list;
        }
    });
  }

  public AtomicInteger getCounter() {
    return counter;
  }

  public <T> PojoTester withFixture(Class<T> c, final T... fixtureData) {
    if (Enum.class.isAssignableFrom(c)) {
      throw new IllegalArgumentException("No need to provide fixture data for enums");
    }
    if (fixtureData == null || fixtureData.length == 0) {
      throw new IllegalArgumentException("Test data is mandatory");
    }
    return withFixture(new FixtureDatumFactory<T>(c, fixtureData));
  }

  public <T> PojoTester withFixture(FixtureDatumFactory<T> factory) {
    fixtureDataByType.put(factory.getType(), factory);
    return this;
  }

  public void exercise(Object bean) {
    exercise(bean, FilterSet.excluding());
  }

  public void exercise(Object bean, FilterSet filterSet) {
    // an array that fills as each property is tested, allowing
    // subsequent properties to be tested against them
    final List<Method> gettersDone = new ArrayList<Method>();
    final List<TestException> problems = new ArrayList<TestException>();

    final Map<String, Method> methods = getMethodsAsMap(bean);
    for (Entry<String, Method> e : methods.entrySet()) {
      final String methodName = e.getKey();
      if (methodName.startsWith("set")
          && e.getValue().getParameterTypes().length == 1) {
        final char first = methodName.charAt(3);
        final String remainder = methodName.substring(4);
        final String property = Character.toLowerCase(first)
            + remainder;
        if (filterSet.shouldInclude(property)) {
          try {
            testOne(bean, methods, property, gettersDone);
          } catch (TestException te) {
            problems.add(te);
          }
        }
      }
    }
    handleExceptions(problems);
  }

  private static void handleExceptions(List<TestException> problems) {
    if (!problems.isEmpty()) {
      Throwable lastCause = null;
      final StringBuilder b = new StringBuilder();
      String newline = "";
      for (TestException te : problems) {
        b.append(newline).append(te.getMessage());
        newline = "\n";
        if (te.getCause() != null) {
          lastCause = te.getCause();
        }
      }
      final AssertionFailedError err = new AssertionFailedError(
          b.toString());
      if (lastCause != null) {
        err.initCause(lastCause);
      }
      throw err;
    }
  }

  private static Map<String, Method> getMethodsAsMap(Object bean) {
    final Map<String, Method> methodMap = new HashMap<String, Method>();
    for (Method m : bean.getClass().getMethods()) {
      methodMap.put(m.getName(), m);
    }
    return methodMap;
  }

  private void testOne(final Object bean, final Map<String, Method> methods,
      String property, List<Method> earlierGetters) throws TestException {
    final String setterName = getAccessor("set", property);
    for (Method setterMethod : methods.values()) {
      final Class<?>[] parameterTypes = setterMethod.getParameterTypes();
      if (setterMethod.getName().equals(setterName)
          && parameterTypes.length == 1) {
        exercise(bean, property, methods, setterMethod,
            parameterTypes[0], earlierGetters);
        return;
      }
    }
    throw new TestException("No matching setter found for " + property
        + ".");
  }

  private void exercise(final Object bean, String property,
      final Map<String, Method> methods, Method setterMethod,
      final Class<?> parameterType, List<Method> earlierGetters)
      throws AssertionFailedError, TestException {

    final String setterName = setterMethod.getName();
    FixtureDatumFactory<?> factory = fixtureDataByType.get(parameterType);
    if (factory == null) {
      // automatically populate for enums
      if (Enum.class.isAssignableFrom(parameterType)) {
        final Object[] testData = parameterType.getEnumConstants();
        factory = new FixtureDatumFactory<Object>(Object.class) {
          private int index = testData.length - 1;
          public Object getNext() {
            index = (index + 1) % testData.length;
            return testData[index];
          }
        };
        fixtureDataByType.put(parameterType, factory);
      } else {
        throw new TestException("No fixture test data is available for "
            + setterName + "( " + parameterType.getName() + " ).");
      }
    }

    checkMethodVisibility(property, setterName, setterMethod);

    String getterName;
    if (parameterType == boolean.class) {
      getterName = getAccessor("is", property);
      if (property.startsWith("Is") && !methods.containsKey(getterName)) {
        getterName = getAccessor("is", property.substring(2));
      }
    } else {
      getterName = getAccessor("get", property);
    }

    try {
      final Method getterMethod = bean.getClass().getMethod(getterName);
      if (getterMethod.getReturnType().equals(void.class)) {
        throw new TestException(getterName + "(...) is void return.");
      }
      checkMethodVisibility(property, getterName, getterMethod);

      Object value = null;
      for (int i = 0; i < 3; i++) {
        value = factory.getNext();
        invokeSetterAndGetter(bean, property, setterMethod,
            getterMethod, value);
      }

      // compare with the methods we have called earlier
      if (!getterMethod.getReturnType().equals(boolean.class)) {
        for (Method earlierGetter : earlierGetters) {
          final Object earlierValue = earlierGetter.invoke(bean);
          if (earlierValue.equals(value)) {
            throw new TestException(setterName
                + " interferes with " + earlierGetter.getName());
          }
        }
      }

      // finally store this getter to be tested against the next property
      earlierGetters.add(getterMethod);

    } catch (NoSuchMethodException e) {
        if(mode == Mode.RELAXED) {
            // ignore
            return;
        }
            final TestException error = new TestException(property + ": "
                    + e.getMessage());
            error.initCause(e);
            throw error;
    } catch (Exception e) {
      final TestException error = new TestException(property + ": "
          + e.getMessage());
      error.initCause(e);
      throw error;
    }
  }

  private static void checkMethodVisibility(String property,
      final String accessorName, final Method method)
      throws AssertionFailedError, TestException {
    if (!Modifier.isPublic(method.getModifiers())) {
      throw new TestException("Test failed for " + property + " because "
          + accessorName + " is not publicly visible.");
    }
    if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
      throw new TestException("Test failed for " + property + " because "
          + accessorName
          + " is declared in a class that is not publicly visible.");
    }
  }

  private static void invokeSetterAndGetter(final Object bean,
      String property, Method setterMethod, final Method getterMethod,
      Object t) throws IllegalAccessException, InvocationTargetException,
      AssertionFailedError, TestException {

    setterMethod.invoke(bean, t);
    final Object r = getterMethod.invoke(bean);
    if (!t.getClass().equals(r.getClass())) {
      throw new TestException("Test failed for " + property
          + " because types do not match.");
    }

    if (!t.equals(r)) {
      throw new TestException("Test failed for " + property + " using "
          + t.toString());
    }

    if (t instanceof Iterable<?>) {
      final Iterator<?> it = ((Iterable<?>) t).iterator();
      final Iterator<?> ir = ((Iterable<?>) r).iterator();
      while (it.hasNext() && ir.hasNext()) {
        final Object ti = it.next();
        final Object ri = ir.next();
        if (!ti.equals(ri)) {
          throw new TestException("Test failed for " + property
              + " with iterator item " + ti.toString());
        }
      }
      if (it.hasNext() || ir.hasNext()) {
        throw new TestException("Test failed for " + property
            + " because iteration lengths differ.");
      }
    }
  }

  private String getAccessor(String prefix, String property) {
    if (property.length() == 1) {
      return prefix + Character.toUpperCase(property.charAt(0));
    }
    return prefix + Character.toUpperCase(property.charAt(0))
        + property.substring(1);
  }

 
 
  public static final class TestException extends Exception {

    private static final long serialVersionUID = 7870820619976334343L;

    public TestException(String message) {
      super(message);
    }

    public TestException(String message, Throwable t) {
      super(message, t);
    }
  }

  public static class FilterSet extends HashSet<String> {
    private static final long serialVersionUID = 1L;

    private boolean include = false;

    private FilterSet(String... string) {
      super.addAll(Arrays.asList(string));
    }

    private boolean shouldInclude(String x) {
      if (include) {
        return isEmpty() || contains(x);
      } else {
        return !contains(x);
      }
    }

    public static FilterSet includingOnly(String... property) {
      final FilterSet filterSet = new FilterSet(property);
      filterSet.include = true;
      return filterSet;
    }

    public static FilterSet excluding(String... property) {
      return new FilterSet(property);
    }
  }

}
TOP

Related Classes of com.danhaywood.java.testsupport.coverage.PojoTester$TestException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.