diff options
author | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2017-05-25 00:53:18 +0200 |
---|---|---|
committer | Evgeny Mandrikov <Godin@users.noreply.github.com> | 2017-05-25 00:53:18 +0200 |
commit | 13f12d39db0b3c0ddc60124572e5d4f984be0255 (patch) | |
tree | 2e1ea16bf0675dfd83730f48ad48edf2e880df89 | |
parent | 10f3ff0dd010d647625ef5937f301126250267d0 (diff) | |
download | jacoco-13f12d39db0b3c0ddc60124572e5d4f984be0255.tar.gz |
Add Command Line Interface (#525)
42 files changed, 2550 insertions, 8 deletions
diff --git a/jacoco/assembly.xml b/jacoco/assembly.xml index b25483f9..81a39386 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 5a1875da..95e29fd8 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>4100000</maxsize> + <minsize>3400000</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 e746d6ac..17e18437 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,12 @@ <artifactId>xml-maven-plugin</artifactId> <version>1.0</version> </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <!-- latest version which runs with Java 5 --> + <version>1.5.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 00000000..0ed344a5 --- /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 00000000..fbd366b0 --- /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 00000000..d31112db --- /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 00000000..bc3c6b7d --- /dev/null +++ b/org.jacoco.cli.test/pom.xml @@ -0,0 +1,41 @@ +<?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> + </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 00000000..4d12a373 --- /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 00000000..5a0d7424 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/MainTest.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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 should_print_usage_when_no_arguments_given() 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 should_print_error_message_when_invalid_command_is_given() + 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 should_print_general_usage_when_help_option_is_given() + 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 should_print_command_usage_when_command_and_help_option_is_given() + 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); + } + + @Test + public void should_not_print_any_output_when_quiet_option_is_given() + throws Exception { + execute("version", "--quiet"); + + assertOk(); + assertNoOutput(out); + assertNoOutput(err); + } + +} diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/XmlDocumentationTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/XmlDocumentationTest.java new file mode 100644 index 00000000..83f79bc9 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/XmlDocumentationTest.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; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.w3c.dom.Document; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Unit tests for {@link XmlDocumentation}. + */ +public class XmlDocumentationTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private DocumentBuilder builder; + private XPath xpath; + + @Before + public void before() throws Exception { + final DocumentBuilderFactory builderFactory = DocumentBuilderFactory + .newInstance(); + builder = builderFactory.newDocumentBuilder(); + builder.setErrorHandler(new ErrorHandler() { + public void error(SAXParseException exception) throws SAXException { + fail(exception.getMessage()); + } + + public void fatalError(SAXParseException exception) + throws SAXException { + fail(exception.getMessage()); + } + + public void warning(SAXParseException exception) + throws SAXException { + fail(exception.getMessage()); + } + }); + + xpath = XPathFactory.newInstance().newXPath(); + } + + @Test + public void should_create_documentation() throws Exception { + File file = new File(tmp.getRoot(), "doc.xml"); + + XmlDocumentation.main(file.getAbsolutePath()); + + Document doc = parse(file); + + assertContains("java -jar jacococli.jar report", + "/documentation/command[@name='report']/usage/text()", doc); + + assertContains("Generate reports", + "/documentation/command[@name='report']/description/text()", + doc); + + assertContains("<execfiles>", + "/documentation/command[@name='report']/option[1]/usage/text()", + doc); + + assertContains("false", + "/documentation/command[@name='report']/option[1]/@required", + doc); + + assertContains("true", + "/documentation/command[@name='report']/option[1]/@multiple", + doc); + + assertContains("-classfiles <path>", + "/documentation/command[@name='report']/option[2]/usage/text()", + doc); + + assertContains("true", + "/documentation/command[@name='report']/option[2]/@multiple", + doc); + + } + + private Document parse(File file) throws Exception { + InputStream in = new FileInputStream(file); + try { + return builder.parse(new InputSource(in)); + } finally { + in.close(); + } + } + + private void assertContains(String expected, String query, Document doc) + throws XPathExpressionException { + final String actual = eval(query, doc); + assertTrue(actual, actual.contains(expected)); + } + + private String eval(String query, Document doc) + throws XPathExpressionException { + return (String) xpath.evaluate(query, doc, XPathConstants.STRING); + } + +} 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 00000000..62934e28 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ClassInfoTest.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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.Test; + +/** + * Unit tests for {@link ClassInfo}. + */ +public class ClassInfoTest extends CommandTestBase { + + @Test + public void should_print_usage_when_invalid_option_is_given() + 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 should_print_warning_when_no_class_files_are_provided() + throws Exception { + execute("classinfo"); + + assertOk(); + assertContains("[WARN] No class files provided.", out); + } + + @Test + public void should_print_class_info() throws Exception { + execute("classinfo", getClassPath()); + + assertOk(); + assertContains( + "class name: org/jacoco/cli/internal/commands/ClassInfoTest", + out); + assertContains("methods: 4", 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 00000000..3a859f52 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/DumpTest.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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.After; +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(); + + private ServerSocket serverSocket; + + @After + public void after() throws IOException { + if (serverSocket != null) { + serverSocket.close(); + } + } + + @Test + public void should_print_usage_when_no_argument_is_given() + throws Exception { + execute("dump"); + assertFailure(); + assertContains("Option \"--destfile\" is required", err); + assertContains("java -jar jacococli.jar dump [--address <address>]", + err); + } + + @Test + public void should_write_dump() 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 should_log_connection_error_when_retry_is_specified() + 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 { + 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 00000000..54540865 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ExecInfoTest.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * 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 should_print_usage_when_invalid_argument_is_given() + 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 should_print_warning_when_no_exec_files_are_provided() + throws Exception { + execute("execinfo"); + + assertOk(); + assertContains("[WARN] No execution data files provided.", out); + } + + @Test + public void should_print_execution_data_info() 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 00000000..5d70b48c --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/InstrumentTest.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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 should_print_usage_when_no_options_are_given() + throws Exception { + execute("instrument"); + assertFailure(); + assertContains("Option \"--dest\" is required", err); + assertContains( + "Usage: java -jar jacococli.jar instrument [<sourcefiles> ...]", + err); + } + + @Test + public void should_instrument_class_files_and_copy_resources() + throws Exception { + File destdir = tmp.getRoot(); + + execute("instrument", "--dest", destdir.getAbsolutePath(), + getClassPath()); + + assertOk(); + assertContains("[INFO] 14 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 should_not_instrument_anything_when_no_source_is_given() + throws Exception { + File destdir = tmp.getRoot(); + + execute("instrument", "--dest", destdir.getAbsolutePath()); + + assertOk(); + assertArrayEquals(new String[0], destdir.list()); + } + + @Test + public void should_not_create_dest_file_when_source_class_is_broken() + throws Exception { + File srcdir = new File(tmp.getRoot(), "src"); + srcdir.mkdir(); + File destdir = new File(tmp.getRoot(), "dest"); + 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", "--dest", 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 00000000..ea534751 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/MergeTest.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.Collections; +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 should_print_usage_when_no_options_are_given() + throws Exception { + execute("merge"); + + assertFailure(); + assertContains("Option \"--destfile\" is required", err); + assertContains("java -jar jacococli.jar merge [<execfiles> ...]", err); + } + + @Test + public void should_print_warning_when_no_exec_files_are_provided() + throws Exception { + File dest = new File(tmp.getRoot(), "merged.exec"); + execute("merge", "--destfile", dest.getAbsolutePath()); + + assertOk(); + assertContains("[WARN] No execution data files provided.", out); + Set<String> names = loadExecFile(dest); + assertEquals(Collections.emptySet(), names); + } + + @Test + public void should_merge_exec_files() 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 00000000..77564c8f --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/ReportTest.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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 should_print_usage_when_no_options_are_given() + throws Exception { + execute("report"); + + assertFailure(); + assertContains("Option \"--classfiles\" is required", err); + assertContains( + "Usage: java -jar jacococli.jar report [<execfiles> ...]", err); + } + + @Test + public void should_print_warning_when_no_exec_files_are_provided() + throws Exception { + execute("report", "--classfiles", getClassPath()); + + assertOk(); + assertContains("[WARN] No execution data files provided.", out); + } + + @Test + public void should_print_number_of_analyzed_classes() throws Exception { + execute("report", "--classfiles", getClassPath()); + + assertOk(); + assertContains("[INFO] Analyzing 14 classes.", out); + } + + @Test + public void should_print_warning_when_exec_data_does_not_match() + 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 should_create_xml_report_when_xml_option_is_provided() + throws Exception { + File xml = new File(tmp.getRoot(), "coverage.xml"); + + execute("report", "--classfiles", getClassPath(), "--xml", + xml.getAbsolutePath()); + + assertOk(); + assertTrue(xml.isFile()); + } + + @Test + public void should_create_csv_report_when_csv_option_is_provided() + throws Exception { + File csv = new File(tmp.getRoot(), "coverage.csv"); + + execute("report", "--classfiles", getClassPath(), "--csv", + csv.getAbsolutePath()); + + assertOk(); + assertTrue(csv.isFile()); + } + + @Test + public void should_create_html_report_when_html_option_is_provided() + 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()); + } + + @Test + public void should_use_all_values_when_multiple_classfiles_options_are_provided() + throws Exception { + File html = new File(tmp.getRoot(), "coverage"); + + final String c1 = getClassPath() + + "/org/jacoco/cli/internal/commands/ReportTest.class"; + final String c2 = getClassPath() + + "/org/jacoco/cli/internal/commands/DumpTest.class"; + + execute("report", "--classfiles", c1, "--classfiles", c2, "--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/DumpTest.html") + .isFile()); + } + +} diff --git a/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/VersionTest.java b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/VersionTest.java new file mode 100644 index 00000000..56b259a5 --- /dev/null +++ b/org.jacoco.cli.test/src/org/jacoco/cli/internal/commands/VersionTest.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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.jacoco.core.JaCoCo; +import org.junit.Test; + +/** + * Unit tests for {@link Version}. + */ +public class VersionTest extends CommandTestBase { + + @Test + public void should_print_version() throws Exception { + execute("version"); + + assertOk(); + assertContains(JaCoCo.VERSION, out); + } + +} diff --git a/org.jacoco.cli/.classpath b/org.jacoco.cli/.classpath new file mode 100644 index 00000000..0ed344a5 --- /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/.project b/org.jacoco.cli/.project new file mode 100644 index 00000000..828621af --- /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 00000000..af7ff88f --- /dev/null +++ b/org.jacoco.cli/about.html @@ -0,0 +1,40 @@ +<?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> + +<p> + The Content includes items that have been sourced from third parties as set + out below. +</p> + +<h4>args4j</h4> + +<p> + This plug-in contains <a href="http://args4j.kohsuke.org/">args4j</a> + which is subject to the terms and conditions of the + <a href="http://www.opensource.org/licenses/mit-license.php">the MIT license</a>. +</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 00000000..a35e0c95 --- /dev/null +++ b/org.jacoco.cli/pom.xml @@ -0,0 +1,92 @@ +<?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> + <Implementation-Title>${project.description}</Implementation-Title> + <Implementation-Vendor>${project.organization.name}</Implementation-Vendor> + <Implementation-Version>${project.version}</Implementation-Version> + <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 00000000..7c4f5c15 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/Command.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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; + + /** + * Flag whether output to stdout should be suppressed. + */ + @Option(name = "--quiet", usage = "suppress all output on stdout") + public boolean quiet = 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(description()); + writer.println(); + writer.println("Usage: " + parser.getCommand().usage(parser)); + 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 00000000..3c6a2a54 --- /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 00000000..0e31093b --- /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 00000000..1c02127f --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/Main.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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.IOException; +import java.io.PrintWriter; +import java.io.Writer; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; + +/** + * Entry point for all command line operations. + */ +public class Main extends Command { + + private static final PrintWriter NUL = new PrintWriter(new Writer() { + + @Override + public void write(final char[] arg0, final int arg1, final int arg2) + throws IOException { + } + + @Override + public void flush() throws IOException { + } + + @Override + public void close() throws IOException { + } + }); + + 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(PrintWriter out, final PrintWriter err) + throws Exception { + + final CommandParser mainParser = new CommandParser(this); + try { + mainParser.parseArgument(args); + } catch (final CmdLineException e) { + ((CommandParser) e.getParser()).getCommand().printHelp(err); + err.println(); + err.println(e.getMessage()); + return -1; + } + + if (help) { + printHelp(out); + return 0; + } + + if (command.help) { + command.printHelp(out); + return 0; + } + + if (command.quiet) { + out = NUL; + } + + 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 { + final PrintWriter out = new PrintWriter(System.out, true); + final PrintWriter err = new PrintWriter(System.err, true); + final int returncode = new Main(args).execute(out, err); + System.exit(returncode); + } + +} 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 00000000..83241c9a --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/XmlDocumentation.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 final class XmlDocumentation { + + private 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.attr("multiple", + String.valueOf(o.setter.isMultiValued())); + optionNode.element("usage").text(o.getNameAndMeta(null)); + optionNode.element("description").text(o.option.usage()); + } + } + + /** + * Called during the build process. + * + * @param args + * exactly one argument expected with the target location + * @throws IOException + * if XML document cannot be written + */ + 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 00000000..e0df1d91 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/AllCommands.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 java.util.Arrays; +import java.util.List; + +import org.jacoco.cli.internal.Command; + +/** + * List of all available commands. + */ +public final class AllCommands { + + private 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(), new Version()); + } + + /** + * @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 00000000..dd6a3718 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ClassInfo.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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 { + if (classfiles.isEmpty()) { + out.println("[WARN] No class files provided."); + } else { + 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 00000000..3c1575d9 --- /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 10)", metaVar = "<count>") + int retrycount = 10; + + @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 00000000..211be983 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/ExecInfo.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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 { + if (execfiles.isEmpty()) { + out.println("[WARN] No execution data files provided."); + } else { + 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 00000000..c52c0722 --- /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 = "--dest", usage = "path to write instrumented Java classes to", metaVar = "<dir>", required = true) + File dest; + + @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, dest); + } + out.printf("[INFO] %s classes instrumented to %s.%n", + Integer.valueOf(total), dest.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 00000000..d4262abc --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Merge.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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(); + if (execfiles.isEmpty()) { + out.println("[WARN] No execution data files provided."); + } else { + 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 00000000..eaa78842 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Report.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * 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 (by default platform encoding is used)", 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(); + if (execfiles.isEmpty()) { + out.println("[WARN] No execution data files provided."); + } else { + 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] Analyzing %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.cli/src/org/jacoco/cli/internal/commands/Version.java b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Version.java new file mode 100644 index 00000000..ef1e33a1 --- /dev/null +++ b/org.jacoco.cli/src/org/jacoco/cli/internal/commands/Version.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.IOException; +import java.io.PrintWriter; + +import org.jacoco.cli.internal.Command; +import org.jacoco.core.JaCoCo; + +/** + * The <code>version</code> command. + */ +public class Version extends Command { + + @Override + public String description() { + return "Print JaCoCo version information."; + } + + @Override + public int execute(final PrintWriter out, final PrintWriter err) + throws IOException { + out.println(JaCoCo.VERSION); + return 0; + } + +} diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 849a4e54..ea3cce8c 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -41,6 +41,8 @@ (GitHub <a href="https://github.com/jacoco/jacoco/issues/529">#529</a>).</li> <li>Maven aggregated reports will now also include modules of runtime dependencies (GitHub <a href="https://github.com/jacoco/jacoco/issues/498">#498</a>).</li> + <li>JaCoCo now comes with a simple command line interface + (GitHub <a href="https://github.com/jacoco/jacoco/issues/525">#525</a>).</li> </ul> <h3>Fixed Bugs</h3> diff --git a/org.jacoco.doc/docroot/doc/index.html b/org.jacoco.doc/docroot/doc/index.html index 4de1a589..e17a3491 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/doc/integrations.html b/org.jacoco.doc/docroot/doc/integrations.html index 992417c3..8de37a63 100644 --- a/org.jacoco.doc/docroot/doc/integrations.html +++ b/org.jacoco.doc/docroot/doc/integrations.html @@ -39,11 +39,16 @@ <td></td> </tr> <tr> - <td>Command Line</td> + <td>Java Agent</td> <td><a href="agent.html">JaCoCo Manual</a></td> <td></td> </tr> <tr> + <td>Command Line Tools</td> + <td><a href="cli.html">JaCoCo Manual</a></td> + <td>Since version 0.8.0</td> + </tr> + <tr> <td>Apache Ant</td> <td><a href="ant.html">JaCoCo Manual</a></td> <td></td> @@ -51,7 +56,7 @@ <tr> <td>Apache Maven</td> <td><a href="maven.html">JaCoCo Manual</a></td> - <td></td> + <td>Since version 0.5.3</td> </tr> <tr> <td>Eclipse</td> diff --git a/org.jacoco.doc/docroot/doc/license.html b/org.jacoco.doc/docroot/doc/license.html index f5717d8a..d7a5e8f0 100644 --- a/org.jacoco.doc/docroot/doc/license.html +++ b/org.jacoco.doc/docroot/doc/license.html @@ -275,6 +275,14 @@ THE POSSIBILITY OF SUCH DAMAGE. of your accepting any such warranty or additional liability. </pre> +<h3>args4j</h3> + +<p> + <a href="http://args4j.kohsuke.org/">args4j</a> is subject to the terms and + conditions of + <a href="http://www.opensource.org/licenses/mit-license.php">the MIT license</a>. +</p> + </div> <div class="footer"> <span class="right"><a href="@jacoco.home.url@">JaCoCo</a> @qualified.bundle.version@</span> diff --git a/org.jacoco.doc/docroot/index.html b/org.jacoco.doc/docroot/index.html index 9a7991ad..f9f082c8 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 Command Line Interface</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 505eb10b..c43b3ec7 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 00000000..1e8e1db9 --- /dev/null +++ b/org.jacoco.doc/xsl/cli.xsl @@ -0,0 +1,125 @@ +<?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. Java 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> + + <p> + The following commands are available. Each command has a + list of optional and required parameters. Some parameters + can be specified multiple times to provide multiple values. + </p> + + <p class="hint"> + <b>Warning:</b> Although a <code>instrument</code> command + is provided the preferred way for code coverage analysis + with JaCoCo is on-the-fly instrumentation with the + <a href="agent.html">JaCoCo agent</a>. Offline + instrumentation has several drawbacks and should only be + used if a specific scenario explicitly requires this mode. + Please consult <a href="offline.html">documentation</a> + about offline instrumentation before using this mode. + </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" style="white-space: pre-wrap"> + <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> + <td>Multiple</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 style="text-align:center"><xsl:if test="@required = 'true'">◼</xsl:if></td> + <td style="text-align:center"><xsl:if test="@multiple = 'true'">◼</xsl:if></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 d3632123..2e676f98 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> |