diff options
Diffstat (limited to 'dexmaker-mockito-inline-tests/src')
5 files changed, 946 insertions, 0 deletions
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/CleanStackTrace.java b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/CleanStackTrace.java new file mode 100644 index 0000000..e90145f --- /dev/null +++ b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/CleanStackTrace.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.mockito.inline.tests; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CleanStackTrace { + public abstract static class TestAbstractClass { + public abstract String returnA(); + } + + public static class TestClass { + public final String returnA() { + return "A"; + } + } + + public interface TestInterface { + String returnA(); + } + + @Test + public void cleanStackTraceProxy() { + TestAbstractClass t = mock(TestAbstractClass.class); + + try { + verify(t).returnA(); + } catch (Throwable verifyLocation) { + try { + throw new Exception(); + } catch (Exception here) { + assertEquals(here.getStackTrace()[0].getMethodName(), verifyLocation + .getStackTrace()[0].getMethodName()); + } + } + } + + @Test + public void cleanStackTraceInline() { + TestClass t = mock(TestClass.class); + + try { + verify(t).returnA(); + } catch (Throwable verifyLocation) { + try { + throw new Exception(); + } catch (Exception here) { + assertEquals(here.getStackTrace()[0].getMethodName(), verifyLocation + .getStackTrace()[1].getMethodName()); + } + } + } + + @Test + public void cleanStackTraceInterface() { + TestInterface t = mock(TestInterface.class); + + try { + verify(t).returnA(); + } catch (Throwable verifyLocation) { + try { + throw new Exception(); + } catch (Exception here) { + assertEquals(here.getStackTrace()[0].getMethodName(), verifyLocation + .getStackTrace()[0].getMethodName()); + } + } + } +} diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java new file mode 100644 index 0000000..fa02471 --- /dev/null +++ b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.mockito.inline.tests; + +import android.content.Intent; +import android.os.IBinder; +import android.print.PrintAttributes; +import android.printservice.PrintService; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +public class MockFinal { + @Test + public void mockFinalJavaMethod() throws Exception { + ClassLoader fakeParent = mock(ClassLoader.class); + ClassLoader mockClassLoader = mock(ClassLoader.class); + + assertNull(mockClassLoader.getParent()); + + // ClassLoader#getParent is final + when(mockClassLoader.getParent()).thenReturn(fakeParent); + + assertSame(fakeParent, mockClassLoader.getParent()); + } + + @Test + public void mockFinalAndroidFrameworkClass() throws Exception { + // PrintAttributes is final + PrintAttributes mockAttributes = mock(PrintAttributes.class); + + assertEquals(0, mockAttributes.getColorMode()); + + when(mockAttributes.getColorMode()).thenReturn(42); + + assertEquals(42, mockAttributes.getColorMode()); + } + + @Test + public void mockFinalMethodOfAndroidFrameworkClass() throws Exception { + IBinder fakeBinder = mock(IBinder.class); + PrintService mockService = mock(PrintService.class); + + assertNull(mockService.onBind(new Intent())); + + // PrintService#onBind is final + when(mockService.onBind(any(Intent.class))).thenReturn(fakeBinder); + + assertSame(fakeBinder, mockService.onBind(new Intent())); + } + + private final class FinalNonDefaultConstructorClass { + public FinalNonDefaultConstructorClass(int i) { + } + + String returnA() { + return "A"; + } + } + + @Test + public void mockNonDefaultConstructorClass() throws Exception { + FinalNonDefaultConstructorClass mock = mock(FinalNonDefaultConstructorClass.class); + + assertNull(mock.returnA()); + when(mock.returnA()).thenReturn("fakeA"); + + assertEquals("fakeA", mock.returnA()); + } + + private interface NonDefaultConstructorInterface { + String returnA(); + } + + @Test + public void mockNonDefaultConstructorInterface() throws Exception { + NonDefaultConstructorInterface mock = mock(NonDefaultConstructorInterface.class); + + assertNull(mock.returnA()); + when(mock.returnA()).thenReturn("fakeA"); + + assertEquals("fakeA", mock.returnA()); + } + + private static class SuperClass { + final String returnA() { + return "superA"; + } + + String returnB() { + return "superB"; + } + + String returnC() { + return "superC"; + } + } + + private static final class SubClass extends SuperClass { + String returnC() { + return "subC"; + } + } + + @Test + public void mockSubClass() throws Exception { + SubClass mocked = mock(SubClass.class); + SuperClass mockedSuper = mock(SuperClass.class); + SubClass nonMocked = new SubClass(); + SuperClass nonMockedSuper = new SuperClass(); + + // Mock returns dummy value by default + assertNull(mocked.returnA()); + assertNull(mocked.returnB()); + assertNull(mocked.returnC()); + assertNull(mockedSuper.returnA()); + assertNull(mockedSuper.returnB()); + assertNull(mockedSuper.returnC()); + + // Set fake values for mockedSuper + when(mockedSuper.returnA()).thenReturn("fakeA"); + when(mockedSuper.returnB()).thenReturn("fakeB"); + when(mockedSuper.returnC()).thenReturn("fakeC"); + + // mocked is unaffected + assertNull(mocked.returnA()); + assertNull(mocked.returnB()); + assertNull(mocked.returnC()); + + // Verify fake values of mockedSuper + assertEquals("fakeA", mockedSuper.returnA()); + assertEquals("fakeB", mockedSuper.returnB()); + assertEquals("fakeC", mockedSuper.returnC()); + + // Set fake values for mocked + when(mocked.returnA()).thenReturn("fake2A"); + when(mocked.returnB()).thenReturn("fake2B"); + when(mocked.returnC()).thenReturn("fake2C"); + + // Verify fake values of mocked + assertEquals("fake2A", mocked.returnA()); + assertEquals("fake2B", mocked.returnB()); + assertEquals("fake2C", mocked.returnC()); + + // non mocked instances are unaffected + assertEquals("superA", nonMocked.returnA()); + assertEquals("superB", nonMocked.returnB()); + assertEquals("subC", nonMocked.returnC()); + assertEquals("superA", nonMockedSuper.returnA()); + assertEquals("superB", nonMockedSuper.returnB()); + assertEquals("superC", nonMockedSuper.returnC()); + } + + @Test + public void spySubClass() throws Exception { + SubClass spied = spy(SubClass.class); + SuperClass spiedSuper = spy(SuperClass.class); + SubClass nonSpied = new SubClass(); + SuperClass nonSpiedSuper = new SuperClass(); + + // Spies call real method by default + assertEquals("superA", spied.returnA()); + assertEquals("superB", spied.returnB()); + assertEquals("subC", spied.returnC()); + assertEquals("superA", spiedSuper.returnA()); + assertEquals("superB", spiedSuper.returnB()); + assertEquals("superC", spiedSuper.returnC()); + + // Set fake values for spiedSuper + when(spiedSuper.returnA()).thenReturn("fakeA"); + when(spiedSuper.returnB()).thenReturn("fakeB"); + when(spiedSuper.returnC()).thenReturn("fakeC"); + + // spied is unaffected + assertEquals("superA", spied.returnA()); + assertEquals("superB", spied.returnB()); + assertEquals("subC", spied.returnC()); + + // Verify fake values of spiedSuper + assertEquals("fakeA", spiedSuper.returnA()); + assertEquals("fakeB", spiedSuper.returnB()); + assertEquals("fakeC", spiedSuper.returnC()); + + // Set fake values for spied + when(spied.returnA()).thenReturn("fake2A"); + when(spied.returnB()).thenReturn("fake2B"); + when(spied.returnC()).thenReturn("fake2C"); + + // Verify fake values of spied + assertEquals("fake2A", spied.returnA()); + assertEquals("fake2B", spied.returnB()); + assertEquals("fake2C", spied.returnC()); + + // non spied instances are unaffected + assertEquals("superA", nonSpied.returnA()); + assertEquals("superB", nonSpied.returnB()); + assertEquals("subC", nonSpied.returnC()); + assertEquals("superA", nonSpiedSuper.returnA()); + assertEquals("superB", nonSpiedSuper.returnB()); + assertEquals("superC", nonSpiedSuper.returnC()); + } +} diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java new file mode 100644 index 0000000..aa828e5 --- /dev/null +++ b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.mockito.inline.tests; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class MockNonPublic { + private interface SingleMethodInterface { + String returnA(); + } + + private static <T extends Class> void mockSingleMethod(T clazz) { + SingleMethodInterface c = (SingleMethodInterface) mock(clazz); + assertNull(c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + } + + private static <T extends Class> void spySingleMethod(T clazz) { + SingleMethodInterface c = (SingleMethodInterface) spy(clazz); + assertEquals("A", c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + } + + private static <T extends SingleMethodInterface> void spyWrappedSingleMethod(T original) { + T c = spy(original); + assertEquals("A", c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + + // original is unaffected + assertEquals("A", original.returnA()); + } + + private interface DualMethodInterface { + String returnA(); + String returnB(); + } + + private static <T extends Class> void mockDualMethod(T clazz) { + DualMethodInterface c = (DualMethodInterface) mock(clazz); + assertNull(c.returnA()); + assertNull(c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertNull(c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + } + + private static <T extends Class> void spyDualMethod(T clazz) { + DualMethodInterface c = (DualMethodInterface) spy(clazz); + assertEquals("A", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + } + + private static <T extends DualMethodInterface> void spyWrappedDualMethod(T original) { + T c = spy(original); + assertEquals("A", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + + // original is unaffected + assertEquals("A", original.returnA()); + assertEquals("B", original.returnB()); + } + + private static class PrivateClass implements SingleMethodInterface { + public String returnA() { + return "A"; + } + } + + @Test + public void mockPrivateClass() { + mockSingleMethod(PrivateClass.class); + } + + @Test + public void spyPrivateClass() { + spySingleMethod(PrivateClass.class); + } + + @Test + public void spyWrappedPrivateClass() { + spyWrappedSingleMethod(new PrivateClass()); + } + + private interface PrivateInterface extends SingleMethodInterface { + String returnA(); + } + + @Test + public void mockPrivateInterface() { + mockSingleMethod(PrivateInterface.class); + } + + private static class SubOfPrivateInterface implements PrivateInterface { + public String returnA() { + return "A"; + } + } + + @Test + public void mockSubOfPrivateInterface() { + mockSingleMethod(SubOfPrivateInterface.class); + } + + @Test + public void spySubOfPrivateInterface() { + spySingleMethod(SubOfPrivateInterface.class); + } + + @Test + public void spyWrappedSubOfPrivateInterface() { + spyWrappedSingleMethod(new SubOfPrivateInterface()); + } + + private static abstract class PrivateAbstractClass implements DualMethodInterface { + public String returnA() { + return "A"; + } + + public abstract String returnB(); + } + + @Test + public void mockPrivateAbstractClass() { + mockDualMethod(PrivateAbstractClass.class); + } + + private static class SubOfPrivateAbstractClass extends PrivateAbstractClass { + public String returnB() { + return "B"; + } + } + + @Test + public void mockSubOfPrivateAbstractClass() { + mockDualMethod(SubOfPrivateAbstractClass.class); + } + + @Test + public void spySubOfPrivateAbstractClass() { + spyDualMethod(SubOfPrivateAbstractClass.class); + } + + @Test + public void spyWrappedSubOfPrivateAbstractClass() { + spyWrappedDualMethod(new SubOfPrivateAbstractClass()); + } + + static class PackagePrivateClass implements SingleMethodInterface { + public String returnA() { + return "A"; + } + } + + @Test + public void mockPackagePrivateClass() { + mockSingleMethod(PackagePrivateClass.class); + } + + static abstract class PackagePrivateAbstractClass implements DualMethodInterface { + public String returnA() { + return "A"; + } + + public abstract String returnB(); + } + + @Test + public void mockPackagePrivateAbstractClass() { + mockDualMethod(PackagePrivateAbstractClass.class); + } + + static class SubOfPackagePrivateAbstractClass extends PackagePrivateAbstractClass { + public String returnB() { + return "B"; + } + } + + @Test + public void mockSubOfPackagePrivateAbstractClass() { + mockDualMethod(SubOfPackagePrivateAbstractClass.class); + } + + @Test + public void spySubOfPackagePrivateAbstractClass() { + spyDualMethod(SubOfPackagePrivateAbstractClass.class); + } + + @Test + public void spyWrappedSubOfPackagePrivateAbstractClass() { + spyWrappedDualMethod(new SubOfPackagePrivateAbstractClass()); + } + + interface PackagePrivateInterface extends SingleMethodInterface { + String returnA(); + } + + @Test + public void mockPackagePrivateInterface() { + mockSingleMethod(PackagePrivateInterface.class); + } + + static class SubOfPackagePrivateInterface implements PackagePrivateInterface { + public String returnA() { + return "A"; + } + } + + @Test + public void mockSubOfPackagePrivateInterface() { + mockSingleMethod(SubOfPackagePrivateInterface.class); + } + + @Test + public void spySubOfPackagePrivateInterface() { + spySingleMethod(SubOfPackagePrivateInterface.class); + } + + @Test + public void spyWrappedSubOfPackagePrivateInterface() { + spyWrappedSingleMethod(new SubOfPackagePrivateInterface()); + } + + // Cannot implement SingleMethodInterface as returnA would have to be public + public static class ClassWithPackagePrivateMethod { + String returnA() { + return "A"; + } + } + + @Test + public void mockClassWithPackagePrivateMethod() { + ClassWithPackagePrivateMethod c = mock(ClassWithPackagePrivateMethod.class); + assertNull(c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + } + + @Test + public void spyClassWithPackagePrivateMethod() { + ClassWithPackagePrivateMethod c = spy(ClassWithPackagePrivateMethod.class); + assertEquals("A", c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + } + + @Test + public void spyWrappedClassWithPackagePrivateMethod() { + ClassWithPackagePrivateMethod original = new ClassWithPackagePrivateMethod(); + ClassWithPackagePrivateMethod c = spy(original); + assertEquals("A", c.returnA()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + + // original is unaffected + assertEquals("A", original.returnA()); + } + + // Cannot implement DualMethodInterface as returnA/returnB would have to be public + public static abstract class AbstractClassWithPackagePrivateMethod { + String returnA() { + return "A"; + } + + abstract String returnB(); + } + + @Test + public void mockAbstractClassWithPackagePrivateMethod() { + AbstractClassWithPackagePrivateMethod c = mock(AbstractClassWithPackagePrivateMethod.class); + assertNull(c.returnA()); + assertNull(c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertNull(c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + } + + public static class SubOfAbstractClassWithPackagePrivateMethod extends + AbstractClassWithPackagePrivateMethod { + String returnB() { + return "B"; + } + } + + @Test + public void mockSubOfAbstractClassWithPackagePrivateMethod() { + SubOfAbstractClassWithPackagePrivateMethod c = mock + (SubOfAbstractClassWithPackagePrivateMethod.class); + assertNull(c.returnA()); + assertNull(c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertNull(c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + } + + @Test + public void spySubOfAbstractClassWithPackagePrivateMethod() { + SubOfAbstractClassWithPackagePrivateMethod c = spy + (SubOfAbstractClassWithPackagePrivateMethod.class); + assertEquals("A", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + } + + @Test + public void spyWrappedSubOfAbstractClassWithPackagePrivateMethod() { + SubOfAbstractClassWithPackagePrivateMethod original = new + SubOfAbstractClassWithPackagePrivateMethod(); + SubOfAbstractClassWithPackagePrivateMethod c = spy(original); + assertEquals("A", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnA()).thenReturn("fakeA"); + assertEquals("fakeA", c.returnA()); + assertEquals("B", c.returnB()); + + when(c.returnB()).thenReturn("fakeB"); + assertEquals("fakeA", c.returnA()); + assertEquals("fakeB", c.returnB()); + + // original is unaffected + assertEquals("A", original.returnA()); + assertEquals("B", original.returnB()); + } +} diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java new file mode 100644 index 0000000..d66b128 --- /dev/null +++ b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.mockito.inline.tests; + +import android.os.Build; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import dalvik.system.BaseDexClassLoader; + +import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; + +public class MultipleJvmtiAgentsInterference { + private static final String AGENT_LIB_NAME = "multiplejvmtiagentsinterferenceagent"; + + public class TestClass { + public String returnA() { + return "A"; + } + } + + @BeforeClass + public static void installTestAgent() throws Exception { + // TODO (moltmann@google.com): Replace with proper check for >= P + assumeTrue(Build.VERSION.CODENAME.equals("P")); + + // Currently Debug.attachJvmtiAgent requires a file in the right directory + File copiedAgent = File.createTempFile("testagent", ".so"); + copiedAgent.deleteOnExit(); + + try (InputStream is = new FileInputStream(((BaseDexClassLoader) + MultipleJvmtiAgentsInterference.class.getClassLoader()).findLibrary + (AGENT_LIB_NAME))) { + try (OutputStream os = new FileOutputStream(copiedAgent)) { + byte[] buffer = new byte[64 * 1024]; + + while (true) { + int numRead = is.read(buffer); + if (numRead == -1) { + break; + } + os.write(buffer, 0, numRead); + } + } + } + + // TODO (moltmann@google.com): Replace with regular method call once the API becomes public + Class.forName("android.os.Debug").getMethod("attachJvmtiAgent", String.class, String + .class).invoke(null, copiedAgent.getAbsolutePath(), null); + } + + @Test + public void otherAgentTransformsWhileMocking() { + TestClass t = mock(TestClass.class); + + assertNull(t.returnA()); + + // Unrelated class re-transform does not affect mocking + nativeRetransformClasses(new Class<?>[]{MultipleJvmtiAgentsInterference.class}); + assertNull(t.returnA()); + + // Re-transform of classes that are mocked does not affect mocking + nativeRetransformClasses(new Class<?>[]{TestClass.class}); + assertNull(t.returnA()); + } + + private native int nativeRetransformClasses(Class<?>[] classes); +} diff --git a/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc b/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc new file mode 100644 index 0000000..a293fe7 --- /dev/null +++ b/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <jni.h> + +#include <cstring> +#include <cstdlib> +#include <sstream> + +#include "jvmti.h" + +#include <dex_ir.h> +#include <writer.h> +#include <reader.h> + +using namespace dex; + +namespace com_android_dx_mockito_inline_tests { + static jvmtiEnv *localJvmtiEnv; + + // Converts a class name to a type descriptor + // (ex. "java.lang.String" to "Ljava/lang/String;") + static std::string + ClassNameToDescriptor(const char* class_name) { + std::stringstream ss; + ss << "L"; + for (auto p = class_name; *p != '\0'; ++p) { + ss << (*p == '.' ? '/' : *p); + } + ss << ";"; + return ss.str(); + } + + static void + Transform(jvmtiEnv *jvmti_env, + JNIEnv *env, + jclass classBeingRedefined, + jobject loader, + const char *name, + jobject protectionDomain, + jint classDataLen, + const unsigned char *classData, + jint *newClassDataLen, + unsigned char **newClassData) { + // Isolate byte code of class class. This is needed as Android usually gives us more + // than the class we need. + // Then just return the isolated byte code without modification. + Reader reader(classData, (size_t) classDataLen); + + u4 index = reader.FindClassIndex(ClassNameToDescriptor(name).c_str()); + reader.CreateClassIr(index); + std::shared_ptr<ir::DexFile> ir = reader.GetIr(); + + class Allocator : public Writer::Allocator { + jvmtiEnv *jvmti_env; + + public: + Allocator(jvmtiEnv *jvmti_env) : Writer::Allocator(), jvmti_env(jvmti_env) { + } + + virtual void *Allocate(size_t size) { + unsigned char *mem; + jvmti_env->Allocate(size, &mem); + return mem; + } + + virtual void Free(void *ptr) { ::free(ptr); } + }; + + Allocator allocator(jvmti_env); + Writer writer(ir); + size_t newClassLen; + *newClassData = writer.CreateImage(&allocator, &newClassLen); + *newClassDataLen = (jint) newClassLen; + } + + // Initializes the agent + extern "C" jint Agent_OnAttach(JavaVM *vm, + char *options, + void *reserved) { + jint jvmError = vm->GetEnv(reinterpret_cast<void **>(&localJvmtiEnv), JVMTI_VERSION_1_2); + if (jvmError != JNI_OK) { + return jvmError; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_retransform_classes = 1; + + jvmtiError error = localJvmtiEnv->AddCapabilities(&caps); + if (error != JVMTI_ERROR_NONE) { + return error; + } + + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = Transform; + + error = localJvmtiEnv->SetEventCallbacks(&cb, sizeof(cb)); + if (error != JVMTI_ERROR_NONE) { + return error; + } + + error = localJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + NULL); + if (error != JVMTI_ERROR_NONE) { + return error; + } + + return JVMTI_ERROR_NONE; + } + + + // Triggers retransformation of classes via this file's Transform method + extern "C" JNIEXPORT jint JNICALL + Java_com_android_dx_mockito_inline_tests_MultipleJvmtiAgentsInterference_nativeRetransformClasses( + JNIEnv *env, + jobject thiz, + jobjectArray classes) { + jsize numTransformedClasses = env->GetArrayLength(classes); + jclass *transformedClasses = (jclass *) malloc(numTransformedClasses * sizeof(jclass)); + for (int i = 0; i < numTransformedClasses; i++) { + transformedClasses[i] = (jclass) env->NewGlobalRef(env->GetObjectArrayElement(classes, + i)); + } + + jvmtiError error = localJvmtiEnv->RetransformClasses(numTransformedClasses, + transformedClasses); + + for (int i = 0; i < numTransformedClasses; i++) { + env->DeleteGlobalRef(transformedClasses[i]); + } + free(transformedClasses); + + return error; + } +}
\ No newline at end of file |