package rocks.inspectit.agent.java.sensor.exception; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.lang.reflect.Field; import java.sql.Timestamp; import java.util.HashMap; import java.util.Map; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import rocks.inspectit.agent.java.AbstractLogSupport; import rocks.inspectit.agent.java.analyzer.classes.MyTestException; import rocks.inspectit.agent.java.config.impl.RegisteredSensorConfig; import rocks.inspectit.agent.java.core.ICoreService; import rocks.inspectit.agent.java.core.IPlatformManager; import rocks.inspectit.agent.java.util.StringConstraint; import rocks.inspectit.shared.all.communication.ExceptionEvent; import rocks.inspectit.shared.all.communication.data.ExceptionSensorData; @SuppressWarnings("PMD") public class ExceptionSensorHookTest extends AbstractLogSupport { @Mock private IPlatformManager platformManager; @Mock private ICoreService coreService; @Mock private RegisteredSensorConfig registeredSensorConfig; private Map<String, Object> parameter; private int stringLength; private ExceptionSensorHook exceptionHook; @BeforeMethod public void initTestClass() { stringLength = 1000; parameter = new HashMap<String, Object>(); parameter.put("stringLength", String.valueOf(stringLength)); exceptionHook = new ExceptionSensorHook(platformManager, parameter); } @Test public void throwableObjectWasCreated() throws Exception { long constructorId = 5L; long sensorTypeId = 3L; long platformId = 1L; Object[] parameters = new Object[0]; MyTestException exceptionObject = MyTestException.class.newInstance(); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(1)).getPlatformId(); verifyNoMoreInteractions(platformManager); // verify(coreService, never()); } @Test public void throwableObjectDifferentThenRSCTargetClass() throws Exception { long constructorId = 5L; long sensorTypeId = 3L; long platformId = 1L; Object[] parameters = new Object[0]; Exception exceptionObject = Exception.class.newInstance(); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); verifyNoMoreInteractions(platformManager); } @Test public void throwableObjectCreatedThrownAndHandled() throws Exception { long methodId = 5L; long constructorId = 4L; long sensorTypeId = 3L; long platformId = 1L; long methodIdTwo = 20L; Object[] parameters = new Object[0]; Object object = mock(Object.class); MyTestException exceptionObject = MyTestException.class.newInstance(); ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, sensorTypeId, methodIdTwo); exceptionSensorData.setErrorMessage(exceptionObject.getMessage()); exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(1)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); exceptionSensorData.setExceptionEvent(ExceptionEvent.PASSED); exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(2)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); exceptionSensorData.setExceptionEvent(ExceptionEvent.HANDLED); exceptionHook.dispatchBeforeCatchBody(coreService, methodIdTwo, sensorTypeId, exceptionObject, registeredSensorConfig); verify(platformManager, times(3)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); verifyNoMoreInteractions(platformManager, coreService); } @Test public void differentThrowableObjectsCreatedAndThrown() throws Exception { long methodId = 5L; long constructorId = 4L; long sensorTypeId = 3L; long platformId = 1L; Object[] parameters = new Object[0]; Object object = mock(Object.class); MyTestException firstExceptionObject = MyTestException.class.newInstance(); MyTestException secondExceptionObject = MyTestException.class.newInstance(); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, firstExceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(1)).getPlatformId(); // verify(coreService, // times(1)).addExceptionSensorData(eq(registeredSensorTypeId), // argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, secondExceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(2)).getPlatformId(); verifyNoMoreInteractions(platformManager); } @Test public void throwableHasCause() throws Exception, SecurityException, NoSuchFieldException { long methodId = 5L; long constructorId = 4L; long sensorTypeId = 3L; long platformId = 1L; long methodIdTwo = 20L; Object[] parameters = new Object[0]; Object object = mock(Object.class); MyTestException exceptionObject = MyTestException.class.newInstance(); Throwable cause = Throwable.class.newInstance(); // setting the cause at the exceptionObject // we can only access the cause field from the overall superclass // Throwable Field causeField = exceptionObject.getClass().getSuperclass().getSuperclass().getDeclaredField("cause"); causeField.setAccessible(true); causeField.set(exceptionObject, cause); ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, sensorTypeId, methodIdTwo); exceptionSensorData.setErrorMessage(exceptionObject.getMessage()); exceptionSensorData.setCause(exceptionObject.getCause().getClass().getName()); exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(1)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); assertThat(exceptionSensorData.getCause(), is(equalTo(cause.getClass().getName()))); // resetting the cause to null as we need the cause only in the first // data object exceptionSensorData.setCause(null); exceptionSensorData.setExceptionEvent(ExceptionEvent.PASSED); exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(2)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); exceptionSensorData.setExceptionEvent(ExceptionEvent.HANDLED); exceptionHook.dispatchBeforeCatchBody(coreService, methodIdTwo, sensorTypeId, exceptionObject, registeredSensorConfig); verify(platformManager, times(3)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); verifyNoMoreInteractions(platformManager, coreService); } @Test public void valueTooLong() throws Exception { long constructorId = 5L; long sensorTypeId = 3L; long platformId = 1L; Object[] parameters = new Object[0]; StringConstraint strConstraint = new StringConstraint(parameter); // the actual error message is too long, so it should be cropped later on String exceptionMessage = fillString('x', stringLength + 1); MyTestException exceptionObject = new MyTestException(exceptionMessage); ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, sensorTypeId, constructorId); exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); // the actual error message to be verified against exceptionSensorData.setErrorMessage(strConstraint.crop(exceptionMessage)); when(platformManager.getPlatformId()).thenReturn(platformId); when(registeredSensorConfig.getTargetClassFqn()).thenReturn(MyTestException.class.getName()); exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); verify(platformManager, times(1)).getPlatformId(); verify(coreService, times(1)).addExceptionSensorData(eq(sensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); verifyNoMoreInteractions(platformManager); } private static class ExceptionSensorDataVerifier extends ArgumentMatcher<ExceptionSensorData> { private final ExceptionSensorData exceptionSensorData; public ExceptionSensorDataVerifier(ExceptionSensorData exceptionSensorData) { this.exceptionSensorData = exceptionSensorData; } @Override public boolean matches(Object object) { if (!ExceptionSensorData.class.isInstance(object)) { return false; } ExceptionSensorData otherExceptionSensorData = (ExceptionSensorData) object; if ((null != exceptionSensorData.getCause()) && !exceptionSensorData.getCause().equals(otherExceptionSensorData.getCause())) { return false; } if ((null != exceptionSensorData.getErrorMessage()) && !exceptionSensorData.getErrorMessage().equals(otherExceptionSensorData.getErrorMessage())) { return false; } if (!exceptionSensorData.getExceptionEvent().equals(otherExceptionSensorData.getExceptionEvent())) { return false; } if ((null != exceptionSensorData.getThrowableType()) && !exceptionSensorData.getThrowableType().equals(otherExceptionSensorData.getThrowableType())) { return false; } if (exceptionSensorData.getThrowableIdentityHashCode() != otherExceptionSensorData.getThrowableIdentityHashCode()) { return false; } return true; } } public String fillString(char character, int count) { // creates a string of 'x' repeating characters char[] chars = new char[count]; while (count > 0) { chars[--count] = character; } return new String(chars); } }