package net.bytebuddy.agent.builder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.test.utility.MockitoRule;
import net.bytebuddy.test.utility.ObjectPropertyAssertion;
import net.bytebuddy.utility.JavaModule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
public class AgentBuilderRedefinitionStrategyResubmissionStrategyTest {
@Rule
public TestRule mockitoRule = new MockitoRule(this);
@Mock
private Instrumentation instrumentation;
@Mock
private AgentBuilder.RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler;
@Mock
private AgentBuilder.LocationStrategy locationStrategy;
@Mock
private AgentBuilder.Listener listener;
@Mock
private AgentBuilder.InstallationListener installationListener;
@Mock
private ResettableClassFileTransformer classFileTransformer;
@Mock
private AgentBuilder.CircularityLock circularityLock;
@Mock
private AgentBuilder.RawMatcher rawMatcher;
@Mock
private ElementMatcher<? super Throwable> matcher;
@Mock
private Throwable error;
@Mock
private AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator;
@Mock
private AgentBuilder.RedefinitionStrategy.Listener redefinitionListener;
@Test
@SuppressWarnings("unchecked")
public void testRetransformation() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verify(instrumentation).retransformClasses(Foo.class);
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verify(redefinitionBatchAllocator).batch(Collections.<Class<?>>singletonList(Foo.class));
verifyNoMoreInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinition() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verify(instrumentation).redefineClasses(Mockito.argThat(new ArgumentMatcher<ClassDefinition>() {
@Override
public boolean matches(ClassDefinition classDefinition) {
return classDefinition.getDefinitionClass() == Foo.class && Arrays.equals(classDefinition.getDefinitionClassFile(), new byte[]{1, 2, 3});
}
}));
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verify(redefinitionBatchAllocator).batch(Collections.<Class<?>>singletonList(Foo.class));
verifyNoMoreInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationNonModifiable() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(false);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionNonModifiable() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(false);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testNoRetransformation() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(false);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(false);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.DISABLED,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyZeroInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verifyZeroInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationNonAlive() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(false);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(false);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verify(resubmissionScheduler).isAlive();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verifyZeroInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionNonAlive() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(false);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(false);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verify(resubmissionScheduler).isAlive();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verifyZeroInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationNonMatched() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(false);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionNonMatched() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(false);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationAlreadyLoaded() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(false);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
verifyNoMoreInteractions(listener);
verifyZeroInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionAlreadyLoaded() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(false);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
verifyNoMoreInteractions(listener);
verifyZeroInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationNonMatchedError() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(false);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionNonMatchedError() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenReturn(true);
when(matcher.matches(error)).thenReturn(false);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verifyZeroInteractions(instrumentation);
verifyZeroInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRetransformationError() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
RuntimeException runtimeException = new RuntimeException();
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenThrow(runtimeException);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, runtimeException);
verify(listener).onComplete(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
@SuppressWarnings("unchecked")
public void testRedefinitionError() throws Exception {
when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return Collections.singleton(invocationOnMock.getArgument(0));
}
});
RuntimeException runtimeException = new RuntimeException();
when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain())).thenThrow(runtimeException);
when(matcher.matches(error)).thenReturn(true);
when(resubmissionScheduler.isAlive()).thenReturn(true);
ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener);
installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(resubmissionScheduler).isAlive();
verify(resubmissionScheduler).schedule(argumentCaptor.capture());
argumentCaptor.getValue().run();
verifyNoMoreInteractions(resubmissionScheduler);
verify(instrumentation).isModifiableClass(Foo.class);
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
Foo.class.getClassLoader(),
JavaModule.ofType(Foo.class),
Foo.class,
Foo.class.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(redefinitionBatchAllocator);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, runtimeException);
verify(listener).onComplete(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true);
verifyNoMoreInteractions(listener);
verify(matcher).matches(error);
verifyNoMoreInteractions(matcher);
}
@Test
public void testDisabledListener() throws Exception {
assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE.apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener).getListener(), sameInstance(listener));
}
@Test
public void testDisabledInstallationListener() throws Exception {
assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE.apply(instrumentation,
locationStrategy,
listener,
installationListener,
circularityLock,
rawMatcher,
AgentBuilder.RedefinitionStrategy.REDEFINITION,
redefinitionBatchAllocator,
redefinitionListener).getInstallationListener(), sameInstance(installationListener));
}
@Test
public void testLookupKeyBootstrapLoaderReference() throws Exception {
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER);
assertThat(key.hashCode(), is(0));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey other = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0]));
System.gc();
assertThat(key, not(is(other)));
assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
assertThat(key, is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0])))));
assertThat(key, is(key));
assertThat(key, not(is(new Object())));
}
@Test
public void testLookupKeyNonBootstrapReference() throws Exception {
ClassLoader classLoader = new URLClassLoader(new URL[0]);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(classLoader);
assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(classLoader)));
assertThat(key.hashCode(), is(classLoader.hashCode()));
assertThat(key, not(is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER))));
assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0])))));
assertThat(key, is(key));
assertThat(key, not(is(new Object())));
}
@Test
public void testStorageKeyBootstrapLoaderReference() throws Exception {
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER);
assertThat(key.isBootstrapLoader(), is(true));
assertThat(key.hashCode(), is(0));
assertThat(key.get(), nullValue(ClassLoader.class));
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey other = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0]));
System.gc();
assertThat(other.get(), nullValue(ClassLoader.class));
assertThat(key, not(is(other)));
assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
assertThat(key, is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0])))));
assertThat(key, is(key));
assertThat(key, not(is(new Object())));
}
@Test
public void testStorageKeyNonBootstrapReference() throws Exception {
ClassLoader classLoader = new URLClassLoader(new URL[0]);
AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(classLoader);
assertThat(key.isBootstrapLoader(), is(false));
assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(classLoader)));
assertThat(key.hashCode(), is(classLoader.hashCode()));
assertThat(key.get(), is(classLoader));
classLoader = null; // Make GC eligible.
System.gc();
assertThat(key.get(), nullValue(ClassLoader.class));
assertThat(key, not(is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER))));
assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0])))));
assertThat(key, is(key));
assertThat(key, not(is(new Object())));
assertThat(key.isBootstrapLoader(), is(false));
}
@Test
public void testSchedulerNoOp() throws Exception {
Runnable runnable = mock(Runnable.class);
AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.INSTANCE.schedule(runnable);
verifyZeroInteractions(runnable);
assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.INSTANCE.isAlive(), is(false));
}
@Test
public void testSchedulerAtFixedRate() throws Exception {
ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
Runnable runnable = mock(Runnable.class);
new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate(scheduledExecutorService, 42L, TimeUnit.SECONDS).schedule(runnable);
verify(scheduledExecutorService).scheduleAtFixedRate(runnable, 42L, 42L, TimeUnit.SECONDS);
}
@Test
public void testSchedulerAtFixedRateIsAlive() throws Exception {
ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
assertThat(new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate(scheduledExecutorService, 42L, TimeUnit.SECONDS).isAlive(), is(true));
verify(scheduledExecutorService).isShutdown();
}
@Test
public void testSchedulerWithFixedDelay() throws Exception {
ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
assertThat(new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.WithFixedDelay(scheduledExecutorService, 42L, TimeUnit.SECONDS).isAlive(), is(true));
verify(scheduledExecutorService).isShutdown();
}
@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.class).apply();
ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.class).apply();
ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.class).apply();
ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate.class).apply();
ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.WithFixedDelay.class).apply();
}
private static class Foo {
/* empty */
}
}