openHFT/java-runtime-compilerを使用して、ディスク アクセスの多用からインメモリ コンパイルのみの使用まで、ミューテーション テスト ツールを改善しようとしています。
変異テストでは、2 種類のクラスがありました 。 A. 変異クラス、その定義が常に操作/変更され、再コンパイルされるクラス。 B. その他のクラス、その定義が変更されないクラス、つまりテスト ケース クラス、または変更されたクラスが必要とするその他のクラス。
openHFT/java-runtime-compiler を使用すると、以下のコードを使用して簡単に実行できます。これは、変更されたクラスと他のクラスの両方を再コンパイルするたびに新しい classLoader を作成することでした。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
Class AClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "A" , ASourceCode);
Class BClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "B" , BSourceCode);
}
これはうまく機能し、クラス A の新しい定義がコンパイルされるたびに、AClassは新しい定義に適応します。
ただし、以下のコード ( BClassが最初にロードされ、次にAClass ) のように順序が逆になっていると、これは機能しません。クラス A の再コンパイルは、新しい定義に適応せず、クラス A のコンパイルに使用された最初の定義を常に使用します。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
Class BClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "B" , BSourceCode);
Class AClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "A" , ASourceCode);
}
openHFT/java-runtime-compiler ライブラリ (以下のコード) からloadFromJavaクラスを変更する必要があったのではないかと思います。私はすでに行を省略して試しています
//if (clazz != null)
//return clazz;
loadFromJavaが呼び出されるたびに、すべてのソースコード (既にコンパイルされているものも含む) を常に再コンパイルするようにすることを期待していました。しかし、それは間違った結果をもたらします。
機能させるために必要な変更を指摘するのを手伝ってください。
public Class loadFromJava(@NotNull ClassLoader classLoader,
@NotNull String className,
@NotNull String javaCode,
@Nullable PrintWriter writer) throws ClassNotFoundException {
Class clazz = null;
Map<String, Class> loadedClasses;
synchronized (loadedClassesMap) {
loadedClasses = loadedClassesMap.get(classLoader);
if (loadedClasses == null){
loadedClassesMap.put(classLoader, loadedClasses = new LinkedHashMap<String, Class>());
}else{
clazz = loadedClasses.get(className);
}
}
PrintWriter printWriter = (writer == null ? DEFAULT_WRITER : writer);
if (clazz != null)
return clazz;
for (Map.Entry<String, byte[]> entry : compileFromJava(className, javaCode, printWriter).entrySet()) {
String className2 = entry.getKey();
synchronized (loadedClassesMap) {
if (loadedClasses.containsKey(className2))
continue;
}
byte[] bytes = entry.getValue();
if (classDir != null) {
String filename = className2.replaceAll("\\.", '\\' + File.separator) + ".class";
boolean changed = writeBytes(new File(classDir, filename), bytes);
if (changed) {
LOG.info("Updated {} in {}", className2, classDir);
}
}
Class clazz2 = CompilerUtils.defineClass(classLoader, className2, bytes);
synchronized (loadedClassesMap) {
loadedClasses.put(className2, clazz2);
}
}
synchronized (loadedClassesMap) {
loadedClasses.put(className, clazz = classLoader.loadClass(className));
}
return clazz;
}
大変お世話になりました。
編集済み
ピーター・ローリーに感謝します。あなたの提案を試してみましたが、同じ結果が得られました。A クラスは最初に使用された定義に固執し (最初の反復で)、新しい定義への変更/使用に失敗しました (次の反復で)。 .
私は症状を集めました。考えられる説明は、次の反復とは異なる最初の反復 (初めてクラスがコンパイル/ロードされた) の処理があったことです。そこから、いくつかのことを試します。
第 1 の症状
loadFromJava (下) に出力行 (System.out.println) を入れたときでした。
Class clazz = null;
Map<String, Class> loadedClasses;
synchronized (loadedClassesMap) {
loadedClasses = loadedClassesMap.get(classLoader);
if (loadedClasses == null){
loadedClassesMap.put(classLoader, loadedClasses = new LinkedHashMap<String, Class>());
System.out.println("loadedClasses Null "+className);
}else{
clazz = loadedClasses.get(className);
if(clazz == null)
System.out.println("clazz Null "+className);
else
System.out.println("clazz not Null "+className);
}
}
出力は次のとおりです。
1st Iteration (new ClassLoader and new CachedCompiler)
(when loading B):loadedClasses Null
(when loading A):clazz Null
next Iteration (new ClassLoader and new CachedCompiler)
(when loading B):loadedClasses Null
(when loading A):clazz Not Null
最初の反復では、loadClassesMap には classLoader がないため、正しい出力 "loadClasses Null" (B をロードするとき) が返され、loadClassesMap には classLoader があるが、ないため、"clazz Null" (A をロードするとき) が返されました。 t は A クラス名を持っています。
ただし、次の反復では (A をロードするとき)、「clazz Not Null」が出力されます。A クラス名が既に loadedClassesMap.get(classLoader) に格納されているように見えますが、これは発生しないはずです。CachedCompiler コンストラクターで loadedClassesMap をクリアしようとしました。
loadedClassesMap.clear();
しかし、それは LinkageError: loader (main/Utama$2 のインスタンス): 重複したクラス定義を試みました。
2 番目の症状
最初の繰り返しでの差別化のより強い症状は、s_fileManager バッファーをチェックしたときでした。
1st Iteration (new ClassLoader and new CachedCompiler)
(when load B):CompilerUtils.s_fileManager.getAllBuffers().size()=1
(when load A):CompilerUtils.s_fileManager.getAllBuffers().size()=2
Next Iteration (new ClassLoader and new CachedCompiler)
(when load B):CompilerUtils.s_fileManager.getAllBuffers().size()=2
(when load A):CompilerUtils.s_fileManager.getAllBuffers().size()=2
最初のイテレーションは予想どおりでしたが、次のイテレーションでは、s_fileManager バッファーは既にサイズ 2 を取得しているようで、0 にリセットされていません。
CachedCompiler コンストラクター (以下) で FileManager バッファーをクリアしようとしましたが、
CompilerUtils.s_fileManager.clearBuffers();
しかし、ExceptionInInitializerError が発生します。