All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.tasks.test.AbstractTestResultAction Maven / Gradle / Ivy

package hudson.tasks.test;

import hudson.Functions;
import hudson.Util;
import hudson.model.*;
import hudson.tasks.junit.CaseResult;
import hudson.util.ChartUtil;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.ShiftedCategoryAxis;
import hudson.util.StackedAreaRenderer2;
import hudson.util.Area;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.StackedAreaRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.ExportedBean;

import java.awt.Color;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * Common base class for recording test result.
 *
 * 

* {@link Project} and {@link Build} recognizes {@link Action}s that derive from this, * and displays it nicely (regardless of the underlying implementation.) * * @author Kohsuke Kawaguchi */ @ExportedBean public abstract class AbstractTestResultAction implements HealthReportingAction { public final AbstractBuild owner; protected AbstractTestResultAction(AbstractBuild owner) { this.owner = owner; } /** * Gets the number of failed tests. */ public abstract int getFailCount(); /** * Gets the number of skipped tests. */ public int getSkipCount() { // Not all sub-classes will understand the concept of skipped tests. // This default implementation is for them, so that they don't have // to implement it (this avoids breaking existing plug-ins - i.e. those // written before this method was added in 1.178). // Sub-classes that do support skipped tests should over-ride this method. return 0; } /** * Gets the total number of tests. */ public abstract int getTotalCount(); /** * Gets the diff string of failures. */ public final String getFailureDiffString() { T prev = getPreviousResult(); if(prev==null) return ""; // no record return " / "+Functions.getDiffString(this.getFailCount()-prev.getFailCount()); } public String getDisplayName() { return Messages.AbstractTestResultAction_getDisplayName(); } public String getUrlName() { return "testReport"; } public String getIconFileName() { return "clipboard.gif"; } public HealthReport getBuildHealth() { final int totalCount = getTotalCount(); final int failCount = getFailCount(); int score = (totalCount == 0) ? 100 : (int) (100.0 * (1.0 - ((double)failCount) / totalCount)); StringBuilder description = new StringBuilder(getDisplayName()); description.append(": "); if (totalCount == 0) { description.append(Messages.AbstractTestResultAction_zeroTestDescription()); } else { description.append(Messages.AbstractTestResultAction_TestsDescription(failCount, totalCount)); } return new HealthReport(score, description.toString()); } /** * Exposes this object to the remote API. */ public Api getApi() { return new Api(this); } /** * Returns the object that represents the actual test result. * This method is used by the remote API so that the XML/JSON * that we are sending won't contain unnecessary indirection * (that is, {@link AbstractTestResultAction} in between. * *

* If such a concept doesn't make sense for a particular subtype, * return this. */ public abstract Object getResult(); /** * Gets the test result of the previous build, if it's recorded, or null. */ public T getPreviousResult() { return (T)getPreviousResult(getClass()); } private U getPreviousResult(Class type) { AbstractBuild b = owner; while(true) { b = b.getPreviousBuild(); if(b==null) return null; U r = b.getAction(type); if(r!=null) return r; } } /** * A shortcut for summary.jelly * * @return List of failed tests from associated test result. */ public List getFailedTests() { return Collections.emptyList(); } /** * Generates a PNG image for the test result trend. */ public void doGraph( StaplerRequest req, StaplerResponse rsp) throws IOException { if(ChartUtil.awtProblem) { // not available. send out error message rsp.sendRedirect2(req.getContextPath()+"/images/headless.png"); return; } if(req.checkIfModified(owner.getTimestamp(),rsp)) return; ChartUtil.generateGraph(req,rsp,createChart(req,buildDataSet(req)),calcDefaultSize()); } /** * Generates a clickable map HTML for {@link #doGraph(StaplerRequest, StaplerResponse)}. */ public void doGraphMap( StaplerRequest req, StaplerResponse rsp) throws IOException { if(req.checkIfModified(owner.getTimestamp(),rsp)) return; ChartUtil.generateClickableMap(req,rsp,createChart(req,buildDataSet(req)),calcDefaultSize()); } /** * Determines the default size of the trend graph. * * This is default because the query parameter can choose arbitrary size. * If the screen resolution is too low, use a smaller size. */ private Area calcDefaultSize() { Area res = Functions.getScreenResolution(); if(res!=null && res.width<=800) return new Area(250,100); else return new Area(500,200); } private CategoryDataset buildDataSet(StaplerRequest req) { boolean failureOnly = Boolean.valueOf(req.getParameter("failureOnly")); DataSetBuilder dsb = new DataSetBuilder(); for( AbstractTestResultAction a=this; a!=null; a=a.getPreviousResult(AbstractTestResultAction.class) ) { dsb.add( a.getFailCount(), "failed", new NumberOnlyBuildLabel(a.owner)); if(!failureOnly) { dsb.add( a.getSkipCount(), "skipped", new NumberOnlyBuildLabel(a.owner)); dsb.add( a.getTotalCount()-a.getFailCount()-a.getSkipCount(),"total", new NumberOnlyBuildLabel(a.owner)); } } return dsb.build(); } private JFreeChart createChart(StaplerRequest req,CategoryDataset dataset) { final String relPath = getRelPath(req); final JFreeChart chart = ChartFactory.createStackedAreaChart( null, // chart title null, // unused "count", // range axis label dataset, // data PlotOrientation.VERTICAL, // orientation false, // include legend true, // tooltips false // urls ); // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... // set the background color for the chart... // final StandardLegend legend = (StandardLegend) chart.getLegend(); // legend.setAnchor(StandardLegend.SOUTH); chart.setBackgroundPaint(Color.white); final CategoryPlot plot = chart.getCategoryPlot(); // plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0)); plot.setBackgroundPaint(Color.WHITE); plot.setOutlinePaint(null); plot.setForegroundAlpha(0.8f); // plot.setDomainGridlinesVisible(true); // plot.setDomainGridlinePaint(Color.white); plot.setRangeGridlinesVisible(true); plot.setRangeGridlinePaint(Color.black); CategoryAxis domainAxis = new ShiftedCategoryAxis(null); plot.setDomainAxis(domainAxis); domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); domainAxis.setLowerMargin(0.0); domainAxis.setUpperMargin(0.0); domainAxis.setCategoryMargin(0.0); final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); StackedAreaRenderer ar = new StackedAreaRenderer2() { @Override public String generateURL(CategoryDataset dataset, int row, int column) { NumberOnlyBuildLabel label = (NumberOnlyBuildLabel) dataset.getColumnKey(column); return relPath+label.build.getNumber()+"/testReport/"; } @Override public String generateToolTip(CategoryDataset dataset, int row, int column) { NumberOnlyBuildLabel label = (NumberOnlyBuildLabel) dataset.getColumnKey(column); AbstractTestResultAction a = label.build.getAction(AbstractTestResultAction.class); switch (row) { case 0: return String.valueOf(Util.combine(a.getFailCount(), "failure")); case 1: return String.valueOf(Util.combine(a.getSkipCount(), "skip")); default: return String.valueOf(Util.combine(a.getTotalCount(), "test")); } } }; plot.setRenderer(ar); ar.setSeriesPaint(0,ColorPalette.RED); // Failures. ar.setSeriesPaint(1,ColorPalette.YELLOW); // Skips. ar.setSeriesPaint(2,ColorPalette.BLUE); // Total. // crop extra space around the graph plot.setInsets(new RectangleInsets(0,0,0,5.0)); return chart; } private String getRelPath(StaplerRequest req) { String relPath = req.getParameter("rel"); if(relPath==null) return ""; return relPath; } }