/*
* Copyright (C) 2014 RoboVM AB
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
*/
package org.robovm.compiler.plugin.objc;
import static org.junit.Assert.*;
import static org.robovm.compiler.Annotations.*;
import static org.robovm.compiler.plugin.objc.ObjCBlockPlugin.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.robovm.compiler.CompilerException;
import org.robovm.compiler.Types;
import org.robovm.compiler.util.generic.SootClassType;
import org.robovm.compiler.util.generic.SootMethodType;
import org.robovm.compiler.util.generic.SootTypeType;
import org.robovm.compiler.util.generic.Type;
import soot.BooleanType;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.SootResolver;
import soot.VoidType;
import soot.options.Options;
import soot.tagkit.SignatureTag;
/**
* Tests {@link ObjCBlockPlugin}.
*/
public class ObjCBlockPluginTest {
private Type BOOLEAN, VOID;
@BeforeClass
public static void initializeSoot() throws IOException {
soot.G.reset();
Options.v().set_output_format(Options.output_format_jimple);
Options.v().set_include_all(true);
Options.v().set_print_tags_in_output(true);
Options.v().set_allow_phantom_refs(true);
Options.v().set_soot_classpath(System.getProperty("sun.boot.class.path") +
":" + System.getProperty("java.class.path"));
Scene.v().loadNecessaryClasses();
}
@Before
public void setup() {
BOOLEAN = new SootTypeType(BooleanType.v());
VOID = new SootTypeType(VoidType.v());
}
public static interface F<R, A extends Number, B extends Number> {
R run(A a, B b, boolean c);
}
public static interface G extends F<BigDecimal, Double, Double> {
}
public static interface H extends F<String, Integer, Integer> {
void foo();
}
public static interface I {
}
public static interface J<T> extends F<Comparable<T>, Integer, Integer> {
}
public static class Runners<T> {
public native void runner1(F<String, Integer, Integer> f);
public native void runner2(G g);
public native void runner3(F<?, ?, ? extends Double> f);
public native void runner4(F<Comparable<String>, ?, ?> f);
public native void runner5(J<String> f);
public native void runner6(F<Comparable<String>[][], ?, ?> f);
public native void runner7(F<T, Integer, Integer> f);
public native <U extends Number> void runner8(F<Object, Integer, U> f);
public native void runner9(Runnable r);
public native void runner10(@SuppressWarnings("rawtypes") F f);
public native void runner11(H h);
public native void runner12(I i);
}
private SootClass toSootClass(Class<?> cls) {
return SootResolver.v().resolveClass(cls.getName(), SootClass.SIGNATURES);
}
private Type signatureToType(String desc) {
String rawDesc = desc.replaceAll("<.*>", "");
String internalName = rawDesc.replaceAll("^\\[*", "");
int dims = rawDesc.length() - internalName.length();
internalName = Types.getInternalNameFromDescriptor(internalName);
soot.Type sootType = SootResolver.v().makeClassRef(internalName.replace('/', '.')).getType();
for (int i = 0; i < dims; i++) {
sootType = sootType.makeArrayType();
}
SootMethod m = new SootMethod("foo", Arrays.asList(sootType), VoidType.v());
m.addTag(new SignatureTag("(" + desc + ")V"));
SootMethodType mType = new SootMethodType(m);
return mType.getGenericParameterTypes()[0];
}
private Type classNameToType(String name) {
return new SootClassType(SootResolver.v().makeClassRef(name));
}
@Test
public void testGetTargetBlockMethodDirect1() throws Exception {
assertEquals(toSootClass(F.class).getMethodByName("run"),
ObjCBlockPlugin.getBlockTargetMethod(
toSootClass(Runners.class).getMethodByName("runner1"), 0));
}
@Test
public void testGetTargetBlockMethodDirect2() throws Exception {
assertEquals(toSootClass(Runnable.class).getMethodByName("run"),
ObjCBlockPlugin.getBlockTargetMethod(
toSootClass(Runners.class).getMethodByName("runner9"), 0));
}
@Test
public void testGetTargetBlockMethodInSuperInterface() throws Exception {
assertEquals(toSootClass(F.class).getMethodByName("run"),
ObjCBlockPlugin.getBlockTargetMethod(
toSootClass(Runners.class).getMethodByName("runner2"), 0));
}
@Test(expected = CompilerException.class)
public void testGetTargetBlockMethodTooManyMethods() throws Exception {
ObjCBlockPlugin.getBlockTargetMethod(toSootClass(Runners.class).getMethodByName("runner11"), 0);
}
@Test(expected = CompilerException.class)
public void testGetTargetBlockMethodNoMethods() throws Exception {
ObjCBlockPlugin.getBlockTargetMethod(toSootClass(Runners.class).getMethodByName("runner12"), 0);
}
private void testResolveTargetMethodSignature(String runnerMethodName,
Type expectedReturnType, Type ... expectedParamTypes) {
SootMethod m = toSootClass(Runners.class).getMethodByName(runnerMethodName);
SootMethodType mType = new SootMethodType(m);
SootMethod target = ObjCBlockPlugin.getBlockTargetMethod(m, 0);
Type[] types = ObjCBlockPlugin.resolveTargetMethodSignature(m, target, mType.getGenericParameterTypes()[0]);
assertEquals(target.getParameterCount() + 1, types.length);
assertEquals(expectedReturnType, types[0]);
for (int i = 0; i < types.length - 1; i++) {
assertEquals(expectedParamTypes[i], types[i + 1]);
}
}
@Test
public void testResolveTargetMethodSignatureDirectGeneric() throws Exception {
testResolveTargetMethodSignature("runner1", classNameToType("java.lang.String"),
classNameToType("java.lang.Integer"), classNameToType("java.lang.Integer"),
BOOLEAN);
}
@Test
public void testResolveTargetMethodSignatureIndirectGeneric() throws Exception {
testResolveTargetMethodSignature("runner2", classNameToType("java.math.BigDecimal"),
classNameToType("java.lang.Double"), classNameToType("java.lang.Double"),
BOOLEAN);
}
@Test
public void testResolveTargetMethodSignatureGenericWithWildcards() throws Exception {
testResolveTargetMethodSignature("runner3", classNameToType("java.lang.Object"),
classNameToType("java.lang.Number"), classNameToType("java.lang.Double"),
BOOLEAN);
}
@Test
public void testResolveTargetMethodSignatureGenericWithDirectParameterizedType() throws Exception {
testResolveTargetMethodSignature("runner4", signatureToType("Ljava/lang/Comparable<Ljava/lang/String;>;"),
classNameToType("java.lang.Number"), classNameToType("java.lang.Number"),
BOOLEAN);
}
@Test
public void testResolveTargetMethodSignatureGenericWithIndirectParameterizedType() throws Exception {
testResolveTargetMethodSignature("runner5", signatureToType("Ljava/lang/Comparable<Ljava/lang/String;>;"),
classNameToType("java.lang.Integer"), classNameToType("java.lang.Integer"),
BOOLEAN);
}
@Test
public void testResolveTargetMethodSignatureGenericWithGenericArrayType() throws Exception {
testResolveTargetMethodSignature("runner6", signatureToType("[[Ljava/lang/Comparable<Ljava/lang/String;>;"),
classNameToType("java.lang.Number"), classNameToType("java.lang.Number"),
BOOLEAN);
}
@Test(expected = CompilerException.class)
public void testResolveTargetMethodSignatureGenericWithUnresolvedIndirectTypeVariable() throws Exception {
SootMethod target = toSootClass(F.class).getMethodByName("run");
SootMethod m = toSootClass(Runners.class).getMethodByName("runner7");
SootMethodType mType = new SootMethodType(m);
ObjCBlockPlugin.resolveTargetMethodSignature(m, target, mType.getGenericParameterTypes()[0]);
}
@Test(expected = CompilerException.class)
public void testResolveTargetMethodSignatureGenericWithUnresolvedDirectTypeVariable() throws Exception {
SootMethod target = toSootClass(F.class).getMethodByName("run");
SootMethod m = toSootClass(Runners.class).getMethodByName("runner8");
SootMethodType mType = new SootMethodType(m);
ObjCBlockPlugin.resolveTargetMethodSignature(m, target, mType.getGenericParameterTypes()[0]);
}
@Test
public void testResolveTargetMethodSignatureNonGeneric() throws Exception {
testResolveTargetMethodSignature("runner9", VOID);
}
@Test
public void testResolveTargetMethodSignatureRawType() throws Exception {
testResolveTargetMethodSignature("runner10", classNameToType("java.lang.Object"),
classNameToType("java.lang.Number"), classNameToType("java.lang.Number"),
BOOLEAN);
}
@Test
public void testParseTargetMethodAnnotations() throws Exception {
SootMethod m = toSootClass(Runners.class).getMethodByName("runner1");
assertArrayEquals(new String[][] {{}}, ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, ""));
assertArrayEquals(new String[][] {{}}, ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, " "));
assertArrayEquals(new String[][] {{}}, ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "()"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "@Pointer@ByVal"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "@Pointer @ByVal ()"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}, {BY_REF}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 1, "@Pointer @ByVal (@ByRef)"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}, {BY_REF, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 1, "@Pointer @ByVal ( @ByRef @Pointer )"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}, {BY_REF, POINTER}, {MACHINE_SIZED_S_INT}, {}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 3, "@Pointer @ByVal ( @ByRef @Pointer , @MachineSizedSInt , )"));
assertArrayEquals(new String[][] {{BY_VAL, POINTER}, {}, {BY_REF, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 2, "@Pointer @ByVal(,@ByRef @Pointer)"));
assertArrayEquals(new String[][] {{}, {BY_REF, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 1, "(@ByRef @Pointer) "));
assertArrayEquals(new String[][] {{}, {BLOCK, BY_REF, BY_VAL, MACHINE_SIZED_FLOAT,
MACHINE_SIZED_S_INT, MACHINE_SIZED_U_INT, POINTER}},
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 1,
"(@ByRef @ByVal @Pointer @MachineSizedFloat @MachineSizedSInt "
+ "@MachineSizedUInt @Block) "));
}
@Test(expected = CompilerException.class)
public void testParseTargetMethodAnnotationsInvalid1() throws Exception {
SootMethod m = toSootClass(Runners.class).getMethodByName("runner1");
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "(");
}
@Test(expected = CompilerException.class)
public void testParseTargetMethodAnnotationsInvalid2() throws Exception {
SootMethod m = toSootClass(Runners.class).getMethodByName("runner1");
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "@Yada");
}
@Test(expected = CompilerException.class)
public void testParseTargetMethodAnnotationsInvalid3() throws Exception {
SootMethod m = toSootClass(Runners.class).getMethodByName("runner1");
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "garbage");
}
@Test(expected = CompilerException.class)
public void testParseTargetMethodAnnotationsInvalid4() throws Exception {
SootMethod m = toSootClass(Runners.class).getMethodByName("runner1");
ObjCBlockPlugin.parseTargetMethodAnnotations(m, 0, "@ByVal(");
}
}