package rocks.inspectit.shared.all.cmr.cache.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import rocks.inspectit.shared.all.cmr.cache.IObjectSizes;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.communication.ExceptionEvent;
import rocks.inspectit.shared.all.communication.MethodSensorData;
import rocks.inspectit.shared.all.communication.Sizeable;
import rocks.inspectit.shared.all.communication.data.AggregatedExceptionSensorData;
import rocks.inspectit.shared.all.communication.data.AggregatedHttpTimerData;
import rocks.inspectit.shared.all.communication.data.AggregatedSqlStatementData;
import rocks.inspectit.shared.all.communication.data.AggregatedTimerData;
import rocks.inspectit.shared.all.communication.data.ClassLoadingInformationData;
import rocks.inspectit.shared.all.communication.data.CompilationInformationData;
import rocks.inspectit.shared.all.communication.data.ExceptionSensorData;
import rocks.inspectit.shared.all.communication.data.HttpInfo;
import rocks.inspectit.shared.all.communication.data.HttpTimerData;
import rocks.inspectit.shared.all.communication.data.InvocationAwareData;
import rocks.inspectit.shared.all.communication.data.InvocationSequenceData;
import rocks.inspectit.shared.all.communication.data.MemoryInformationData;
import rocks.inspectit.shared.all.communication.data.ParameterContentData;
import rocks.inspectit.shared.all.communication.data.ParameterContentType;
import rocks.inspectit.shared.all.communication.data.RuntimeInformationData;
import rocks.inspectit.shared.all.communication.data.SqlStatementData;
import rocks.inspectit.shared.all.communication.data.SystemInformationData;
import rocks.inspectit.shared.all.communication.data.ThreadInformationData;
import rocks.inspectit.shared.all.communication.data.TimerData;
import rocks.inspectit.shared.all.communication.data.VmArgumentData;
import rocks.inspectit.shared.all.tracing.data.ClientSpan;
import rocks.inspectit.shared.all.tracing.data.ServerSpan;
/**
* This tests checks all the {@link DefaultData} classes for the proper use of the
* {@link IObjectSizes#getPrimitiveTypesSize(int, int, int, int, int, int)} method in the
* {@link DefaultData#getObjectSize(IObjectSizes)} call.
*
* @author Ivan Senic
*
*/
public class ObjectSizesPrimitiveTypesSizeTest {
/**
* Classes to be tested.
*/
public static final Object[][] TESTING_CLASSES = new Object[][] { { TestDefaultData.class }, { TestMethodSensorData.class }, { TestInvocationAwareData.class }, { TimerData.class },
{ SqlStatementData.class }, { ExceptionSensorData.class }, { InvocationSequenceData.class }, { ClassLoadingInformationData.class }, { CompilationInformationData.class },
{ MemoryInformationData.class }, { RuntimeInformationData.class }, { SystemInformationData.class }, { ThreadInformationData.class }, { HttpTimerData.class },
{ AggregatedExceptionSensorData.class }, { AggregatedHttpTimerData.class }, { AggregatedSqlStatementData.class }, { AggregatedTimerData.class }, { ParameterContentData.class },
{ HttpInfo.class }, { VmArgumentData.class }, { ServerSpan.class }, { ClientSpan.class } };
/**
* Enums that implement sizable.
*/
@SuppressWarnings("rawtypes")
public static final Enum[][] ENUM_CLASSES = new Enum[][] { { ExceptionEvent.CREATED }, { ParameterContentType.FIELD } };
/**
* Mocked {@link IObjectSizes}.
*/
private IObjectSizes objectSizes;
/**
* Tests the class that extends the {@link DefaultData} class via reflection. Note that tested
* class can not be abstract.
*
* @param sizableClass
* Class to test.
* @throws InstantiationException
* InstantiationException
* @throws IllegalAccessException
* IllegalAccessException
*/
@Test(dataProvider = "classProvider")
public void sizeableClass(Class<? extends Sizeable> sizableClass) throws InstantiationException, IllegalAccessException {
testClassForProperUseOfObjectSizesInternal(sizableClass, sizableClass.newInstance());
}
@SuppressWarnings("unchecked")
@Test(dataProvider = "enumProvider")
public <E extends Enum<?> & Sizeable> void sizeableEnum(E enumValue) {
testClassForProperUseOfObjectSizesInternal((Class<? extends Sizeable>) enumValue.getClass(), enumValue);
}
private void testClassForProperUseOfObjectSizesInternal(Class<? extends Sizeable> sizableClass, Sizeable sizable) {
assertThat("Class is not abstract.", !Modifier.isAbstract(sizableClass.getModifiers()));
objectSizes = mock(IObjectSizes.class);
Map<PrimitiveCount, Integer> primitiveCountMap = new HashMap<PrimitiveCount, Integer>();
// In order to correctly verify we need to check all attributes of the whole
// class hierarchy. In addition we need to ensure that each class hierarchy
// called the object size factory with the correct number of attributes. Due to
// the verification method in Mockito it is necessary to check whether the same
// call to the object size factory was in fact performed twice (if they have
// the same amount of attributes) as we then need to verify that the method was
// called times(x).
Class<?> clazz = sizableClass;
while (!clazz.equals(Object.class)) {
Field[] fields = clazz.getDeclaredFields();
PrimitiveCount primitiveCount = new PrimitiveCount();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())) {
// Static attributes are stored with the class.
continue;
}
if (field.getType().isPrimitive() && !field.getType().isArray()) {
if (field.getType().equals(Boolean.TYPE)) {
primitiveCount.booleanCount++;
} else if (field.getType().equals(Integer.TYPE)) {
primitiveCount.intCount++;
} else if (field.getType().equals(Float.TYPE)) {
primitiveCount.floatCount++;
} else if (field.getType().equals(Long.TYPE)) {
primitiveCount.longCount++;
} else if (field.getType().equals(Double.TYPE)) {
primitiveCount.doubleCount++;
}
} else if (!field.getType().isArray()) {
primitiveCount.referenceCount++;
}
}
Integer cachedPrimitiveCount = primitiveCountMap.get(primitiveCount);
if (null == cachedPrimitiveCount) {
primitiveCountMap.put(primitiveCount, Integer.valueOf(1));
} else {
primitiveCountMap.put(primitiveCount, Integer.valueOf(cachedPrimitiveCount.intValue() + 1));
}
clazz = clazz.getSuperclass();
}
sizable.getObjectSize(objectSizes);
int primitiveCountSize = 0;
for (Map.Entry<PrimitiveCount, Integer> entry : primitiveCountMap.entrySet()) {
PrimitiveCount primitiveCount = entry.getKey();
if (primitiveCount.shouldBeCounted()) {
primitiveCountSize += entry.getValue().intValue();
verify(objectSizes, times(entry.getValue().intValue())).getPrimitiveTypesSize(primitiveCount.referenceCount, primitiveCount.booleanCount, primitiveCount.intCount,
primitiveCount.floatCount, primitiveCount.longCount, primitiveCount.doubleCount);
}
}
verify(objectSizes, times(primitiveCountSize)).getPrimitiveTypesSize(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
}
/**
* Provides classes to be tested.
*
* @return Provides classes to be tested.
*/
@DataProvider(name = "classProvider")
public Object[][] classProvider() {
return TESTING_CLASSES;
}
@DataProvider(name = "enumProvider")
public Object[][] enumProvider() {
return ENUM_CLASSES;
}
/**
* Simple class for counting purposes.
*
* @author Ivan Senic
*
*/
private class PrimitiveCount {
int referenceCount = 0;
int booleanCount = 0;
int intCount = 0;
int floatCount = 0;
int longCount = 0;
int doubleCount = 0;
public boolean shouldBeCounted() {
return !((referenceCount == 0) && (booleanCount == 0) && (intCount == 0) && (floatCount == 0) && (longCount == 0) && (doubleCount == 0));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + getOuterType().hashCode();
result = (prime * result) + booleanCount;
result = (prime * result) + doubleCount;
result = (prime * result) + floatCount;
result = (prime * result) + intCount;
result = (prime * result) + longCount;
result = (prime * result) + referenceCount;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrimitiveCount other = (PrimitiveCount) obj;
if (!getOuterType().equals(other.getOuterType())) {
return false;
}
if (booleanCount != other.booleanCount) {
return false;
}
if (doubleCount != other.doubleCount) {
return false;
}
if (floatCount != other.floatCount) {
return false;
}
if (intCount != other.intCount) {
return false;
}
if (longCount != other.longCount) {
return false;
}
if (referenceCount != other.referenceCount) {
return false;
}
return true;
}
private ObjectSizesPrimitiveTypesSizeTest getOuterType() {
return ObjectSizesPrimitiveTypesSizeTest.this;
}
}
public static class TestDefaultData extends DefaultData {
private static final long serialVersionUID = -8907800333606213369L;
};
public static class TestMethodSensorData extends MethodSensorData {
private static final long serialVersionUID = 3859181039818602878L;
};
public static class TestInvocationAwareData extends InvocationAwareData {
private static final long serialVersionUID = 3283986124498709204L;
@Override
public double getInvocationAffiliationPercentage() {
return 0;
}
};
}