// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package com.uber.jenkins.phabricator;
import com.uber.jenkins.phabricator.coverage.CodeCoverageMetrics;
import com.uber.jenkins.phabricator.utils.CommonUtils;
import com.uber.jenkins.phabricator.utils.Logger;
import hudson.model.Result;
class CommentBuilder {
private static final String UBERALLS_TAG = "uberalls";
private final Logger logger;
private final CodeCoverageMetrics currentCoverage;
private final StringBuilder comment;
private final String buildURL;
private final Result result;
private final boolean preserveFormatting;
private final double maximumCoverageDecreaseInPercent;
public CommentBuilder(Logger logger, Result result, CodeCoverageMetrics currentCoverage, String buildURL,
boolean preserveFormatting, double maximumCoverageDecreaseInPercent) {
this.maximumCoverageDecreaseInPercent = maximumCoverageDecreaseInPercent;
this.logger = logger;
this.result = result;
this.currentCoverage = currentCoverage;
this.buildURL = buildURL;
this.preserveFormatting = preserveFormatting;
this.comment = new StringBuilder();
}
/**
* Get the final comment to post to Phabricator
* @return
*/
public String getComment() {
return comment.toString();
}
/**
* Determine whether to attempt to process coverage
* @return
*/
public boolean hasCoverageAvailable() {
return currentCoverage != null && currentCoverage.getLineCoveragePercent() > 0.0f;
}
/**
* Query uberalls for parent coverage and add appropriate comment
* @param parentCoverage the parent coverage returned from uberalls
* @param baseCommit
* @param branchName the name of the current branch
*
* @return boolean if we fail coverage reporting from threshold
*/
public boolean processParentCoverage(CodeCoverageMetrics parentCoverage, String baseCommit, String branchName) {
boolean passCoverage = true;
if (parentCoverage == null) {
logger.info(UBERALLS_TAG, "unable to find coverage for parent commit");
return passCoverage;
}
Float lineCoveragePercent = currentCoverage.getLineCoveragePercent();
logger.info(UBERALLS_TAG, "line coverage: " + lineCoveragePercent);
logger.info(UBERALLS_TAG, "found parent coverage as " + parentCoverage.getLineCoveragePercent());
double coverageDelta = lineCoveragePercent - parentCoverage.getLineCoveragePercent();
String coverageDeltaDisplay = String.format("%.3f", coverageDelta);
String lineCoverageDisplay = String.format("%.3f", lineCoveragePercent);
if (coverageDelta > 0) {
comment.append("Coverage increased (+" + coverageDeltaDisplay + "%) to " + lineCoverageDisplay + "%");
} else if (coverageDelta < 0) {
comment.append("Coverage decreased (" + coverageDeltaDisplay + "%) to " + lineCoverageDisplay + "%");
} else {
comment.append("Coverage remained the same (" + lineCoverageDisplay + "%)");
}
// If coverage change is less than zero and dips below a certain threshold fail the build
if (coverageDelta < 0 && Math.abs(coverageDelta) > Math.abs(maximumCoverageDecreaseInPercent)) {
passCoverage = false;
}
comment.append(" when pulling **" + branchName + "** into ");
comment.append(baseCommit.substring(0, 7));
comment.append(".");
return passCoverage;
}
public void processBuildResult(boolean commentOnSuccess, boolean commentWithConsoleLinkOnFailure, boolean runHarbormaster) {
if (result == Result.SUCCESS) {
if (comment.length() == 0 && (commentOnSuccess || !runHarbormaster)) {
comment.append("Build is green");
}
} else if (result == Result.UNSTABLE) {
comment.append("Build is unstable");
} else if (result == Result.FAILURE) {
if (!runHarbormaster || commentWithConsoleLinkOnFailure) {
comment.append("Build has FAILED");
}
} else if (result == Result.ABORTED) {
comment.append("Build was aborted");
} else {
logger.info(UBERALLS_TAG, "Unknown build status " + result.toString());
}
}
/**
* Add user-defined content via a .phabricator-comment file
* @param customComment the contents of the file
*/
public void addUserComment(String customComment) {
if (CommonUtils.isBlank(customComment)) {
return;
}
// Ensure we separate previous parts of the comment with newlines
if (hasComment()) {
comment.append("\n\n");
}
if (preserveFormatting) {
comment.append(String.format("%s\n", customComment));
} else {
comment.append(String.format("```\n%s\n```\n\n", customComment));
}
}
/**
* Determine if there exists a comment already
* @return
*/
public boolean hasComment() {
return comment.length() > 0;
}
/**
* Add a build link to the comment
*/
public void addBuildLink() {
comment.append(String.format(" %s for more details.", buildURL));
}
/**
* Add a build failure message to the comment
*/
public void addBuildFailureMessage() {
comment.append(String.format("\n\nLink to build: %s", buildURL));
comment.append(String.format("\nSee console output for more information: %sconsole", buildURL));
}
}