Skip to content

Commit

Permalink
Fix: failed the build gracefully when coverage enforcement is failed
Browse files Browse the repository at this point in the history
  • Loading branch information
cizezsy committed Aug 15, 2019
1 parent adb8c3f commit e77a0e7
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 45 deletions.
9 changes: 9 additions & 0 deletions src/main/java/io/jenkins/plugins/coverage/CoverageAction.java
Expand Up @@ -20,6 +20,7 @@ public class CoverageAction implements StaplerProxy, SimpleBuildStep.LastBuildAc
private transient Run<?, ?> owner; private transient Run<?, ?> owner;
private transient WeakReference<CoverageResult> report; private transient WeakReference<CoverageResult> report;
private HealthReport healthReport; private HealthReport healthReport;
private String failMessage;




public CoverageAction(CoverageResult result) { public CoverageAction(CoverageResult result) {
Expand Down Expand Up @@ -87,6 +88,14 @@ public void setHealthReport(HealthReport healthReport) {
this.healthReport = healthReport; this.healthReport = healthReport;
} }


public String getFailMessage() {
return failMessage;
}

public void setFailMessage(String failMessage) {
this.failMessage = failMessage;
}

private synchronized void setOwner(Run<?, ?> owner) { private synchronized void setOwner(Run<?, ?> owner) {
this.owner = owner; this.owner = owner;
if (report != null) { if (report != null) {
Expand Down
116 changes: 71 additions & 45 deletions src/main/java/io/jenkins/plugins/coverage/CoverageProcessor.java
Expand Up @@ -4,12 +4,7 @@
import com.google.common.collect.Sets; import com.google.common.collect.Sets;


import hudson.FilePath; import hudson.FilePath;
import hudson.model.HealthReport; import hudson.model.*;
import hudson.model.Job;
import hudson.model.ItemGroup;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.remoting.VirtualChannel; import hudson.remoting.VirtualChannel;
import hudson.util.RunList; import hudson.util.RunList;
import io.jenkins.plugins.coverage.adapter.CoverageReportAdapter; import io.jenkins.plugins.coverage.adapter.CoverageReportAdapter;
Expand All @@ -32,10 +27,24 @@
import org.jvnet.localizer.Localizable; import org.jvnet.localizer.Localizable;


import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
Expand Down Expand Up @@ -104,26 +113,25 @@ public void performCoverageReport(List<CoverageReportAdapter> reportAdapters, Li
sourceFileResolver.resolveSourceFiles(run, workspace, listener, coverageReport.getPaintedSources()); sourceFileResolver.resolveSourceFiles(run, workspace, listener, coverageReport.getPaintedSources());
} }


HealthReport healthReport = processThresholds(results, globalThresholds);

if (calculateDiffForChangeRequests) { if (calculateDiffForChangeRequests) {
setDiffInCoverageForChangeRequest(coverageReport); setDiffInCoverageForChangeRequest(coverageReport);
} }
convertResultToAction(coverageReport, healthReport); CoverageAction action = convertResultToAction(coverageReport);

HealthReport healthReport = processThresholds(results, globalThresholds, action);
action.setHealthReport(healthReport);
} }


private void convertResultToAction(CoverageResult coverageReport, HealthReport healthReport) throws IOException { private CoverageAction convertResultToAction(CoverageResult coverageReport) throws IOException {
synchronized (CoverageProcessor.class) { synchronized (CoverageProcessor.class) {

CoverageAction previousAction = run.getAction(CoverageAction.class); CoverageAction previousAction = run.getAction(CoverageAction.class);

if (previousAction == null) { if (previousAction == null) {
saveCoverageResult(run, coverageReport); saveCoverageResult(run, coverageReport);


CoverageAction action = new CoverageAction(coverageReport); CoverageAction action = new CoverageAction(coverageReport);
action.setHealthReport(healthReport);
run.addAction(action); run.addAction(action);


return action;
} else { } else {
CoverageResult previousResult = previousAction.getResult(); CoverageResult previousResult = previousAction.getResult();
Collection<CoverageResult> previousReports = previousResult.getChildrenReal().values(); Collection<CoverageResult> previousReports = previousResult.getChildrenReal().values();
Expand Down Expand Up @@ -151,6 +159,7 @@ private void convertResultToAction(CoverageResult coverageReport, HealthReport h


previousResult.setOwner(run); previousResult.setOwner(run);
saveCoverageResult(run, previousResult); saveCoverageResult(run, previousResult);
return previousAction;
} }
} }
} }
Expand Down Expand Up @@ -204,8 +213,8 @@ private void setDiffInCoverageForChangeRequest(CoverageResult coverageReport) {
Run buildToTakeCoverageFrom = null; Run buildToTakeCoverageFrom = null;


// Search for a build from the target branch job to compare coverage with // Search for a build from the target branch job to compare coverage with
RunList<Run> buildsFromTargetBranchJob = ((RunList<Run>)targetBranchJob.getBuilds()).limit(numberOfBuildsFromTargetBranch); RunList<Run> buildsFromTargetBranchJob = ((RunList<Run>) targetBranchJob.getBuilds()).limit(numberOfBuildsFromTargetBranch);
for (Run targetBranchBuild: buildsFromTargetBranchJob){ for (Run targetBranchBuild : buildsFromTargetBranchJob) {
SCMRevisionAction targetBranchBuildRevision = targetBranchBuild.getAction(SCMRevisionAction.class); SCMRevisionAction targetBranchBuildRevision = targetBranchBuild.getAction(SCMRevisionAction.class);
if (targetBranchBuildRevision != null) { if (targetBranchBuildRevision != null) {
if (targetBranchBuildRevision.getRevision().hashCode() == targetBranchSCMHash) { if (targetBranchBuildRevision.getRevision().hashCode() == targetBranchSCMHash) {
Expand Down Expand Up @@ -271,7 +280,7 @@ private Map<CoverageReportAdapter, List<CoverageResult>> convertToResults(List<C
//if copy exist, it means there have reports have same name. //if copy exist, it means there have reports have same name.
int i = 1; int i = 1;
while (copy.exists()) { while (copy.exists()) {
copy = new File(runRootDir, String.format("%s(%d)", f.getName() , i++)); copy = new File(runRootDir, String.format("%s(%d)", f.getName(), i++));
} }


f.copyTo(new FilePath(copy)); f.copyTo(new FilePath(copy));
Expand Down Expand Up @@ -380,13 +389,18 @@ private Map<CoverageReportAdapter, List<CoverageResult>> convertToResults(List<C
* *
* @param adapterWithResults Coverage report adapter and its correspond Coverage results. * @param adapterWithResults Coverage report adapter and its correspond Coverage results.
* @param globalThresholds global threshold * @param globalThresholds global threshold
* @param action coverage action
* @return Health report * @return Health report
*/ */
private HealthReport processThresholds(Map<CoverageReportAdapter, List<CoverageResult>> adapterWithResults, private HealthReport processThresholds(Map<CoverageReportAdapter, List<CoverageResult>> adapterWithResults,
List<Threshold> globalThresholds) throws CoverageException { List<Threshold> globalThresholds, CoverageAction action) throws CoverageException {


int healthyCount = 0; int healthyCount = 0;
int unhealthyCount = 0; int unhealthyCount = 0;
int unstableCount = 0;

Set<Threshold> unstableThresholds = new HashSet<>();
Set<Threshold> unhealthyThresholds = new HashSet<>();


LinkedList<CoverageResult> resultTask = new LinkedList<>(); LinkedList<CoverageResult> resultTask = new LinkedList<>();


Expand Down Expand Up @@ -419,45 +433,56 @@ private HealthReport processThresholds(Map<CoverageReportAdapter, List<CoverageR
} }


float percentage = ratio.getPercentageFloat(); float percentage = ratio.getPercentageFloat();
if (percentage < threshold.getUnhealthyThreshold()) { if (percentage < threshold.getUnstableThreshold()) {
if (getFailUnhealthy() || threshold.isFailUnhealthy()) { unstableCount++;
throw new CoverageException( listener.getLogger().printf("Code coverage enforcement failed: %s coverage in %s level '%s' is lower than %.2f stable threshold\n",
String.format("Publish Coverage Failed (Unhealthy): %s coverage in %s is lower than %.2f", threshold.getThresholdTarget(),
threshold.getThresholdTarget(), r.getElement().getName(),
r.getName(), threshold.getUnhealthyThreshold())); r.getName(), threshold.getUnstableThreshold());
} else { unstableThresholds.add(threshold);
unhealthyCount++; } else if (percentage < threshold.getUnhealthyThreshold()) {
} unhealthyCount++;
listener.getLogger().printf("Code coverage enforcement failed: %s coverage in %s level '%s' is lower than %.2f healthy threshold\n",
threshold.getThresholdTarget(),
r.getElement().getName(),
r.getName(), threshold.getUnhealthyThreshold());
unhealthyThresholds.add(threshold);
} else { } else {
healthyCount++; healthyCount++;
} }

if (percentage < threshold.getUnstableThreshold()) {
if (getFailUnstable()) {
throw new CoverageException(
String.format("Publish Coverage Failed (Unstable): %s coverage in %s is lower than %.2f",
threshold.getThresholdTarget(),
r.getName(), threshold.getUnstableThreshold()));
} else {
run.setResult(Result.UNSTABLE);
}
}
} }
} }
} }
} }


if (unstableCount > 0) {
if (getFailUnstable()) {
action.setFailMessage(String.format("Build failed because following metrics did not meet stability target: %s.", unstableThresholds.toString()));
throw new CoverageException(action.getFailMessage());
} else {
run.setResult(Result.UNSTABLE);
}
}

if (unhealthyCount > 0) {
if (getFailUnhealthy()) {
action.setFailMessage(String.format("Build failed because following metrics did not meet health target: %s.", unhealthyThresholds.toString()));
throw new CoverageException(action.getFailMessage());
}


resultTask.addAll( unhealthyThresholds = unhealthyThresholds.stream().filter(Threshold::isFailUnhealthy)
adapterWithResults.values().stream() .collect(Collectors.toSet());
.flatMap((Function<List<CoverageResult>, Stream<CoverageResult>>) Collection::stream) if (unhealthyThresholds.size() > 0) {
.collect(Collectors.toList())); action.setFailMessage(String.format("Build failed because following metrics did not meet health target: %s.", unhealthyThresholds.toString()));
throw new CoverageException(action.getFailMessage());
}
}


int score; int score;
if (healthyCount == 0 && unhealthyCount == 0) { if (healthyCount == 0 && unhealthyCount == 0 && unstableCount == 0) {
score = 100; score = 100;
} else { } else {
score = healthyCount * 100 / (healthyCount + unhealthyCount); score = healthyCount * 100 / (healthyCount + unhealthyCount + unstableCount);
} }
Localizable localizeDescription = Messages._CoverageProcessor_healthReportDescriptionTemplate(score); Localizable localizeDescription = Messages._CoverageProcessor_healthReportDescriptionTemplate(score);


Expand All @@ -467,6 +492,7 @@ private HealthReport processThresholds(Map<CoverageReportAdapter, List<CoverageR


/** /**
* aggregate coverage results into one report * aggregate coverage results into one report
*
* @param adapter CoverageAdapter * @param adapter CoverageAdapter
* @param results CoverageResults converted by adapter * @param results CoverageResults converted by adapter
* @return Coverage report that have all coverage results * @return Coverage report that have all coverage results
Expand Down
Expand Up @@ -81,6 +81,14 @@ public int hashCode() {
return Objects.hash(getThresholdTarget()); return Objects.hash(getThresholdTarget());
} }


@Override
public String toString() {
return thresholdTarget + "'{'" +
"unstableThreshold=" + unstableThreshold +
", unhealthyThreshold=" + unhealthyThreshold +
'}';
}

@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Descriptor<Threshold> getDescriptor() { public Descriptor<Threshold> getDescriptor() {
Expand Down

0 comments on commit e77a0e7

Please sign in to comment.