最近在执行单元测试的时候,发现一个奇怪的问题:本地Junit的单测单独执行,程序正常;但是在集成了JaCoCo覆盖率插件后,执行maven test命令一直会报ArrayIndexOutOfBounds数组越界异常,查了好久,才找到最终原因。
单测代码如下:
@Test
public void getSingleProductProperty(){
String productCode = "C030060008";
List<PropertyOptionVO> propertyOptionVOList = (productCode);
(!(propertyOptionVOList));
}
maven test命令报出的异常:
[20190822-17:46:30.840]-[INFO]-[1566467084465_GqwN]-[Thread-18]-[:202]- Shutting down ExecutorService
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] :62 » ArrayIndexOutOfBounds
[INFO]
[ERROR] Tests run: 41, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] wares-mall-api 1.8.0 ............................... SUCCESS [ 2.386 s][INFO] support-platform-user .............................. SUCCESS [ 25.723 s]
[INFO] mall-bootstrap ..................................... FAILURE [01:49 min]
[INFO] mall-rest 3.3-SNAPSHOT ............................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 05:21 min
[INFO] Finished at: 2019-08-22T17:46:31+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal :maven-surefire-plugin:2.21.0:test (default-test) on project mall-bootstrap: There are test failures.
[ERROR]
[ERROR] Please refer to /Users/ding******/wares-manage-center/mall-bootstrap/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
[ERROR] -> [Help 1]
: Failed to execute goal :maven-surefire-plugin:2.21.0:test (default-test) on project mall-bootstrap: There are test failures.
Please refer to /Users/ding******/wares-manage-center/mall-bootstrap/target/surefire-reports for the individual test results.
Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
at (:213)
at (:154)
at (:146)
at (:117)
at (:81)
at (:56)
at (:128)
at (:305)
at (:192)
at (:105)
at (:954)
at (:288)
at (:192)
at .invoke0 (Native Method)
at (:62)
at (:43)
at (:498)
at (:289)
at (:229)
at (:415)
at (:356)
Caused by: : There are test failures.
Please refer to /Users/ding******/wares-manage-center/mall-bootstrap/target/surefire-reports for the individual test results.
Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
at (:240)
at (:112)
at (:354)
at (:1008)
at (:854)
at (:137)
at (:208)
at (:154)
at (:146)
at (:117)
at (:81)
at (:56)
at (:128)
at (:305)
at (:192)
at (:105)
at (:954)
at (:288)
at (:192)
at .invoke0 (Native Method)
at (:62)
at (:43)
at (:498)
at (:289)
at (:229)
at (:415)
at (:356)
[ERROR]
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] /confluence/display/MAVEN/MojoFailureException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR] mvn <goals> -rf :mall-bootstrap
使用maven -X -e -l,也查不出来具体的信息。一开始不知道是JaCoCo导致的问题,以为是maven的问题,更换了最新的maven版本也不行,因为自己一直搜的关键词是maven + ArrayIndexOutOfBounds,谷歌搜索给了一条搜索记录了,进去之后才发现是JaCoCo覆盖率插件的问题。
JaCoCo GitHub项目上的issues:/jacoco/jacoco/issues/799
JaCoCo官网上FAQ的解释:/jacoco/trunk/doc/
My code uses reflection. Why does it fail when I execute it with JaCoCo?
To collect execution data JaCoCo instruments the classes under test which adds two members to the classes: A private static field $jacocoData
and a private static method $jacocoInit()
. Both members are marked as synthetic.
Please change your code to ignore synthetic members. This is a good practice anyways as also the Java compiler creates synthetic members in certain situation.
是说为了收集执行数据,jacoco在测试中的类加入两个成员:私有静态字段$jacodata 和私有静态方法160;$jacoinit(),这两个成员都是合成(synthetic)的。Java 里面的synthetic机制,有兴趣大家可以看一下,是说Java编译器会给类添加额外的成员信息,这些成员字段在源码级别往往是看不到的,即这些成员不是程序员定义的,比如常见的内部类,Java编译器通过生成一些在源代码中不存在的synthetic方法和类的方式,实现了对private级别的字段和类的访问,从而绕开了语言限制。我检查了一下单测中(productCode)方法的实现,里面果然用到了反射。是一个Bean字段copy的函数:
public static <T> T copyProperties(Object orig, T dest){
if (dest == null || orig == null) return dest;
if (orig instanceof Map) {
Map map = ((Map)orig);
for(Object o:()){
setProperties(dest, (String)o, (o));
}
} else /* if (orig is a standard JavaBean) */ {
Class<?> clazz = ();
while(().equals(())==false){
for(Field origField:()){
Object value = null;
try {
(true);
value = (orig);
} catch (IllegalArgumentException e1) {
();
} catch (IllegalAccessException e1) {
();
}
setProperties(dest, (), value);
}
clazz = ();
}
}
return dest;
}
在JaCoCo执行的时候,修改了字节码文件(比如添加了synthetic成员),导致了错误,因此如果修改的话,可以看到JDK 反射涉及的Class、Method、Field都有synthetic相关的处理,并且都有一个函数 boolean isSynthetic(); 用来判断一个类或者类的成员函数或者字段是不是合成的。如果JaCoCo上涉及反射的单测出错了,可以考虑用这个方法排除synthetic合成成员的干扰。
关于Java compiler synthetic的介绍:/core-java/java-synthetic-class-method-field/
这次排查这个问题耗费不少时间,有以下教训:自己给项目选用了JaCoCo做单测覆盖率统计插件,没有仔细阅读官方文档资料,以前没有太关注过一些官网上的FAQ,这是不正确的。很多问题FAQ上都表明了,所以要养成看FAQ的好习惯。另外,排查问题的时候,可以去所在的GitHub项目上,找一找对应的issues,往往能事半功倍。