/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.collect;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
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;
import org.joda.beans.test.BeanAssert;
import org.joda.beans.test.JodaBeanTests;
import org.joda.convert.StringConvert;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
/**
* Test helper.
* <p>
* Provides additional classes to help with testing.
*/
public class TestHelper {
/**
* UTF-8 encoding name.
*/
private static final String UTF_8 = "UTF-8";
//-------------------------------------------------------------------------
/**
* Creates a {@code LocalDate}, intended for static import.
*
* @param year the year
* @param month the month
* @param dayOfMonth the day of month
* @return the date
*/
public static LocalDate date(int year, int month, int dayOfMonth) {
return LocalDate.of(year, month, dayOfMonth);
}
/**
* Creates a {@code LocalDate}, intended for static import.
*
* @param year the year
* @param month the month
* @param dayOfMonth the day of month
* @return the date
*/
public static LocalDate date(int year, Month month, int dayOfMonth) {
return LocalDate.of(year, month, dayOfMonth);
}
//-------------------------------------------------------------------------
/**
* Creates a {@code ZonedDateTime} from the date.
* <p>
* The time is start of day and the zone is UTC.
*
* @param year the year
* @param month the month
* @param dayOfMonth the day of month
* @return the date-time, representing the date at midnight UTC
*/
public static ZonedDateTime dateUtc(int year, int month, int dayOfMonth) {
return LocalDate.of(year, month, dayOfMonth).atStartOfDay(ZoneOffset.UTC);
}
//-------------------------------------------------------------------------
/**
* Asserts that two beans are equal.
* Provides better error messages than a normal {@code assertEquals} comparison.
*
* @param actual the actual bean under test
* @param expected the expected bean
*/
public static void assertEqualsBean(Bean actual, Bean expected) {
BeanAssert.assertBeanEquals(expected, actual);
}
/**
* Asserts that two beans are equal.
* Provides better error messages than a normal {@code assertEquals} comparison.
* <p>
* This provides extra detail used when debugging an issue.
* Normal use should be to call {@link #assertEqualsBean(Bean, Bean)}.
*
* @param actual the actual bean under test
* @param expected the expected bean
*/
public static void assertEqualsBeanDetailed(Bean actual, Bean expected) {
BeanAssert.assertBeanEqualsFullDetail(expected, actual);
}
//-------------------------------------------------------------------------
/**
* Asserts that the object can be serialized and deserialized to an equal form.
*
* @param base the object to be tested
*/
public static void assertSerialization(Object base) {
assertNotNull(base);
try {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(base);
oos.close();
try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray())) {
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
assertEquals(ois.readObject(), base);
}
}
}
}
} catch (IOException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
//-------------------------------------------------------------------------
/**
* Asserts that the object can be serialized and deserialized via a string using Joda-Convert.
*
* @param <T> the type
* @param cls the effective type
* @param base the object to be tested
*/
public static <T> void assertJodaConvert(Class<T> cls, Object base) {
assertNotNull(base);
StringConvert convert = StringConvert.create();
String str = convert.convertToString(base);
T result = convert.convertFromString(cls, str);
assertEquals(result, base);
}
//-------------------------------------------------------------------------
/**
* Asserts that the lambda-based code throws the specified exception.
* <p>
* For example:
* <pre>
* assertThrows(() -> bean.property(""), NoSuchElementException.class);
* </pre>
*
* @param runner the lambda containing the code to test
* @param expected the expected exception
*/
public static void assertThrows(AssertRunnable runner, Class<? extends Throwable> expected) {
assertThrowsImpl(runner, expected, null);
}
/**
* Asserts that the lambda-based code throws the specified exception
* and that the exception message matches the supplied regular
* expression.
* <p>
* For example:
* <pre>
* assertThrows(() -> bean.property(""), NoSuchElementException.class, "Unknown property.*");
* </pre>
*
* @param runner the lambda containing the code to test
* @param expected the expected exception
* @param regex the regex that the exception message is expected to match
*/
public static void assertThrows(AssertRunnable runner, Class<? extends Throwable> expected, String regex) {
assertNotNull(regex, "assertThrows() called with null regex");
assertThrowsImpl(runner, expected, regex);
}
private static void assertThrowsImpl(
AssertRunnable runner,
Class<? extends Throwable> expected,
String regex) {
assertNotNull(runner, "assertThrows() called with null AssertRunnable");
assertNotNull(expected, "assertThrows() called with null expected Class");
try {
runner.run();
fail("Expected " + expected.getSimpleName() + " but code succeeded normally");
} catch (AssertionError ex) {
throw ex;
} catch (Throwable ex) {
if (expected.isInstance(ex)) {
String message = ex.getMessage();
if (regex == null || message.matches(regex)) {
return;
} else {
fail("Expected exception message to match: [" + regex + "] but received: " + message);
}
}
fail("Expected " + expected.getSimpleName() + " but received " + ex.getClass().getSimpleName(), ex);
}
}
/**
* Asserts that the lambda-based code throws an {@code RuntimeException}.
* <p>
* For example:
* <pre>
* assertThrowsRuntime(() -> new Foo(null));
* </pre>
*
* @param runner the lambda containing the code to test
*/
public static void assertThrowsRuntime(AssertRunnable runner) {
assertThrows(runner, RuntimeException.class);
}
/**
* Asserts that the lambda-based code throws an {@code IllegalArgumentException}.
* <p>
* For example:
* <pre>
* assertThrowsIllegalArg(() -> new Foo(null));
* </pre>
*
* @param runner the lambda containing the code to test
*/
public static void assertThrowsIllegalArg(AssertRunnable runner) {
assertThrows(runner, IllegalArgumentException.class);
}
/**
* Asserts that the lambda-based code throws an {@code IllegalArgumentException} and checks the message
* matches an regex.
* <p>
* For example:
* <pre>
* assertThrowsIllegalArg(() -> new Foo(null), "Foo constructor argument must not be null");
* </pre>
*
* @param runner the lambda containing the code to test
* @param regex regular expression that must match the exception message
*/
public static void assertThrowsIllegalArg(AssertRunnable runner, String regex) {
assertThrows(runner, IllegalArgumentException.class, regex);
}
/**
* Asserts that the lambda-based code throws an exception
* and that the cause of the exception is the supplied cause.
* <p>
* For example:
* <pre>
* assertThrowsWithCause(() ->
* executeSql("INSERT DATA THAT ALREADY EXISTS"), SQLIntegrityConstraintViolationException.class);
* </pre>
*
* @param runner the lambda containing the code to test
* @param cause the expected cause of the exception thrown
*/
public static void assertThrowsWithCause(
AssertRunnable runner, Class<? extends Throwable> cause) {
assertNotNull(runner, "assertThrowsWithCause() called with null AssertRunnable");
assertNotNull(cause, "assertThrowsWithCause() called with null expected cause");
try {
runner.run();
fail("Expected " + cause.getSimpleName() + " but code succeeded normally");
} catch (AssertionError ex) {
throw ex;
} catch (Throwable ex) {
Throwable ex2 = ex;
while (ex2 != null && !cause.isInstance(ex2)) {
ex2 = ex2.getCause();
}
if (ex2 == null) {
fail("Expected cause of exception to be: " + cause.getSimpleName() + " but got different exception", ex);
}
}
}
/**
* Ignore any exception thrown by the lambda-based code.
* <p>
* For example:
* <pre>
* ignoreThrows(() -> bean.property(""));
* </pre>
*
* @param runner the lambda containing the code to test
*/
public static void ignoreThrows(AssertRunnable runner) {
assertNotNull(runner, "ignoreThrows() called with null AssertRunnable");
try {
runner.run();
} catch (Throwable ex) {
// ignore
}
}
//-------------------------------------------------------------------------
/**
* Capture system out for testing.
* <p>
* This returns the output from calls to {@code System.out}.
* This is thread-safe, providing that no other utility alters system out.
* <p>
* For example:
* <pre>
* String sysOut = captureSystemOut(() -> myCode);
* </pre>
*
* @param runner the lambda containing the code to test
* @return the captured output
*/
public static synchronized String caputureSystemOut(Runnable runner) {
// it would be possible to use some form of thread-local PrintStream to increase concurrency,
// but that should be done only if synchronized is insufficient
assertNotNull(runner, "caputureSystemOut() called with null Runnable");
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
PrintStream ps = Unchecked.wrap(() -> new PrintStream(baos, false, UTF_8));
PrintStream old = System.out;
try {
System.setOut(ps);
runner.run();
System.out.flush();
} finally {
System.setOut(old);
}
return Unchecked.wrap(() -> baos.toString(UTF_8));
}
/**
* Capture system err for testing.
* <p>
* This returns the output from calls to {@code System.err}.
* This is thread-safe, providing that no other utility alters system out.
* <p>
* For example:
* <pre>
* String sysErr = captureSystemErr(() -> myCode);
* </pre>
*
* @param runner the lambda containing the code to test
* @return the captured output
*/
public static synchronized String caputureSystemErr(Runnable runner) {
// it would be possible to use some form of thread-local PrintStream to increase concurrency,
// but that should be done only if synchronized is insufficient
assertNotNull(runner, "caputureSystemErr() called with null Runnable");
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
PrintStream ps = Unchecked.wrap(() -> new PrintStream(baos, false, UTF_8));
PrintStream old = System.err;
try {
System.setErr(ps);
runner.run();
System.err.flush();
} finally {
System.setErr(old);
}
return Unchecked.wrap(() -> baos.toString(UTF_8));
}
/**
* Capture log for testing.
* <p>
* This returns the output from calls to the java logger.
* This is thread-safe, providing that no other utility alters the logger.
* <p>
* For example:
* <pre>
* String log = captureLog(Foo.class, () -> myCode);
* </pre>
*
* @param loggerClass the class defining the logger to trap
* @param runner the lambda containing the code to test
* @return the captured output
*/
public static synchronized List<LogRecord> caputureLog(Class<?> loggerClass, Runnable runner) {
assertNotNull(loggerClass, "caputureLog() called with null Class");
assertNotNull(runner, "caputureLog() called with null Runnable");
Logger logger = Logger.getLogger(loggerClass.getName());
LogHandler handler = new LogHandler();
try {
handler.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
logger.addHandler(handler);
runner.run();
return handler.records;
} finally {
logger.removeHandler(handler);
}
}
private static class LogHandler extends Handler {
List<LogRecord> records = new ArrayList<>();
@Override
public void publish(LogRecord record) {
records.add(record);
}
@Override
public void close() {
}
@Override
public void flush() {
}
}
//-------------------------------------------------------------------------
/**
* Asserts that a class is a well-defined utility class.
* <p>
* Must be final and with one zero-arg private constructor.
* All public methods must be static.
*
* @param clazz the class to test
*/
public static void assertUtilityClass(Class<?> clazz) {
assertNotNull(clazz, "assertUtilityClass() called with null class");
assertTrue(Modifier.isFinal(clazz.getModifiers()), "Utility class must be final");
assertEquals(clazz.getDeclaredConstructors().length, 1, "Utility class must have one constructor");
Constructor<?> con = clazz.getDeclaredConstructors()[0];
assertEquals(con.getParameterTypes().length, 0, "Utility class must have zero-arg constructor");
assertTrue(Modifier.isPrivate(con.getModifiers()), "Utility class must have private constructor");
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
assertTrue(Modifier.isStatic(method.getModifiers()), "Utility class public methods must be static");
}
}
// coverage
ignoreThrows(() -> {
con.setAccessible(true);
con.newInstance();
});
}
//-------------------------------------------------------------------------
/**
* Test a private no-arg constructor the primary purpose of increasing test coverage.
*
* @param clazz the class to test
*/
public static void coverPrivateConstructor(Class<?> clazz) {
assertNotNull(clazz, "coverPrivateConstructor() called with null class");
AtomicBoolean isPrivate = new AtomicBoolean(false);
ignoreThrows(() -> {
Constructor<?> con = clazz.getDeclaredConstructor();
isPrivate.set(Modifier.isPrivate(con.getModifiers()));
con.setAccessible(true);
con.newInstance();
});
assertTrue(isPrivate.get(), "No-arg constructor must be private");
}
//-------------------------------------------------------------------------
/**
* Test an enum for the primary purpose of increasing test coverage.
*
* @param <E> the enum type
* @param clazz the class to test
*/
public static <E extends Enum<E>> void coverEnum(Class<E> clazz) {
assertNotNull(clazz, "coverEnum() called with null class");
ignoreThrows(() -> {
Method method = clazz.getDeclaredMethod("values");
method.setAccessible(true);
method.invoke(null);
});
for (E val : clazz.getEnumConstants()) {
ignoreThrows(() -> {
Method method = clazz.getDeclaredMethod("valueOf", String.class);
method.setAccessible(true);
method.invoke(null, val.name());
});
}
ignoreThrows(() -> {
Method method = clazz.getDeclaredMethod("valueOf", String.class);
method.setAccessible(true);
method.invoke(null, "");
});
ignoreThrows(() -> {
Method method = clazz.getDeclaredMethod("valueOf", String.class);
method.setAccessible(true);
method.invoke(null, (Object) null);
});
}
//-------------------------------------------------------------------------
/**
* 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.
* <p>
* 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
*/
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<MetaProperty<?>> buildableProps = metaBean.metaPropertyMap().values().stream()
.filter(mp -> mp.style().isBuildable())
.collect(Collectors.toList());
Set<Bean> 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<? extends Bean> 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<Bean> 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<String, MetaProperty<?>> 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));
}
for (String setStr : sampleStrings(mp)) {
ignoreThrows(() -> metaBean.builder().setString(mp, setStr));
}
for (String setStr : sampleStrings(mp)) {
ignoreThrows(() -> metaBean.builder().setString(mp.name(), setStr));
}
ignoreThrows(() -> metaBean.builder().setString(mp, JodaBeanTests.TEST_COVERAGE_STRING));
ignoreThrows(() -> metaBean.builder().setString(mp.name(), JodaBeanTests.TEST_COVERAGE_STRING));
}
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<String> fakeMetaProp = StandaloneMetaProperty.of("fake", metaBean, String.class);
ignoreThrows(() -> metaBean.builder().set(fakeMetaProp, JodaBeanTests.TEST_COVERAGE_STRING));
ignoreThrows(() -> metaBean.builder().setString(fakeMetaProp, JodaBeanTests.TEST_COVERAGE_STRING));
ignoreThrows(() -> metaBean.builder().set(JodaBeanTests.TEST_COVERAGE_PROPERTY, JodaBeanTests.TEST_COVERAGE_STRING));
ignoreThrows(() -> metaBean.builder().setString(JodaBeanTests.TEST_COVERAGE_PROPERTY, JodaBeanTests.TEST_COVERAGE_STRING));
ignoreThrows(() -> bean.property(JodaBeanTests.TEST_COVERAGE_PROPERTY));
}
// cover parts of a bean that are not property-based
private static void coverNonProperties(Bean bean) {
MetaBean metaBean = bean.metaBean();
assertFalse(metaBean.metaPropertyExists(""));
metaBean.builder().setAll(ImmutableMap.of());
assertThrows(() -> metaBean.builder().get("foo_bar"), NoSuchElementException.class);
assertThrows(() -> metaBean.builder().set("foo_bar", ""), NoSuchElementException.class);
assertThrows(() -> metaBean.builder().setString("foo_bar", ""), NoSuchElementException.class);
assertThrows(() -> metaBean.metaProperty("foo_bar"), NoSuchElementException.class);
if (metaBean instanceof DirectMetaBean) {
DirectMetaProperty<String> 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<String> propertyNameSet = bean.propertyNames();
assertNotNull(propertyNameSet);
for (String propertyName : propertyNameSet) {
assertNotNull(bean.property(propertyName));
}
assertThrows(() -> bean.property(""), NoSuchElementException.class);
Class<? extends Bean> 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()
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<MetaProperty<?>> buildableProps = bean.metaBean().metaPropertyMap().values().stream()
.filter(mp -> mp.style().isBuildable())
.collect(Collectors.toList());
for (int i = 0; i < buildableProps.size(); i++) {
try {
BeanBuilder<? extends Bean> 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();
ImmutableList.Builder<Object> builder = ImmutableList.builder();
builder.addAll(buildSampleConstants(type, type));
ignoreThrows(() -> {
// cat -> cats
builder.addAll(buildSampleConstants(Class.forName(typeName + "s"), type));
});
ignoreThrows(() -> {
// dish -> dishes
builder.addAll(buildSampleConstants(Class.forName(typeName + "es"), type));
});
ignoreThrows(() -> {
// lady -> ladies
builder.addAll(buildSampleConstants(Class.forName(typeName.substring(0, typeName.length() - 1) + "ies"), type));
});
ignoreThrows(() -> {
// index -> indices
builder.addAll(buildSampleConstants(Class.forName(typeName.substring(0, typeName.length() - 2) + "ices"), type));
});
// none
return builder.build();
}
// adds sample constants to the
private static ImmutableList<Object> buildSampleConstants(Class<?> queryType, Class<?> targetType) {
ImmutableList.Builder<Object> builder = ImmutableList.builder();
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(() -> builder.add(field.get(null)));
}
}
return builder.build();
}
// sample strings for setters
private static List<String> sampleStrings(MetaProperty<?> mp) {
List<?> values = sampleValues(mp);
List<String> strings = values.stream().map(Object::toString).collect(Collectors.toList());
strings.add("");
return strings;
}
private static final Map<Class<?>, List<?>> SAMPLES =
ImmutableMap.<Class<?>, List<?>>builder()
.put(String.class, Arrays.asList("Hello", "Goodbye", " ", ""))
.put(Byte.class, Arrays.asList((byte) 0, (byte) 1))
.put(Byte.TYPE, Arrays.asList((byte) 0, (byte) 1))
.put(Short.class, Arrays.asList((short) 0, (short) 1))
.put(Short.TYPE, Arrays.asList((short) 0, (short) 1))
.put(Integer.class, Arrays.asList((int) 0, (int) 1))
.put(Integer.TYPE, Arrays.asList((int) 0, (int) 1))
.put(Long.class, Arrays.asList((long) 0, (long) 1))
.put(Long.TYPE, Arrays.asList((long) 0, (long) 1))
.put(Float.class, Arrays.asList((float) 0, (float) 1))
.put(Float.TYPE, Arrays.asList((float) 0, (float) 1))
.put(Double.class, Arrays.asList((double) 0, (double) 1))
.put(Double.TYPE, Arrays.asList((double) 0, (double) 1))
.put(Character.class, Arrays.asList(' ', 'A', 'z'))
.put(Character.TYPE, Arrays.asList(' ', 'A', 'z'))
.put(Boolean.class, Arrays.asList(Boolean.TRUE, Boolean.FALSE))
.put(Boolean.TYPE, Arrays.asList(Boolean.TRUE, Boolean.FALSE))
.put(LocalDate.class, Arrays.asList(LocalDate.now(ZoneOffset.UTC), LocalDate.of(2012, 6, 30)))
.put(LocalTime.class, Arrays.asList(LocalTime.now(ZoneOffset.UTC), LocalTime.of(11, 30)))
.put(LocalDateTime.class, Arrays.asList(LocalDateTime.now(ZoneOffset.UTC), LocalDateTime.of(2012, 6, 30, 11, 30)))
.put(OffsetTime.class, Arrays.asList(
OffsetTime.now(ZoneOffset.UTC), OffsetTime.of(11, 30, 0, 0, ZoneOffset.ofHours(1))))
.put(OffsetDateTime.class, Arrays.asList(
OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.of(2012, 6, 30, 11, 30, 0, 0, ZoneOffset.ofHours(1))))
.put(ZonedDateTime.class, Arrays.asList(
ZonedDateTime.now(ZoneOffset.UTC), ZonedDateTime.of(2012, 6, 30, 11, 30, 0, 0, ZoneId.systemDefault())))
.put(Instant.class, Arrays.asList(Instant.now(), Instant.EPOCH))
.put(Year.class, Arrays.asList(Year.now(ZoneOffset.UTC), Year.of(2012)))
.put(YearMonth.class, Arrays.asList(YearMonth.now(ZoneOffset.UTC), YearMonth.of(2012, 6)))
.put(MonthDay.class, Arrays.asList(MonthDay.now(ZoneOffset.UTC), MonthDay.of(12, 25)))
.put(Month.class, Arrays.asList(Month.JULY, Month.DECEMBER))
.put(DayOfWeek.class, Arrays.asList(DayOfWeek.FRIDAY, DayOfWeek.SATURDAY))
.put(URI.class, Arrays.asList(URI.create("http://www.opengamma.com"), URI.create("http://www.joda.org")))
.put(Class.class, Arrays.asList(Throwable.class, RuntimeException.class, String.class))
.put(Object.class, Arrays.asList("", 6))
.put(Collection.class, Arrays.asList(new ArrayList<>()))
.put(List.class, Arrays.asList(new ArrayList<>()))
.put(Set.class, Arrays.asList(new HashSet<>()))
.put(SortedSet.class, Arrays.asList(new TreeSet<>()))
.put(ImmutableList.class, Arrays.asList(ImmutableList.<String>of()))
.put(ImmutableSet.class, Arrays.asList(ImmutableSet.<String>of()))
.put(ImmutableSortedSet.class, Arrays.asList(ImmutableSortedSet.<String>naturalOrder()))
.build();
}