package rocks.inspectit.shared.cs.storage.serializer.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.slf4j.Logger;
import org.springframework.core.io.ClassPathResource;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.esotericsoftware.kryo.io.ByteBufferInputStream;
import com.esotericsoftware.kryo.io.ByteBufferOutputStream;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import rocks.inspectit.shared.all.cmr.model.MethodIdent;
import rocks.inspectit.shared.all.cmr.model.MethodIdentToSensorType;
import rocks.inspectit.shared.all.cmr.model.MethodSensorTypeIdent;
import rocks.inspectit.shared.all.cmr.model.PlatformIdent;
import rocks.inspectit.shared.all.cmr.model.PlatformSensorTypeIdent;
import rocks.inspectit.shared.all.communication.DefaultData;
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.HttpTimerData;
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.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.communication.data.cmr.AgentStatusData;
import rocks.inspectit.shared.all.communication.data.cmr.CmrStatusData;
import rocks.inspectit.shared.all.exception.BusinessException;
import rocks.inspectit.shared.all.exception.RemoteException;
import rocks.inspectit.shared.all.exception.enumeration.StorageErrorCodeEnum;
import rocks.inspectit.shared.all.serializer.ISerializer;
import rocks.inspectit.shared.all.serializer.SerializationException;
import rocks.inspectit.shared.all.serializer.impl.SerializationManager;
import rocks.inspectit.shared.all.serializer.schema.ClassSchemaManager;
import rocks.inspectit.shared.all.testbase.TestBase;
import rocks.inspectit.shared.all.util.KryoNetNetwork;
import rocks.inspectit.shared.cs.communication.data.cmr.RecordingData;
import rocks.inspectit.shared.cs.indexing.indexer.impl.InvocationChildrenIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.MethodIdentIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.ObjectTypeIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.PlatformIdentIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.SensorTypeIdentIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.SqlStringIndexer;
import rocks.inspectit.shared.cs.indexing.indexer.impl.TimestampIndexer;
import rocks.inspectit.shared.cs.indexing.storage.impl.ArrayBasedStorageLeaf;
import rocks.inspectit.shared.cs.indexing.storage.impl.SimpleStorageDescriptor;
import rocks.inspectit.shared.cs.indexing.storage.impl.StorageBranch;
import rocks.inspectit.shared.cs.indexing.storage.impl.StorageBranchIndexer;
import rocks.inspectit.shared.cs.storage.LocalStorageData;
import rocks.inspectit.shared.cs.storage.StorageData;
import rocks.inspectit.shared.cs.storage.label.BooleanStorageLabel;
import rocks.inspectit.shared.cs.storage.label.DateStorageLabel;
import rocks.inspectit.shared.cs.storage.label.NumberStorageLabel;
import rocks.inspectit.shared.cs.storage.label.StringStorageLabel;
import rocks.inspectit.shared.cs.storage.label.type.impl.AssigneeLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.CreationDateLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.CustomBooleanLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.CustomDateLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.CustomNumberLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.CustomStringLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.ExploredByLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.RatingLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.StatusLabelType;
import rocks.inspectit.shared.cs.storage.label.type.impl.UseCaseLabelType;
import rocks.inspectit.shared.cs.storage.serializer.SerializationManagerPostProcessor;
/**
* Test the implementation of the {@link ISerializer} for correctness.
*
* @author Ivan Senic
*
*/
@SuppressWarnings("PMD")
public class SerializerTest extends TestBase {
/**
* Classes to be tested in the {@link #testClassesForPlanSerialization(Class)}, so to be sure
* that every class can be serialized by our Kryo implementation.
*/
public static final Object[][] TESTING_CLASSES = new Object[][] { { TimerData.class }, { SqlStatementData.class }, { ExceptionSensorData.class }, { InvocationSequenceData.class },
{ ClassLoadingInformationData.class }, { CompilationInformationData.class }, { MemoryInformationData.class }, { RuntimeInformationData.class }, { SystemInformationData.class },
{ ThreadInformationData.class }, { HttpTimerData.class }, { ParameterContentData.class }, { VmArgumentData.class }, { PlatformIdent.class }, { MethodIdent.class },
{ MethodSensorTypeIdent.class }, { MethodIdentToSensorType.class }, { PlatformSensorTypeIdent.class }, { SimpleStorageDescriptor.class }, { ArrayBasedStorageLeaf.class },
{ StorageData.class }, { LocalStorageData.class }, { PlatformIdentIndexer.class }, { ObjectTypeIndexer.class }, { MethodIdentIndexer.class }, { SensorTypeIdentIndexer.class },
{ TimestampIndexer.class }, { InvocationChildrenIndexer.class }, { StorageBranch.class }, { StorageBranchIndexer.class }, { BooleanStorageLabel.class }, { DateStorageLabel.class },
{ NumberStorageLabel.class }, { StringStorageLabel.class }, { AssigneeLabelType.class }, { CreationDateLabelType.class }, { CustomBooleanLabelType.class }, { CustomDateLabelType.class },
{ CustomNumberLabelType.class }, { CustomStringLabelType.class }, { ExploredByLabelType.class }, { RatingLabelType.class }, { StatusLabelType.class }, { UseCaseLabelType.class },
{ SqlStringIndexer.class }, { BooleanStorageLabel.class }, { DateStorageLabel.class }, { NumberStorageLabel.class }, { StringStorageLabel.class }, { CustomDateLabelType.class },
{ CmrStatusData.class }, { AgentStatusData.class }, { RecordingData.class }, { CustomBooleanLabelType.class }, { CustomNumberLabelType.class }, { CustomStringLabelType.class },
{ AssigneeLabelType.class }, { RatingLabelType.class }, { ExploredByLabelType.class }, { CreationDateLabelType.class }, { StatusLabelType.class }, { UseCaseLabelType.class },
{ AggregatedHttpTimerData.class }, { AggregatedSqlStatementData.class }, { AggregatedTimerData.class }, { ArrayBasedStorageLeaf.class } };
/**
* Serializer.
*/
private SerializationManager serializer;
/**
* Byte buffer.
*/
private final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 20);
@InjectMocks
private ClassSchemaManager schemaManager;
@Mock
private Logger log;
/**
* Instantiates the {@link SerializationManager}.
*
* @throws IOException
* If {@link IOException} occurs.
*/
@BeforeMethod
public void initSerializer() throws IOException {
schemaManager.setSchemaListFile(new ClassPathResource(ClassSchemaManager.SCHEMA_DIR + "/" + ClassSchemaManager.SCHEMA_LIST_FILE, schemaManager.getClass().getClassLoader()));
schemaManager.loadSchemasFromLocations();
serializer = new SerializationManager();
serializer.setSchemaManager(schemaManager);
serializer.setKryoNetNetwork(new KryoNetNetwork());
serializer.initKryo();
SerializationManagerPostProcessor postProcessor = new SerializationManagerPostProcessor();
postProcessor.postProcessAfterInitialization(serializer, "serializerTest");
}
/**
* Prepare the buffer before the test.
*/
@BeforeMethod
public void prepareBuffer() {
byteBuffer.clear();
}
/**
* Tests if the data deserialzied from empty buffer is <code>null</code>.
*
* @throws SerializationException
* Serialization Exception
*/
@Test
public void emptyBufferSerialization() throws SerializationException {
// I need to create a new buffer, because clear on the buffer will not actually erase the
// data in the buffer, but only move the pointers
ByteBuffer newByteBuffer = ByteBuffer.allocateDirect(1024);
ByteBufferInputStream byteBufferInputStream = new ByteBufferInputStream(newByteBuffer);
Input input = new Input(byteBufferInputStream);
Object data = serializer.deserialize(input);
assertThat(data, is(nullValue()));
}
/**
* Tests if the data de-serialzed from buffer with random data is <code>null</code>.
*
* @throws SerializationException
* Serialization Exception
*/
@Test
public void radomBufferDataSerialization() throws SerializationException {
for (int i = 0; i < 64; i++) {
byteBuffer.putInt(i);
}
byteBuffer.flip();
ByteBufferInputStream byteBufferInputStream = new ByteBufferInputStream(byteBuffer);
Input input = new Input(byteBufferInputStream);
Object data = serializer.deserialize(input);
assertThat(data, is(nullValue()));
}
/**
* Provides classes to be tested.
*
* @return Provides classes to be tested.
*/
@DataProvider(name = "classProvider")
public Object[][] classProvider() {
return TESTING_CLASSES;
}
/**
* Tests the class that extends the {@link DefaultData} class via reflection. Note that tested
* class can not be abstract.
*
* @param testingClass
* Class to test.
* @throws InstantiationException
* InstantiationException
* @throws IllegalAccessException
* IllegalAccessException
* @throws SerializationException
* SerializationException
*/
@Test(dataProvider = "classProvider")
public void classesPlanSerialization(Class<?> testingClass) throws InstantiationException, IllegalAccessException, SerializationException {
Object object = getInstanceWithPrimitiveFieldsSet(testingClass);
Object deserialized = serializeBackAndForth(object);
assertThat(deserialized, is(equalTo(object)));
}
@Test(dataProvider = "classProvider")
public void copy(Class<?> testingClass) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Object object = getInstanceWithPrimitiveFieldsSet(testingClass);
Object copy = serializer.copy(object);
assertThat(copy, is(equalTo(object)));
assertThat(copy == object, is(false));
}
private Object getInstanceWithPrimitiveFieldsSet(Class<?> testingClass) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Object object = testingClass.newInstance();
for (Field field : testingClass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true);
if (field.getType().equals(long.class)) {
field.set(object, RandomUtils.nextLong());
} else if (field.getType().equals(int.class)) {
field.set(object, RandomUtils.nextInt());
} else if (field.getType().equals(double.class)) {
field.set(object, RandomUtils.nextDouble());
} else if (field.getType().equals(boolean.class)) {
field.set(object, RandomUtils.nextBoolean());
} else if (field.getType().equals(String.class)) {
field.set(object, RandomStringUtils.random(100));
}
}
return object;
}
/**
* Tests the java collections. Compares array gotten from collections because the equal is
* missing in those collections.
*/
@Test(dataProvider = "javaCollectionsProvider")
public void javaCollections(Collection<?> testCollection) throws SerializationException {
Collection<?> deserialized = serializeBackAndForth(testCollection);
assertThat(deserialized, is(instanceOf(testCollection.getClass())));
assertThat(deserialized.toArray(), is(equalTo(testCollection.toArray())));
}
@DataProvider(name = "javaCollectionsProvider")
public Object[][] javaCollectionsProvider() {
List<Collection<?>> collections = new ArrayList<>();
// unmodifiable empty
collections.add(Collections.unmodifiableCollection(new ArrayList<>()));
collections.add(Collections.unmodifiableList(new ArrayList<>()));
collections.add(Collections.unmodifiableSet(new HashSet<>()));
collections.add(Collections.unmodifiableSortedSet(new TreeSet<>()));
// unmodifiable with one element
collections.add(Collections.unmodifiableCollection(Collections.singleton("blub")));
collections.add(Collections.unmodifiableList(Collections.singletonList("blub")));
collections.add(Collections.unmodifiableSet(Collections.singleton("blub")));
TreeSet<Object> treeSet = new TreeSet<>();
treeSet.add("blub");
collections.add(Collections.unmodifiableSortedSet(treeSet));
// synchronized empty
collections.add(Collections.synchronizedCollection(new ArrayList<>()));
collections.add(Collections.synchronizedList(new ArrayList<>()));
collections.add(Collections.synchronizedSet(new HashSet<>()));
collections.add(Collections.synchronizedSortedSet(new TreeSet<>()));
// synchronized with one element
collections.add(Collections.synchronizedCollection(Collections.singleton("blub")));
collections.add(Collections.synchronizedList(Collections.singletonList("blub")));
collections.add(Collections.synchronizedSet(Collections.singleton("blub")));
collections.add(Collections.synchronizedSortedSet(treeSet));
// singletons
collections.add(Collections.singleton("blub"));
collections.add(Collections.singletonList("blub"));
collections.add(Collections.singleton("blub"));
Object[][] returnData = new Object[collections.size()][1];
for (int i = 0; i < collections.size(); i++) {
returnData[i][0] = collections.get(i);
}
return returnData;
}
/**
* Tests the java maps. Compares the entries of maps.
*/
@Test(dataProvider = "javaMapsProvider")
public void javaMaps(Map<Object, Object> testMap) throws SerializationException {
Map<?, ?> deserialized = serializeBackAndForth(testMap);
assertThat(deserialized, is(instanceOf(testMap.getClass())));
for (Entry<Object, Object> originalEntry : testMap.entrySet()) {
assertThat(deserialized, hasEntry(originalEntry.getKey(), originalEntry.getValue()));
}
}
@DataProvider(name = "javaMapsProvider")
public Object[][] javaMapsProvider() {
List<Map<?, ?>> maps = new ArrayList<>();
// unmodifiable
maps.add(Collections.unmodifiableMap(Collections.singletonMap("Key", "Value")));
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("Key", "Value");
maps.add(Collections.unmodifiableSortedMap(treeMap));
// synchronized
maps.add(Collections.synchronizedMap(Collections.singletonMap("Key", "Value")));
maps.add(Collections.synchronizedSortedMap(treeMap));
// singleton
maps.add(Collections.singletonMap("Key", "Value"));
Object[][] returnData = new Object[maps.size()][1];
for (int i = 0; i < maps.size(); i++) {
returnData[i][0] = maps.get(i);
}
return returnData;
}
/**
* Tests serialization of remote exception.
*/
@Test
public void remoteException() throws SerializationException {
Exception exception = new Exception("Cause message");
RemoteException remoteException = new RemoteException(exception);
Exception deserialized = serializeBackAndForth(remoteException);
assertThat(deserialized, is(instanceOf(RemoteException.class)));
assertThat(deserialized.getMessage(), is(equalTo(exception.getMessage())));
assertThat(deserialized.getStackTrace(), is(equalTo(exception.getStackTrace())));
}
/**
* Test the Business exception.
*/
@Test
public void businessException() throws SerializationException {
BusinessException businessException = new BusinessException("Message", StorageErrorCodeEnum.CAN_NOT_START_RECORDING);
businessException.printStackTrace();
BusinessException deserialized = serializeBackAndForth(businessException);
assertThat(deserialized, is(instanceOf(BusinessException.class)));
assertThat(deserialized.getMessage(), is(equalTo(businessException.getMessage())));
assertThat(deserialized.getStackTrace(), is(equalTo(businessException.getStackTrace())));
assertThat(deserialized.getActionPerformed(), is(equalTo(businessException.getActionPerformed())));
assertThat(deserialized.getErrorCode(), is(equalTo(businessException.getErrorCode())));
}
/**
* Test a {@link IOException} throw from another method.
*/
@Test
public void thrownException() throws SerializationException {
try {
throwIOException();
} catch (IOException original) {
original.printStackTrace();
Exception deserialized = serializeBackAndForth(original);
assertThat(deserialized, is(instanceOf(original.getClass())));
assertThat(deserialized.getMessage(), is(equalTo(original.getMessage())));
assertThat(deserialized.getStackTrace(), is(equalTo(original.getStackTrace())));
}
}
private void throwIOException() throws IOException {
throw new IOException("Just for testing");
}
/**
* Performs the serialization of the given object to bytes and then performs de-serialization
* from those bytes and returns the de-serialized object back.
*
* @param original
* Original object.
* @return De-serialized objects from bytes gotten from the serialization of original.
* @throws SerializationException
* If serialization fails.
*/
@SuppressWarnings("unchecked")
private <T> T serializeBackAndForth(Object original) throws SerializationException {
ByteBufferOutputStream byteBufferOutputStream = new ByteBufferOutputStream(byteBuffer);
Output output = new Output(byteBufferOutputStream);
serializer.serialize(original, output);
byteBuffer.flip();
ByteBufferInputStream byteBufferInputStream = new ByteBufferInputStream(byteBuffer);
Input input = new Input(byteBufferInputStream);
return (T) serializer.deserialize(input);
}
}