how to do java application’s code coverage with emma?

本文主要讨论如何用emma给Java Application做代码覆盖,区别于java单元测试的code coverage, Java Application一直处于运行状态,所以不能暴力的强行kill进程来收集代码覆盖数据,所以本文梳理下网络上的相关文章,整理下过程,并记录下尝试过程中一些常见问题的解决:

基本步骤分为五步:

(1)修改编译选项:

将编译的选项设置为debug启用,同时要注意debug的level.例如maven compiler默认就启用了debug但是没有将debug的level都设置进去,所以这里注意设置启用和启用的级别。例如对于maven项目中的compile plugin: (如果使用了progard进行混淆,则去掉混淆)

<configuration>
<debug>true</debug>
<debuglevel>lines,source</debuglevel>
</configuration>

(2) 注入:
注入coverage信息,可以使用-ix来过滤指定包:
java emma instr -m overwrite -cp server.jar -ix +com.server* -Dmetadata.out.file=coverage.em

note:

2.1 其中在 “+” 符号后的文件为包含进的文件, “-” 后面的内容为排除在外的文件,例如

-ix +com.foo.*,-com.foo.test.*,-com.foo.*Test*

2.2 要支持EMMA,需要复制emma.jar到lib目录,记得修改权限,例如:jdk1.7.0_60/jre/lib/ext/emma.jar

2.3 有时候加上过滤条件导出并没有生效,显示过滤的文件少了,但是产生的coverage.em基本没有变,可以删掉原先的,估计有merge操作。

(3)修改应用启动的jvm参数并启动应用:

3.1 修改启动应用的jvm参数:
-Demma.rt.control=true

为什么要增加这项,可以查看代码:默认是不启动实时收集功能。

IProperties appProperties = getAppProperties();
if (appProperties != null) {
String property = appProperties.getProperty("rt.control", "false");
enableController = Property.toBoolean(property);
}

if (enableController) {
int port = 47653;

if (appProperties != null) {
String property = appProperties.getProperty("rt.control.port");
if (property != null) {
try {
port = Integer.parseInt(property);
} catch (NumberFormatException ignore) {
System.err
.println("ignoring malformed [rt.control.port] value: "
+ property);
}

ps: 还存在其他很多参数,例如-Demma.coverage.out.file=/var/coverage.ec控制输出文件,-Demma.rt.control.port=12345,用来控制端口

3.2 启动,并查看log来确认是否正常:

应该存在2行log:其中第二行对应3.1要解决的问题。

EMMA: collecting runtime coverage data ...
EMMA: runtime controller started on port [47653]

其中,如果启动失败并提示class format错误,这是应用是JDK1.7编译的,而之前注入使用的emma的注入方式应该是不兼容的,所以可以通过增加JVM启动参数来解决:
1.7使用-XX:-UseSplitVerifier  如果是1.8使用-Xverify:none

(4)执行测试用例,然后使用命令收集数据:

java emma ctl -connect localhost:47653 -command coverage.get,coverage.ec

假设需要合并之前的,执行命令:

java emma merge -input coverage1.ec,coverage2.ec -out coverage.ec

PS: 也可以支持em的Merge.

(5)结合(2)产生的em和(4)产生的ec文件以及源代码的路径产生html报告:

java emma report -r html -sp /xxxx/xxxx/src/main/java -in coverage.em,coverage.ec -Dreport.html.out.file=coverage.html

这里需要注意的是源代码要和产生的统计信息一致,否则假设源代码当中的一个文件已经重命名,那么在html报告中还是提示找不到源文件。

实际测试中,我们还会应用单元测试,产生覆盖率,然后可以与上述产生的覆盖率合并。-in 支持传多个ec,em.

对于单元测试如何产生覆盖数据,可以直接配置EMMA的maven插件,然后使用mvn emma:emma来产生在target目录:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:-UseSplitVerifier</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>emma-maven-plugin</artifactId>
<version>1.0-alpha-3</version>
</plugin>

 

PS:

通过上面可知,对于Coverage数据的过滤只有在注入阶段,在报告阶段并没有,好在本人同事对emma.jar进行了二次开发(emma_rar),支持在上面的第五步Report阶段过滤Class/Package。

具体命令:

java emma report -r html  -props filter.txt   -sp /xxxx/xxxx/src/main/java -in coverage.em,coverage.ec -Dreport.html.out.file=coverage.html

filter.txt 文件内容的格式可以是report.html.filter.class或者report.html.filter.package,例如示例:

report.html.filter.class=com.Tp.java,com.Constants.java   //多个类用,分割。

这里一定要注意的是:

(1)如果是内部类的过滤,一定不要带上外部类的名字,比如不应该是outer.inner.java,而应该是inner.java.这个可以查看EM文件的内容获知。

(2)支持正则表达式。

Notes:

(1) debug log: -Dverbosity.level=trace1

以上即为整个过程的记录以备忘!

 

Eclipse luna with nuwen: Toolchain “MinGW GCC” is not detected

安装完最新版(luna)的eclipse cpp(或eclipse java with cdt plugin)和nuwen后,新建c++ project并没有显示支持mingw toolchain. 打开属性有所提示“Toolchain “MinGW GCC” is not detected”。

而之前的一些eclipse版本是支持的,且没有任何特殊配置。

通过eclipse源码分析可知:新的eclipse由简单的check mingw的路径存在与否改变成check对应32版本的mingw32-gcc.exe和对应64版本的x86_64-w64-mingw32-gcc.exe,而nuwen安装之后存在的仅仅是gcc.exe,将此文件复制成对应的文件名即可呈现支持mingw toolchain.所以这个问题是由eclipse本身的代码改变所引起,与其他无关。

同时从源码可以看出,eclipse 32仅匹配mingw 32,而eclipse 64可以匹配mingw 32和mingw 64.

MinGW 定位的全部源码

新版本的eclipse源码:


	@Override
	public boolean isSupported(IToolChain toolChain, Version version, String instance) {
		IEnvironmentVariable var = new EnvironmentVariableManagerToolChain(toolChain).getVariable(ENV_PATH, true);
		String envPath = var != null ? var.getValue() : null;
		return MinGW.isAvailable(envPath);
	}


}

	/**
	 * Check if MinGW is available in the path.
	 *
	 * @param envPath - list of directories to search for MinGW separated
	 *    by path separator (format of environment variable $PATH)
	 *    or {@code null} to use current $PATH.
	 * @return {@code true} if MinGW is available, {@code false} otherwise.
	 */
	public static boolean isAvailable(String envPath) {
		return isWindowsPlatform && findMingwInPath(envPath) != null;
	}

	private static String findMingwInPath(String envPath) {
		if (envPath == null) {
			// $PATH from user preferences
			IEnvironmentVariable varPath = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariable(ENV_PATH, null, true);
			if (varPath != null) {
				envPath = varPath.getValue();
			}
		}

		String mingwLocation = mingwLocationCache.get(envPath);
		// check if WeakHashMap contains the key as null may be the cached value
		if (mingwLocation == null && !mingwLocationCache.containsKey(envPath)) {
			// Check for MinGW-w64 on Windows 64 bit, see http://mingw-w64.sourceforge.net/
			if (Platform.ARCH_X86_64.equals(Platform.getOSArch())) {
				IPath gcc64Loc = PathUtil.findProgramLocation("x86_64-w64-mingw32-gcc.exe", envPath); //$NON-NLS-1$
				if (gcc64Loc != null) {
					mingwLocation  = gcc64Loc.removeLastSegments(2).toOSString();
				}
			}

			// Look for mingw32-gcc.exe
			if (mingwLocation == null) {
				IPath gccLoc = PathUtil.findProgramLocation("mingw32-gcc.exe", envPath); //$NON-NLS-1$
				if (gccLoc != null) {
					mingwLocation = gccLoc.removeLastSegments(2).toOSString();
				}
			}
			mingwLocationCache.put(envPath, mingwLocation);
		}

		return mingwLocation;
	}

旧版本eclipse代码:


public class MingwIsToolChainSupported implements IManagedIsToolChainSupported {
	@Override
	public boolean isSupported(IToolChain toolChain, Version version, String instance) {
		// Only supported if we can find the mingw bin dir to run the compiler
		return MingwEnvironmentVariableSupplier.getBinDir() != null;
	}