浅析jacoco Off-line instrumentation
18914 ワード
JAcocoはコードオーバーライドテストのオープンソースツール(java)であり、多くの統合方法があり、統合後、それらのコードが実行され、実行されていないことがわかります.これがコードオーバーライドテストです.独自の書き込みユニットテストを開発したり、手動でポイントを削除したりすることができます.実行するだけで、記録があり、コードがテストに上書きされていても.
引用文:
この2,3日コードオーバーライドテストを書いたもので、プロジェクトでjacocoを使った同僚がいました.注釈&&ドキュメントは何もありませんが、私は少しバック調をして、資料を見れば見るほど霧の中にいます.夜はjacocoの公式ドキュメントから入手し、少し分析します.一つ一つ包んだフレームワークを捨てて、jacocoはいったい何をしたのだろうか.
まず簡単なjavaを書きましょう~
OK、これはオリジナルのjavaと実行結果です.jacocoで処理したら?
jacocoの取得
JAcocoはclassファイルを処理し、cliを参照
jacocoが見えます.execはコードのオーバーライド実行レポートです.JAcocoはすでにその機能を完成しました.
次に、バイトコードの部分がどれだけ修正されたかを見てみましょう.
定数プールにはjacocoagentに依存するこれらのコードが組み込まれていることがわかる.jar.(だから修正したら、このjarパッケージを手動で参照します.)
各関数セグメントには、呼び出しのバイトコードがいくつか追加されています.例を挙げます.
この2つはdiffを取って、jacocoが何をしたかを見ることができて、主にいくつかのバイトコードを追加しました
表象から推測すると,各関数には呼び出し回数があり,呼び出す前にこの数を出し,呼び出し後に1を加えて戻す.しかしclassから修正後のバイトコードの保存は見られず,これらの呼び出し回数のデータを
ドキュメントを振り返ると、ASMが使われていて、よく知っている操作コードがいくつか見えます.少し理解を間違えました.boolean[]arrayを使用しています.
質問に戻ったので、何をしましたか?はい、上記のバイトコードを変更しました.
コードを結合して見ると
git clone https://github.com/jacoco/jacoco
主に修正されたコードは
classの修正はこれらのクラスで行われていることがわかります
中に入るとよく知っている定数プールが見え、よく知っているバイトコードの修正が見えます.この部分がofflineの修正です.では、どのように統計しますか.
定数プールには
コードは
getProbesがコードアクセスの統計データをここに格納していることがわかります.ここでclassのバイトコード情報は終わり、次はofflineという方法を呼び出して統計を取ります.
ExecutionDataはデータベースに似ていて、これらのデータが保存されていて、メモリに存在します.
保存が必要な場合は、ExecutionDataWriterでディスクにファイルを書き込みます.これはオプションの構成で、
簡単に言えばVMが終了したときにHookが追加され、保存された属性が設定されている場合は、終了した時点でメモリの統計をファイルにシーケンス化し、もちろんこのdumpの方法も手動で呼び出すことができます.
PS:nannyでこの文章を見たら、それは私が送ったのです:)
引用文:
この2,3日コードオーバーライドテストを書いたもので、プロジェクトでjacocoを使った同僚がいました.注釈&&ドキュメントは何もありませんが、私は少しバック調をして、資料を見れば見るほど霧の中にいます.夜はjacocoの公式ドキュメントから入手し、少し分析します.一つ一つ包んだフレームワークを捨てて、jacocoはいったい何をしたのだろうか.
まず簡単なjavaを書きましょう~
public final class Test{
public String me = "Yeshen";
public static void main(String[] args){
Test t = new Test();
System.out.println(t.me);
if(args != null && args.length > 0){
System.out.println(args[0]);
}else{
t.hi();
}
}
public void hi(){
System.out.println("hi");
}
public void nonono(){
System.out.println("nonono");
}
}
javac Test.java
java Test longlongArgs
OK、これはオリジナルのjavaと実行結果です.jacocoで処理したら?
jacocoの取得
wget http://search.maven.org/remotecontent?filepath=org/jacoco/jacoco/0.8.1/jacoco-0.8.1.zip
7z x remotecontent?filepath=org/jacoco/jacoco/0.8.1/jacoco-0.8.1.zip
# jacococli.jar lib/jacocoagent.jar is what we need
JAcocoはclassファイルを処理し、cliを参照
# jacoco offline
java -jar jacococli.jar instrument Test.class --dest out
# cp jacococli.jar out && cp lib/jacocoagent.jar out && cd out
# exec Test
java -cp .:jacocoagent.jar Test anotherArgs
# check the code coverage
java -jar jacococli.jar execinfo jacoco.exec
jacocoが見えます.execはコードのオーバーライド実行レポートです.JAcocoはすでにその機能を完成しました.
次に、バイトコードの部分がどれだけ修正されたかを見てみましょう.
javap -c -v Test.class
cd out && javap -c -v Test.class
定数プールにはjacocoagentに依存するこれらのコードが組み込まれていることがわかる.jar.(だから修正したら、このjarパッケージを手動で参照します.)
#42 = Utf8 $jacocoInit
#43 = Utf8 ()[Z
#44 = NameAndType #42:#43 // $jacocoInit:()[Z
#45 = Methodref #21.#44 // Test.$jacocoInit:()[Z
#46 = Utf8 [Z
#47 = Class #46 // "[Z"
#48 = Utf8 $jacocoData
#49 = NameAndType #48:#46 // $jacocoData:[Z
#50 = Fieldref #4.#49 // Test.$jacocoData:[Z
#51 = Long 4767435040597437437l
#53 = String #29 // Test
#54 = Utf8 org/jacoco/agent/rt/internal_c13123e/Offline
#55 = Class #54 // org/jacoco/agent/rt/internal_c13123e/Offline
#56 = Utf8 getProbes
#57 = Utf8 (JLjava/lang/String;I)[Z
#58 = NameAndType #56:#57 // getProbes:(JLjava/lang/String;I)[Z
#59 = Methodref #55.#58 // org/jacoco/agent/rt/internal_c13123e/Offline.getProbes:(JLjava/lang/String;I)[Z
各関数セグメントには、呼び出しのバイトコードがいくつか追加されています.例を挙げます.
public void hi();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #9 // String hi
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
public void hi();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=5, locals=2, args_size=1
0: invokestatic #45 // Method $jacocoInit:()[Z
3: astore_1
4: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #9 // String hi
9: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: bipush 8
15: iconst_1
16: bastore
17: return
LineNumberTable:
line 15: 4
line 16: 12
この2つはdiffを取って、jacocoが何をしたかを見ることができて、主にいくつかのバイトコードを追加しました
#
invokestatic
astore_1 # JVM returnAddress , 1 , returnAddress 1
#
aload_1 # 1
bipush #
iconst_1 # 1 ( )
bastore #
表象から推測すると,各関数には呼び出し回数があり,呼び出す前にこの数を出し,呼び出し後に1を加えて戻す.しかしclassから修正後のバイトコードの保存は見られず,これらの呼び出し回数のデータを
jacoco.exec
に戻すグローバルな方法があると推測される.ドキュメントを振り返ると、ASMが使われていて、よく知っている操作コードがいくつか見えます.少し理解を間違えました.boolean[]arrayを使用しています.
質問に戻ったので、何をしましたか?はい、上記のバイトコードを変更しました.
コードを結合して見ると
git clone https://github.com/jacoco/jacoco
jacoco/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
主に修正されたコードは
private byte[] instrument(final byte[] source) {
final long classId = CRC64.classId(source);
final int originalVersion = BytecodeVersion.get(source);
final byte[] b = BytecodeVersion.downgradeIfNeeded(originalVersion,
source);
final ClassReader reader = new ClassReader(b);
final ClassWriter writer = new ClassWriter(reader, 0) {
@Override
protected String getCommonSuperClass(final String type1,
final String type2) {
throw new IllegalStateException();
}
};
final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory
.createFor(classId, reader, accessorGenerator);
final ClassVisitor visitor = new ClassProbesAdapter(
new ClassInstrumenter(strategy, writer),
InstrSupport.needsFrames(originalVersion));
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
final byte[] instrumented = writer.toByteArray();
BytecodeVersion.set(instrumented, originalVersion);
return instrumented;
}
classの修正はこれらのクラスで行われていることがわかります
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
中に入るとよく知っている定数プールが見え、よく知っているバイトコードの修正が見えます.この部分がofflineの修正です.では、どのように統計しますか.
定数プールには
org/jacoco/agent/rt/internal_c13123e/Offline
の呼び出しが表示されますコードは
jacoco/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Offline.java
public final class Offline {
private static final RuntimeData DATA;
private static final String CONFIG_RESOURCE = "/jacoco-agent.properties";
static {
final Properties config = ConfigLoader.load(CONFIG_RESOURCE,
System.getProperties());
DATA = Agent.getInstance(new AgentOptions(config)).getData();
}
private Offline() {
// no instances
}
/**
* API for offline instrumented classes.
*
* @param classid
* class identifier
* @param classname
* VM class name
* @param probecount
* probe count for this class
* @return probe array instance for this class
*/
public static boolean[] getProbes(final long classid,
final String classname, final int probecount) {
return DATA.getExecutionData(Long.valueOf(classid), classname,
probecount).getProbes();
}
}
getProbesがコードアクセスの統計データをここに格納していることがわかります.ここでclassのバイトコード情報は終わり、次はofflineという方法を呼び出して統計を取ります.
ExecutionDataはデータベースに似ていて、これらのデータが保存されていて、メモリに存在します.
private final long id;
private final String name;
private final boolean[] probes;
保存が必要な場合は、ExecutionDataWriterでディスクにファイルを書き込みます.これはオプションの構成で、
jacoco/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
/**
* Sets whether coverage data should be dumped on exit.
*
* @param dumpOnExit
* true
if coverage data should be written on VM
* exit
*/
public void setDumpOnExit(final boolean dumpOnExit) {
setOption(DUMPONEXIT, dumpOnExit);
}
public static synchronized Agent getInstance(final AgentOptions options) {
if (singleton == null) {
final Agent agent = new Agent(options, IExceptionLogger.SYSTEM_ERR);
agent.startup();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
agent.shutdown();
}
});
singleton = agent;
}
return singleton;
}
/**
* Shutdown the agent again.
*/
public void shutdown() {
try {
if (options.getDumpOnExit()) {
output.writeExecutionData(false);
}
output.shutdown();
if (jmxRegistration != null) {
jmxRegistration.call();
}
} catch (final Exception e) {
logger.logExeption(e);
}
}
簡単に言えばVMが終了したときにHookが追加され、保存された属性が設定されている場合は、終了した時点でメモリの統計をファイルにシーケンス化し、もちろんこのdumpの方法も手動で呼び出すことができます.
PS:nannyでこの文章を見たら、それは私が送ったのです:)