diff options
3 files changed, 167 insertions, 7 deletions
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 0912fb1e105c..afaa3997adf7 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -53,12 +53,10 @@ public class DelegateClassAdapterTest { private MockLog mLog; - private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName(); - private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); - private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + - InnerClass.class.getSimpleName(); - private static final String STATIC_INNER_CLASS_NAME = - OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName(); + private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getName(); + private static final String OUTER_CLASS_NAME = OuterClass.class.getName(); + private static final String INNER_CLASS_NAME = InnerClass.class.getName(); + private static final String STATIC_INNER_CLASS_NAME = StaticInnerClass.class.getName(); @Before public void setUp() throws Exception { @@ -69,12 +67,12 @@ public class DelegateClassAdapterTest { /** * Tests that a class not being modified still works. */ - @SuppressWarnings("unchecked") @Test public void testNoOp() throws Throwable { // create an instance of the class that will be modified // (load the class in a distinct class loader so that we can trash its definition later) ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { }; + @SuppressWarnings("unchecked") Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME); ClassWithNative instance1 = clazz1.newInstance(); assertEquals(42, instance1.add(20, 22)); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java new file mode 100644 index 000000000000..3db3e2364eec --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 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.tools.layoutlib.create; + +import com.android.tools.layoutlib.create.dataclass.StubClass; + +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.util.function.BiPredicate; +import java.util.function.Consumer; + +import static org.junit.Assert.*; + +public class StubMethodAdapterTest { + + private static final String STUB_CLASS_NAME = StubClass.class.getName(); + + /** + * Load a dummy class, stub one of its method and ensure that the modified class works as + * intended. + */ + @Test + public void testBoolean() throws Exception { + final String methodName = "returnTrue"; + // First don't change the method and assert that it returns true + testBoolean((name, type) -> false, Assert::assertTrue, methodName); + // Change the method now and assert that it returns false. + testBoolean((name, type) -> methodName.equals(name) && + Type.BOOLEAN_TYPE.equals(type.getReturnType()), Assert::assertFalse, methodName); + } + + /** + * @param methodPredicate tests if the method should be replaced + */ + private void testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion, + String methodName) throws Exception { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + // Always rename the class to avoid conflict with the original class. + String newClassName = STUB_CLASS_NAME + '_'; + new ClassReader(STUB_CLASS_NAME).accept( + new ClassAdapter(newClassName, writer, methodPredicate), 0); + MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray()); + Class<?> aClass = myClassLoader.loadClass(newClassName); + assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.", + myClassLoader.findClassCalled); + Method method = aClass.getMethod(methodName); + Object o = aClass.newInstance(); + assertion.accept((Boolean) method.invoke(o)); + } + + private static class ClassAdapter extends ClassVisitor { + + private final String mClassName; + private final BiPredicate<String, Type> mMethodPredicate; + + private ClassAdapter(String className, ClassVisitor cv, + BiPredicate<String, Type> methodPredicate) { + super(Main.ASM_VERSION, cv); + mClassName = className.replace('.', '/'); + mMethodPredicate = methodPredicate; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, mClassName, signature, superName, + interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + // Copied partly from + // com.android.tools.layoutlib.create.DelegateClassAdapter.visitMethod() + // but not generating the _Original method. + boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; + MethodVisitor originalMethod = + super.visitMethod(access, name, desc, signature, exceptions); + Type descriptor = Type.getMethodType(desc); + if (mMethodPredicate.test(name, descriptor)) { + String methodSignature = mClassName + "#" + name; + String invokeSignature = methodSignature + desc; + return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(), + invokeSignature, isStatic, isNative); + } + return originalMethod; + } + } + + private static class MyClassLoader extends ClassLoader { + private final String mName; + private final byte[] mBytes; + private boolean findClassCalled; + + private MyClassLoader(String name, byte[] bytes) { + mName = name; + mBytes = bytes; + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals(mName)) { + findClassCalled = true; + return defineClass(name, mBytes, 0, mBytes.length); + } + return super.findClass(name); + } + } +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java new file mode 100644 index 000000000000..3ae8e47de6e3 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.StubMethodAdapterTest; + +/** + * Used by {@link StubMethodAdapterTest} + */ +@SuppressWarnings("unused") +public class StubClass { + + public boolean returnTrue() { + return true; + } +} |