Skip to content
This repository was archived by the owner on Oct 29, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,19 @@ dependencies {

optionalJenkinsPlugins 'org.jenkins-ci.plugins:junit:1.6@jar'
optionalJenkinsPlugins 'org.jenkins-ci.plugins:cobertura:1.11@jar'
optionalJenkinsPlugins 'org.jenkins-ci.plugins:jacoco:2.2.1@jar'
providedCompile 'org.jacoco:org.jacoco.report:0.7.8'

testCompile 'junit:junit:4.12'
testCompile 'org.apache.httpcomponents:httpclient:4.3:tests'
testCompile 'net.sf.trove4j:trove4j:3.0.3'
testCompile('org.mockito:mockito-core:1.10.19') {
exclude module: 'hamcrest-core'
}
testCompile 'org.powermock:powermock-api-mockito:1.6.2'
testCompile 'org.powermock:powermock-module-junit4:1.6.2'
testCompile 'org.powermock:powermock-classloading-xstream:1.6.2'
testCompile 'org.jenkins-ci.plugins:jacoco:2.2.1'

jenkinsTest 'org.jenkins-ci.main:jenkins-war:2.6@war'
jenkinsTest 'org.jenkins-ci.plugins:matrix-project:1.4@jar'
Expand All @@ -65,6 +71,7 @@ configurations.all {
if (it.name.toLowerCase().contains("test")) {
resolutionStrategy {
force 'org.apache.httpcomponents:httpclient:4.3:tests'
force 'xml-apis:xml-apis:1.4.01'
}
}
}
Expand Down
68 changes: 42 additions & 26 deletions src/main/java/com/uber/jenkins/phabricator/PhabricatorNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

Expand All @@ -66,6 +68,8 @@ public class PhabricatorNotifier extends Notifier implements SimpleBuildStep {
private static final String JUNIT_PLUGIN_NAME = "junit";
private static final String JUNIT_CLASS_NAME = "com.uber.jenkins.phabricator.unit.JUnitTestProvider";
private static final String COBERTURA_PLUGIN_NAME = "cobertura";
private static final String JACOCO_PLUGIN_NAME = "jacoco";
private static final String JACOCO_CLASS_NAME = "com.uber.jenkins.phabricator.coverage.JacocoCoverageProvider";
private static final String ABORT_TAG = "abort";
private static final String UBERALLS_TAG = "uberalls";
private static final String CONDUIT_TAG = "conduit";
Expand Down Expand Up @@ -312,48 +316,60 @@ private CoverageProvider getCoverageProvider(Run<?, ?> build, FilePath workspace
}

Logger logger = new Logger(listener.getLogger());
InstanceProvider<CoverageProvider> provider = new InstanceProvider<CoverageProvider>(
Jenkins.getInstance(),
COBERTURA_PLUGIN_NAME,
COBERTURA_CLASS_NAME,
logger
);
CoverageProvider coverage = provider.getInstance();
List<CoverageProvider> coverageProviders = new ArrayList<CoverageProvider>();

if (coverage == null) {
return null;
CoverageProvider coberturaCoverage = makeProvider(COBERTURA_PLUGIN_NAME, COBERTURA_CLASS_NAME, logger);
if (coberturaCoverage != null) {
coverageProviders.add(coberturaCoverage);
}

coverage.setBuild(build);
coverage.setWorkspace(workspace);
coverage.setIncludeFileNames(includeFileNames);
coverage.setCoverageReportPattern(coverageReportPattern);
if (coverage.hasCoverage()) {
return coverage;
} else {
logger.info(UBERALLS_TAG, "No cobertura results found");
return null;
CoverageProvider jacocoCoverage = makeProvider(JACOCO_PLUGIN_NAME, JACOCO_CLASS_NAME, logger);
if (jacocoCoverage != null) {
coverageProviders.add(jacocoCoverage);
}

for (Iterator<CoverageProvider> i = coverageProviders.iterator(); i.hasNext(); ) {
CoverageProvider coverageProvider = i.next();
coverageProvider.setBuild(build);
coverageProvider.setWorkspace(workspace);
coverageProvider.setIncludeFileNames(includeFileNames);
coverageProvider.setCoverageReportPattern(coverageReportPattern);

if (!coverageProvider.hasCoverage()) {
i.remove();
}
}
if (!coverageProviders.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a logger.info statement here about which coverage we picked if its a non empty list?

It will help with debugging issues

CoverageProvider provider = coverageProviders.get(0);
logger.info(UBERALLS_TAG, "Selected Coverage Provider: " + provider);
return provider;
}

logger.info(UBERALLS_TAG, "No coverage results found");
return null;
}

private UnitTestProvider getUnitProvider(Run<?, ?> build, TaskListener listener) {
Logger logger = new Logger(listener.getLogger());

InstanceProvider<UnitTestProvider> provider = new InstanceProvider<UnitTestProvider>(
Jenkins.getInstance(),
JUNIT_PLUGIN_NAME,
JUNIT_CLASS_NAME,
logger
);

UnitTestProvider unitProvider = provider.getInstance();
UnitTestProvider unitProvider = makeProvider(JUNIT_PLUGIN_NAME, JUNIT_CLASS_NAME, logger);
if (unitProvider == null) {
return null;
}
unitProvider.setBuild(build);
return unitProvider;
}

private <T> T makeProvider(String pluginName, String className, Logger logger) {
InstanceProvider<T> instanceProvider = new InstanceProvider<T>(
Jenkins.getInstance(),
pluginName,
className,
logger
);
return instanceProvider.getInstance();
}

@SuppressWarnings("UnusedDeclaration")
public boolean isCommentOnSuccess() {
return commentOnSuccess;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package com.uber.jenkins.phabricator.coverage;

import com.google.common.annotations.VisibleForTesting;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.Project;
import hudson.model.Run;
import hudson.plugins.jacoco.ExecutionFileLoader;
import hudson.plugins.jacoco.JacocoBuildAction;
import hudson.plugins.jacoco.JacocoPublisher;
import hudson.plugins.jacoco.report.CoverageReport;
import hudson.remoting.VirtualChannel;
import jenkins.MasterToSlaveFileCallable;
import org.apache.tools.ant.DirectoryScanner;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.analysis.ILine;
import org.jacoco.core.analysis.IPackageCoverage;
import org.jacoco.core.analysis.ISourceFileCoverage;
import org.jacoco.core.analysis.ISourceNode;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Provides Jacoco coverage data
*/
@SuppressWarnings("unused")
public class JacocoCoverageProvider extends CoverageProvider {

private static final int PERCENTAGE_UNAVAILABLE = -1;

@Override
public boolean hasCoverage() {
CoverageReport result = getCoverageResult();
return result != null && result.hasLineCoverage();
}

private CoverageReport getCoverageResult() {
JacocoBuildAction jacocoAction = getJacocoBuildAction();
if (jacocoAction == null) {
return null;
}
return jacocoAction.getResult();
}

private JacocoBuildAction getJacocoBuildAction() {
Run<?, ?> build = getBuild();
if (build == null) {
return null;
}

return build.getAction(JacocoBuildAction.class);
}

private JacocoPublisher getJacocoPublisher() {
Run<?, ?> run = getBuild();
if (run == null) {
return null;
}
Project<?,?> project = (Project<?,?>) ((AbstractBuild) run).getProject();
return (JacocoPublisher) project.getPublisher(JacocoPublisher.DESCRIPTOR);
}

@Override
protected CodeCoverageMetrics getCoverageMetrics() {
return convertJacoco(getCoverageResult());
}

@Override
public Map<String, List<Integer>> readLineCoverage() {
JacocoBuildAction jacocoAction = getJacocoBuildAction();
JacocoPublisher jacocoPublisher = getJacocoPublisher();
if (jacocoAction == null || jacocoPublisher == null) {
return null;
}

PathResolver pathResolver = new PathResolver(getWorkspace(), getSourceDirs());
HashMap<String, List<Integer>> lineCoverage = new HashMap<String, List<Integer>>();

String[] includes = null;
if (jacocoPublisher.getInclusionPattern() != null) {
includes = new String[]{ jacocoPublisher.getInclusionPattern() };
}

String[] excludes = null;
if (jacocoPublisher.getExclusionPattern() != null) {
excludes = new String[]{jacocoPublisher.getExclusionPattern()};
}

try {
ExecutionFileLoader executionFileLoader = jacocoAction.getJacocoReport().parse(includes, excludes);
for (IPackageCoverage packageCoverage : executionFileLoader.getBundleCoverage().getPackages()) {
for (ISourceFileCoverage fileCoverage : packageCoverage.getSourceFiles()) {
String relativePathFromProjectRoot = getRelativePathFromProjectRoot(pathResolver, fileCoverage);
if (relativePathFromProjectRoot != null) {
lineCoverage.put(relativePathFromProjectRoot, getPerLineCoverage(fileCoverage));
}
}
}
} catch (IOException e) {
e.printStackTrace();
return null;
}

return lineCoverage;
}

private static String getRelativePathFromProjectRoot(PathResolver pathResolver, ISourceFileCoverage fileCoverage) {
String relativeSourcePath = fileCoverage.getPackageName() + "/" + fileCoverage.getName();
Map<String, String> stringMap = pathResolver.choose(Collections.singletonList(relativeSourcePath));
String dirPath = stringMap.get(relativeSourcePath);
if (dirPath == null) {
return null;
}
return dirPath + "/" + relativeSourcePath;
}

private List<String> getSourceDirs() {
JacocoPublisher jacocoPublisher = getJacocoPublisher();
FilePath[] dirPaths = resolveDirPaths(getWorkspace(), jacocoPublisher.getSourcePattern());

List<String> relativePaths = new ArrayList<String>();
for (FilePath dirPath : dirPaths) {
relativePaths.add(makeRelative(dirPath.getRemote()));
}
return relativePaths;
}

private static List<Integer> getPerLineCoverage(ISourceFileCoverage fileCoverage) {
List<Integer> perLineCoverages = new ArrayList<Integer>();

if (fileCoverage.getFirstLine() == ISourceNode.UNKNOWN_LINE ||
fileCoverage.getLastLine() == ISourceNode.UNKNOWN_LINE) {
return null;
}
for (int i = 1; i <= fileCoverage.getLastLine(); i++) {
ILine line = fileCoverage.getLine(i);
perLineCoverages.add(getHitCount(line));
}
return perLineCoverages;
}

private static Integer getHitCount(ILine line) {
// Fake hit-count as Jacoco doesn't provide it
Integer fakeHitCount;
switch (line.getStatus()) {
case ICounter.EMPTY:
fakeHitCount = null;
break;
case ICounter.NOT_COVERED:
fakeHitCount = 0;
break;
case ICounter.FULLY_COVERED:
fakeHitCount = 1;
break;
case ICounter.PARTLY_COVERED:
fakeHitCount = 0; // Harbormaster format doesn't allow for partially covered lines
break;
default:
fakeHitCount = null;
}
return fakeHitCount;
}

@SuppressWarnings("WeakerAccess")
@VisibleForTesting
static CodeCoverageMetrics convertJacoco(CoverageReport coverageResult) {
if (coverageResult == null) {
return null;
}
float methodCoverage = coverageResult.getMethodCoverage().getPercentageFloat();
float classCoverage = coverageResult.getClassCoverage().getPercentageFloat();
float lineCoverage = coverageResult.getLineCoverage().getPercentageFloat();
float branchCoverage = coverageResult.getBranchCoverage().getPercentageFloat();

return new CodeCoverageMetrics(
PERCENTAGE_UNAVAILABLE,
PERCENTAGE_UNAVAILABLE,
classCoverage,
methodCoverage,
lineCoverage,
branchCoverage
);
}

private String makeRelative(String srcDir) {
return srcDir.replaceFirst(getWorkspace() + "/", "");
}

// From Jacoco Jenkins plugin
private static FilePath[] resolveDirPaths(FilePath workspace, final String input) {
FilePath[] directoryPaths = null;
try {
directoryPaths = workspace.act(new MasterToSlaveFileCallable<FilePath[]>() {
static final long serialVersionUID = 1552178457453558870L;

public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
FilePath base = new FilePath(f);
ArrayList<FilePath> localDirectoryPaths = new ArrayList<FilePath>();
String[] includes = input.split(",");
DirectoryScanner ds = new DirectoryScanner();

ds.setIncludes(includes);
ds.setCaseSensitive(false);
ds.setBasedir(f);
ds.scan();
String[] dirs = ds.getIncludedDirectories();

for (String dir : dirs) {
localDirectoryPaths.add(base.child(dir));
}
FilePath[] lfp = {};//trick to have an empty array as a parameter, so the returned array will contain the elements
return localDirectoryPaths.toArray(lfp);
}
});

} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (IOException io) {
io.printStackTrace();
}
return directoryPaths;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public void testPostCoverageWithoutPublisherWithNoFilesMatchingReportPattern() t

FreeStyleBuild build = buildWithConduit(getFetchDiffResponse(), null, new JSONObject());
assertEquals(Result.SUCCESS, build.getResult());
assertLogContains("No cobertura results found", build);
assertLogContains("No coverage results found", build);
}

@Test
Expand Down
Loading