From 13f12d39db0b3c0ddc60124572e5d4f984be0255 Mon Sep 17 00:00:00 2001 From: "Marc R. Hoffmann" Date: Thu, 25 May 2017 00:53:18 +0200 Subject: Add Command Line Interface (#525) --- .../src/org/jacoco/cli/internal/Command.java | 92 +++++++++++ .../org/jacoco/cli/internal/CommandHandler.java | 89 +++++++++++ .../src/org/jacoco/cli/internal/CommandParser.java | 33 ++++ .../src/org/jacoco/cli/internal/Main.java | 108 +++++++++++++ .../org/jacoco/cli/internal/XmlDocumentation.java | 78 +++++++++ .../jacoco/cli/internal/commands/AllCommands.java | 49 ++++++ .../jacoco/cli/internal/commands/ClassInfo.java | 75 +++++++++ .../src/org/jacoco/cli/internal/commands/Dump.java | 77 +++++++++ .../org/jacoco/cli/internal/commands/ExecInfo.java | 94 +++++++++++ .../jacoco/cli/internal/commands/Instrument.java | 96 ++++++++++++ .../org/jacoco/cli/internal/commands/Merge.java | 66 ++++++++ .../org/jacoco/cli/internal/commands/Report.java | 174 +++++++++++++++++++++ .../org/jacoco/cli/internal/commands/Version.java | 37 +++++ 13 files changed, 1068 insertions(+) create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/Command.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/CommandHandler.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/CommandParser.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/Main.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/XmlDocumentation.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/AllCommands.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/ClassInfo.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/Dump.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/ExecInfo.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/Instrument.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/Merge.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/Report.java create mode 100644 org.jacoco.cli/src/org/jacoco/cli/internal/commands/Version.java (limited to 'org.jacoco.cli/src/org/jacoco') 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 { + + /** + * This constructor is required by the args4j framework. + * + * @param parser + * @param option + * @param setter + */ + public CommandHandler(final CmdLineParser parser, final OptionDef option, + final Setter setter) { + super(parser, + new OptionDef(AllCommands.names(), "", + 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() { + @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 ""; + } + +} 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 | "; + } + + @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 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 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 classinfo command. + */ +public class ClassInfo extends Command { + + @Argument(usage = "location of Java class files", metaVar = "") + List classfiles = new ArrayList(); + + @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 dump command. + */ +public class Dump extends Command { + + @Option(name = "--address", usage = "host name or ip address to connect to (default localhost)", metaVar = "
") + String address = AgentOptions.DEFAULT_ADDRESS; + + @Option(name = "--port", usage = "the port to connect to (default 6300)", metaVar = "") + int port = AgentOptions.DEFAULT_PORT; + + @Option(name = "--destfile", usage = "file to write execution data to", metaVar = "", 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 = "") + 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 execinfo command. + */ +public class ExecInfo extends Command { + + @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "") + List execfiles = new ArrayList(); + + @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 instrument command. + */ +public class Instrument extends Command { + + @Option(name = "--dest", usage = "path to write instrumented Java classes to", metaVar = "", required = true) + File dest; + + @Argument(usage = "list of folder or files to instrument recusively", metaVar = "") + List source = new ArrayList(); + + 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 merge command. + */ +public class Merge extends Command { + + @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "") + List execfiles = new ArrayList(); + + @Option(name = "--destfile", usage = "file to write merged execution data to", metaVar = "", 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 report command. + */ +public class Report extends Command { + + @Argument(usage = "list of JaCoCo *.exec files to read", metaVar = "") + List execfiles = new ArrayList(); + + @Option(name = "--classfiles", usage = "location of Java class files", metaVar = "", required = true) + List classfiles = new ArrayList(); + + @Option(name = "--sourcefiles", usage = "location of the source files", metaVar = "") + List sourcefiles = new ArrayList(); + + @Option(name = "--tabwith", usage = "tab stop width for the source pages (default 4)", metaVar = "") + int tabwidth = 4; + + @Option(name = "--name", usage = "name used for this report", metaVar = "") + String name = "JaCoCo Coverage Report"; + + @Option(name = "--encoding", usage = "source file encoding (by default platform encoding is used)", metaVar = "") + String encoding; + + @Option(name = "--xml", usage = "output file for the XML report", metaVar = "") + File xml; + + @Option(name = "--csv", usage = "output file for the CSV report", metaVar = "") + File csv; + + @Option(name = "--html", usage = "output directory for the HTML report", metaVar = "") + 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 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 visitors = new ArrayList(); + + 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 version 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; + } + +} -- cgit v1.2.3