package net.bytebuddy.build.gradle; import net.bytebuddy.ByteBuddy; import net.bytebuddy.build.EntryPoint; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.test.*; import net.bytebuddy.test.utility.MockitoRule; import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.Logger; import org.gradle.api.tasks.compile.AbstractCompile; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import static junit.framework.TestCase.fail; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class TransformationActionTest { private static final String FOO = "foo", BAR = "bar", QUX = "qux", TEMP = ".tmp"; @Rule public TestRule mockitoRule = new MockitoRule(this); @Mock private Project project; @Mock private Logger logger; @Mock private ByteBuddyExtension byteBuddyExtension; @Mock private AbstractCompile parent; @Mock private Task task; @Mock private Transformation transformation; @Mock private Initialization initialization; @Mock private FileCollection fileCollection; private File target; private TransformationAction transformationAction; @Before @SuppressWarnings("unchecked") public void setUp() throws Exception { target = File.createTempFile(FOO, TEMP); assertThat(target.delete(), is(true)); assertThat(target.mkdir(), is(true)); when(project.getLogger()).thenReturn(logger); when(byteBuddyExtension.getTransformations()).thenReturn(Collections.singletonList(transformation)); when(byteBuddyExtension.getInitialization()).thenReturn(initialization); when(parent.getDestinationDir()).thenReturn(target); when(transformation.getClassPath(any(File.class), any(Iterable.class))).thenReturn((Iterable) Collections.emptySet()); when(parent.getClasspath()).thenReturn(fileCollection); when(fileCollection.iterator()).then(new Answer<Iterator<File>>() { @Override public Iterator<File> answer(InvocationOnMock invocationOnMock) throws Throwable { return Collections.singleton(target).iterator(); } }); when(byteBuddyExtension.getMethodNameTransformer()).thenReturn(MethodNameTransformer.Suffixing.withRandomSuffix()); transformationAction = new TransformationAction(project, byteBuddyExtension, parent); } @After public void tearDown() throws Exception { assertThat(target.delete(), is(true)); } @Test @SuppressWarnings("unchecked") public void testSimpleTransformation() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); files.addAll(addClass("foo.Qux")); try { when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR); assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO); assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test @SuppressWarnings("unchecked") public void testLiveInitializer() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); try { when(transformation.getPlugin()).thenReturn(LiveInitializerPlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); try { assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); fail(); } catch (InvocationTargetException exception) { assertThat(exception.getCause(), instanceOf(NullPointerException.class)); } } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test @SuppressWarnings("unchecked") public void testLiveInitializerAllowed() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); try { when(transformation.getPlugin()).thenReturn(LiveInitializerPlugin.class.getName()); when(byteBuddyExtension.isFailOnLiveInitializer()).thenReturn(false); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); try { assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); fail(); } catch (InvocationTargetException exception) { assertThat(exception.getCause(), instanceOf(NullPointerException.class)); } } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test(expected = GradleException.class) @SuppressWarnings("unchecked") public void testIllegalTransformer() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); try { when(transformation.getPlugin()).thenReturn(IllegalTransformPlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE); transformationAction.execute(task); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test(expected = GradleException.class) @SuppressWarnings("unchecked") public void testIllegalTransformation() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); try { when(transformation.getPlugin()).thenReturn(IllegalPlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE); transformationAction.execute(task); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test @SuppressWarnings("unchecked") public void testSimpleEntry() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); files.addAll(addClass("foo.Qux")); try { when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new SimpleEntryPoint()); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR); assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO); assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test(expected = GradleException.class) @SuppressWarnings("unchecked") public void testIllegalByteBuddy() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); files.addAll(addClass("foo.Qux")); try { when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new IllegalEntryPoint()); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR); assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO); assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test(expected = GradleException.class) @SuppressWarnings("unchecked") public void testIllegalTransform() throws Exception { Set<File> files = new HashSet<File>(); files.addAll(addClass("foo.Bar")); files.addAll(addClass("foo.Qux")); try { when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName()); when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new IllegalTransformEntryPoint()); transformationAction.execute(task); ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()}); assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX); assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR); assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO); assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR); } finally { for (File file : files) { assertThat(file.delete(), is(true)); } assertThat(new File(target, FOO).delete(), is(true)); } } @Test(expected = GradleException.class) public void testNoDirectory() throws Exception { when(parent.getDestinationDir()).thenReturn(mock(File.class)); transformationAction.execute(task); } private void assertMethod(Class<?> type, String name, Object expected) throws Exception { assertThat(type.getDeclaredMethod(name).invoke(type.getDeclaredConstructor().newInstance()), is(expected)); } private Collection<File> addClass(String name) throws IOException { return new ByteBuddy() .subclass(Object.class) .name(name) .defineMethod(FOO, String.class, Visibility.PUBLIC).intercept(FixedValue.value(FOO)) .defineMethod(BAR, String.class, Visibility.PUBLIC).intercept(FixedValue.value(BAR)) .make() .saveIn(target) .values(); } }