diff --git a/src/main/java/com/alibaba/testable/generator/TestSetupMethodGenerator.java b/src/main/java/com/alibaba/testable/generator/TestSetupMethodGenerator.java new file mode 100644 index 0000000..d3f6560 --- /dev/null +++ b/src/main/java/com/alibaba/testable/generator/TestSetupMethodGenerator.java @@ -0,0 +1,112 @@ +package com.alibaba.testable.generator; + +import com.alibaba.testable.model.TestLibType; +import com.alibaba.testable.model.TestableContext; +import com.alibaba.testable.util.ConstPool; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Pair; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Generate test class setup method definition + * + * @author flin + */ +public class TestSetupMethodGenerator { + + private static final String TYPE_CLASS = "Class"; + private final TestableContext cx; + + /** + * MethodName -> (ResultType -> ParameterTypes) + */ + public ListBuffer>>> injectMethods = new ListBuffer<>(); + public String testSetupMethodName = ""; + public TestLibType testLibType = TestLibType.JUnit4; + public final ListBuffer memberMethods = new ListBuffer<>(); + + public TestSetupMethodGenerator(TestableContext cx) { + this.cx = cx; + } + + public JCMethodDecl fetch() { + JCModifiers mods = cx.treeMaker.Modifiers(Modifier.PUBLIC, makeAnnotations(ConstPool.ANNOTATION_JUNIT5_SETUP)); + return cx.treeMaker.MethodDef(mods, cx.names.fromString("testableSetup"), + cx.treeMaker.Type(new Type.JCVoidType()), List.nil(), + List.nil(), List.nil(), testableSetupBlock(), null); + } + + private List makeAnnotations(String fullAnnotationName) { + JCExpression setupAnnotation = nameToExpression(fullAnnotationName); + return List.of(cx.treeMaker.Annotation(setupAnnotation, List.nil())); + } + + private JCBlock testableSetupBlock() { + ListBuffer statements = new ListBuffer<>(); + for (Pair>> m : injectMethods.toList()) { + if (isMemberMethod(m)) { + statements.append(addToPoolStatement(m, ConstPool.NE_ADD_F)); + } else { + statements.append(addToPoolStatement(m, ConstPool.NE_ADD_W)); + } + } + if (!testSetupMethodName.isEmpty()) { + statements.append(cx.treeMaker.Exec(cx.treeMaker.Apply(List.nil(), + nameToExpression(testSetupMethodName), List.nil()))); + } + return cx.treeMaker.Block(0, statements.toList()); + } + + private JCExpression nameToExpression(String dotName) { + String[] nameParts = dotName.split("\\."); + JCExpression e = cx.treeMaker.Ident(cx.names.fromString(nameParts[0])); + for (int i = 1; i < nameParts.length; i++) { + e = cx.treeMaker.Select(e, cx.names.fromString(nameParts[i])); + } + return e; + } + + private boolean isMemberMethod(Pair>> m) { + for (Method method : memberMethods) { + if (method.getName().equals(m.fst.toString()) && parameterEquals(m.snd.snd, method.getParameterTypes())) { + return true; + } + } + return false; + } + + private boolean parameterEquals(List injectMethodArgs, Class[] memberMethodArgs) { + if (injectMethodArgs.length() != memberMethodArgs.length) { + return false; + } + for (int i = 0; i < injectMethodArgs.length(); i++) { + if (!memberMethodArgs[i].getName().equals(((JCFieldAccess)injectMethodArgs.get(i)).selected.type + .toString())) { + return false; + } + } + return true; + } + + private JCStatement addToPoolStatement(Pair>> m, String addPoolMethod) { + JCExpression pool = nameToExpression(ConstPool.NE_POOL); + JCExpression classType = m.snd.fst; + JCExpression methodName = cx.treeMaker.Literal(m.fst.toString()); + JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString(TYPE_CLASS)), + List.nil(), m.snd.snd); + JCExpression thisIns = cx.treeMaker.Ident(cx.names.fromString(ConstPool.REF_THIS)); + JCNewClass poolClass = cx.treeMaker.NewClass(null, List.nil(), pool, + List.of(classType, methodName, parameterTypes, thisIns), null); + JCExpression addInjectMethod = nameToExpression(addPoolMethod); + JCMethodInvocation apply = cx.treeMaker.Apply(List.nil(), addInjectMethod, + List.from(new JCExpression[] {poolClass})); + return cx.treeMaker.Exec(apply); + } + +} diff --git a/src/main/java/com/alibaba/testable/generator/TestableClassDevRoleGenerator.java b/src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java similarity index 95% rename from src/main/java/com/alibaba/testable/generator/TestableClassDevRoleGenerator.java rename to src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java index ad82e57..772a092 100644 --- a/src/main/java/com/alibaba/testable/generator/TestableClassDevRoleGenerator.java +++ b/src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java @@ -3,7 +3,7 @@ package com.alibaba.testable.generator; import com.alibaba.testable.generator.model.Statement; import com.alibaba.testable.generator.statement.CallSuperMethodStatementGenerator; import com.alibaba.testable.model.TestableContext; -import com.alibaba.testable.translator.TestableClassDevRoleTranslator; +import com.alibaba.testable.translator.EnableTestableInjectTranslator; import com.alibaba.testable.util.ConstPool; import com.squareup.javapoet.*; import com.sun.tools.javac.code.Symbol; @@ -23,17 +23,17 @@ import java.util.Set; * * @author flin */ -public class TestableClassDevRoleGenerator { +public class TestableClassGenerator { private final TestableContext cx; - public TestableClassDevRoleGenerator(TestableContext cx) { + public TestableClassGenerator(TestableContext cx) { this.cx = cx; } public String fetch(Symbol.ClassSymbol clazz, String packageName, String className) { JCTree tree = cx.trees.getTree(clazz); - TestableClassDevRoleTranslator translator = new TestableClassDevRoleTranslator(cx); + EnableTestableInjectTranslator translator = new EnableTestableInjectTranslator(cx); tree.accept(translator); List methodSpecs = new ArrayList<>(); diff --git a/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java b/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java index 74b631c..7212289 100644 --- a/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java +++ b/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java @@ -2,7 +2,7 @@ package com.alibaba.testable.processor; import com.alibaba.testable.annotation.EnableTestableInject; import com.alibaba.testable.generator.StaticNewClassGenerator; -import com.alibaba.testable.generator.TestableClassDevRoleGenerator; +import com.alibaba.testable.generator.TestableClassGenerator; import com.alibaba.testable.util.ConstPool; import com.sun.tools.javac.code.Symbol; @@ -76,7 +76,7 @@ public class EnableTestableInjectProcessor extends BaseProcessor { String fullQualityTypeName = packageName + "." + testableTypeName; try { writeSourceFile(fullQualityTypeName, - new TestableClassDevRoleGenerator(cx).fetch(clazz, packageName, testableTypeName)); + new TestableClassGenerator(cx).fetch(clazz, packageName, testableTypeName)); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/com/alibaba/testable/processor/EnableTestableProcessor.java b/src/main/java/com/alibaba/testable/processor/EnableTestableProcessor.java index 5b40797..a8b2ef9 100644 --- a/src/main/java/com/alibaba/testable/processor/EnableTestableProcessor.java +++ b/src/main/java/com/alibaba/testable/processor/EnableTestableProcessor.java @@ -1,7 +1,7 @@ package com.alibaba.testable.processor; import com.alibaba.testable.annotation.EnableTestable; -import com.alibaba.testable.translator.TestableClassTestRoleTranslator; +import com.alibaba.testable.translator.EnableTestableTranslator; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree; @@ -38,7 +38,7 @@ public class EnableTestableProcessor extends BaseProcessor { private void processClassElement(Symbol.ClassSymbol clazz) { JCTree tree = cx.trees.getTree(clazz); - tree.accept(new TestableClassTestRoleTranslator(getPkgName(clazz), getOriginClassName(clazz), cx)); + tree.accept(new EnableTestableTranslator(getPkgName(clazz), getOriginClassName(clazz), cx)); } private String getPkgName(Symbol.ClassSymbol clazz) { diff --git a/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java b/src/main/java/com/alibaba/testable/translator/EnableTestableInjectTranslator.java similarity index 97% rename from src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java rename to src/main/java/com/alibaba/testable/translator/EnableTestableInjectTranslator.java index 51f3747..9a8c753 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/EnableTestableInjectTranslator.java @@ -14,7 +14,7 @@ import com.sun.tools.javac.util.Name; * * @author flin */ -public class TestableClassDevRoleTranslator extends TreeTranslator { +public class EnableTestableInjectTranslator extends TreeTranslator { private final TestableContext cx; @@ -36,7 +36,7 @@ public class TestableClassDevRoleTranslator extends TreeTranslator { return fields; } - public TestableClassDevRoleTranslator(TestableContext cx) { + public EnableTestableInjectTranslator(TestableContext cx) { this.cx = cx; } @@ -175,7 +175,7 @@ public class TestableClassDevRoleTranslator extends TreeTranslator { private JCTree.JCMethodInvocation getGlobalMemberInvocation(Name methodName, List param) { JCTree.JCFieldAccess snClass = cx.treeMaker.Select(cx.treeMaker.Ident(cx.names.fromString(ConstPool.NE_PKG)), cx.names.fromString(ConstPool.NE_CLS)); - JCTree.JCFieldAccess snMethod = cx.treeMaker.Select(snClass, cx.names.fromString(ConstPool.NE_INK)); + JCTree.JCFieldAccess snMethod = cx.treeMaker.Select(snClass, cx.names.fromString(ConstPool.NE_FUN)); ListBuffer args = new ListBuffer(); args.add(cx.treeMaker.Ident(cx.names.fromString(ConstPool.REF_THIS))); args.add(cx.treeMaker.Literal(methodName.toString())); diff --git a/src/main/java/com/alibaba/testable/translator/EnableTestableTranslator.java b/src/main/java/com/alibaba/testable/translator/EnableTestableTranslator.java new file mode 100644 index 0000000..30a3dec --- /dev/null +++ b/src/main/java/com/alibaba/testable/translator/EnableTestableTranslator.java @@ -0,0 +1,181 @@ +package com.alibaba.testable.translator; + +import com.alibaba.testable.generator.TestSetupMethodGenerator; +import com.alibaba.testable.model.TestLibType; +import com.alibaba.testable.model.TestableContext; +import com.alibaba.testable.util.ConstPool; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeTranslator; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.tree.JCTree.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +/** + * Travel AST + * + * @author flin + */ +public class EnableTestableTranslator extends TreeTranslator { + + private final TestableContext cx; + private String sourceClassName = ""; + private final ListBuffer sourceClassIns = new ListBuffer<>(); + private final ListBuffer stubbornFields = new ListBuffer<>(); + private final TestSetupMethodGenerator testSetupMethodGenerator; + + public EnableTestableTranslator(String pkgName, String className, TestableContext cx) { + this.sourceClassName = className; + this.cx = cx; + this.testSetupMethodGenerator = new TestSetupMethodGenerator(cx); + try { + Class cls = Class.forName(pkgName + "." + className); + Field[] fields = cls.getDeclaredFields(); + for (Field f : fields) { + if (Modifier.isFinal(f.getModifiers()) || Modifier.isPrivate(f.getModifiers())) { + stubbornFields.add(f.getName()); + } + } + testSetupMethodGenerator.memberMethods.addAll(Arrays.asList(cls.getDeclaredMethods())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Demo d = new Demo() -> DemoTestable d = new Demo() + */ + @Override + public void visitVarDef(JCVariableDecl jcVariableDecl) { + super.visitVarDef(jcVariableDecl); + if (jcVariableDecl.vartype.getClass().equals(JCIdent.class) && + ((JCIdent)jcVariableDecl.vartype).name.toString().equals(sourceClassName)) { + jcVariableDecl.vartype = getTestableClassIdent(jcVariableDecl.vartype); + sourceClassIns.add(jcVariableDecl.name); + } + } + + /** + * Demo d = new Demo() -> Demo d = new DemoTestable() + */ + @Override + public void visitNewClass(JCNewClass jcNewClass) { + super.visitNewClass(jcNewClass); + if (getSimpleClassName(jcNewClass).equals(sourceClassName)) { + jcNewClass.clazz = getTestableClassIdent(jcNewClass.clazz); + } + } + + /** + * d.privateField = val -> d.privateFieldTestableSet(val) + */ + @Override + public void visitExec(JCExpressionStatement jcExpressionStatement) { + if (jcExpressionStatement.expr.getClass().equals(JCAssign.class) && + isAssignStubbornField((JCAssign)jcExpressionStatement.expr)) { + JCAssign assign = (JCAssign)jcExpressionStatement.expr; + JCFieldAccess stubbornSetter = cx.treeMaker.Select(((JCFieldAccess)assign.lhs).selected, + getStubbornSetterMethodName(assign)); + jcExpressionStatement.expr = cx.treeMaker.Apply(List.nil(), stubbornSetter, + com.sun.tools.javac.util.List.of(assign.rhs)); + } + super.visitExec(jcExpressionStatement); + } + + /** + * Search for TestableInject and TestSetup annotations + */ + @Override + public void visitMethodDef(JCMethodDecl jcMethodDecl) { + for (JCAnnotation a : jcMethodDecl.mods.annotations) { + switch (a.type.tsym.toString()) { + case ConstPool.ANNOTATION_TESTABLE_INJECT: + ListBuffer args = new ListBuffer<>(); + for (JCVariableDecl p : jcMethodDecl.params) { + args.add(cx.treeMaker.Select(p.vartype, cx.names.fromString(ConstPool.TYPE_TO_CLASS))); + } + JCExpression retType = jcMethodDecl.restype == null ? null : + cx.treeMaker.Select(jcMethodDecl.restype, cx.names.fromString(ConstPool.TYPE_TO_CLASS)); + testSetupMethodGenerator.injectMethods.add(Pair.of(jcMethodDecl.name, Pair.of(retType, args.toList()))); + break; + case ConstPool.ANNOTATION_JUNIT5_SETUP: + testSetupMethodGenerator.testSetupMethodName = jcMethodDecl.name.toString(); + jcMethodDecl.mods.annotations = removeAnnotation(jcMethodDecl.mods.annotations, + ConstPool.ANNOTATION_JUNIT5_SETUP); + break; + case ConstPool.ANNOTATION_JUNIT5_TEST: + testSetupMethodGenerator.testLibType = TestLibType.JUnit5; + break; + default: + } + } + super.visitMethodDef(jcMethodDecl); + } + + /** + * Generate test setup method to initialize n.e.pool + */ + @Override + public void visitClassDef(JCClassDecl jcClassDecl) { + super.visitClassDef(jcClassDecl); + ListBuffer ndefs = new ListBuffer<>(); + ndefs.addAll(jcClassDecl.defs); + ndefs.add(testSetupMethodGenerator.fetch()); + jcClassDecl.defs = ndefs.toList(); + } + + /** + * For setter break point + */ + @Override + public void visitAssign(JCAssign jcAssign) { + super.visitAssign(jcAssign); + } + + /** + * For getter break point + */ + @Override + public void visitSelect(JCFieldAccess jcFieldAccess) { + super.visitSelect(jcFieldAccess); + } + + private String getSimpleClassName(JCNewClass jcNewClass) { + if (jcNewClass.clazz.getClass().equals(JCIdent.class)) { + return ((JCIdent)jcNewClass.clazz).name.toString(); + } else if (jcNewClass.clazz.getClass().equals(JCFieldAccess.class)) { + return ((JCFieldAccess)jcNewClass.clazz).name.toString(); + } else { + return ""; + } + } + + private List removeAnnotation(List annotations, String target) { + ListBuffer nb = new ListBuffer<>(); + for (JCAnnotation i : annotations) { + if (!i.type.tsym.toString().equals(target)) { + nb.add(i); + } + } + return nb.toList(); + } + + private Name getStubbornSetterMethodName(JCAssign assign) { + String name = ((JCFieldAccess)assign.lhs).name.toString() + ConstPool.TESTABLE_SET_METHOD_PREFIX; + return cx.names.fromString(name); + } + + private boolean isAssignStubbornField(JCAssign expr) { + return expr.lhs.getClass().equals(JCFieldAccess.class) && + sourceClassIns.contains(((JCIdent)((JCFieldAccess)(expr).lhs).selected).name) && + stubbornFields.contains(((JCFieldAccess)(expr).lhs).name.toString()); + } + + private JCIdent getTestableClassIdent(JCExpression clazz) { + Name className = ((JCIdent)clazz).name; + return cx.treeMaker.Ident(cx.names.fromString(className + ConstPool.TESTABLE)); + } + +} diff --git a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java deleted file mode 100644 index 725b78f..0000000 --- a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.alibaba.testable.translator; - -import com.alibaba.testable.model.TestLibType; -import com.alibaba.testable.model.TestableContext; -import com.alibaba.testable.util.ConstPool; -import com.sun.tools.javac.code.Type; -import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.TreeTranslator; -import com.sun.tools.javac.util.*; -import com.sun.tools.javac.tree.JCTree.*; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Arrays; - -/** - * Travel AST - * - * @author flin - */ -public class TestableClassTestRoleTranslator extends TreeTranslator { - - private static final String ANNOTATION_TESTABLE_INJECT = "com.alibaba.testable.annotation.TestableInject"; - private static final String ANNOTATION_JUNIT5_SETUP = "org.junit.jupiter.api.BeforeEach"; - private static final String ANNOTATION_JUNIT5_TEST = "org.junit.jupiter.api.Test"; - private static final String TYPE_CLASS = "Class"; - private final TestableContext cx; - private String sourceClassName = ""; - private final ListBuffer sourceClassIns = new ListBuffer(); - private final ListBuffer stubbornFields = new ListBuffer(); - private final ListBuffer memberMethods = new ListBuffer(); - - /** - * MethodName -> (ResultType -> ParameterTypes) - */ - private ListBuffer>>> injectMethods = new ListBuffer<>(); - private String testSetupMethodName = ""; - private TestLibType testLibType = TestLibType.JUnit4; - - public TestableClassTestRoleTranslator(String pkgName, String className, TestableContext cx) { - this.sourceClassName = className; - this.cx = cx; - try { - Class cls = Class.forName(pkgName + "." + className); - Field[] fields = cls.getDeclaredFields(); - for (Field f : fields) { - if (Modifier.isFinal(f.getModifiers()) || Modifier.isPrivate(f.getModifiers())) { - stubbornFields.add(f.getName()); - } - } - memberMethods.addAll(Arrays.asList(cls.getDeclaredMethods())); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Demo d = new Demo() -> DemoTestable d = new Demo() - */ - @Override - public void visitVarDef(JCVariableDecl jcVariableDecl) { - super.visitVarDef(jcVariableDecl); - if (jcVariableDecl.vartype.getClass().equals(JCIdent.class) && - ((JCIdent)jcVariableDecl.vartype).name.toString().equals(sourceClassName)) { - jcVariableDecl.vartype = getTestableClassIdent(jcVariableDecl.vartype); - sourceClassIns.add(jcVariableDecl.name); - } - } - - /** - * Demo d = new Demo() -> Demo d = new DemoTestable() - */ - @Override - public void visitNewClass(JCNewClass jcNewClass) { - super.visitNewClass(jcNewClass); - if (getSimpleClassName(jcNewClass).equals(sourceClassName)) { - jcNewClass.clazz = getTestableClassIdent(jcNewClass.clazz); - } - } - - /** - * d.privateField = val -> d.privateFieldTestableSet(val) - */ - @Override - public void visitExec(JCExpressionStatement jcExpressionStatement) { - if (jcExpressionStatement.expr.getClass().equals(JCAssign.class) && - isAssignStubbornField((JCAssign)jcExpressionStatement.expr)) { - JCAssign assign = (JCAssign)jcExpressionStatement.expr; - JCFieldAccess stubbornSetter = cx.treeMaker.Select(((JCFieldAccess)assign.lhs).selected, - getStubbornSetterMethodName(assign)); - jcExpressionStatement.expr = cx.treeMaker.Apply(List.nil(), stubbornSetter, - com.sun.tools.javac.util.List.of(assign.rhs)); - } - super.visitExec(jcExpressionStatement); - } - - /** - * Search for TestableInject and TestSetup annotations - */ - @Override - public void visitMethodDef(JCMethodDecl jcMethodDecl) { - for (JCAnnotation a : jcMethodDecl.mods.annotations) { - switch (a.type.tsym.toString()) { - case ANNOTATION_TESTABLE_INJECT: - ListBuffer args = new ListBuffer<>(); - for (JCVariableDecl p : jcMethodDecl.params) { - args.add(cx.treeMaker.Select(p.vartype, cx.names.fromString(ConstPool.TYPE_TO_CLASS))); - } - JCExpression retType = jcMethodDecl.restype == null ? null : - cx.treeMaker.Select(jcMethodDecl.restype, cx.names.fromString(ConstPool.TYPE_TO_CLASS)); - injectMethods.add(Pair.of(jcMethodDecl.name, Pair.of(retType, args.toList()))); - break; - case ANNOTATION_JUNIT5_SETUP: - testSetupMethodName = jcMethodDecl.name.toString(); - jcMethodDecl.mods.annotations = removeAnnotation(jcMethodDecl.mods.annotations, - ANNOTATION_JUNIT5_SETUP); - break; - case ANNOTATION_JUNIT5_TEST: - testLibType = TestLibType.JUnit5; - break; - default: - } - } - super.visitMethodDef(jcMethodDecl); - } - - /** - * Generate test setup method to initialize n.e.pool - */ - @Override - public void visitClassDef(JCClassDecl jcClassDecl) { - super.visitClassDef(jcClassDecl); - ListBuffer ndefs = new ListBuffer<>(); - ndefs.addAll(jcClassDecl.defs); - JCModifiers mods = cx.treeMaker.Modifiers(Modifier.PUBLIC, makeAnnotations(ANNOTATION_JUNIT5_SETUP)); - ndefs.add(cx.treeMaker.MethodDef(mods, cx.names.fromString("testableSetup"), - cx.treeMaker.Type(new Type.JCVoidType()), List.nil(), - List.nil(), List.nil(), testableSetupBlock(), null)); - jcClassDecl.defs = ndefs.toList(); - } - - /** - * For setter break point - */ - @Override - public void visitAssign(JCAssign jcAssign) { - super.visitAssign(jcAssign); - } - - /** - * For getter break point - */ - @Override - public void visitSelect(JCFieldAccess jcFieldAccess) { - super.visitSelect(jcFieldAccess); - } - - private String getSimpleClassName(JCNewClass jcNewClass) { - if (jcNewClass.clazz.getClass().equals(JCIdent.class)) { - return ((JCIdent)jcNewClass.clazz).name.toString(); - } else if (jcNewClass.clazz.getClass().equals(JCFieldAccess.class)) { - return ((JCFieldAccess)jcNewClass.clazz).name.toString(); - } else { - return ""; - } - } - - private List makeAnnotations(String fullAnnotationName) { - JCExpression setupAnnotation = nameToExpression(fullAnnotationName); - return List.of(cx.treeMaker.Annotation(setupAnnotation, List.nil())); - } - - private JCExpression nameToExpression(String dotName) { - String[] nameParts = dotName.split("\\."); - JCExpression e = cx.treeMaker.Ident(cx.names.fromString(nameParts[0])); - for (int i = 1; i < nameParts.length; i++) { - e = cx.treeMaker.Select(e, cx.names.fromString(nameParts[i])); - } - return e; - } - - private JCBlock testableSetupBlock() { - ListBuffer statements = new ListBuffer<>(); - for (Pair>> m : injectMethods.toList()) { - if (isMemberMethod(m)) { - statements.append(toGlobalInvokeStatement(m)); - } else { - statements.append(toGlobalNewStatement(m)); - } - } - if (!testSetupMethodName.isEmpty()) { - statements.append(cx.treeMaker.Exec(cx.treeMaker.Apply(List.nil(), - nameToExpression(testSetupMethodName), List.nil()))); - } - return cx.treeMaker.Block(0, statements.toList()); - } - - private boolean isMemberMethod(Pair>> m) { - for (Method method : memberMethods) { - if (method.getName().equals(m.fst.toString()) && parameterEquals(m.snd.snd, method.getParameterTypes())) { - return true; - } - } - return false; - } - - private boolean parameterEquals(List injectMethodArgs, Class[] memberMethodArgs) { - if (injectMethodArgs.length() != memberMethodArgs.length) { - return false; - } - for (int i = 0; i < injectMethodArgs.length(); i++) { - if (!memberMethodArgs[i].getName().equals(((JCFieldAccess)injectMethodArgs.get(i)).selected.type - .toString())) { - return false; - } - } - return true; - } - - private JCStatement toGlobalInvokeStatement(Pair>> m) { - JCExpression key = nameToExpression(ConstPool.NE_X_KEY); - JCExpression value = nameToExpression(ConstPool.NE_X_VAL); - JCExpression methodName = cx.treeMaker.Literal(m.fst.toString()); - JCExpression thisIns = cx.treeMaker.Ident(cx.names.fromString(ConstPool.REF_THIS)); - JCExpression returnClassType = m.snd.fst; - JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString(TYPE_CLASS)), - List.nil(), m.snd.snd); - JCNewClass keyClass = cx.treeMaker.NewClass(null, List.nil(), key, - List.of(methodName, parameterTypes), null); - JCNewClass valClass = cx.treeMaker.NewClass(null, List.nil(), value, - List.of(thisIns, returnClassType), null); - JCExpression addInjectMethod = nameToExpression(ConstPool.NE_X_ADD); - JCMethodInvocation apply = cx.treeMaker.Apply(List.nil(), addInjectMethod, - List.from(new JCExpression[] {keyClass, valClass})); - return cx.treeMaker.Exec(apply); - } - - private JCStatement toGlobalNewStatement(Pair>> m) { - JCExpression key = nameToExpression(ConstPool.NE_W_KEY); - JCExpression value = nameToExpression(ConstPool.NE_W_VAL); - JCExpression classType = m.snd.fst; - JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString(TYPE_CLASS)), - List.nil(), m.snd.snd); - JCExpression thisIns = cx.treeMaker.Ident(cx.names.fromString(ConstPool.REF_THIS)); - JCExpression methodName = cx.treeMaker.Literal(m.fst.toString()); - JCNewClass keyClass = cx.treeMaker.NewClass(null, List.nil(), key, - List.of(classType, parameterTypes), null); - JCNewClass valClass = cx.treeMaker.NewClass(null, List.nil(), value, - List.of(thisIns, methodName), null); - JCExpression addInjectMethod = nameToExpression(ConstPool.NE_W_ADD); - JCMethodInvocation apply = cx.treeMaker.Apply(List.nil(), addInjectMethod, - List.from(new JCExpression[] {keyClass, valClass})); - return cx.treeMaker.Exec(apply); - } - - private List removeAnnotation(List annotations, String target) { - ListBuffer nb = new ListBuffer<>(); - for (JCAnnotation i : annotations) { - if (!i.type.tsym.toString().equals(target)) { - nb.add(i); - } - } - return nb.toList(); - } - - private Name getStubbornSetterMethodName(JCAssign assign) { - String name = ((JCFieldAccess)assign.lhs).name.toString() + ConstPool.TESTABLE_SET_METHOD_PREFIX; - return cx.names.fromString(name); - } - - private boolean isAssignStubbornField(JCAssign expr) { - return expr.lhs.getClass().equals(JCFieldAccess.class) && - sourceClassIns.contains(((JCIdent)((JCFieldAccess)(expr).lhs).selected).name) && - stubbornFields.contains(((JCFieldAccess)(expr).lhs).name.toString()); - } - - private JCIdent getTestableClassIdent(JCExpression clazz) { - Name className = ((JCIdent)clazz).name; - return cx.treeMaker.Ident(cx.names.fromString(className + ConstPool.TESTABLE)); - } - -} diff --git a/src/main/java/com/alibaba/testable/util/ConstPool.java b/src/main/java/com/alibaba/testable/util/ConstPool.java index c280694..e1a26dc 100644 --- a/src/main/java/com/alibaba/testable/util/ConstPool.java +++ b/src/main/java/com/alibaba/testable/util/ConstPool.java @@ -11,17 +11,16 @@ public final class ConstPool { public static final String NE_PKG = "n"; public static final String NE_CLS = "e"; public static final String NE_NEW = "w"; - public static final String NE_INK = "x"; + public static final String NE_FUN = "f"; public static final String NE_PKG_CLS = "n.e"; - public static final String NE_W_KEY = "n.e.wk"; - public static final String NE_X_KEY = "n.e.xk"; - public static final String NE_W_VAL = "n.e.wv"; - public static final String NE_X_VAL = "n.e.xv"; - public static final String NE_W_ADD = "n.e.wa"; - public static final String NE_X_ADD = "n.e.xa"; + public static final String NE_POOL = "n.e.p"; + public static final String NE_ADD_W = "n.e.aw"; + public static final String NE_ADD_F = "n.e.af"; public static final String TESTABLE_GET_METHOD_PREFIX = "TestableGet"; public static final String TESTABLE_SET_METHOD_PREFIX = "TestableSet"; public static final String TYPE_TO_CLASS = "class"; public static final String REF_THIS = "this"; - + public static final String ANNOTATION_TESTABLE_INJECT = "com.alibaba.testable.annotation.TestableInject"; + public static final String ANNOTATION_JUNIT5_SETUP = "org.junit.jupiter.api.BeforeEach"; + public static final String ANNOTATION_JUNIT5_TEST = "org.junit.jupiter.api.Test"; } diff --git a/src/main/resources/e.java b/src/main/resources/e.java index 2e79927..baa441d 100644 --- a/src/main/resources/e.java +++ b/src/main/resources/e.java @@ -10,93 +10,35 @@ import java.util.*; public final class e { - /** - * key for contructor pool - */ - public static class wk { - public Class c; // target instance type to new - public Class[] a; // constructor parameter types + public static class p { + public Class c; // target instance type to new / method return type + public Class[] a; // constructor parameter types / member method parameter types + public Object o; // object which provides substitution / object which provides substitution + public String m; // substitutional method name / original member method name - public wk(Class c, Class[] a) { + public p(Class c, String m, Class[] a, Object o) { this.c = c; - this.a = a; - } - - @Override - public boolean equals(Object o) { - return o.getClass().equals(e.wk.class) && c.equals(((e.wk)o).c) && Arrays.equals(a, ((e.wk)o).a); - } - - @Override - public int hashCode() { - return 31 * c.hashCode() + Arrays.hashCode(a); - } - } - - /** - * key for method pool - */ - public static class xk { - public String m; // original member method name - public Class[] a; // member method parameter types - - public xk(String m, Class[] a) { this.m = m; this.a = a; - } - - @Override - public boolean equals(Object o) { - return o.getClass().equals(e.xk.class) && m.equals(((e.xk)o).m) && Arrays.equals(a, ((e.xk)o).a); - } - - @Override - public int hashCode() { - return 31 * m.hashCode() + Arrays.hashCode(a); - } - } - - /** - * value for contructor pool - */ - public static class wv { - public Object o; // object which provides substitution - public String m; // substitutional method name - - public wv(Object o, String m) { this.o = o; - this.m = m; } } - /** - * value for member method pool - */ - public static class xv { - public Object o; // object which provides substitution - public Class c; // method return type - - public xv(Object o, Class c) { - this.o = o; - this.c = c; - } - } - - private static Map wp = new HashMap<>(); - private static Map xp = new HashMap<>(); + private static List

pw = new ArrayList<>(); + private static List

pf = new ArrayList<>(); /** * add item to contructor pool */ - public static void wa(wk k, wv v) { - wp.put(k, v); + public static void aw(p np) { + pw.add(np); } /** * add item to method pool */ - public static void xa(xk k, xv v) { - xp.put(k, v); + public static void af(p np) { + pf.add(np); } /** @@ -107,13 +49,13 @@ public final class e { for (int i = 0; i < cs.length; i++) { cs[i] = as[i].getClass(); } - if (!wp.isEmpty()) { + if (!pw.isEmpty()) { try { - Pair p = gwp(new wk(ct, cs)); - if (p != null) { - Method m = p.snd.o.getClass().getDeclaredMethod(p.snd.m, p.fst.a); + p pi = gpw(ct, cs); + if (pi != null) { + Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); m.setAccessible(true); - return (T)m.invoke(p.snd.o, as); + return (T)m.invoke(pi.o, as); } } catch (Exception e) { return null; @@ -133,22 +75,22 @@ public final class e { /** * subsitituion entry for member call */ - public static T x(Object obj, String method, Object... as) { + public static T f(Object obj, String mn, Object... as) { Class[] cs = gcs(as); - if (!xp.isEmpty()) { + if (!pf.isEmpty()) { try { - Pair p = gxp(new xk(method, cs)); - if (p != null) { - Method m = p.snd.o.getClass().getDeclaredMethod(p.fst.m, p.fst.a); + p pi = gpf(mn, cs); + if (pi != null) { + Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); m.setAccessible(true); - return (T)m.invoke(p.snd.o, as); + return (T)m.invoke(pi.o, as); } } catch (Exception e) { return null; } } try { - Method m = gm(obj.getClass().getDeclaredMethods(), method, cs); + Method m = gm(obj.getClass().getDeclaredMethods(), mn, cs); if (m != null) { m.setAccessible(true); return (T)m.invoke(obj, as); @@ -171,11 +113,11 @@ public final class e { } /** - * get method by parameter matching + * get method by name and parameter matching */ - private static Method gm(Method[] methods, String name, Class[] cs) { - for (Method m : methods) { - if (m.getName().equals(name) && te(m.getParameterTypes(), cs)) { + private static Method gm(Method[] mds, String mn, Class[] cs) { + for (Method m : mds) { + if (m.getName().equals(mn) && te(m.getParameterTypes(), cs)) { return m; } } @@ -185,10 +127,10 @@ public final class e { /** * get from method pool by key */ - private static Pair gxp(xk k1) { - for (xk k2 : xp.keySet()) { - if (k1.m.equals(k2.m) && te(k2.a, k1.a)) { - return Pair.of(k2, xp.get(k2)); + private static p gpf(String mn, Class[] cs) { + for (p f : pf) { + if (f.m.equals(mn) && te(f.a, cs)) { + return f; } } return null; @@ -197,8 +139,8 @@ public final class e { /** * get contructor by parameter matching */ - private static Constructor gc(Constructor[] constructors, Class[] cs) { - for (Constructor c : constructors) { + private static Constructor gc(Constructor[] cons, Class[] cs) { + for (Constructor c : cons) { if (te(c.getParameterTypes(), cs)) { return c; } @@ -209,10 +151,10 @@ public final class e { /** * get from contructor pool by key */ - private static Pair gwp(wk k1) { - for (wk k2 : wp.keySet()) { - if (k1.c.equals(k2.c) && te(k2.a, k1.a)) { - return Pair.of(k2, wp.get(k2)); + private static p gpw(Class ct, Class[] cs) { + for (p w : pw) { + if (w.c.equals(ct) && te(w.a, cs)) { + return w; } } return null;