// Copyright (c) 2015 Uber // // 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.conduit.Differential; import com.uber.jenkins.phabricator.conduit.DifferentialClient; import com.uber.jenkins.phabricator.coverage.CodeCoverageMetrics; import com.uber.jenkins.phabricator.coverage.CoverageConverter; import com.uber.jenkins.phabricator.coverage.CoverageProvider; import com.uber.jenkins.phabricator.lint.LintResult; import com.uber.jenkins.phabricator.lint.LintResults; import com.uber.jenkins.phabricator.tasks.PostCommentTask; import com.uber.jenkins.phabricator.tasks.SendHarbormasterResultTask; import com.uber.jenkins.phabricator.tasks.SendHarbormasterUriTask; import com.uber.jenkins.phabricator.tasks.Task; import com.uber.jenkins.phabricator.uberalls.UberallsClient; import com.uber.jenkins.phabricator.unit.UnitResults; import com.uber.jenkins.phabricator.unit.UnitTestProvider; import com.uber.jenkins.phabricator.utils.CommonUtils; import com.uber.jenkins.phabricator.utils.Logger; import hudson.FilePath; import hudson.model.AbstractBuild; import hudson.model.Result; import net.sf.json.JSONException; import net.sf.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Map; public class BuildResultProcessor { private static final String LOGGING_TAG = "process-build-result"; private final Logger logger; private final Differential diff; private final DifferentialClient diffClient; private final String phid; private final String buildUrl; private final boolean runHarbormaster; private final FilePath workspace; private final AbstractBuild build; private String commentAction; private final CommentBuilder commenter; private UnitResults unitResults; private Map<String, String> harbormasterCoverage; private LintResults lintResults; public BuildResultProcessor( Logger logger, AbstractBuild build, Differential diff, DifferentialClient diffClient, String phid, CodeCoverageMetrics coverageResult, String buildUrl, boolean preserveFormatting, double maximumCoverageDecreaseInPercent) { this.logger = logger; this.diff = diff; this.diffClient = diffClient; this.phid = phid; this.buildUrl = buildUrl; this.build = build; this.workspace = build.getWorkspace(); this.commentAction = "none"; this.commenter = new CommentBuilder(logger, build.getResult(), coverageResult, buildUrl, preserveFormatting, maximumCoverageDecreaseInPercent); this.runHarbormaster = !CommonUtils.isBlank(phid); } public Result getBuildResult() { return this.build.getResult(); } /** * Fetch parent coverage data from Uberalls, if available * * @param uberalls the client to the Uberalls instance * * @return */ public boolean processParentCoverage(UberallsClient uberalls) { // First add in info about the change in coverage, if applicable boolean passBuild = true; if (commenter.hasCoverageAvailable()) { if (uberalls.isConfigured()) { passBuild = commenter.processParentCoverage(uberalls.getParentCoverage(diff.getBaseCommit()), diff.getBaseCommit(), diff.getBranch()); } else { logger.info(LOGGING_TAG, "No Uberalls backend configured, skipping..."); } } else { logger.info(LOGGING_TAG, "No line coverage found, skipping..."); } return passBuild; } /** * Add build result data into the commenter * * @param commentOnSuccess whether a "success" should trigger a comment * @param commentWithConsoleLinkOnFailure whether a failure should trigger a console link */ public void processBuildResult(boolean commentOnSuccess, boolean commentWithConsoleLinkOnFailure) { commenter.processBuildResult(commentOnSuccess, commentWithConsoleLinkOnFailure, runHarbormaster); } /** * Fetch a remote comment from the build workspace * * @param commentFile the path pattern of the file * @param commentSize the maximum number of bytes to read from the remote file */ public void processRemoteComment(String commentFile, String commentSize) { RemoteFileFetcher commentFetcher = new RemoteFileFetcher(workspace, logger, commentFile, commentSize); try { String customComment = commentFetcher.getRemoteFile(); commenter.addUserComment(customComment); } catch (InterruptedException e) { e.printStackTrace(logger.getStream()); } catch (IOException e) { e.printStackTrace(logger.getStream()); } } /** * Fetch remote lint violations from the build workspace and process * * @param lintFile the path pattern of the file * @param lintFileSize maximum number of bytes to read from the remote file */ public void processLintResults(String lintFile, String lintFileSize) { RemoteFileFetcher lintFetcher = new RemoteFileFetcher(workspace, logger, lintFile, lintFileSize); try { String input = lintFetcher.getRemoteFile(); if (input != null && input.length() > 0) { lintResults = new LintResults(); BufferedReader reader = new BufferedReader(new StringReader(input)); String lint; while ((lint = reader.readLine()) != null) { try { JSONObject json = JSONObject.fromObject(lint); lintResults.add(LintResult.fromJsonObject(json)); } catch (JSONException e) { e.printStackTrace(logger.getStream()); } } } } catch (InterruptedException e) { e.printStackTrace(logger.getStream()); } catch (IOException e) { e.printStackTrace(logger.getStream()); } } /** * Send a comment to the differential, if present * * @param commentWithConsoleLinkOnFailure whether we should provide a console link on failure */ public void sendComment(boolean commentWithConsoleLinkOnFailure) { if (!commenter.hasComment()) { return; } if (commentWithConsoleLinkOnFailure && getBuildResult().isWorseOrEqualTo(hudson.model.Result.UNSTABLE)) { commenter.addBuildFailureMessage(); } else { commenter.addBuildLink(); } new PostCommentTask(logger, diffClient, diff.getRevisionID(false), commenter.getComment(), commentAction).run(); } /** * Send Harbormaster result to Phabricator * * @return whether we were able to successfully send the result */ public boolean processHarbormaster() { final boolean harbormasterSuccess = getBuildResult().isBetterOrEqualTo(Result.SUCCESS); if (runHarbormaster) { logger.info("harbormaster", "Sending Harbormaster BUILD_URL via PHID: " + phid); Task.Result sendUriResult = new SendHarbormasterUriTask(logger, diffClient, phid, buildUrl).run(); if (sendUriResult != Task.Result.SUCCESS) { logger.info(LOGGING_TAG, "Unable to send BUILD_URL to Harbormaster. " + "This can be safely ignored, and is usually because it's already set."); } if (unitResults != null) { logger.info( LOGGING_TAG, String.format("Publishing unit results to Harbormaster for %d tests.", unitResults.getResults().size()) ); } if (harbormasterCoverage != null) { logger.info( LOGGING_TAG, String.format("Publishing coverage data to Harbormaster for %d files.", harbormasterCoverage.size()) ); } if (lintResults != null) { logger.info( LOGGING_TAG, String.format("Publishing lint results for %d violations", lintResults.getResults().size()) ); } logger.info( LOGGING_TAG, String.format("Sending build result to Harbormaster with PHID %s, success: %s", phid, harbormasterSuccess ) ); Task.Result result = new SendHarbormasterResultTask( logger, diffClient, phid, harbormasterSuccess, unitResults, harbormasterCoverage, lintResults ).run(); if (result != Task.Result.SUCCESS) { return false; } } else { logger.info("uberalls", "Harbormaster integration not enabled for this build."); if (getBuildResult().isBetterOrEqualTo(Result.SUCCESS)) { commentAction = "resign"; } else if (getBuildResult().isWorseOrEqualTo(Result.UNSTABLE)) { commentAction = "reject"; } } return true; } /** * Process unit test results from the test run * * @param unitProvider a provider for unit test results */ public void processUnitResults(UnitTestProvider unitProvider) { if (unitProvider == null) { logger.info(LOGGING_TAG, "No unit provider available."); return; } if (!unitProvider.resultsAvailable()) { logger.info(LOGGING_TAG, "No unit results available."); return; } unitResults = unitProvider.getResults(); } /** * Process available coverage data into the Harbormaster coverage format * * @param coverageProvider a provider for the coverage data */ void processCoverage(CoverageProvider coverageProvider) { if (coverageProvider == null) { logger.info(LOGGING_TAG, "No coverage provider available."); return; } Map<String, List<Integer>> lineCoverage = coverageProvider.readLineCoverage(); if (lineCoverage == null || lineCoverage.isEmpty()) { logger.info(LOGGING_TAG, "No line coverage available to post to Harbormaster."); return; } harbormasterCoverage = new CoverageConverter().convert(lineCoverage); } public Map<String, String> getCoverage() { return harbormasterCoverage; } public UnitResults getUnitResults() { return unitResults; } public LintResults getLintResults() { return lintResults; } }