// 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.coverage; import hudson.plugins.cobertura.CoberturaBuildAction; import hudson.plugins.cobertura.CoberturaCoverageParser; import hudson.plugins.cobertura.CoberturaPublisher; import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import hudson.FilePath; import hudson.model.AbstractBuild; import hudson.plugins.cobertura.Ratio; import hudson.plugins.cobertura.targets.CoverageMetric; import hudson.plugins.cobertura.targets.CoverageResult; /** * Provide Cobertura coverage data */ @SuppressWarnings("unused") public class CoberturaCoverageProvider extends CoverageProvider { private static final Logger LOGGER = Logger.getLogger(CoberturaCoverageProvider.class.getName()); private static final String COVERAGE_REPORT_FILTER = "**/coverage*.xml, **/cobertura*.xml"; private CoverageResult mCoverageResult = null; private Map<String, List<Integer>> mLineCoverage = null; private boolean mHasComputedCoverage = false; @Override public boolean hasCoverage() { if (!mHasComputedCoverage) { computeCoverage(); } return mCoverageResult != null && mCoverageResult.getCoverage(CoverageMetric.LINE) != null; } @Override protected CodeCoverageMetrics getCoverageMetrics() { if (!mHasComputedCoverage) { computeCoverage(); } return convertCobertura(mCoverageResult); } @Override public Map<String, List<Integer>> readLineCoverage() { if (!mHasComputedCoverage) { computeCoverage(); } return mLineCoverage; } Map<String, List<Integer>> parseReports(CoberturaXMLParser parser, File[] reports) { if (reports == null) { return null; } try { return parser.parse(reports); } catch (IOException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } return null; } private void computeCoverage() { AbstractBuild build = getBuild(); if (build == null) { mHasComputedCoverage = true; return; } // Check if there is a cobertura build action CoberturaBuildAction coberturaAction = build.getAction(CoberturaBuildAction.class); if (coberturaAction != null) { mCoverageResult = coberturaAction.getResult(); if (mCoverageResult != null) { computeLineCoverage(); } mHasComputedCoverage = true; return; } // Fallback to scanning for the reports copyCoverageToJenkinsMaster(build); File[] reports = getCoberturaReports(build); CoverageResult result = null; if (reports != null) { for (File report : reports) { try { result = CoberturaCoverageParser.parse(report, result); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to load " + report, e); } } } if (result != null) { result.setOwner(build); computeLineCoverage(); cleanupCoverageFilesOnJenkinsMaster(); } if (result != null) { result.setOwner(build); } mCoverageResult = result; mHasComputedCoverage = true; } private void computeLineCoverage() { FilePath workspace = getBuild().getWorkspace(); File[] reports = getCoberturaReports(getBuild()); CoberturaXMLParser parser = new CoberturaXMLParser(workspace, getIncludeFileNames()); mLineCoverage = parseReports(parser, reports); } private void copyCoverageToJenkinsMaster(AbstractBuild build) { final FilePath[] moduleRoots = build.getModuleRoots(); final boolean multipleModuleRoots = moduleRoots != null && moduleRoots.length > 1; final FilePath moduleRoot = multipleModuleRoots ? build.getWorkspace() : build.getModuleRoot(); final File buildCoberturaDir = build.getRootDir(); FilePath buildTarget = new FilePath(buildCoberturaDir); if (moduleRoot != null) { try { List<FilePath> reports = Arrays.asList(moduleRoot.list(COVERAGE_REPORT_FILTER)); int i = 0; for (FilePath report : reports) { final FilePath targetPath = new FilePath(buildTarget, "coverage" + (i == 0 ? "" : i) + ".xml"); report.copyTo(targetPath); i++; } } catch (InterruptedException e) { e.printStackTrace(); LOGGER.log(Level.WARNING, "Unable to copy coverage to " + buildTarget); } catch (IOException e) { e.printStackTrace(); LOGGER.log(Level.WARNING, "Unable to copy coverage to " + buildTarget); } } } private void cleanupCoverageFilesOnJenkinsMaster() { File[] reports = getCoberturaReports(getBuild()); if (reports != null) { for (File report : reports) { report.delete(); } } } /** * Convert Cobertura results to an internal CodeCoverageMetrics representation * * @param result The cobertura report * @return The internal representation of coverage */ public static CodeCoverageMetrics convertCobertura(CoverageResult result) { if (result == null) { return null; } float packagesCoverage = getCoveragePercentage(result, CoverageMetric.PACKAGES); float filesCoverage = getCoveragePercentage(result, CoverageMetric.FILES); float classesCoverage = getCoveragePercentage(result, CoverageMetric.CLASSES); float methodCoverage = getCoveragePercentage(result, CoverageMetric.METHOD); float lineCoverage = getCoveragePercentage(result, CoverageMetric.LINE); float conditionalCoverage = getCoveragePercentage(result, CoverageMetric.CONDITIONAL); return new CodeCoverageMetrics( packagesCoverage, filesCoverage, classesCoverage, methodCoverage, lineCoverage, conditionalCoverage ); } private static float getCoveragePercentage(CoverageResult result, CoverageMetric metric) { Ratio ratio = result.getCoverage(metric); if (ratio == null) { return 0.0f; } return ratio.getPercentageFloat(); } private File[] getCoberturaReports(AbstractBuild build) { return build.getRootDir().listFiles(CoberturaPublisher.COBERTURA_FILENAME_FILTER); } }