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

org.joda.beans.test.JodaBeanTests Maven / Gradle / Ivy

There is a newer version: 2.11.1
Show newest version
/*
 *  Copyright 2001-present Stephen Colebourne
 *
 *  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 org.joda.beans.test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.impl.StandaloneMetaProperty;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;

/**
 * A utility class to assist with testing beans.
 * 

* Test coverage statistics can be heavily skewed by getters, setters and generated code. * This class provides a solution, allowing bean test coverage to be artificially increased. * Always remember that the goal of artificially increasing coverage is so that you can * see what you really need to test, not to avoid writing tests altogether. * * @author Stephen Colebourne */ public final class JodaBeanTests { /** * This constant can be used to pass to increase test coverage. * This is used by some {@link MetaBean} methods in generated classes. */ public static final String TEST_COVERAGE_PROPERTY = "!ConstantUsedForTestCoveragePurposes!"; /** * This constant can be used to pass to increase test coverage. * This is used by some {@link BeanBuilder} set methods in generated classes. */ public static final String TEST_COVERAGE_STRING = "!ConstantUsedForTestCoveragePurposes!"; //------------------------------------------------------------------------- /** * Test a mutable bean for the primary purpose of increasing test coverage. * * @param bean the bean to test */ public static void coverMutableBean(Bean bean) { assertNotNull(bean, "coverImmutableBean() called with null bean"); assertFalse(bean instanceof ImmutableBean); assertNotSame(JodaBeanUtils.clone(bean), bean); coverBean(bean); } /** * Test an immutable bean for the primary purpose of increasing test coverage. * * @param bean the bean to test */ public static void coverImmutableBean(ImmutableBean bean) { assertNotNull(bean, "coverImmutableBean() called with null bean"); assertSame(JodaBeanUtils.clone(bean), bean); coverBean(bean); } /** * Test a bean equals method for the primary purpose of increasing test coverage. *

* The two beans passed in should contain a different value for each property. * The method creates a cross-product to ensure test coverage of equals. * * @param bean1 the first bean to test * @param bean2 the second bean to test */ @SuppressWarnings("unlikely-arg-type") public static void coverBeanEquals(Bean bean1, Bean bean2) { assertNotNull(bean1, "coverBeanEquals() called with null bean"); assertNotNull(bean2, "coverBeanEquals() called with null bean"); assertFalse(bean1.equals(null)); assertFalse(bean1.equals("NonBean")); assertTrue(bean1.equals(bean1)); assertTrue(bean2.equals(bean2)); ignoreThrows(() -> assertEquals(bean1, JodaBeanUtils.cloneAlways(bean1))); ignoreThrows(() -> assertEquals(bean2, JodaBeanUtils.cloneAlways(bean2))); assertTrue(bean1.hashCode() == bean1.hashCode()); assertTrue(bean2.hashCode() == bean2.hashCode()); if (bean1.equals(bean2) || bean1.getClass() != bean2.getClass()) { return; } MetaBean metaBean = bean1.metaBean(); List> buildableProps = metaBean.metaPropertyMap().values().stream() .filter(mp -> mp.style().isBuildable()) .collect(Collectors.toList()); Set builtBeansSet = new HashSet<>(); builtBeansSet.add(bean1); builtBeansSet.add(bean2); for (int i = 0; i < buildableProps.size(); i++) { for (int j = 0; j < 2; j++) { try { BeanBuilder bld = metaBean.builder(); for (int k = 0; k < buildableProps.size(); k++) { MetaProperty mp = buildableProps.get(k); if (j == 0) { bld.set(mp, mp.get(k < i ? bean1 : bean2)); } else { bld.set(mp, mp.get(i <= k ? bean1 : bean2)); } } builtBeansSet.add(bld.build()); } catch (RuntimeException ex) { // ignore } } } List builtBeansList = new ArrayList<>(builtBeansSet); for (int i = 0; i < builtBeansList.size() - 1; i++) { for (int j = i + 1; j < builtBeansList.size(); j++) { builtBeansList.get(i).equals(builtBeansList.get(j)); } } } // provide test coverage to all beans private static void coverBean(Bean bean) { coverProperties(bean); coverNonProperties(bean); coverEquals(bean); } // cover parts of a bean that are property-based private static void coverProperties(Bean bean) { MetaBean metaBean = bean.metaBean(); Map> metaPropMap = metaBean.metaPropertyMap(); assertNotNull(metaPropMap); assertEquals(metaBean.metaPropertyCount(), metaPropMap.size()); for (MetaProperty mp : metaBean.metaPropertyIterable()) { assertTrue(metaBean.metaPropertyExists(mp.name())); assertEquals(metaBean.metaProperty(mp.name()), mp); // Ensure we don't use interned value assertEquals(metaBean.metaProperty(new String(mp.name())), mp); assertEquals(metaPropMap.values().contains(mp), true); assertEquals(metaPropMap.keySet().contains(mp.name()), true); if (mp.style().isReadable()) { ignoreThrows(() -> mp.get(bean)); } else { assertThrows(() -> mp.get(bean), UnsupportedOperationException.class); } if (mp.style().isWritable()) { ignoreThrows(() -> mp.set(bean, "")); } else { assertThrows(() -> mp.set(bean, ""), UnsupportedOperationException.class); } if (mp.style().isBuildable()) { ignoreThrows(() -> metaBean.builder().get(mp)); ignoreThrows(() -> metaBean.builder().get(mp.name())); for (Object setValue : sampleValues(mp)) { ignoreThrows(() -> metaBean.builder().set(mp, setValue)); } for (Object setValue : sampleValues(mp)) { ignoreThrows(() -> metaBean.builder().set(mp.name(), setValue)); } } ignoreThrows(() -> { Method m = metaBean.getClass().getDeclaredMethod(mp.name()); m.setAccessible(true); m.invoke(metaBean); }); ignoreThrows(() -> { Method m = metaBean.getClass().getDeclaredMethod( "propertySet", Bean.class, String.class, Object.class, Boolean.TYPE); m.setAccessible(true); m.invoke(metaBean, bean, mp.name(), "", true); }); } ignoreThrows(() -> { Method m = metaBean.getClass().getDeclaredMethod( "propertyGet", Bean.class, String.class, Boolean.TYPE); m.setAccessible(true); m.invoke(metaBean, bean, "Not a real property name", true); }); MetaProperty fakeMetaProp = StandaloneMetaProperty.of("fake", metaBean, String.class); ignoreThrows(() -> metaBean.builder().set(fakeMetaProp, JodaBeanTests.TEST_COVERAGE_STRING)); ignoreThrows(() -> metaBean.builder().set(JodaBeanTests.TEST_COVERAGE_PROPERTY, JodaBeanTests.TEST_COVERAGE_STRING)); ignoreThrows(() -> bean.property(JodaBeanTests.TEST_COVERAGE_PROPERTY)); } private static void assertNotNull(Map> metaPropMap) { } // cover parts of a bean that are not property-based private static void coverNonProperties(Bean bean) { MetaBean metaBean = bean.metaBean(); assertFalse(metaBean.metaPropertyExists("")); assertThrows(() -> metaBean.builder().get("foo_bar"), NoSuchElementException.class); assertThrows(() -> metaBean.builder().set("foo_bar", ""), NoSuchElementException.class); assertThrows(() -> metaBean.metaProperty("foo_bar"), NoSuchElementException.class); if (metaBean instanceof DirectMetaBean) { DirectMetaProperty dummy = DirectMetaProperty.ofReadWrite(metaBean, "foo_bar", metaBean.beanType(), String.class); assertThrows(() -> dummy.get(bean), NoSuchElementException.class); assertThrows(() -> dummy.set(bean, ""), NoSuchElementException.class); assertThrows(() -> dummy.setString(bean, ""), NoSuchElementException.class); assertThrows(() -> metaBean.builder().get(dummy), NoSuchElementException.class); assertThrows(() -> metaBean.builder().set(dummy, ""), NoSuchElementException.class); } Set propertyNameSet = bean.propertyNames(); assertNotNull(propertyNameSet); for (String propertyName : propertyNameSet) { assertNotNull(bean.property(propertyName)); } assertThrows(() -> bean.property(""), NoSuchElementException.class); Class beanClass = bean.getClass(); ignoreThrows(() -> { Method m = beanClass.getDeclaredMethod("meta"); m.setAccessible(true); m.invoke(null); }); ignoreThrows(() -> { Method m = beanClass.getDeclaredMethod("meta" + beanClass.getSimpleName(), Class.class); m.setAccessible(true); m.invoke(null, String.class); }); ignoreThrows(() -> { Method m = beanClass.getDeclaredMethod("meta" + beanClass.getSimpleName(), Class.class, Class.class); m.setAccessible(true); m.invoke(null, String.class, String.class); }); ignoreThrows(() -> { Method m = beanClass.getDeclaredMethod("meta" + beanClass.getSimpleName(), Class.class, Class.class, Class.class); m.setAccessible(true); m.invoke(null, String.class, String.class, String.class); }); ignoreThrows(() -> { Method m = bean.getClass().getDeclaredMethod("builder"); m.setAccessible(true); m.invoke(null); }); ignoreThrows(() -> { Method m = bean.getClass().getDeclaredMethod("toBuilder"); m.setAccessible(true); m.invoke(bean); }); assertNotNull(bean.toString()); assertNotNull(metaBean.toString()); assertNotNull(metaBean.builder().toString()); } // different combinations of values to cover equals() @SuppressWarnings("unlikely-arg-type") private static void coverEquals(Bean bean) { // create beans with different data and compare each to the input bean // this will normally trigger each of the possible branches in equals List> buildableProps = bean.metaBean().metaPropertyMap().values().stream() .filter(mp -> mp.style().isBuildable()) .collect(Collectors.toList()); for (int i = 0; i < buildableProps.size(); i++) { try { BeanBuilder bld = bean.metaBean().builder(); for (int j = 0; j < buildableProps.size(); j++) { MetaProperty mp = buildableProps.get(j); if (j < i) { bld.set(mp, mp.get(bean)); } else { List samples = sampleValues(mp); bld.set(mp, samples.get(0)); } } Bean built = bld.build(); coverBeanEquals(bean, built); assertEquals(built, built); assertEquals(built.hashCode(), built.hashCode()); } catch (RuntimeException ex) { // ignore } } // cover the remaining equals edge cases assertFalse(bean.equals(null)); assertFalse(bean.equals("NonBean")); assertTrue(bean.equals(bean)); ignoreThrows(() -> assertEquals(bean, JodaBeanUtils.cloneAlways(bean))); assertTrue(bean.hashCode() == bean.hashCode()); } // sample values for setters private static List sampleValues(MetaProperty mp) { Class type = mp.propertyType(); // enum constants if (Enum.class.isAssignableFrom(type)) { return Arrays.asList(type.getEnumConstants()); } // lookup pre-canned samples List sample = SAMPLES.get(type); if (sample != null) { return sample; } // find any potential declared constants, using some plural rules String typeName = type.getName(); List samples = new ArrayList<>(); samples.addAll(buildSampleConstants(type, type)); ignoreThrows(() -> { // cat -> cats samples.addAll(buildSampleConstants(Class.forName(typeName + "s"), type)); }); ignoreThrows(() -> { // dish -> dishes samples.addAll(buildSampleConstants(Class.forName(typeName + "es"), type)); }); ignoreThrows(() -> { // lady -> ladies samples.addAll(buildSampleConstants(Class.forName(typeName.substring(0, typeName.length() - 1) + "ies"), type)); }); ignoreThrows(() -> { // index -> indices samples.addAll(buildSampleConstants(Class.forName(typeName.substring(0, typeName.length() - 2) + "ices"), type)); }); // none return samples; } // adds sample constants to the private static List buildSampleConstants(Class queryType, Class targetType) { List samples = new ArrayList<>(); for (Field field : queryType.getFields()) { if (field.getType() == targetType && Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.isSynthetic() == false) { ignoreThrows(() -> samples.add(field.get(null))); } } return samples; } private static final Map, List> SAMPLES; static { Map, List> map = new HashMap<>(); map.put(String.class, Arrays.asList("Hello", "Goodbye", " ", "")); map.put(Byte.class, Arrays.asList((byte) 0, (byte) 1)); map.put(Byte.TYPE, Arrays.asList((byte) 0, (byte) 1)); map.put(Short.class, Arrays.asList((short) 0, (short) 1)); map.put(Short.TYPE, Arrays.asList((short) 0, (short) 1)); map.put(Integer.class, Arrays.asList((int) 0, (int) 1)); map.put(Integer.TYPE, Arrays.asList((int) 0, (int) 1)); map.put(Long.class, Arrays.asList((long) 0, (long) 1)); map.put(Long.TYPE, Arrays.asList((long) 0, (long) 1)); map.put(Float.class, Arrays.asList((float) 0, (float) 1)); map.put(Float.TYPE, Arrays.asList((float) 0, (float) 1)); map.put(Double.class, Arrays.asList((double) 0, (double) 1)); map.put(Double.TYPE, Arrays.asList((double) 0, (double) 1)); map.put(Character.class, Arrays.asList(' ', 'A', 'z')); map.put(Character.TYPE, Arrays.asList(' ', 'A', 'z')); map.put(Boolean.class, Arrays.asList(Boolean.TRUE, Boolean.FALSE)); map.put(Boolean.TYPE, Arrays.asList(Boolean.TRUE, Boolean.FALSE)); map.put(LocalDate.class, Arrays.asList(LocalDate.now(ZoneOffset.UTC), LocalDate.of(2012, 6, 30))); map.put(LocalTime.class, Arrays.asList(LocalTime.now(ZoneOffset.UTC), LocalTime.of(11, 30))); map.put(LocalDateTime.class, Arrays.asList(LocalDateTime.now(ZoneOffset.UTC), LocalDateTime.of(2012, 6, 30, 11, 30))); map.put(OffsetTime.class, Arrays.asList( OffsetTime.now(ZoneOffset.UTC), OffsetTime.of(11, 30, 0, 0, ZoneOffset.ofHours(1)))); map.put(OffsetDateTime.class, Arrays.asList( OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.of(2012, 6, 30, 11, 30, 0, 0, ZoneOffset.ofHours(1)))); map.put(ZonedDateTime.class, Arrays.asList( ZonedDateTime.now(ZoneOffset.UTC), ZonedDateTime.of(2012, 6, 30, 11, 30, 0, 0, ZoneId.systemDefault()))); map.put(Instant.class, Arrays.asList(Instant.now(), Instant.EPOCH)); map.put(Year.class, Arrays.asList(Year.now(ZoneOffset.UTC), Year.of(2012))); map.put(YearMonth.class, Arrays.asList(YearMonth.now(ZoneOffset.UTC), YearMonth.of(2012, 6))); map.put(MonthDay.class, Arrays.asList(MonthDay.now(ZoneOffset.UTC), MonthDay.of(12, 25))); map.put(Month.class, Arrays.asList(Month.JULY, Month.DECEMBER)); map.put(DayOfWeek.class, Arrays.asList(DayOfWeek.FRIDAY, DayOfWeek.SATURDAY)); map.put(URI.class, Arrays.asList(URI.create("http://www.opengamma.com"), URI.create("http://www.joda.org"))); map.put(Class.class, Arrays.asList(Throwable.class, RuntimeException.class, String.class)); map.put(Object.class, Arrays.asList("", 6)); map.put(Collection.class, Arrays.asList(new ArrayList<>())); map.put(List.class, Arrays.asList(new ArrayList<>())); map.put(Set.class, Arrays.asList(new HashSet<>())); map.put(SortedSet.class, Arrays.asList(new TreeSet<>())); try { Class cls = Class.forName("com.google.common.collect.ImmutableList"); Method method = cls.getDeclaredMethod("of"); map.put(cls, Arrays.asList(method.invoke(null))); } catch (Exception ex) { // ignore } try { Class cls = Class.forName("com.google.common.collect.ImmutableSet"); Method method = cls.getDeclaredMethod("of"); map.put(cls, Arrays.asList(method.invoke(null))); } catch (Exception ex) { // ignore } try { Class cls = Class.forName("com.google.common.collect.ImmutableSortedSet"); Method method = cls.getDeclaredMethod("naturalOrder"); map.put(cls, Arrays.asList(method.invoke(null))); } catch (Exception ex) { // ignore } try { Class cls = Class.forName("com.google.common.collect.ImmutableMap"); Method method = cls.getDeclaredMethod("of"); map.put(cls, Arrays.asList(method.invoke(null))); } catch (Exception ex) { // ignore } SAMPLES = map; } //----------------------------------------------------------------------- private static void assertNotNull(Object obj) { if (obj == null) { throw new AssertionError("Expected (a != null), but found (a == null)"); } } private static void assertNotNull(Object obj, String message) { if (obj == null) { throw new AssertionError(message); } } private static void assertSame(Object a, Object b) { if (a != b) { throw new AssertionError("Expected (a == b), but found (a != b)"); } } private static void assertNotSame(Object a, Object b) { if (a == b) { throw new AssertionError("Expected (a != b), but found (a == b)"); } } private static void assertEquals(Object actual, Object expected) { if (!Objects.equals(actual, expected)) { throw new AssertionError("Expected " + expected + ", but found " + actual); } } private static void assertEquals(int actual, int expected) { if (actual != expected) { throw new AssertionError("Expected " + expected + ", but found " + actual); } } private static void assertTrue(boolean actual) { if (!actual) { throw new AssertionError("Expected value to be true, but was false"); } } private static void assertFalse(boolean actual) { if (actual) { throw new AssertionError("Expected value to be false, but was true"); } } //----------------------------------------------------------------------- private static void assertThrows(AssertRunnable runner, Class expected) { assertNotNull(runner, "assertThrows() called with null AssertRunnable"); assertNotNull(expected, "assertThrows() called with null expected Class"); try { runner.run(); throw new AssertionError("Expected " + expected.getSimpleName() + " but code succeeded normally"); } catch (AssertionError ex) { throw ex; } catch (Throwable ex) { if (!expected.isInstance(ex)) { throw new AssertionError( "Expected " + expected.getSimpleName() + " but received " + ex.getClass().getSimpleName(), ex); } } } private static void ignoreThrows(AssertRunnable runner) { assertNotNull(runner, "ignoreThrows() called with null AssertRunnable"); try { runner.run(); } catch (Throwable ex) { // ignore } } @FunctionalInterface interface AssertRunnable { /** * Used to wrap code that is expected to throw an exception. * * @throws Throwable the expected result */ void run() throws Throwable; } }