Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a9de835c authored by Deepanshu Gupta's avatar Deepanshu Gupta
Browse files

Add StubMethodAdapterTest

This tests the bugfix in StubMethodAdapter made in the change with id
I098996e43e330e995d33f12df1c16355bbc02f0f (commit 491523d5)

Change-Id: I1ac897a49071dd9558bdc6b8abec29df913a6047
parent c14893bf
Loading
Loading
Loading
Loading
+5 −7
Original line number Diff line number Diff line
@@ -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));
+132 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+30 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}