package rocks.inspectit.server.instrumentation.classcache;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
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.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import rocks.inspectit.server.instrumentation.config.ClassCacheSearchNarrower;
import rocks.inspectit.server.instrumentation.config.applier.IInstrumentationApplier;
import rocks.inspectit.shared.all.instrumentation.classcache.AnnotationType;
import rocks.inspectit.shared.all.instrumentation.classcache.ClassType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableClassType;
import rocks.inspectit.shared.all.instrumentation.classcache.InterfaceType;
import rocks.inspectit.shared.all.instrumentation.classcache.MethodType;
import rocks.inspectit.shared.all.instrumentation.classcache.Type;
import rocks.inspectit.shared.all.instrumentation.config.impl.AgentConfig;
import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition;
import rocks.inspectit.shared.all.instrumentation.config.impl.MethodInstrumentationConfig;
import rocks.inspectit.shared.all.testbase.TestBase;
import rocks.inspectit.shared.cs.ci.assignment.AbstractClassSensorAssignment;
@SuppressWarnings({ "all", "unchecked" })
public class ClassCacheInstrumentationTest extends TestBase {
private static final String FQN = "FQN";
@InjectMocks
ClassCacheInstrumentation instrumentation;
@Mock
Logger log;
@Mock
ClassCache classCache;
@Mock
ClassCacheLookup lookup;
@Mock
AgentConfig agentConfiguration;
@Mock
ClassType classType;
@Mock
IInstrumentationApplier instrumentationApplier;
@Mock
ClassCacheSearchNarrower searchNarrower;
@Mock
AbstractClassSensorAssignment<?> assignment;
@BeforeMethod
public void setup() throws Exception {
when(classCache.getLookupService()).thenReturn(lookup);
Answer<Object> callableAnswer = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Callable<?> callable = (Callable<?>) invocation.getArguments()[0];
return callable.call();
}
};
doAnswer(callableAnswer).when(classCache).executeWithReadLock(Matchers.<Callable<?>> anyObject());
doAnswer(callableAnswer).when(classCache).executeWithWriteLock(Matchers.<Callable<?>> anyObject());
instrumentation.init(classCache);
}
public static class AddAndGetInstrumentationResult extends ClassCacheInstrumentationTest {
@Test
public void notInitialized() {
when(classType.isInitialized()).thenReturn(false);
InstrumentationDefinition result = instrumentation.addAndGetInstrumentationResult(classType, agentConfiguration, Collections.singleton(instrumentationApplier));
assertThat(result, is(nullValue()));
}
@Test
public void notInstrumented() {
when(classType.isInitialized()).thenReturn(true);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(false);
InstrumentationDefinition result = instrumentation.addAndGetInstrumentationResult(classType, agentConfiguration, Collections.singleton(instrumentationApplier));
assertThat(result, is(nullValue()));
}
@Test
public void instrumented() {
Collection<MethodInstrumentationConfig> configs = mock(Collection.class);
when(classType.isInitialized()).thenReturn(true);
when(classType.getFQN()).thenReturn(FQN);
when(classType.hasInstrumentationPoints()).thenReturn(true);
when(classType.getInstrumentationPoints()).thenReturn(configs);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(true);
InstrumentationDefinition result = instrumentation.addAndGetInstrumentationResult(classType, agentConfiguration, Collections.singleton(instrumentationApplier));
assertThat(result, is(notNullValue()));
assertThat(result.getClassName(), is(FQN));
assertThat(result.getMethodInstrumentationConfigs(), is(configs));
}
}
public static class RemoveInstrumentationPoints extends ClassCacheInstrumentationTest {
@Test
public void removeAll() throws Exception {
MethodType methodType = mock(MethodType.class);
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(classType.hasInstrumentationPoints()).thenReturn(true);
when(classType.getMethods()).thenReturn(Collections.singleton(methodType));
doReturn(Collections.singleton(classType)).when(lookup).findAll();
instrumentation.removeInstrumentationPoints();
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(methodType, times(1)).setMethodInstrumentationConfig(null);
}
@Test
public void removeNothingWhenEmpty() throws Exception {
doReturn(Collections.emptyList()).when(lookup).findAll();
instrumentation.removeInstrumentationPoints();
// not touching the write lock
verify(classCache, times(0)).executeWithWriteLock(Matchers.<Callable<?>> any());
}
@Test
public void removeNothingForAnnotationTypes() throws Exception {
AnnotationType annotationType = new AnnotationType("");
instrumentation.removeInstrumentationPoints(Collections.singleton(annotationType), Collections.singleton(instrumentationApplier));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verifyZeroInteractions(instrumentationApplier);
}
@Test
public void removeNothingForInterfaceTypes() throws Exception {
InterfaceType interfaceType = new InterfaceType("");
instrumentation.removeInstrumentationPoints(Collections.singleton(interfaceType), Collections.singleton(instrumentationApplier));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verifyZeroInteractions(instrumentationApplier);
}
}
public static class AddInstrumentationPoints extends ClassCacheInstrumentationTest {
@Test
public void add() throws Exception {
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(true);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat((Collection<ClassType>) result, hasItem(classType));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).addInstrumentationPoints(agentConfiguration, classType);
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void searchNarrowAdd() throws Exception {
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(true);
doReturn(assignment).when(instrumentationApplier).getSensorAssignment();
doReturn(Collections.singleton(classType)).when(searchNarrower).narrowByClassSensorAssignment(classCache, assignment);
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat((Collection<ClassType>) result, hasItem(classType));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).addInstrumentationPoints(agentConfiguration, classType);
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void addNothingWhenInstrumenterDoesNotAdd() throws Exception {
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(false);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat((Collection<ClassType>) result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).addInstrumentationPoints(agentConfiguration, classType);
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void searchNarrowAddNothingWhenInstrumenterDoesNotAdd() throws Exception {
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(instrumentationApplier.addInstrumentationPoints(agentConfiguration, classType)).thenReturn(false);
doReturn(assignment).when(instrumentationApplier).getSensorAssignment();
doReturn(Collections.singleton(classType)).when(searchNarrower).narrowByClassSensorAssignment(classCache, assignment);
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat((Collection<ClassType>) result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).addInstrumentationPoints(agentConfiguration, classType);
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void addNothingForNonInitializedType() throws Exception {
when(classType.isInitialized()).thenReturn(false);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void searchNarrowAddNothingForNonInitializedType() throws Exception {
when(classType.isInitialized()).thenReturn(false);
doReturn(assignment).when(instrumentationApplier).getSensorAssignment();
doReturn(Collections.singleton(classType)).when(searchNarrower).narrowByClassSensorAssignment(classCache, assignment);
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void addNothingWhenEmpty() throws Exception {
doReturn(Collections.emptyList()).when(lookup).findAll();
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// not touching the write lock
verify(classCache, times(0)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void searchNarrowAddNothingWhenEmpty() throws Exception {
doReturn(assignment).when(instrumentationApplier).getSensorAssignment();
doReturn(Collections.emptyList()).when(searchNarrower).narrowByClassSensorAssignment(classCache, assignment);
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// not touching the write lock
verify(classCache, times(0)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void addNothingForNonClassTypes() throws Exception {
AnnotationType annotationType = new AnnotationType("");
InterfaceType interfaceType = new InterfaceType("");
List<Type> types = new ArrayList<>();
types.add(annotationType);
types.add(interfaceType);
doReturn(types).when(lookup).findAll();
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
@Test
public void searchNarrowAddNothingForNonClassTypes() throws Exception {
AnnotationType annotationType = new AnnotationType("");
InterfaceType interfaceType = new InterfaceType("");
List<Type> types = new ArrayList<>();
types.add(annotationType);
types.add(interfaceType);
doReturn(assignment).when(instrumentationApplier).getSensorAssignment();
doReturn(types).when(searchNarrower).narrowByClassSensorAssignment(classCache, assignment);
Collection<? extends ImmutableClassType> result = instrumentation.addInstrumentationPoints(agentConfiguration, Collections.singleton(instrumentationApplier));
// assert result
assertThat(result, is(empty()));
// must be write lock
verify(classCache, times(1)).executeWithWriteLock(Matchers.<Callable<?>> any());
verify(instrumentationApplier, times(1)).getSensorAssignment();
verifyNoMoreInteractions(instrumentationApplier);
}
}
public static class GetInstrumentationResults extends ClassCacheInstrumentationTest {
@Test
public void collect() throws Exception {
Collection<MethodInstrumentationConfig> configs = mock(Collection.class);
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(classType.getFQN()).thenReturn(FQN);
when(classType.hasInstrumentationPoints()).thenReturn(true);
when(classType.getInstrumentationPoints()).thenReturn(configs);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
Collection<InstrumentationDefinition> result = instrumentation.getInstrumentationResults();
// assert result
assertThat(result, hasSize(1));
InstrumentationDefinition instrumentationResult = result.iterator().next();
assertThat(instrumentationResult.getClassName(), is(FQN));
assertThat(instrumentationResult.getMethodInstrumentationConfigs(), is(configs));
// read lock is enough
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForNonInitializedType() throws Exception {
when(classType.isInitialized()).thenReturn(false);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResults(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
verifyZeroInteractions(log);
}
@Test
public void collectNothingForNonClassType() throws Exception {
when(classType.isClass()).thenReturn(false);
when(classType.isInitialized()).thenReturn(true);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResults(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingWhenEmpty() throws Exception {
doReturn(Collections.emptyList()).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResults(), is(empty()));
// not touching the read lock
verify(classCache, times(0)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForAnnotationTypes() throws Exception {
doReturn(Collections.singleton(new AnnotationType(""))).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResults(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForInterfaceTypes() throws Exception {
doReturn(Collections.singleton(new InterfaceType(""))).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResults(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
}
public static class GetInstrumentationResultsWithHashes extends ClassCacheInstrumentationTest {
@Test
public void collect() throws Exception {
Collection<MethodInstrumentationConfig> configs = mock(Collection.class);
Set<String> hashes = mock(Set.class);
when(classType.isClass()).thenReturn(true);
when(classType.castToClass()).thenReturn(classType);
when(classType.isInitialized()).thenReturn(true);
when(classType.getFQN()).thenReturn(FQN);
when(classType.hasInstrumentationPoints()).thenReturn(true);
when(classType.getInstrumentationPoints()).thenReturn(configs);
when(classType.getHashes()).thenReturn(hashes);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
Map<Collection<String>, InstrumentationDefinition> result = instrumentation.getInstrumentationResultsWithHashes();
// assert result
assertThat(result.size(), is(1));
Entry<Collection<String>, InstrumentationDefinition> entry = result.entrySet().iterator().next();
assertThat((Set<String>) entry.getKey(), is(hashes));
assertThat(entry.getValue().getClassName(), is(FQN));
assertThat(entry.getValue().getMethodInstrumentationConfigs(), is(configs));
// read lock is enough
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForNonInitializedType() throws Exception {
when(classType.isInitialized()).thenReturn(false);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResultsWithHashes().entrySet(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForNonClassType() throws Exception {
when(classType.isClass()).thenReturn(false);
when(classType.isInitialized()).thenReturn(true);
doReturn(Collections.singleton(classType)).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResultsWithHashes().entrySet(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingWhenEmpty() throws Exception {
doReturn(Collections.emptyList()).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResultsWithHashes().entrySet(), is(empty()));
// not touching the read lock
verify(classCache, times(0)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForAnnotationTypes() throws Exception {
doReturn(Collections.singleton(new AnnotationType(""))).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResultsWithHashes().entrySet(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
@Test
public void collectNothingForInterfaceTypes() throws Exception {
doReturn(Collections.singleton(new InterfaceType(""))).when(lookup).findAll();
assertThat(instrumentation.getInstrumentationResultsWithHashes().entrySet(), is(empty()));
// must be read lock
verify(classCache, times(1)).executeWithReadLock(Matchers.<Callable<?>> any());
}
}
}