From e6f450dff4a121818a26b6e462231a1b038e2dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 13 May 2020 11:24:27 +0800 Subject: [PATCH] success to run unit test on private method --- .../testable/generator/CallSuperMethod.java | 72 ++++++---- .../testable/generator/model/Statement.java | 28 ++++ .../testable/processor/TestableProcessor.java | 128 +++++++++++------- ...ator.java => TestableClassTranslator.java} | 2 +- .../translator/TestableFieldTranslator.java | 36 +++++ 5 files changed, 191 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/alibaba/testable/generator/model/Statement.java rename src/main/java/com/alibaba/testable/translator/{TestableTreeTranslator.java => TestableClassTranslator.java} (93%) create mode 100644 src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java diff --git a/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java b/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java index 8c72fd4..08caef3 100644 --- a/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java +++ b/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java @@ -1,9 +1,10 @@ package com.alibaba.testable.generator; +import com.alibaba.testable.generator.model.Statement; import com.alibaba.testable.util.ConstPool; import com.alibaba.testable.util.StringUtil; import com.sun.tools.javac.tree.JCTree; - +import java.lang.reflect.Method; import javax.lang.model.element.Modifier; import java.util.ArrayList; import java.util.List; @@ -14,38 +15,26 @@ import java.util.List; * @author flin */ public class CallSuperMethod { + private final String className; private final JCTree.JCMethodDecl method; - private Object[] params; - private String statement; public CallSuperMethod(String className, JCTree.JCMethodDecl method) { this.className = className; this.method = method; } - public Object[] getParams() { - return params; + public Statement[] invoke() { + if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { + return reflectCall(); + } else { + return commonCall(); + } } - public String getStatement() { - return statement; - } - - public CallSuperMethod invoke() { + private Statement[] commonCall() { List args = new ArrayList<>(); StringBuilder code = new StringBuilder(); - if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - reflectCall(args, code); - } else { - commonCall(args, code); - } - statement = code.toString(); - params = args.toArray(); - return this; - } - - private void commonCall(List args, StringBuilder code) { List placeholders = new ArrayList<>(); for (JCTree.JCVariableDecl p : method.params) { args.add(p.name.toString()); @@ -56,21 +45,52 @@ public class CallSuperMethod { code.append(".").append(method.name); } code.append("(").append(StringUtil.join(placeholders, ", ")).append(")"); + return new Statement[] { returnStatement(new Statement(code.toString(), args.toArray())) }; } - private void reflectCall(List args, StringBuilder code) { - if (!method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { - code.append("(").append(method.restype).append(")"); + private Statement[] reflectCall() { + List statements = new ArrayList<>(); + statements.add(getMethodStatement()); + statements.add(setAccessibleStatement()); + statements.add(returnStatement(invokeStatement())); + return statements.toArray(new Statement[0]); + } + + private Statement returnStatement(Statement statement) { + if (method.restype != null && !method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { + statement.setLine("return " + statement.getLine()); } - code.append(className).append(".class.getMethod(\"").append(method.name).append("\""); + return statement; + } + + private Statement getMethodStatement() { + List args = new ArrayList<>(); + StringBuilder code = new StringBuilder(); + code.append("$T m = "); + args.add(Method.class); + code.append(className).append(".class.getDeclaredMethod(\"").append(method.name).append("\""); for (JCTree.JCVariableDecl p : method.params) { code.append(", $T.class"); args.add(p.sym.type); } - code.append(").invoke(this"); + code.append(")"); + return new Statement(code.toString(), args.toArray()); + } + + private Statement setAccessibleStatement() { + return new Statement("m.setAccessible(true)", new Object[0]); + } + + private Statement invokeStatement() { + StringBuilder code = new StringBuilder(); + if (!method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { + code.append("(").append(method.restype).append(")"); + } + code.append("m.invoke(this"); for (JCTree.JCVariableDecl p : method.params) { code.append(", ").append(p.name); } code.append(")"); + return new Statement(code.toString(), new Object[0]); } } diff --git a/src/main/java/com/alibaba/testable/generator/model/Statement.java b/src/main/java/com/alibaba/testable/generator/model/Statement.java new file mode 100644 index 0000000..d353d51 --- /dev/null +++ b/src/main/java/com/alibaba/testable/generator/model/Statement.java @@ -0,0 +1,28 @@ +package com.alibaba.testable.generator.model; + +public class Statement { + + private String line; + private Object[] params; + + public String getLine() { + return line; + } + + public void setLine(String line) { + this.line = line; + } + + public Object[] getParams() { + return params; + } + + public void setParams(Object[] params) { + this.params = params; + } + + public Statement(String line, Object[] params) { + this.line = line; + this.params = params; + } +} diff --git a/src/main/java/com/alibaba/testable/processor/TestableProcessor.java b/src/main/java/com/alibaba/testable/processor/TestableProcessor.java index b21f64b..bc28764 100644 --- a/src/main/java/com/alibaba/testable/processor/TestableProcessor.java +++ b/src/main/java/com/alibaba/testable/processor/TestableProcessor.java @@ -2,7 +2,9 @@ package com.alibaba.testable.processor; import com.alibaba.testable.annotation.Testable; import com.alibaba.testable.generator.CallSuperMethod; -import com.alibaba.testable.translator.TestableTreeTranslator; +import com.alibaba.testable.generator.model.Statement; +import com.alibaba.testable.translator.TestableClassTranslator; +import com.alibaba.testable.translator.TestableFieldTranslator; import com.alibaba.testable.util.ConstPool; import com.squareup.javapoet.*; import com.sun.tools.javac.code.Type; @@ -32,71 +34,56 @@ public class TestableProcessor extends BaseProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - Set clazz = roundEnv.getElementsAnnotatedWith(Testable.class); - for (Element classElement : clazz) { - String packageName = elementUtils.getPackageOf(classElement).getQualifiedName().toString(); - String testableTypeName = classElement.getSimpleName().toString().replace(".", "_") + "Testable"; - String fullQualityTypeName = packageName + "." + testableTypeName; - try { - JavaFileObject jfo = filter.createSourceFile(fullQualityTypeName); - Writer writer = jfo.openWriter(); - writer.write(createTestableClass(classElement, packageName, testableTypeName)); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); + Set elements = roundEnv.getElementsAnnotatedWith(Testable.class); + for (Element element : elements) { + if (element.getKind().isClass()) { + processClassElement(element); + } else if (element.getKind().isField()) { + processFieldElement(element); } } return true; } - private String createTestableClass(Element classElement, String packageName, String className) { + private void processFieldElement(Element field) { + JCTree tree = trees.getTree(field); + tree.accept(new TestableFieldTranslator(treeMaker)); + } - JCTree tree = trees.getTree(classElement); - TestableTreeTranslator translator = new TestableTreeTranslator(); + private void processClassElement(Element clazz) { + String packageName = elementUtils.getPackageOf(clazz).getQualifiedName().toString(); + String testableTypeName = clazz.getSimpleName().toString().replace(".", "_") + "Testable"; + String fullQualityTypeName = packageName + "." + testableTypeName; + try { + JavaFileObject jfo = filter.createSourceFile(fullQualityTypeName); + Writer writer = jfo.openWriter(); + writer.write(createTestableClass(clazz, packageName, testableTypeName)); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String createTestableClass(Element clazz, String packageName, String className) { + JCTree tree = trees.getTree(clazz); + TestableClassTranslator translator = new TestableClassTranslator(); tree.accept(translator); List methodSpecs = new ArrayList<>(); for (JCTree.JCMethodDecl method : translator.getMethods()) { - if (method.getModifiers().getFlags().contains(Modifier.ABSTRACT)) { + if (isNoncallableMethod(method)) { continue; } - if (method.name.toString().equals(ConstPool.CONSTRUCTOR_NAME)) { - MethodSpec.Builder builder = MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC); - for (JCTree.JCVariableDecl p : method.getParameters()) { - builder.addParameter(getParameterSpec(p)); - } - CallSuperMethod callSuperMethod = new CallSuperMethod(classElement.getSimpleName().toString(), method).invoke(); - builder.addStatement(callSuperMethod.getStatement(), callSuperMethod.getParams()); - methodSpecs.add(builder.build()); + if (isConstructorMethod(method)) { + buildConstructorMethod(clazz, methodSpecs, method); } else { - MethodSpec.Builder builder = MethodSpec.methodBuilder(method.name.toString()) - .addModifiers(toPublicFlags(method.getModifiers())) - .returns(TypeName.get(((Type.MethodType)method.sym.type).restype)); - for (JCTree.JCVariableDecl p : method.getParameters()) { - builder.addParameter(getParameterSpec(p)); - } - CallSuperMethod callSuperMethod = new CallSuperMethod(classElement.getSimpleName().toString(), method).invoke(); - String statement = callSuperMethod.getStatement(); - if (!method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { - statement = "return " + statement; - } - if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - builder.addException(Exception.class); - } else { - builder.addAnnotation(Override.class); - for (JCTree.JCExpression exception : method.getThrows()) { - builder.addException(TypeName.get(exception.type)); - } - } - builder.addStatement(statement, callSuperMethod.getParams()); - methodSpecs.add(builder.build()); + buildMemberMethod(clazz, methodSpecs, method); } } TypeSpec.Builder builder = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .superclass(classElement.asType()); + .superclass(clazz.asType()); for (MethodSpec m : methodSpecs) { builder.addMethod(m); } @@ -105,6 +92,51 @@ public class TestableProcessor extends BaseProcessor { return javaFile.toString(); } + private void buildMemberMethod(Element classElement, List methodSpecs, JCTree.JCMethodDecl method) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(method.name.toString()) + .addModifiers(toPublicFlags(method.getModifiers())) + .returns(TypeName.get(((Type.MethodType)method.sym.type).restype)); + for (JCTree.JCVariableDecl p : method.getParameters()) { + builder.addParameter(getParameterSpec(p)); + } + if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { + builder.addException(Exception.class); + } else { + builder.addAnnotation(Override.class); + for (JCTree.JCExpression exception : method.getThrows()) { + builder.addException(TypeName.get(exception.type)); + } + } + addStatements(builder, classElement, method); + methodSpecs.add(builder.build()); + } + + private void buildConstructorMethod(Element classElement, List methodSpecs, + JCTree.JCMethodDecl method) { + MethodSpec.Builder builder = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC); + for (JCTree.JCVariableDecl p : method.getParameters()) { + builder.addParameter(getParameterSpec(p)); + } + addStatements(builder, classElement, method); + methodSpecs.add(builder.build()); + } + + private void addStatements(MethodSpec.Builder builder, Element classElement, JCTree.JCMethodDecl method) { + Statement[] statements = new CallSuperMethod(classElement.getSimpleName().toString(), method).invoke(); + for (Statement s : statements) { + builder.addStatement(s.getLine(), s.getParams()); + } + } + + private boolean isConstructorMethod(JCTree.JCMethodDecl method) { + return method.name.toString().equals(ConstPool.CONSTRUCTOR_NAME); + } + + private boolean isNoncallableMethod(JCTree.JCMethodDecl method) { + return method.getModifiers().getFlags().contains(Modifier.ABSTRACT); + } + private Set toPublicFlags(JCTree.JCModifiers modifiers) { Set flags = new HashSet<>(modifiers.getFlags()); flags.remove(Modifier.PRIVATE); diff --git a/src/main/java/com/alibaba/testable/translator/TestableTreeTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableClassTranslator.java similarity index 93% rename from src/main/java/com/alibaba/testable/translator/TestableTreeTranslator.java rename to src/main/java/com/alibaba/testable/translator/TestableClassTranslator.java index 08ef961..2a302e6 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableTreeTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/TestableClassTranslator.java @@ -12,7 +12,7 @@ import java.lang.reflect.Modifier; * * @author flin */ -public class TestableTreeTranslator extends TreeTranslator { +public class TestableClassTranslator extends TreeTranslator { /** * Methods to inject diff --git a/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java new file mode 100644 index 0000000..e070a81 --- /dev/null +++ b/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java @@ -0,0 +1,36 @@ +package com.alibaba.testable.translator; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.TreeTranslator; +import com.sun.tools.javac.util.Name; + +/** + * Travel AST + * + * @author flin + */ +public class TestableFieldTranslator extends TreeTranslator { + + private TreeMaker treeMaker; + + public TestableFieldTranslator(TreeMaker treeMaker) {this.treeMaker = treeMaker;} + + @Override + public void visitVarDef(JCTree.JCVariableDecl decl) { + super.visitVarDef(decl); + decl.vartype = getTestableClassIdent(decl.vartype); + } + + @Override + public void visitNewClass(JCTree.JCNewClass jcNewClass) { + super.visitNewClass(jcNewClass); + jcNewClass.clazz = getTestableClassIdent(jcNewClass.clazz); + } + + private JCTree.JCIdent getTestableClassIdent(JCTree.JCExpression clazz) { + Name className = ((JCTree.JCIdent)clazz).name; + return treeMaker.Ident(className.table.fromString(className + "Testable")); + } + +}