使用新的 class 定义重新编译以进行变异测试
Recompile with new class definition for mutation testing
我正在尝试使用 openHFT/java-runtime-compiler 来改进我的突变测试工具,从大量使用磁盘访问到仅使用内存中编译。
在突变测试中,有两种class:
一个。变异的 class,一个 class 其定义将不断地 manipulated/altered,并重新编译。
乙。 other class,a class 其定义不会改变,即测试用例 class,或变异 class 所需的其他 class =].
通过使用 openHFT/java-runtime-compiler,可以使用下面的代码轻松完成,它是通过为变异的 class 和其他的每次重新编译创建一个新的 classLoader class。
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);
}
效果很好,每次对classA的新定义进行编译时,AClass都会调整为新的定义。
但是,如果顺序颠倒,这将不起作用,例如下面的代码(先加载 BClass,然后加载 AClass),有时需要这样,例如当 AClass 使用 BClass 时。 class A 的重新编译不会调整到新的定义,并且将始终使用用于编译 class 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 class。我已经尝试省略行
//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;
}
非常感谢您的帮助。
已编辑
谢谢 Peter Lawrey,我试过你的建议,但它给出了相同的结果,A class 坚持使用的第一个定义(在第一次迭代中),并且未能 change/use 到新定义(在下一次迭代中)。
我收集了症状,可能的解释是第一次迭代(第一次 class 是 compiled/loaded)与下一次迭代有一些不同的处理方式。从那里我尝试了一些东西。
第一个症状
当我在 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
在第一次迭代中,它给出了正确的输出,"loadClasses Null"(当加载 B 时)因为 loadedClassesMap 没有 classLoader,并给出 "clazz Null"(当加载 B 时) loading A) 因为 loadedClassesMap 有 classLoader 但没有 A classname。
但是在下一次迭代中,(加载A时)它输出"clazz Not Null",似乎A classname已经存储在loadedClassesMap.get(classLoader中),这是不应该发生的。我已尝试清除 CachedCompiler 构造函数中的 loadedClassesMap。
loadedClassesMap.clear();
但它给出了 LinkageError: loader (instance of main/Utama$2): attempted duplicate class definition.
第二个症状
当我检查 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。
如果您想使用一组全新的 classes,我建议您不要使用与 classes 相同的缓存。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
CachedCompiler compiler = new CachedCompiler(null, null)
Class AClass = compiler.loadFromJava( classLoader, "A" , ASourceCode);
Class BClass = compiler.loadFromJava( classLoader, "B" , BSourceCode);
}
这将每次使用新的缓存,并且不受之前测试中加载的 class 的影响。
我正在尝试使用 openHFT/java-runtime-compiler 来改进我的突变测试工具,从大量使用磁盘访问到仅使用内存中编译。
在突变测试中,有两种class: 一个。变异的 class,一个 class 其定义将不断地 manipulated/altered,并重新编译。 乙。 other class,a class 其定义不会改变,即测试用例 class,或变异 class 所需的其他 class =].
通过使用 openHFT/java-runtime-compiler,可以使用下面的代码轻松完成,它是通过为变异的 class 和其他的每次重新编译创建一个新的 classLoader class。
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);
}
效果很好,每次对classA的新定义进行编译时,AClass都会调整为新的定义。
但是,如果顺序颠倒,这将不起作用,例如下面的代码(先加载 BClass,然后加载 AClass),有时需要这样,例如当 AClass 使用 BClass 时。 class A 的重新编译不会调整到新的定义,并且将始终使用用于编译 class 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 class。我已经尝试省略行
//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;
}
非常感谢您的帮助。
已编辑
谢谢 Peter Lawrey,我试过你的建议,但它给出了相同的结果,A class 坚持使用的第一个定义(在第一次迭代中),并且未能 change/use 到新定义(在下一次迭代中)。
我收集了症状,可能的解释是第一次迭代(第一次 class 是 compiled/loaded)与下一次迭代有一些不同的处理方式。从那里我尝试了一些东西。
第一个症状
当我在 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
在第一次迭代中,它给出了正确的输出,"loadClasses Null"(当加载 B 时)因为 loadedClassesMap 没有 classLoader,并给出 "clazz Null"(当加载 B 时) loading A) 因为 loadedClassesMap 有 classLoader 但没有 A classname。
但是在下一次迭代中,(加载A时)它输出"clazz Not Null",似乎A classname已经存储在loadedClassesMap.get(classLoader中),这是不应该发生的。我已尝试清除 CachedCompiler 构造函数中的 loadedClassesMap。
loadedClassesMap.clear();
但它给出了 LinkageError: loader (instance of main/Utama$2): attempted duplicate class definition.
第二个症状
当我检查 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。
如果您想使用一组全新的 classes,我建议您不要使用与 classes 相同的缓存。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
CachedCompiler compiler = new CachedCompiler(null, null)
Class AClass = compiler.loadFromJava( classLoader, "A" , ASourceCode);
Class BClass = compiler.loadFromJava( classLoader, "B" , BSourceCode);
}
这将每次使用新的缓存,并且不受之前测试中加载的 class 的影响。