Skip to content

Commit

Permalink
Added support to check coverage on new code with jacoco reports. Reso…
Browse files Browse the repository at this point in the history
…lves issue pfeuffer#2
  • Loading branch information
dimapin committed Nov 10, 2014
1 parent 35b84d3 commit b0ff460
Show file tree
Hide file tree
Showing 11 changed files with 554 additions and 5 deletions.
15 changes: 15 additions & 0 deletions cli/src/main/java/de/pfeufferweb/gitcover/GCOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ class GCOptions
private String ignoreFile = null;
private String repository = null;
private String reference = null;

private boolean failed = false;

private CoverageTool coverageTool = CoverageTool.COBERTURA;

public void parse(String[] args)
{
Options options = buildOptions();
Expand Down Expand Up @@ -54,6 +57,16 @@ private void parse(String[] args, Options options, CommandLineParser parser) thr
printHelp(options);
failed = true;
}
if(line.hasOption("ct"))
{
switch (line.getOptionValue("ct")){
case "cobertura": coverageTool = CoverageTool.COBERTURA;
break;
case "jacoco": coverageTool = CoverageTool.JACOCO;
break;
default: break;
}
}
}

@SuppressWarnings("static-access")
Expand All @@ -66,6 +79,8 @@ private Options buildOptions()
.withDescription("use this to ignore files that have been modified").create("em");
Option excludeAddedOption = OptionBuilder.withLongOpt("exclude-added")
.withDescription("use this to ignore files that have been modified").create("ea");
Option coverageToolOption = OptionBuilder.withLongOpt("coverage-tool")
.withDescription("use this to select between cobertura and jacoco").create("ct");
options.addOption(ignoreFileOption);
options.addOption(excludeModifiedOption);
options.addOption(excludeAddedOption);
Expand Down
10 changes: 5 additions & 5 deletions cli/src/main/java/de/pfeufferweb/gitcover/GitCover.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ private void process(GCOptions options) throws Exception
out.println("a.ignored {background:#CDF;}");
out.println("a.allCovered {background:#CFC;}");
out.println("a.coverageMissing {background:#FDC;}");
out.println("a.exp::after {content:\"»\";float:right;}");
out.println("a.exp::after {content:\"\";float:right;}");
out.println("a.exp:focus {border-width: 1px 1px 0 1px;border-radius:4px 4px 0 0}");
out.println("a.exp + div {display:none;}");
out.println("a.exp:focus + div {display:block;border-width: 0 1px 1px 1px;border-style:solid; border-radius:0 0 4px 4px;border-color:black;}");
out.println("a.exp:focus::after {content:\"\";}");
out.println("div.exp *{padding:0.3em 10px 0em 10px;}");
out.println("div.exp table:last-child::after {content:\"«\";float:right;}");
out.println("div.exp table:last-child::after {content:\"\";float:right;}");
out.println("div.exp *:first-child {margin-top:0;}");
out.println("tr.notCovered {background: orangered;}");
out.println("tr.covered {background: lightgreen;}");
Expand All @@ -91,9 +91,9 @@ private void process(GCOptions options) throws Exception
out.println("</style>");
ChangedLines changedLines = createChangedLinesBuilder(options, options.getRepository()).build(
options.getReference());
Coverage coverage = new CoverageBuilder().computeAll(new File(options.getRepository()));
Coverage coverage = new CoberturaCoverageBuilder().computeAll(new File(options.getRepository()));
out.println("<body>");
out.println("<h1>Unittestabdeckung der Änderungen bzgl. Branch " + options.getReference() + "</h1>");
out.println("<h1>Unittestabdeckung der �nderungen bzgl. Branch " + options.getReference() + "</h1>");
List<String> fileNames = new ArrayList<String>(changedLines.getFileNames());
sort(fileNames);
OverallCoverage overall = new OverallCoverage();
Expand Down Expand Up @@ -135,7 +135,7 @@ private void process(GCOptions options) throws Exception
}
out.println("</table></div>");
}
out.println("Durchschnittliche Abdeckung aller testrelevanten Änderungen: " + overall.getCoverage() + "%");
out.println("Durchschnittliche Abdeckung aller testrelevanten �nderungen: " + overall.getCoverage() + "%");
out.println("</body>");
out.println("</html>");
}
Expand Down
33 changes: 33 additions & 0 deletions core/src/main/java/de/pfeufferweb/gitcover/ChangedLines.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.pfeufferweb.gitcover;

import static java.util.Collections.unmodifiableCollection;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class ChangedLines
{
private final Map<String, Map<Integer, String>> changedLines = new HashMap<String, Map<Integer, String>>();

public void addFile(String fileName, Map<Integer, String> lines)
{
changedLines.put(fileName, new HashMap<Integer, String>(lines));
}

public Collection<String> getFileNames()
{
return unmodifiableCollection(changedLines.keySet());
}

public Collection<Integer> getChangedLines(String changedFile)
{
return unmodifiableCollection(changedLines.get(changedFile).keySet());
}

public String getLine(String changedFile, int line)
{
return changedLines.get(changedFile).get(line);
}

}
125 changes: 125 additions & 0 deletions core/src/main/java/de/pfeufferweb/gitcover/ChangedLinesBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package de.pfeufferweb.gitcover;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;

import difflib.Delta;
import difflib.DiffUtils;
import difflib.Patch;

public class ChangedLinesBuilder
{
private final Repository repository;
private boolean includeModified = true;
private boolean includeAdded = true;

public ChangedLinesBuilder(String repoFolder) throws Exception
{
this.repository = new RepositoryBuilder().findGitDir(new File(repoFolder)).build();
}

public ChangedLines build(String revision) throws Exception
{
ChangedLines changedLines = new ChangedLines();
Git git = new Git(repository);

ObjectId headId = repository.resolve("HEAD^{tree}");
ObjectId oldId = repository.resolve(revision + "^{tree}");

ObjectReader reader = repository.newObjectReader();

CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset(reader, headId);
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
oldTreeIter.reset(reader, oldId);

List<DiffEntry> diffs = git.diff().setNewTree(newTreeIter).setOldTree(oldTreeIter).call();

for (DiffEntry diff : diffs)
{
boolean isRelevantFile = diff.getNewPath().endsWith(".java");
if (isRelevantFile)
{
if (includeModified && isModified(diff))
{
Map<Integer, String> lines = process(diff);
changedLines.addFile(diff.getNewPath(), lines);
}
else if (includeAdded && isAdded(diff))
{
Map<Integer, String> lines = createLines(load(diff.getNewId()));
changedLines.addFile(diff.getNewPath(), lines);
}
}
}
return changedLines;
}

public void setIncludeModified(boolean includeModified)
{
this.includeModified = includeModified;
}

public void setIncludeAdded(boolean includeAdded)
{
this.includeAdded = includeAdded;
}

private boolean isModified(DiffEntry diff)
{
return diff.getChangeType() == ChangeType.MODIFY;
}

private boolean isAdded(DiffEntry diff)
{
return diff.getChangeType() == ChangeType.ADD;
}

private Map<Integer, String> createLines(List<String> content)
{
Map<Integer, String> lines = new HashMap<Integer, String>();
for (int i = 0; i < content.size(); ++i)
{
lines.put(i + 1, content.get(i));
}
return lines;
}

private Map<Integer, String> process(DiffEntry diff) throws Exception
{
Patch patch = DiffUtils.diff(load(diff.getOldId()), load(diff.getNewId()));
Map<Integer, String> lines = new HashMap<Integer, String>();
for (Delta delta : patch.getDeltas())
{
int initialPosition = delta.getRevised().getPosition();
int diffLength = delta.getRevised().getLines().size();
for (int i = 0; i < diffLength; ++i)
{
lines.put(initialPosition + i + 1, delta.getRevised().getLines().get(i).toString());
}
}
return lines;
}

private List<String> load(AbbreviatedObjectId objectId) throws Exception
{
ObjectLoader loader = repository.open(objectId.toObjectId());

ObjectStream stream = loader.openStream();
return new FileLoader(stream).load();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.pfeufferweb.gitcover;

import static java.lang.Integer.parseInt;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class CoberturaCoverageBuilder
{
public Coverage computeAll(File directory) throws Exception
{
Coverage overallCoverage = new Coverage();
Collection<File> files = FileUtils.listFiles(directory, new IOFileFilter()
{

@Override
public boolean accept(File dir, String name)
{
return false;
}

@Override
public boolean accept(File file)
{
return file.getName().equals("coverage.xml");
}
}, new IOFileFilter()
{

@Override
public boolean accept(File dir, String name)
{
return false;
}

@Override
public boolean accept(File file)
{
return true;
}
});
for (File file : files)
{
overallCoverage.addAll(compute(file));
}
return overallCoverage;
}

public Coverage compute(File file) throws Exception
{
Coverage result = new Coverage();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new EntityResolver()
{
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException
{
if (systemId.contains("cobertura.sourceforge.net"))
{
return new InputSource(new StringReader(""));
}
else
{
return null;
}
}
});
Document doc = builder.parse(file);
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression classExpr = xpath.compile("//class/@filename");
NodeList foundFileNodes = (NodeList) classExpr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < foundFileNodes.getLength(); ++i)
{
String fileName = foundFileNodes.item(i).getNodeValue();
result.addFile(fileName);
XPathExpression linesExpr = xpath.compile("//class[@filename='" + fileName + "']//line");
NodeList foundLineNodes = (NodeList) linesExpr.evaluate(doc, XPathConstants.NODESET);
for (int j = 0; j < foundLineNodes.getLength(); ++j)
{
NamedNodeMap attributes = foundLineNodes.item(j).getAttributes();
int line = parseInt(attributes.getNamedItem("number").getNodeValue());
int hits = parseInt(attributes.getNamedItem("hits").getNodeValue());
result.addLine(fileName, line, hits);
}
}
return result;
}
}
49 changes: 49 additions & 0 deletions core/src/main/java/de/pfeufferweb/gitcover/Coverage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package de.pfeufferweb.gitcover;

import static java.util.Collections.unmodifiableCollection;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;

public class Coverage
{
private final Map<String, Map<Integer, Integer>> coverage = new HashMap<String, Map<Integer, Integer>>();

void addFile(String fileName)
{
coverage.put(fileName, new HashMap<Integer, Integer>());
}

void addLine(String fileName, int line, int hits)
{
coverage.get(fileName).put(line, hits);
}

public void addAll(Coverage subCoverage)
{
this.coverage.putAll(subCoverage.coverage);
}

/**
* @throws NoSuchElementException
* whenever there is no coverage for the given file.
*/
public Map<Integer, Integer> getCoverage(String name)
{
for (String candidate : getFileNames())
{
if (name.endsWith(candidate) || candidate.endsWith(name))
{
return coverage.get(candidate);
}
}
throw new NoSuchElementException("no coverage found for file " + name);
}

public Collection<String> getFileNames()
{
return unmodifiableCollection(coverage.keySet());
}
}
Loading

0 comments on commit b0ff460

Please sign in to comment.