Github #525: Simple Command Line Interface
diff --git a/jacoco/assembly.xml b/jacoco/assembly.xml
index b25483f..81a3938 100644
--- a/jacoco/assembly.xml
+++ b/jacoco/assembly.xml
@@ -65,6 +65,14 @@
</dependencySet>
<dependencySet>
<outputDirectory>lib</outputDirectory>
+ <outputFileNameMapping>jacococli.jar</outputFileNameMapping>
+ <useProjectArtifact>false</useProjectArtifact>
+ <includes>
+ <include>${project.groupId}:org.jacoco.cli:jar:nodeps</include>
+ </includes>
+ </dependencySet>
+ <dependencySet>
+ <outputDirectory>lib</outputDirectory>
<outputFileNameMapping>jacocoagent.jar</outputFileNameMapping>
<useProjectArtifact>false</useProjectArtifact>
<includes>
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 5a1875d..0e0787a 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -55,6 +55,12 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.cli</artifactId>
+ <version>${project.version}</version>
+ <classifier>nodeps</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>org.jacoco.examples</artifactId>
<version>${project.version}</version>
<type>zip</type>
@@ -104,8 +110,8 @@
<configuration>
<rules>
<requireFilesSize>
- <maxsize>3500000</maxsize>
- <minsize>2500000</minsize>
+ <maxsize>3600000</maxsize>
+ <minsize>3000000</minsize>
<files>
<file>${project.build.directory}/jacoco-${qualified.bundle.version}.zip</file>
</files>
diff --git a/org.jacoco.build/pom.xml b/org.jacoco.build/pom.xml
index e746d6a..655b8db 100644
--- a/org.jacoco.build/pom.xml
+++ b/org.jacoco.build/pom.xml
@@ -94,12 +94,12 @@
<module>../org.jacoco.agent.rt</module>
<module>../org.jacoco.agent</module>
<module>../org.jacoco.ant</module>
-
+ <module>../org.jacoco.cli</module>
+ <module>../org.jacoco.examples</module>
<module>../jacoco-maven-plugin</module>
<module>../org.jacoco.tests</module>
- <module>../org.jacoco.examples</module>
<module>../org.jacoco.doc</module>
<module>../jacoco</module>
</modules>
@@ -143,6 +143,7 @@
<!-- Dependencies versions -->
<asm.version>5.2</asm.version>
<ant.version>1.7.1</ant.version>
+ <args4j.version>2.0.28</args4j.version>
<junit.version>4.8.2</junit.version>
<!-- ================== -->
@@ -198,6 +199,11 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>org.jacoco.examples</artifactId>
<version>${project.version}</version>
</dependency>
@@ -223,6 +229,11 @@
<version>1.2</version>
</dependency>
<dependency>
+ <groupId>args4j</groupId>
+ <artifactId>args4j</artifactId>
+ <version>${args4j.version}</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
@@ -394,6 +405,11 @@
<artifactId>xml-maven-plugin</artifactId>
<version>1.0</version>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ </plugin>
<!-- Third-party plugins -->
<plugin>
<groupId>org.codehaus.groovy.maven</groupId>
diff --git a/org.jacoco.cli.test/.classpath b/org.jacoco.cli.test/.classpath
new file mode 100644
index 0000000..0ed344a
--- /dev/null
+++ b/org.jacoco.cli.test/.classpath
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/org.jacoco.cli.test/.project b/org.jacoco.cli.test/.project
new file mode 100644
index 0000000..fbd366b
--- /dev/null
+++ b/org.jacoco.cli.test/.project
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.jacoco.cli.test</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+ <linkedResources>
+ <link>
+ <name>.settings</name>
+ <type>2</type>
+ <locationURI>PARENT-1-PROJECT_LOC/org.jacoco.core.test/.settings</locationURI>
+ </link>
+ </linkedResources>
+</projectDescription>
diff --git a/org.jacoco.cli.test/about.html b/org.jacoco.cli.test/about.html
new file mode 100644
index 0000000..d31112d
--- /dev/null
+++ b/org.jacoco.cli.test/about.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>
+ @build.date@
+</p>
+
+<h3>License</h3>
+
+<p>
+ All Content in this plug-in is made available by Mountainminds GmbH & Co.
+ KG, Munich. Unless otherwise indicated below, the Content is provided to you
+ under the terms and conditions of the Eclipse Public License Version 1.0
+ ("EPL"). A copy of the EPL is available at
+ <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+ For purposes of the EPL, "Program" will mean the Content.
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/org.jacoco.cli.test/pom.xml b/org.jacoco.cli.test/pom.xml
new file mode 100644
index 0000000..b0a5b61
--- /dev/null
+++ b/org.jacoco.cli.test/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+ Evgeny Mandrikov - initial API and implementation
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.jacoco</groupId>
+ <artifactId>org.jacoco.tests</artifactId>
+ <version>0.7.10-SNAPSHOT</version>
+ <relativePath>../org.jacoco.tests</relativePath>
+ </parent>
+
+ <artifactId>org.jacoco.cli.test</artifactId>
+
+ <name>JaCoCo :: Test :: Command Line Interface</name>
+
+ <properties>
+ <jacoco.includes>org.jacoco.cli.*</jacoco.includes>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.cli</artifactId>
+ <!--
+ <version>${project.version}</version>
+ <classifier>nodeps</classifier>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ -->
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/CommandTestBase.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/CommandTestBase.java
new file mode 100644
index 0000000..4d12a37
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/CommandTestBase.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+import org.junit.Before;
+
+/**
+ * Base class for command tests.
+ */
+public abstract class CommandTestBase {
+
+ protected StringWriter out;
+ protected StringWriter err;
+ protected int result;
+
+ @Before
+ public void before() {
+ out = new StringWriter();
+ err = new StringWriter();
+ }
+
+ protected int execute(String... args) throws Exception {
+ result = new Main(args).execute(new PrintWriter(out),
+ new PrintWriter(err));
+ return result;
+ }
+
+ protected void assertOk() {
+ assertEquals(err.toString(), 0, result);
+ }
+
+ protected void assertFailure() {
+ assertEquals(-1, result);
+ }
+
+ protected void assertNoOutput(StringWriter buffer) {
+ assertEquals("", buffer.toString());
+ }
+
+ protected void assertContains(String expected, StringWriter buffer) {
+ final String content = buffer.toString();
+ assertTrue(content, content.contains(expected));
+ }
+
+ protected String getClassPath() {
+ final String name = getClass().getName();
+ final String res = "/" + name.replace('.', '/') + ".class";
+ String loc = getClass().getResource(res).getFile();
+ try {
+ loc = URLDecoder.decode(loc, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ }
+ return loc.substring(0, loc.length() - res.length());
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/MainTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/MainTest.java
new file mode 100644
index 0000000..7a79d1e
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/MainTest.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link Main}.
+ */
+public class MainTest extends CommandTestBase {
+
+ @Test
+ public void shouldPrintUsage_whenNoArgumentsGiven() throws Exception {
+ execute();
+
+ assertFailure();
+ assertNoOutput(out);
+ assertContains("Argument \"<command>\" is required", err);
+ assertContains("Usage: java -jar jacococli.jar -help | <command>", err);
+ assertContains("Command line interface for JaCoCo.", err);
+ }
+
+ @Test
+ public void shouldPrintErrorMessage_whenInvalidCommandIsGiven()
+ throws Exception {
+ execute("foo");
+
+ assertFailure();
+ assertNoOutput(out);
+ assertContains("\"foo\" is not a valid value for \"<command>\"", err);
+ assertContains("Usage: java -jar jacococli.jar -help | <command>", err);
+ }
+
+ @Test
+ public void shouldPrintGeneralHelp_whenHelpOptionIsGiven()
+ throws Exception {
+ execute("-help");
+
+ assertOk();
+ assertNoOutput(err);
+ assertContains("Usage: java -jar jacococli.jar -help | <command>", out);
+ assertContains("<command> : dump|instrument|merge|report", out);
+ }
+
+ @Test
+ public void shouldPrintCommandHelp_whenHelpOptionIsGiven()
+ throws Exception {
+ execute("dump", "-help");
+
+ assertOk();
+ assertNoOutput(err);
+ assertContains("Usage: java -jar jacococli.jar dump", out);
+ assertContains(
+ "Request execution data from a JaCoCo agent running in 'tcpserver' output mode.",
+ out);
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ClassInfoTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ClassInfoTest.java
new file mode 100644
index 0000000..d812d0c
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ClassInfoTest.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link ExecInfo}.
+ */
+public class ClassInfoTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenInvalidArgumentIsGiven() throws Exception {
+ execute("classinfo", "-invalid");
+
+ assertFailure();
+ assertContains("\"-invalid\" is not a valid option", err);
+ assertContains(
+ "java -jar jacococli.jar classinfo [<classlocations> ...]",
+ err);
+ }
+
+ @Test
+ public void shouldProvideClassInfoInfo() throws Exception {
+ execute("classinfo", getClassPath());
+
+ assertOk();
+ assertContains(
+ "class name: org/jacoco/cli/internal/commands/ClassInfoTest",
+ out);
+ assertContains("methods: 3", out);
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/DumpTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/DumpTest.java
new file mode 100644
index 0000000..6f7616e
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/DumpTest.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.jacoco.core.runtime.IRemoteCommandVisitor;
+import org.jacoco.core.runtime.RemoteControlReader;
+import org.jacoco.core.runtime.RemoteControlWriter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link Dump}.
+ */
+public class DumpTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenNoArgumentsGiven() throws Exception {
+ execute("dump");
+ assertFailure();
+ assertContains("Option \"-destfile\" is required", err);
+ assertContains("java -jar jacococli.jar dump [-address <address>]",
+ err);
+ }
+
+ @Test
+ public void shouldWriteDump() throws Exception {
+
+ File execfile = new File(tmp.getRoot(), "jacoco.exec");
+ int port = startMockServer();
+
+ execute("dump", "-destfile", execfile.getAbsolutePath(), "-port",
+ String.valueOf(port));
+
+ assertOk();
+ assertContains("[INFO] Connecting to ", out);
+ assertContains("[INFO] Writing execution data to "
+ + execfile.getAbsolutePath(), out);
+ assertTrue(execfile.exists());
+ }
+
+ @Test
+ public void shouldLogConnectionError() throws Exception {
+
+ File execfile = new File(tmp.getRoot(), "jacoco.exec");
+ int port = unusedPort();
+
+ try {
+ execute("dump", "-destfile", execfile.getAbsolutePath(), "-port",
+ String.valueOf(port), "-retry", "1");
+ fail("IOException expected");
+ } catch (IOException ignore) {
+ }
+
+ assertContains("[WARN] Connection refused", err);
+ }
+
+ private int startMockServer() throws IOException {
+ final ServerSocket serverSocket = new ServerSocket(0, 0,
+ InetAddress.getByName(null));
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ serveRequest(serverSocket.accept());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }.start();
+ return serverSocket.getLocalPort();
+ }
+
+ private void serveRequest(Socket socket) throws IOException {
+ final RemoteControlWriter writer = new RemoteControlWriter(
+ socket.getOutputStream());
+ final RemoteControlReader reader = new RemoteControlReader(
+ socket.getInputStream());
+ reader.setRemoteCommandVisitor(new IRemoteCommandVisitor() {
+
+ public void visitDumpCommand(boolean dump, boolean reset)
+ throws IOException {
+ writer.sendCmdOk();
+ }
+ });
+ while (reader.read()) {
+ }
+ }
+
+ private int unusedPort() throws IOException {
+ final ServerSocket serverSocket = new ServerSocket(0, 0,
+ InetAddress.getByName(null));
+ final int port = serverSocket.getLocalPort();
+ serverSocket.close();
+ return port;
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ExecInfoTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ExecInfoTest.java
new file mode 100644
index 0000000..15a1b99
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ExecInfoTest.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.data.ExecutionDataWriter;
+import org.jacoco.core.data.SessionInfo;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link ExecInfo}.
+ */
+public class ExecInfoTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenInvalidArgumentIsGiven() throws Exception {
+ execute("execinfo", "-invalid");
+
+ assertFailure();
+ assertContains("\"-invalid\" is not a valid option", err);
+ assertContains("java -jar jacococli.jar execinfo [<execfiles> ...]",
+ err);
+ }
+
+ @Test
+ public void shouldProvideExecutionDataInfo() throws Exception {
+ File execfile = createExecFile();
+
+ execute("execinfo", execfile.getAbsolutePath());
+
+ assertOk();
+ assertContains("[INFO] Loading exec file " + execfile.getAbsolutePath(),
+ out);
+ assertContains("CLASS ID HITS/PROBES CLASS NAME", out);
+ assertContains("Session \"testid\":", out);
+ assertContains("0000000000001234 2 of 3 foo/MyClass", out);
+ }
+
+ private File createExecFile() throws IOException {
+ File f = new File(tmp.getRoot(), "test.exec");
+ final FileOutputStream out = new FileOutputStream(f);
+ final ExecutionDataWriter writer = new ExecutionDataWriter(out);
+ writer.visitSessionInfo(new SessionInfo("testid", 1, 2));
+ writer.visitClassExecution(new ExecutionData(0x1234, "foo/MyClass",
+ new boolean[] { false, true, true }));
+ out.close();
+ return f;
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java
new file mode 100644
index 0000000..7ea8b5c
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Unit tests for {@link Instrument}.
+ */
+public class InstrumentTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenNoArgumentsGiven() throws Exception {
+ execute("instrument");
+ assertFailure();
+ assertContains("Option \"-destdir\" is required", err);
+ assertContains(
+ "Usage: java -jar jacococli.jar instrument [<sourcefiles> ...]",
+ err);
+ }
+
+ @Test
+ public void shouldInstrumentClassFiles() throws Exception {
+ File destdir = tmp.getRoot();
+
+ execute("instrument", "-destdir", destdir.getAbsolutePath(),
+ getClassPath());
+
+ assertOk();
+ assertContains("[INFO] 11 classes instrumented to "
+ + destdir.getAbsolutePath(), out);
+
+ // non class-file resources are copied:
+ assertTrue(new File(destdir, "about.html").isFile());
+
+ assertInstrumented(new File(destdir,
+ "org/jacoco/cli/internal/commands/InstrumentTest.class"));
+ }
+
+ @Test
+ public void shouldNotInstrumentAnything_whenNoSourceIsGiven()
+ throws Exception {
+ File destdir = tmp.getRoot();
+
+ execute("instrument", "-destdir", destdir.getAbsolutePath());
+
+ assertOk();
+ assertArrayEquals(new String[0], destdir.list());
+ }
+
+ @Test
+ public void shouldNotCreateTargetFile_whenSourceClassFileIsBroken()
+ throws Exception {
+ File srcdir = new File(tmp.getRoot(), "src");
+ srcdir.mkdir();
+ File destdir = new File(tmp.getRoot(), "sdest");
+ destdir.mkdir();
+
+ OutputStream out = new FileOutputStream(
+ new File(srcdir, "Broken.class"));
+ out.write((byte) 0xca);
+ out.write((byte) 0xfe);
+ out.write((byte) 0xba);
+ out.write((byte) 0xbe);
+ out.write((byte) 0x00);
+ out.write((byte) 0x00);
+ out.write((byte) 0x00);
+ out.write((byte) 50);
+ out.close();
+
+ try {
+ execute("instrument", "-destdir", destdir.getAbsolutePath(),
+ srcdir.getAbsolutePath());
+ fail("exception expected");
+ } catch (IOException expected) {
+ }
+
+ assertFalse(new File(destdir, "Broken.class").exists());
+ }
+
+ private void assertInstrumented(File classfile) throws IOException {
+ InputStream in = new FileInputStream(classfile);
+ ClassReader reader = new ClassReader(in);
+ in.close();
+ final Set<String> fields = new HashSet<String>();
+ reader.accept(new ClassVisitor(Opcodes.ASM5) {
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ fields.add(name);
+ return null;
+ }
+ }, 0);
+ assertTrue(fields.contains("$jacocoData"));
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/MergeTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/MergeTest.java
new file mode 100644
index 0000000..b6d3edc
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/MergeTest.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.data.ExecutionDataWriter;
+import org.jacoco.core.tools.ExecFileLoader;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link Merge}.
+ */
+public class MergeTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenNoArgumentsGiven() throws Exception {
+ execute("merge");
+
+ assertFailure();
+ assertContains("Option \"-destfile\" is required", err);
+ assertContains("java -jar jacococli.jar merge [<execfiles> ...]", err);
+ }
+
+ @Test
+ public void shouldMergeExecFiles() throws Exception {
+ File a = createExecFile("a");
+ File b = createExecFile("b");
+ File c = createExecFile("c");
+ File dest = new File(tmp.getRoot(), "merged.exec");
+
+ execute("merge", "-destfile", dest.getAbsolutePath(),
+ a.getAbsolutePath(), b.getAbsolutePath(), c.getAbsolutePath());
+
+ assertOk();
+ Set<String> names = loadExecFile(dest);
+ assertEquals(new HashSet<String>(Arrays.asList("a", "b", "c")), names);
+ }
+
+ private File createExecFile(String name) throws IOException {
+ File file = new File(tmp.getRoot(), name + ".exec");
+ final FileOutputStream execout = new FileOutputStream(file);
+ ExecutionDataWriter writer = new ExecutionDataWriter(execout);
+ writer.visitClassExecution(new ExecutionData(name.hashCode(), name,
+ new boolean[] { true }));
+ execout.close();
+ return file;
+ }
+
+ private Set<String> loadExecFile(File file) throws IOException {
+ ExecFileLoader loader = new ExecFileLoader();
+ loader.load(file);
+ Set<String> names = new HashSet<String>();
+ for (ExecutionData d : loader.getExecutionDataStore().getContents()) {
+ names.add(d.getName());
+ }
+ return names;
+ }
+
+}
diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ReportTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ReportTest.java
new file mode 100644
index 0000000..1c56088
--- /dev/null
+++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ReportTest.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+import org.jacoco.cli.internal.CommandTestBase;
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.data.ExecutionDataWriter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Unit tests for {@link Report}.
+ */
+public class ReportTest extends CommandTestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void shouldPrintUsage_whenNoArgumentsGiven() throws Exception {
+ execute("report");
+
+ assertFailure();
+ assertContains("Option \"-classfiles\" is required", err);
+ assertContains(
+ "Usage: java -jar jacococli.jar report [<execfiles> ...]", err);
+ }
+
+ @Test
+ public void shouldPrintNumberOfAnalyzedClasses() throws Exception {
+ execute("report", "-classfiles", getClassPath());
+
+ assertOk();
+ assertContains("[INFO] Writing report with 11 classes.", out);
+ }
+
+ @Test
+ public void shouldPrintWarning_whenExecDataDoesNotMatch() throws Exception {
+ File exec = new File(tmp.getRoot(), "jacoco.exec");
+ final FileOutputStream execout = new FileOutputStream(exec);
+ ExecutionDataWriter writer = new ExecutionDataWriter(execout);
+ // Add probably invalid id for this test class:
+ writer.visitClassExecution(
+ new ExecutionData(0x123, getClass().getName().replace('.', '/'),
+ new boolean[] { true }));
+ execout.close();
+
+ execute("report", exec.getAbsolutePath(), "-classfiles",
+ getClassPath());
+
+ assertOk();
+ assertContains("[WARN] Some classes do not match with execution data.",
+ out);
+ assertContains(
+ "[WARN] For report generation the same class files must be used as at runtime.",
+ out);
+ assertContains(
+ "[WARN] Execution data for class org/jacoco/cli/internal/commands/ReportTest does not match.",
+ out);
+ }
+
+ @Test
+ public void shouldCreateXmlReport_whenXmlOptionIsProvided()
+ throws Exception {
+ File xml = new File(tmp.getRoot(), "coverage.xml");
+
+ execute("report", "-classfiles", getClassPath(), "-xml",
+ xml.getAbsolutePath());
+
+ assertOk();
+ assertTrue(xml.isFile());
+ }
+
+ @Test
+ public void shouldCreateCsvReport_whenXmlOptionIsProvided()
+ throws Exception {
+ File csv = new File(tmp.getRoot(), "coverage.csv");
+
+ execute("report", "-classfiles", getClassPath(), "-csv",
+ csv.getAbsolutePath());
+
+ assertOk();
+ assertTrue(csv.isFile());
+ }
+
+ @Test
+ public void shouldCreateHtmlReport_whenHtmlOptionIsProvided()
+ throws Exception {
+ File html = new File(tmp.getRoot(), "coverage");
+
+ execute("report", "-classfiles", getClassPath(), "-sourcefiles",
+ "./src", "-html", html.getAbsolutePath());
+
+ assertOk();
+ assertTrue(html.isDirectory());
+ assertTrue(new File(html,
+ "org.jacoco.cli.internal.commands/ReportTest.html").isFile());
+ assertTrue(new File(html,
+ "org.jacoco.cli.internal.commands/ReportTest.java.html")
+ .isFile());
+ }
+
+}
diff --git a/org.jacoco.cli/.classpath b/org.jacoco.cli/.classpath
new file mode 100644
index 0000000..0ed344a
--- /dev/null
+++ b/org.jacoco.cli/.classpath
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/org.jacoco.cli/.gitignore b/org.jacoco.cli/.gitignore
new file mode 100644
index 0000000..4dc0091
--- /dev/null
+++ b/org.jacoco.cli/.gitignore
@@ -0,0 +1,2 @@
+/target
+/bin
diff --git a/org.jacoco.cli/.project b/org.jacoco.cli/.project
new file mode 100644
index 0000000..828621a
--- /dev/null
+++ b/org.jacoco.cli/.project
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.jacoco.cli</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+ <linkedResources>
+ <link>
+ <name>.settings</name>
+ <type>2</type>
+ <locationURI>PARENT-1-PROJECT_LOC/org.jacoco.core/.settings</locationURI>
+ </link>
+ </linkedResources>
+</projectDescription>
diff --git a/org.jacoco.cli/about.html b/org.jacoco.cli/about.html
new file mode 100644
index 0000000..d31112d
--- /dev/null
+++ b/org.jacoco.cli/about.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>
+ @build.date@
+</p>
+
+<h3>License</h3>
+
+<p>
+ All Content in this plug-in is made available by Mountainminds GmbH & Co.
+ KG, Munich. Unless otherwise indicated below, the Content is provided to you
+ under the terms and conditions of the Eclipse Public License Version 1.0
+ ("EPL"). A copy of the EPL is available at
+ <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+ For purposes of the EPL, "Program" will mean the Content.
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/org.jacoco.cli/pom.xml b/org.jacoco.cli/pom.xml
new file mode 100644
index 0000000..553472e
--- /dev/null
+++ b/org.jacoco.cli/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+ Marc R. Hoffmann - initial API and implementation
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.jacoco</groupId>
+ <artifactId>org.jacoco.build</artifactId>
+ <version>0.7.10-SNAPSHOT</version>
+ <relativePath>../org.jacoco.build</relativePath>
+ </parent>
+
+ <artifactId>org.jacoco.cli</artifactId>
+
+ <name>JaCoCo :: Command Line Interface</name>
+ <description>JaCoCo Command Line Interface</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.report</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>args4j</groupId>
+ <artifactId>args4j</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>org.jacoco.cli.internal.XmlDocumentation</mainClass>
+ <arguments>
+ <argument>${project.build.directory}/generated-documentation/cli.xml</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>nodeps</shadedClassifierName>
+ <minimizeJar>true</minimizeJar>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Main-Class>org.jacoco.cli.internal.Main</Main-Class>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/Command.java b/org.jacoco.cli/src/org/jacoco/cli/internal/Command.java
new file mode 100644
index 0000000..5e83b8b
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/Command.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.kohsuke.args4j.Option;
+
+/**
+ * Common interface for all commands.
+ */
+public abstract class Command {
+
+ /**
+ * Common command line prefix.
+ */
+ public static final String JAVACMD = "java -jar jacococli.jar ";
+
+ /**
+ * Flag whether help should be printed for this command.
+ */
+ @Option(name = "-help", usage = "show help", help = true)
+ public boolean help = false;
+
+ /**
+ * @return Short description of the command.
+ */
+ public abstract String description();
+
+ /**
+ * @return name of the command
+ */
+ public String name() {
+ return getClass().getSimpleName().toLowerCase();
+ }
+
+ /**
+ * @param parser
+ * parser for this command
+ * @return usage string displayed for help
+ */
+ public String usage(final CommandParser parser) {
+ final StringWriter writer = new StringWriter();
+ parser.printSingleLineUsage(writer, null);
+ return JAVACMD + name() + writer;
+ }
+
+ /**
+ * Executes the given command.
+ *
+ * @param out
+ * std out
+ * @param err
+ * std err
+ * @return exit code, should be 0 for normal operation
+ * @throws Exception
+ * any exception that my occur during execution
+ */
+ public abstract int execute(PrintWriter out, PrintWriter err)
+ throws Exception;
+
+ /**
+ * Prints textual help for this command.
+ *
+ * @param writer
+ * output destination
+ */
+ protected void printHelp(final PrintWriter writer) {
+ final CommandParser parser = new CommandParser(this);
+ writer.println("Usage: " + parser.getCommand().usage(parser));
+ writer.println(description());
+ parser.printUsage(writer, null);
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/CommandHandler.java b/org.jacoco.cli/src/org/jacoco/cli/internal/CommandHandler.java
new file mode 100644
index 0000000..3c6a2a5
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/CommandHandler.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import java.util.AbstractList;
+
+import org.jacoco.cli.internal.commands.AllCommands;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.Messages;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+/**
+ * {@link OptionHandler} which uses {@link CommandParser} internally to provide
+ * help context also for sub-commands.
+ */
+public class CommandHandler extends OptionHandler<Command> {
+
+ /**
+ * This constructor is required by the args4j framework.
+ *
+ * @param parser
+ * @param option
+ * @param setter
+ */
+ public CommandHandler(final CmdLineParser parser, final OptionDef option,
+ final Setter<Object> setter) {
+ super(parser,
+ new OptionDef(AllCommands.names(), "<command>",
+ option.required(), option.help(), option.hidden(),
+ CommandHandler.class, option.isMultiValued()) {
+ }, setter);
+ }
+
+ @Override
+ public int parseArguments(final Parameters params) throws CmdLineException {
+ final String subCmd = params.getParameter(0);
+
+ for (final Command c : AllCommands.get()) {
+ if (c.name().equals(subCmd)) {
+ parseSubArguments(c, params);
+ setter.addValue(c);
+ return params.size(); // consume all the remaining tokens
+ }
+ }
+
+ throw new CmdLineException(owner,
+ Messages.ILLEGAL_OPERAND.format(option.toString(), subCmd));
+ }
+
+ private void parseSubArguments(final Command c, final Parameters params)
+ throws CmdLineException {
+ final CmdLineParser p = new CommandParser(c);
+ p.parseArgument(new AbstractList<String>() {
+ @Override
+ public String get(final int index) {
+ try {
+ return params.getParameter(index + 1);
+ } catch (final CmdLineException e) {
+ // invalid index was accessed.
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public int size() {
+ return params.size() - 1;
+ }
+ });
+ }
+
+ @Override
+ public String getDefaultMetaVariable() {
+ return "<command>";
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/CommandParser.java b/org.jacoco.cli/src/org/jacoco/cli/internal/CommandParser.java
new file mode 100644
index 0000000..0e31093
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/CommandParser.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import org.kohsuke.args4j.CmdLineParser;
+
+/**
+ * Parser which remembers the parsed command to have additional context
+ * information to produce help output.
+ */
+public class CommandParser extends CmdLineParser {
+
+ private final Command command;
+
+ CommandParser(final Command command) {
+ super(command);
+ this.command = command;
+ }
+
+ Command getCommand() {
+ return command;
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/Main.java b/org.jacoco.cli/src/org/jacoco/cli/internal/Main.java
new file mode 100644
index 0000000..63a464d
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/Main.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import java.io.PrintWriter;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
+
+/**
+ * Entry point for all command line operations.
+ */
+public class Main extends Command {
+
+ private final String[] args;
+
+ Main(final String... args) {
+ this.args = args;
+ }
+
+ @Argument(handler = CommandHandler.class, required = true)
+ Command command;
+
+ @Override
+ public String description() {
+ return "Command line interface for JaCoCo.";
+ }
+
+ @Override
+ public String usage(final CommandParser parser) {
+ return JAVACMD + "-help | <command>";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws Exception {
+
+ final CommandParser mainParser = new CommandParser(this);
+ try {
+ mainParser.parseArgument(args);
+ } catch (final CmdLineException e) {
+ err.println(e.getMessage());
+ err.println();
+ ((CommandParser) e.getParser()).getCommand().printHelp(err);
+ return -1;
+ }
+
+ if (help) {
+ printHelp(out);
+ return 0;
+ } else if (command.help) {
+ command.printHelp(out);
+ return 0;
+ } else {
+ return command.execute(out, err);
+ }
+ }
+
+ /**
+ * Main entry point for program invocations.
+ *
+ * @param args
+ * program arguments
+ * @throws Exception
+ * All internal exceptions are directly passed on to get printed
+ * on the console
+ */
+ public static void main(final String... args) throws Exception {
+ new Main(args).execute(new PrintWriter(System.out),
+ new PrintWriter(System.err));
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/XmlDocumentation.java b/org.jacoco.cli/src/org/jacoco/cli/internal/XmlDocumentation.java
new file mode 100644
index 0000000..e10e847
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/XmlDocumentation.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.jacoco.cli.internal.commands.AllCommands;
+import org.jacoco.report.internal.xml.XMLDocument;
+import org.jacoco.report.internal.xml.XMLElement;
+import org.kohsuke.args4j.spi.OptionHandler;
+
+/**
+ * Internal utility to dump all command descriptions as XML.
+ */
+public class XmlDocumentation {
+
+ private static void writeCommand(final Command command,
+ final XMLElement parent) throws IOException {
+ final CommandParser parser = new CommandParser(command);
+ final XMLElement element = parent.element("command");
+ element.attr("name", command.name());
+ element.element("usage").text(command.usage(parser));
+ element.element("description").text(command.description());
+ writeOptions(element, parser.getArguments());
+ writeOptions(element, parser.getOptions());
+ }
+
+ private static void writeOptions(final XMLElement parent,
+ @SuppressWarnings("rawtypes") final List<OptionHandler> list)
+ throws IOException {
+ for (final OptionHandler<?> o : list) {
+ final XMLElement optionNode = parent.element("option");
+ optionNode.attr("required", String.valueOf(o.option.required()));
+ optionNode.element("usage").text(o.getNameAndMeta(null));
+ optionNode.element("description").text(o.option.usage());
+ }
+ }
+
+ /**
+ * Called during the build process.
+ *
+ * @param args
+ * @throws IOException
+ */
+ public static void main(final String... args) throws IOException {
+ final File file = new File(args[0]);
+ file.getParentFile().mkdirs();
+
+ final XMLElement root = new XMLDocument("documentation", null, null,
+ "UTF-8", true, new FileOutputStream(file));
+
+ for (final Command c : AllCommands.get()) {
+ writeCommand(c, root);
+ }
+
+ root.close();
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/AllCommands.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/AllCommands.java
new file mode 100644
index 0000000..accc7fb
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/AllCommands.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+
+/**
+ * List of all available commands.
+ */
+public class AllCommands {
+
+ /**
+ * @return list of new instances of all available commands
+ */
+ public static List<Command> get() {
+ return Arrays.asList(new Dump(), new Instrument(), new Merge(),
+ new Report(), new ClassInfo(), new ExecInfo());
+ }
+
+ /**
+ * @return String containing all available command names
+ */
+ public static String names() {
+ final StringBuilder sb = new StringBuilder();
+ for (final Command c : get()) {
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(c.name());
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ClassInfo.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ClassInfo.java
new file mode 100644
index 0000000..6cf66e1
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ClassInfo.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.analysis.Analyzer;
+import org.jacoco.core.analysis.IClassCoverage;
+import org.jacoco.core.analysis.ICoverageVisitor;
+import org.jacoco.core.data.ExecutionDataStore;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * The <code>classinfo</code> command.
+ */
+public class ClassInfo extends Command {
+
+ @Argument(usage = "location of Java class files", metaVar = "<classlocations>")
+ List<File> classfiles = new ArrayList<File>();
+
+ @Override
+ public String description() {
+ return "Print information about Java class files at the provided location.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws IOException {
+ final Analyzer analyzer = new Analyzer(new ExecutionDataStore(),
+ new ICoverageVisitor() {
+ public void visitCoverage(final IClassCoverage coverage) {
+ print(coverage, out);
+ }
+ });
+
+ for (final File file : classfiles) {
+ analyzer.analyzeAll(file);
+ }
+ return 0;
+ }
+
+ private void print(final IClassCoverage coverage, final PrintWriter out) {
+ out.printf("class name: %s%n", coverage.getName());
+ out.printf("class id: %016x%n", Long.valueOf(coverage.getId()));
+ out.printf("instructions: %s%n", Integer
+ .valueOf(coverage.getInstructionCounter().getTotalCount()));
+ out.printf("branches: %s%n",
+ Integer.valueOf(coverage.getBranchCounter().getTotalCount()));
+ out.printf("lines: %s%n",
+ Integer.valueOf(coverage.getLineCounter().getTotalCount()));
+ out.printf("methods: %s%n",
+ Integer.valueOf(coverage.getMethodCounter().getTotalCount()));
+ out.printf("complexity: %s%n%n", Integer
+ .valueOf(coverage.getComplexityCounter().getTotalCount()));
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Dump.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Dump.java
new file mode 100644
index 0000000..d56a2a1
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Dump.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.runtime.AgentOptions;
+import org.jacoco.core.tools.ExecDumpClient;
+import org.jacoco.core.tools.ExecFileLoader;
+import org.kohsuke.args4j.Option;
+
+/**
+ * The <code>dump</code> command.
+ */
+public class Dump extends Command {
+
+ @Option(name = "-address", usage = "host name or ip address to connect to (default localhost)", metaVar = "<address>")
+ String address = AgentOptions.DEFAULT_ADDRESS;
+
+ @Option(name = "-port", usage = "the port to connect to (default 6300)", metaVar = "<port>")
+ int port = AgentOptions.DEFAULT_PORT;
+
+ @Option(name = "-destfile", usage = "file to write execution data to", metaVar = "<path>", required = true)
+ File destfile;
+
+ @Option(name = "-reset", usage = "reset execution data on test target after dump")
+ boolean reset = false;
+
+ @Option(name = "-retry", usage = "number of retries (default 0)", metaVar = "<count>")
+ int retrycount = 0;
+
+ @Override
+ public String description() {
+ return "Request execution data from a JaCoCo agent running in 'tcpserver' output mode.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws Exception {
+ final ExecDumpClient client = new ExecDumpClient() {
+ @Override
+ protected void onConnecting(final InetAddress address,
+ final int port) {
+ out.printf("[INFO] Connecting to %s:%s.%n", address,
+ Integer.valueOf(port));
+ }
+
+ @Override
+ protected void onConnectionFailure(final IOException exception) {
+ err.printf("[WARN] %s.%n", exception.getMessage());
+ }
+ };
+ client.setReset(reset);
+ client.setRetryCount(retrycount);
+
+ final ExecFileLoader loader = client.dump(address, port);
+ out.printf("[INFO] Writing execution data to %s.%n",
+ destfile.getAbsolutePath());
+ loader.save(destfile, true);
+
+ return 0;
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ExecInfo.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ExecInfo.java
new file mode 100644
index 0000000..e798bf6
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ExecInfo.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.data.ExecutionDataReader;
+import org.jacoco.core.data.IExecutionDataVisitor;
+import org.jacoco.core.data.ISessionInfoVisitor;
+import org.jacoco.core.data.SessionInfo;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * The <code>execinfo</code> command.
+ */
+public class ExecInfo extends Command {
+
+ @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "<execfiles>")
+ List<File> execfiles = new ArrayList<File>();
+
+ @Override
+ public String description() {
+ return "Print exec file content in human readable format.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws IOException {
+ for (final File file : execfiles) {
+ dump(file, out);
+ }
+ return 0;
+ }
+
+ private void dump(final File file, final PrintWriter out)
+ throws IOException {
+ out.printf("[INFO] Loading exec file %s.%n", file);
+ out.println("CLASS ID HITS/PROBES CLASS NAME");
+
+ final FileInputStream in = new FileInputStream(file);
+ final ExecutionDataReader reader = new ExecutionDataReader(in);
+ reader.setSessionInfoVisitor(new ISessionInfoVisitor() {
+ public void visitSessionInfo(final SessionInfo info) {
+ out.printf("Session \"%s\": %s - %s%n", info.getId(),
+ new Date(info.getStartTimeStamp()),
+ new Date(info.getDumpTimeStamp()));
+ }
+ });
+ reader.setExecutionDataVisitor(new IExecutionDataVisitor() {
+ public void visitClassExecution(final ExecutionData data) {
+ out.printf("%016x %3d of %3d %s%n",
+ Long.valueOf(data.getId()),
+ Integer.valueOf(getHitCount(data.getProbes())),
+ Integer.valueOf(data.getProbes().length),
+ data.getName());
+ }
+ });
+ reader.read();
+ in.close();
+ out.println();
+ }
+
+ private int getHitCount(final boolean[] data) {
+ int count = 0;
+ for (final boolean hit : data) {
+ if (hit) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Instrument.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Instrument.java
new file mode 100644
index 0000000..d098057
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Instrument.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Keeping - initial implementation
+ * Marc R. Hoffmann - rework
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.instr.Instrumenter;
+import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+/**
+ * The <code>instrument</code> command.
+ */
+public class Instrument extends Command {
+
+ @Option(name = "-destdir", usage = "directory to write instrumented Java classes to", metaVar = "<dir>", required = true)
+ File destdir;
+
+ @Argument(usage = "list of folder or files to instrument recusively", metaVar = "<sourcefiles>")
+ List<File> source = new ArrayList<File>();
+
+ private Instrumenter instrumenter;
+
+ @Override
+ public String description() {
+ return "Off-line instrumentation of Java class files and JAR files.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws IOException {
+ instrumenter = new Instrumenter(
+ new OfflineInstrumentationAccessGenerator());
+ int total = 0;
+ for (final File s : source) {
+ total += instrumentRecursive(s, destdir);
+ }
+ out.printf("[INFO] %s classes instrumented to %s.%n",
+ Integer.valueOf(total), destdir.getAbsolutePath());
+ return 0;
+ }
+
+ private int instrumentRecursive(final File src, final File dest)
+ throws IOException {
+ int total = 0;
+ if (src.isDirectory()) {
+ for (final File child : src.listFiles()) {
+ total += instrumentRecursive(child,
+ new File(dest, child.getName()));
+ }
+ } else {
+ total += instrument(src, dest);
+ }
+ return total;
+ }
+
+ private int instrument(final File src, final File dest) throws IOException {
+ dest.getParentFile().mkdirs();
+ final InputStream input = new FileInputStream(src);
+ try {
+ final OutputStream output = new FileOutputStream(dest);
+ try {
+ return instrumenter.instrumentAll(input, output,
+ src.getAbsolutePath());
+ } finally {
+ output.close();
+ }
+ } catch (final IOException e) {
+ dest.delete();
+ throw e;
+ } finally {
+ input.close();
+ }
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Merge.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Merge.java
new file mode 100644
index 0000000..530285c
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Merge.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.tools.ExecFileLoader;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+/**
+ * The <code>merge</code> command.
+ */
+public class Merge extends Command {
+
+ @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "<execfiles>")
+ List<File> execfiles = new ArrayList<File>();
+
+ @Option(name = "-destfile", usage = "file to write merged execution data to", metaVar = "<path>", required = true)
+ File destfile;
+
+ @Override
+ public String description() {
+ return "Merges multiple exec files into a new one.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws IOException {
+ final ExecFileLoader loader = loadExecutionData(out);
+ out.printf("[INFO] Writing execution data to %s.%n",
+ destfile.getAbsolutePath());
+ loader.save(destfile, true);
+ return 0;
+ }
+
+ private ExecFileLoader loadExecutionData(final PrintWriter out)
+ throws IOException {
+ final ExecFileLoader loader = new ExecFileLoader();
+ for (final File file : execfiles) {
+ out.printf("[INFO] Loading execution data file %s.%n",
+ file.getAbsolutePath());
+ loader.load(file);
+ }
+ return loader;
+ }
+
+}
diff --git a/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Report.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Report.java
new file mode 100644
index 0000000..be2fbfe
--- /dev/null
+++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Report.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Keeping - initial implementation
+ * Marc R. Hoffmann - rework
+ *
+ *******************************************************************************/
+package org.jacoco.cli.internal.commands;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.jacoco.cli.internal.Command;
+import org.jacoco.core.analysis.Analyzer;
+import org.jacoco.core.analysis.CoverageBuilder;
+import org.jacoco.core.analysis.IBundleCoverage;
+import org.jacoco.core.analysis.IClassCoverage;
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.tools.ExecFileLoader;
+import org.jacoco.report.DirectorySourceFileLocator;
+import org.jacoco.report.FileMultiReportOutput;
+import org.jacoco.report.IReportVisitor;
+import org.jacoco.report.ISourceFileLocator;
+import org.jacoco.report.MultiReportVisitor;
+import org.jacoco.report.MultiSourceFileLocator;
+import org.jacoco.report.csv.CSVFormatter;
+import org.jacoco.report.html.HTMLFormatter;
+import org.jacoco.report.xml.XMLFormatter;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+/**
+ * The <code>report</code> command.
+ */
+public class Report extends Command {
+
+ @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "<execfiles>")
+ List<File> execfiles = new ArrayList<File>();
+
+ @Option(name = "-classfiles", usage = "location of Java class files", metaVar = "<path>", required = true)
+ List<File> classfiles = new ArrayList<File>();
+
+ @Option(name = "-sourcefiles", usage = "location of the source files", metaVar = "<path>")
+ List<File> sourcefiles = new ArrayList<File>();
+
+ @Option(name = "-tabwith", usage = "tab stop width for the source pages (default 4)", metaVar = "<n>")
+ int tabwidth = 4;
+
+ @Option(name = "-name", usage = "name used for this report", metaVar = "<name>")
+ String name = "JaCoCo Coverage Report";
+
+ @Option(name = "-encoding", usage = "source file encoding (default platform encoding)", metaVar = "<charset>")
+ String encoding;
+
+ @Option(name = "-xml", usage = "output file for the XML report", metaVar = "<file>")
+ File xml;
+
+ @Option(name = "-csv", usage = "output file for the CSV report", metaVar = "<file>")
+ File csv;
+
+ @Option(name = "-html", usage = "output directory for the HTML report", metaVar = "<dir>")
+ File html;
+
+ @Override
+ public String description() {
+ return "Generate reports in different formats by reading exec and Java class files.";
+ }
+
+ @Override
+ public int execute(final PrintWriter out, final PrintWriter err)
+ throws IOException {
+ final ExecFileLoader loader = loadExecutionData(out);
+ final IBundleCoverage bundle = analyze(loader.getExecutionDataStore(),
+ out);
+ writeReports(bundle, loader, out);
+ return 0;
+ }
+
+ private ExecFileLoader loadExecutionData(final PrintWriter out)
+ throws IOException {
+ final ExecFileLoader loader = new ExecFileLoader();
+ for (final File file : execfiles) {
+ out.printf("[INFO] Loading execution data file %s.%n",
+ file.getAbsolutePath());
+ loader.load(file);
+ }
+ return loader;
+ }
+
+ private IBundleCoverage analyze(final ExecutionDataStore data,
+ final PrintWriter out) throws IOException {
+ final CoverageBuilder builder = new CoverageBuilder();
+ final Analyzer analyzer = new Analyzer(data, builder);
+ for (final File f : classfiles) {
+ analyzer.analyzeAll(f);
+ }
+ printNoMatchWarning(builder.getNoMatchClasses(), out);
+ return builder.getBundle(name);
+ }
+
+ private void printNoMatchWarning(final Collection<IClassCoverage> nomatch,
+ final PrintWriter out) {
+ if (!nomatch.isEmpty()) {
+ out.println(
+ "[WARN] Some classes do not match with execution data.");
+ out.println(
+ "[WARN] For report generation the same class files must be used as at runtime.");
+ for (final IClassCoverage c : nomatch) {
+ out.printf(
+ "[WARN] Execution data for class %s does not match.%n",
+ c.getName());
+ }
+ }
+ }
+
+ private void writeReports(final IBundleCoverage bundle,
+ final ExecFileLoader loader, final PrintWriter out)
+ throws IOException {
+ out.printf("[INFO] Writing report with %s classes.%n",
+ Integer.valueOf(bundle.getClassCounter().getTotalCount()));
+ final IReportVisitor visitor = createReportVisitor();
+ visitor.visitInfo(loader.getSessionInfoStore().getInfos(),
+ loader.getExecutionDataStore().getContents());
+ visitor.visitBundle(bundle, getSourceLocator());
+ visitor.visitEnd();
+ }
+
+ private IReportVisitor createReportVisitor()
+ throws IOException, IOException {
+ final List<IReportVisitor> visitors = new ArrayList<IReportVisitor>();
+
+ if (xml != null) {
+ final XMLFormatter formatter = new XMLFormatter();
+ visitors.add(formatter.createVisitor(new FileOutputStream(xml)));
+ }
+
+ if (csv != null) {
+ final CSVFormatter formatter = new CSVFormatter();
+ visitors.add(formatter.createVisitor(new FileOutputStream(csv)));
+ }
+
+ if (html != null) {
+ final HTMLFormatter formatter = new HTMLFormatter();
+ visitors.add(
+ formatter.createVisitor(new FileMultiReportOutput(html)));
+ }
+
+ return new MultiReportVisitor(visitors);
+ }
+
+ private ISourceFileLocator getSourceLocator() {
+ final MultiSourceFileLocator multi = new MultiSourceFileLocator(
+ tabwidth);
+ for (final File f : sourcefiles) {
+ multi.add(new DirectorySourceFileLocator(f, encoding, tabwidth));
+ }
+ return multi;
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/tools/ExecDumpClientTest.java b/org.jacoco.core.test/src/org/jacoco/core/tools/ExecDumpClientTest.java
index 3fe1688..8091814 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/tools/ExecDumpClientTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/tools/ExecDumpClientTest.java
@@ -101,7 +101,7 @@
// 3. Retry
"onConnectionFailure", "onConnecting")
- , callbacks);
+ , callbacks);
}
@Test
@@ -126,6 +126,12 @@
assertTrue(resetRequested);
}
+ @Test(expected = IOException.class)
+ public void testNopServer() throws IOException {
+ int port = createNopServer();
+ client.dump((String) null, port);
+ }
+
private int getFreePort() throws IOException {
final ServerSocket server = new ServerSocket(0, 0,
InetAddress.getByName(null));
@@ -159,11 +165,27 @@
dumpRequested = dump;
resetRequested = reset;
if (dump) {
- writer.visitSessionInfo(new SessionInfo("TestId", 100, 200));
+ writer.visitSessionInfo(
+ new SessionInfo("TestId", 100, 200));
}
writer.sendCmdOk();
}
});
reader.read();
}
+
+ private int createNopServer() throws IOException {
+ server = new ServerSocket(0, 0, InetAddress.getByName(null));
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ server.accept().close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }).start();
+ return server.getLocalPort();
+ }
+
}
diff --git a/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java b/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java
index 39fd8f3..e99204d 100644
--- a/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java
+++ b/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java
@@ -123,7 +123,10 @@
.setExecutionDataVisitor(loader.getExecutionDataStore());
remoteWriter.visitDumpCommand(dump, reset);
- remoteReader.read();
+
+ if (!remoteReader.read()) {
+ throw new IOException("Socket closed unexpectedly.");
+ }
} finally {
socket.close();
diff --git a/org.jacoco.doc/docroot/doc/index.html b/org.jacoco.doc/docroot/doc/index.html
index 4de1a58..e17a349 100644
--- a/org.jacoco.doc/docroot/doc/index.html
+++ b/org.jacoco.doc/docroot/doc/index.html
@@ -44,6 +44,7 @@
<li><a href="examples/build/pom.xml">Maven Usage Example</a> -
<a href="examples/build/pom-offline.xml">Offline Example</a></li>
<li><a href="agent.html">Java Agent</a></li>
+ <li><a href="cli.html">Command Line Interface</a></li>
<li><a href="classids.html">Class Ids</a></li>
<li><a href="offline.html">Offline Instrumentation</a></li>
<li><a href="faq.html">FAQ</a></li>
diff --git a/org.jacoco.doc/docroot/index.html b/org.jacoco.doc/docroot/index.html
index 9a7991a..8f35d33 100644
--- a/org.jacoco.doc/docroot/index.html
+++ b/org.jacoco.doc/docroot/index.html
@@ -68,6 +68,12 @@
<td>Ant <i>(all other dependencies included)</i></td>
</tr>
<tr>
+ <td><span class="el_jar">jacococli.jar</span></td>
+ <td>no</td>
+ <td>JaCoCo Ant tasks</td>
+ <td>- <i>(all dependencies included)</i></td>
+ </tr>
+ <tr>
<td><span class="el_jar">org.jacoco.agent_@qualified.bundle.version@.jar</span></td>
<td>yes</td>
<td>JaCoCo agent</td>
diff --git a/org.jacoco.doc/pom.xml b/org.jacoco.doc/pom.xml
index 505eb10..c43b3ec 100644
--- a/org.jacoco.doc/pom.xml
+++ b/org.jacoco.doc/pom.xml
@@ -82,6 +82,16 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.cli</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.jacoco.cli.test</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>org.jacoco.examples</artifactId>
<version>${project.version}</version>
</dependency>
@@ -152,6 +162,7 @@
<fileset dir="../org.jacoco.agent.test/target" includes="surefire-reports/**/*.xml"/>
<fileset dir="../org.jacoco.agent.rt.test/target" includes="surefire-reports/**/*.xml"/>
<fileset dir="../org.jacoco.ant.test/target" includes="surefire-reports/**/*.xml"/>
+ <fileset dir="../org.jacoco.cli.test/target" includes="surefire-reports/**/*.xml"/>
<fileset dir="../org.jacoco.core.test/target" includes="surefire-reports/**/*.xml"/>
<fileset dir="../org.jacoco.report.test/target" includes="surefire-reports/**/*.xml"/>
<fileset dir="../org.jacoco.examples.test/target" includes="surefire-reports/**/*.xml"/>
@@ -230,7 +241,7 @@
<artifactId>xml-maven-plugin</artifactId>
<executions>
<execution>
- <id>default-transform</id>
+ <id>transform-generated-doc</id>
<goals>
<goal>transform</goal>
</goals>
@@ -262,6 +273,32 @@
</parameter>
</parameters>
</transformationSet>
+ <transformationSet>
+ <dir>../org.jacoco.cli/target/generated-documentation</dir>
+ <includes>
+ <include>*.xml</include>
+ </includes>
+ <stylesheet>xsl/cli.xsl</stylesheet>
+ <fileMappers>
+ <fileMapper implementation="org.codehaus.plexus.components.io.filemappers.FileExtensionMapper">
+ <targetExtension>.html</targetExtension>
+ </fileMapper>
+ </fileMappers>
+ <parameters>
+ <parameter>
+ <name>qualified.bundle.version</name>
+ <value>${qualified.bundle.version}</value>
+ </parameter>
+ <parameter>
+ <name>jacoco.home.url</name>
+ <value>${jacoco.home.url}</value>
+ </parameter>
+ <parameter>
+ <name>copyright.years</name>
+ <value>${copyright.years}</value>
+ </parameter>
+ </parameters>
+ </transformationSet>
</transformationSets>
</configuration>
</execution>
diff --git a/org.jacoco.doc/xsl/cli.xsl b/org.jacoco.doc/xsl/cli.xsl
new file mode 100644
index 0000000..71eb563
--- /dev/null
+++ b/org.jacoco.doc/xsl/cli.xsl
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+
+<!--
+ Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+ Marc R. Hoffmann - initial API and implementation
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xdoc">
+
+ <xsl:output method="xml" indent="yes" encoding="UTF-8"
+ doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" />
+
+ <xsl:param name="qualified.bundle.version" />
+ <xsl:param name="jacoco.home.url" />
+ <xsl:param name="copyright.years" />
+
+ <xsl:template match="/">
+ <html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link rel="stylesheet" href="resources/doc.css" charset="UTF-8"
+ type="text/css" />
+ <link rel="shortcut icon" href="resources/report.gif" type="image/gif" />
+ <title>
+ JaCoCo - Command Line Interface
+ </title>
+ </head>
+ <body>
+ <div class="breadcrumb">
+ <a href="../index.html" class="el_report">JaCoCo</a> >
+ <a href="index.html" class="el_group">Documentation</a> >
+ <span class="el_source">Command Line Interface</span>
+ </div>
+ <div id="content">
+
+ <h1>Command Line Interface</h1>
+
+ <p>
+ JaCoCo comes with a command line interface to perform
+ basic operations from the command line. The command line
+ tools with all dependencies are packaged in
+ <code>jacococli.jar</code> and are available with the
+ JaCoCo download. A Java VM with version 1.5 or greater is
+ required for execution.
+ </p>
+
+ <p>
+ For more sophisticated usage especially with larger
+ projects please use our
+ <a href="integrations.html">integrations</a> with various
+ build tools.
+ </p>
+ <xsl:apply-templates select="documentation" />
+ </div>
+ <div class="footer">
+ <span class="right">
+ <a href="{$jacoco.home.url}">JaCoCo</a>
+  
+ <xsl:value-of select="$qualified.bundle.version" />
+ </span>
+ <a href="../doc/license.html">Copyright</a>
+ ©
+ <xsl:value-of select="$copyright.years" />
+ Mountainminds GmbH & Co. KG and Contributors
+ </div>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template match="command">
+ <h2><xsl:value-of select="@name" /></h2>
+ <pre class="source">
+ <xsl:value-of select="usage" />
+ </pre>
+ <p><xsl:value-of select="description" /></p>
+ <table class="coverage">
+ <thead>
+ <tr>
+ <td>Option</td>
+ <td>Description</td>
+ <td>Required</td>
+ </tr>
+ </thead>
+ <tbody>
+ <xsl:for-each select="option">
+ <tr>
+ <td><code><xsl:value-of select="usage" /></code></td>
+ <td><xsl:value-of select="description" /></td>
+ <td><xsl:choose>
+ <xsl:when test="@required = 'true'">
+ yes
+ </xsl:when>
+ <xsl:otherwise>
+ no
+ </xsl:otherwise>
+ </xsl:choose></td>
+ </tr>
+ </xsl:for-each>
+ </tbody>
+ </table>
+ </xsl:template>
+
+</xsl:stylesheet>
+
diff --git a/org.jacoco.tests/pom.xml b/org.jacoco.tests/pom.xml
index d363212..2e676f9 100644
--- a/org.jacoco.tests/pom.xml
+++ b/org.jacoco.tests/pom.xml
@@ -31,8 +31,9 @@
<module>../org.jacoco.agent.rt.test</module>
<module>../org.jacoco.agent.test</module>
<module>../org.jacoco.ant.test</module>
- <module>../jacoco-maven-plugin.test</module>
+ <module>../org.jacoco.cli.test</module>
<module>../org.jacoco.examples.test</module>
+ <module>../jacoco-maven-plugin.test</module>
</modules>
<properties>