move agent jar to class folder and bump version to v0.1.0-snapshot

This commit is contained in:
金戟 2020-10-14 20:18:45 +08:00
parent c869caa0c9
commit 3bf9e1a623
7 changed files with 87 additions and 72 deletions

View File

@ -5,7 +5,7 @@
<groupId>com.alibaba.testable</groupId> <groupId>com.alibaba.testable</groupId>
<artifactId>agent</artifactId> <artifactId>agent</artifactId>
<version>0.0.5-SNAPSHOT</version> <version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View File

@ -8,7 +8,7 @@
<description>Unit test enhancement toolkit</description> <description>Unit test enhancement toolkit</description>
<groupId>com.alibaba.testable</groupId> <groupId>com.alibaba.testable</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>0.0.5-SNAPSHOT</version> <version>0.1.0-SNAPSHOT</version>
<properties> <properties>
<plugin.compiler.version>3.8.1</plugin.compiler.version> <plugin.compiler.version>3.8.1</plugin.compiler.version>

View File

@ -60,4 +60,14 @@ public class TestableContext {
this.names = names; this.names = names;
} }
public TestableContext(TestableLogger logger, Filer filter) {
this.logger = logger;
this.filter = filter;
this.elementUtils = null;
this.typeUtils = null;
this.trees = null;
this.treeMaker = null;
this.names = null;
}
} }

View File

@ -1,30 +0,0 @@
package com.alibaba.testable.core.processor;
import com.alibaba.testable.core.model.TestableContext;
import com.alibaba.testable.core.util.TestableLogger;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
/**
* @author flin
*/
public abstract class BaseProcessor extends AbstractProcessor {
protected TestableContext cx;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment)processingEnv).getContext();
cx = new TestableContext(new TestableLogger(processingEnv.getMessager()), processingEnv.getFiler(),
processingEnv.getElementUtils(), processingEnv.getTypeUtils(), JavacTrees.instance(processingEnv),
TreeMaker.instance(context), Names.instance(context));
}
}

View File

@ -1,15 +1,20 @@
package com.alibaba.testable.core.processor; package com.alibaba.testable.core.processor;
import com.alibaba.testable.core.annotation.EnableTestable; import com.alibaba.testable.core.annotation.EnableTestable;
import com.alibaba.testable.core.translator.EnableTestableTranslator;
import com.alibaba.testable.core.constant.ConstPool; import com.alibaba.testable.core.constant.ConstPool;
import com.alibaba.testable.core.model.TestableContext;
import com.alibaba.testable.core.translator.EnableTestableTranslator;
import com.alibaba.testable.core.util.ResourceUtil; import com.alibaba.testable.core.util.ResourceUtil;
import com.alibaba.testable.core.util.TestableLogger;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.*;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.Name; import javax.lang.model.element.Name;
@ -24,15 +29,41 @@ import java.util.Set;
* @author flin * @author flin
*/ */
@SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnableTestable") @SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnableTestable")
@SupportedSourceVersion(SourceVersion.RELEASE_7) public class EnableTestableProcessor extends AbstractProcessor {
public class EnableTestableProcessor extends BaseProcessor {
private static final String TESTABLE_AGENT_JAR = "testable-agent.jar"; private static final String TESTABLE_AGENT_JAR = "testable-agent.jar";
private static boolean hasFirstClassCompiled = false; private static final String TEST_OUTPUT_FOLDER_MARK = "/test-classes/";
private TestableContext cx;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = getJavacProcessingContext(processingEnv);
if (context == null) {
cx = new TestableContext(new TestableLogger(processingEnv.getMessager()), processingEnv.getFiler());
cx.logger.info("Skip testable compile time processing");
} else {
cx = new TestableContext(new TestableLogger(processingEnv.getMessager()), processingEnv.getFiler(),
processingEnv.getElementUtils(), processingEnv.getTypeUtils(), JavacTrees.instance(processingEnv),
TreeMaker.instance(context), Names.instance(context));
}
createTestableAgentJar();
cx.logger.info("Testable processor initialized");
}
private Context getJavacProcessingContext(ProcessingEnvironment processingEnv) {
try {
return ((JavacProcessingEnvironment)processingEnv).getContext();
} catch (Exception e) {
return null;
}
}
@Override @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
createTestableAgentJar(); if (cx.names == null) {
return true;
}
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableTestable.class); Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableTestable.class);
for (Element element : elements) { for (Element element : elements) {
if (element.getKind().isClass() && isTestClass(element.getSimpleName())) { if (element.getKind().isClass() && isTestClass(element.getSimpleName())) {
@ -42,40 +73,41 @@ public class EnableTestableProcessor extends BaseProcessor {
return true; return true;
} }
@Override
public SourceVersion getSupportedSourceVersion() {
// always return the latest version
return SourceVersion.values()[SourceVersion.values().length - 1];
}
private boolean isTestClass(Name name) { private boolean isTestClass(Name name) {
return name.toString().endsWith(ConstPool.TEST_POSTFIX); return name.toString().endsWith(ConstPool.TEST_POSTFIX);
} }
private void processClassElement(Symbol.ClassSymbol clazz) { private void processClassElement(Symbol.ClassSymbol clazz) {
JCTree tree = cx.trees.getTree(clazz); JCTree tree = cx.trees.getTree(clazz);
tree.accept(new EnableTestableTranslator(getPkgName(clazz), clazz.getSimpleName().toString(), cx)); String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
} tree.accept(new EnableTestableTranslator(pkgName, clazz.getSimpleName().toString(), cx));
private String getPkgName(Symbol.ClassSymbol clazz) {
return ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
} }
private void createTestableAgentJar() { private void createTestableAgentJar() {
if (!hasFirstClassCompiled) { byte[] bytes = ResourceUtil.fetchBinary(TESTABLE_AGENT_JAR);
hasFirstClassCompiled = true; if (bytes.length == 0) {
byte[] bytes = ResourceUtil.fetchBinary(TESTABLE_AGENT_JAR); cx.logger.info("Failed to fetch testable agent jar");
if (bytes.length == 0) {
cx.logger.error("Failed to generate testable agent jar");
}
writeBinaryFile("", TESTABLE_AGENT_JAR, bytes);
} }
}
private void writeBinaryFile(String path, String fileName, byte[] content) {
try { try {
FileObject resource = cx.filter.createResource(StandardLocation.SOURCE_OUTPUT, path, fileName); FileObject resource = cx.filter.createResource(StandardLocation.CLASS_OUTPUT, "", TESTABLE_AGENT_JAR);
if (!resource.getName().contains(TEST_OUTPUT_FOLDER_MARK)) {
cx.logger.info("Skip generate testable agent jar");
return;
}
cx.logger.info("Generating " + resource.getName());
try (OutputStream out = resource.openOutputStream()) { try (OutputStream out = resource.openOutputStream()) {
out.write(content); out.write(bytes);
out.flush(); out.flush();
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
cx.logger.error("Failed to write " + fileName); cx.logger.error("Failed to generate testable agent jar");
} }
} }
} }

View File

@ -13,8 +13,7 @@ public class ResourceUtil {
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
String line; String line;
try { try {
while ((line = reader.readLine()) != null) while ((line = reader.readLine()) != null) {
{
buffer.append(line).append('\n'); buffer.append(line).append('\n');
} }
reader.close(); reader.close();

View File

@ -1,22 +1,26 @@
# Release Note # Release Note
## v0.0.1 ## v0.1.0
- PoC version - move generated agent jar to class folder
- use compile time code modification to support new operation mocking and private field & method access - now support mock method of any object
## v0.0.2 ## v0.0.5
- add support of member method mocking by compile time code modification - use dynamically runtime modification to replace static `e.java` file
- get rid of unit test framework dependence
- add testable ref field in test class at runtime instead of compile time
## v0.0.4
- use runtime byte code rewrite to invoke testable setup method
- add `TestableUtil` class to fetch current test case and invocation source
## v0.0.3 ## v0.0.3
- use global method invoke to access private members instead of modification in place - use global method invoke to access private members instead of modification in place
- use `e.java` replace `testable` class make code more readable - use `e.java` replace `testable` class make code more readable
- introduce `agent` module, use runtime byte code modification to support new operation and member method mocking - introduce `agent` module, use runtime byte code modification to support new operation and member method mocking
## v0.0.4 ## v0.0.2
- use runtime byte code rewrite to invoke testable setup method - add support of member method mocking by compile time code modification
- add `TestableUtil` class to fetch current test case and invocation source
## v0.0.5 ## v0.0.1
- use dynamically runtime modification to replace static `e.java` file - PoC version
- get rid of unit test framework dependence - use compile time code modification to support new operation mocking and private field & method access
- add testable ref field in test class at runtime instead of compile time