diff --git a/.gitignore b/.gitignore index 29f617f0..bd5bafac 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,8 @@ work .project build +# Generated images from CoverageObjectGraphTest +/*.png + # other *~ diff --git a/resources/test/Font Monkey License.txt b/resources/test/Font Monkey License.txt new file mode 100644 index 00000000..38b92144 --- /dev/null +++ b/resources/test/Font Monkey License.txt @@ -0,0 +1,13 @@ +This font is copyright 2008 by P.D. Magnus. Like all the Fontmonkey fonts, it is free for for all commercial or non-commercial use. To be clear: They do not cost anything. + +If you do use them for something, though, I would love to here about it. I would appreciate a sample of the thing for which you used the font, a photo of it, or even just an e-mail telling me about it. + +You can contact me via the website or by e-mail at pmagnus@fecundity.com + +You are also encouraged to acknowledge fontmonkey or link to me, although neither is strictly speaking required. + +The font files may be freely distributed provided this license, attribution to me, and the fontmonkey URL are included. + +VERSION HISTORY + +26apr2008 first release \ No newline at end of file diff --git a/resources/test/baseStroke.png b/resources/test/baseStroke.png new file mode 100644 index 00000000..bf71efba Binary files /dev/null and b/resources/test/baseStroke.png differ diff --git a/resources/test/belligerent.ttf b/resources/test/belligerent.ttf new file mode 100644 index 00000000..f0ad37c6 Binary files /dev/null and b/resources/test/belligerent.ttf differ diff --git a/resources/test/crop100.png b/resources/test/crop100.png new file mode 100644 index 00000000..fbe29071 Binary files /dev/null and b/resources/test/crop100.png differ diff --git a/resources/test/crop5.png b/resources/test/crop5.png new file mode 100644 index 00000000..529d4d7f Binary files /dev/null and b/resources/test/crop5.png differ diff --git a/resources/test/multiple.png b/resources/test/multiple.png new file mode 100644 index 00000000..90e32a15 Binary files /dev/null and b/resources/test/multiple.png differ diff --git a/resources/test/simple.png b/resources/test/simple.png new file mode 100644 index 00000000..57092753 Binary files /dev/null and b/resources/test/simple.png differ diff --git a/resources/test/skipzero.png b/resources/test/skipzero.png new file mode 100644 index 00000000..704f8ea2 Binary files /dev/null and b/resources/test/skipzero.png differ diff --git a/src/main/java/hudson/plugins/jacoco/model/CoverageGraphLayout.java b/src/main/java/hudson/plugins/jacoco/model/CoverageGraphLayout.java new file mode 100644 index 00000000..b8410dff --- /dev/null +++ b/src/main/java/hudson/plugins/jacoco/model/CoverageGraphLayout.java @@ -0,0 +1,344 @@ +package hudson.plugins.jacoco.model; + +import hudson.plugins.jacoco.Messages; +import java.awt.BasicStroke; +import java.awt.Color; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.renderer.category.LineAndShapeRenderer; +import org.jfree.ui.RectangleEdge; +import org.jfree.ui.RectangleInsets; + +/** + * @author Martin Heinzerling + */ +public class CoverageGraphLayout +{ + enum CoverageType + { + INSTRUCTION(Messages.CoverageObject_Legend_Instructions()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.instruction; + } + + }, + BRANCH(Messages.CoverageObject_Legend_Branch()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.branch; + } + + }, + COMPLEXITY(Messages.CoverageObject_Legend_Complexity()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.complexity; + } + + }, + METHOD(Messages.CoverageObject_Legend_Method()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.method; + } + + }, + CLAZZ(Messages.CoverageObject_Legend_Class()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.clazz; + } + + }, + LINE(Messages.CoverageObject_Legend_Line()) + { + @Override + public Coverage getCoverage(CoverageObject a) + { + return a.line; + } + + }; + + private String message; + + CoverageType(String message) + { + this.message = message; + } + + public String getMessage() + { + + return message; + } + + public abstract Coverage getCoverage(CoverageObject a); + + public Number getValue(CoverageObject a, CoverageValue value) + { + Coverage c = getCoverage(a); + if (c == null) return 0; + return value.getValue(c); + + } + + } + + enum CoverageValue + { + MISSED + { + @Override + public String getMessage(CoverageType type) + { + return + Messages.CoverageObject_Legend_Missed(type.getMessage()); + + } + + @Override + public Number getValue(Coverage c) + { + return c.getMissed(); + } + }, + COVERED + { + @Override + public String getMessage(CoverageType type) + { + return Messages.CoverageObject_Legend_Covered(type.getMessage()); + } + + @Override + public Number getValue(Coverage c) + { + return c.getCovered(); + } + }, + PERCENTAGE + { + @Override + public String getMessage(CoverageType type) + { + return type.getMessage(); + } + + @Override + public Number getValue(Coverage c) + { + return c.getPercentageFloat(); + } + }; + + + public abstract String getMessage(CoverageType type); + + public abstract Number getValue(Coverage c); + } + + static class Axis + { + private String label = null; + private int crop = -1; + private boolean skipZero = false; + + public boolean isCrop() + { + return crop != -1; + } + + public boolean isSkipZero() + { + return skipZero; + } + + public String getLabel() + { + return label; + } + + public int getCrop() + { + return crop; + } + } + + static class Plot + { + private CoverageValue value; + private CoverageType type; + private Axis axis; + private Color color; + + public Plot(Axis axis) + { + this.axis = axis; + } + + public Number getValue(CoverageObject a) + { + return type.getValue(a, value); + } + + public String getMessage() + { + return value.getMessage(type); + } + + public Axis getAxis() + { + return axis; + } + + @Override + public String toString() + { + return axis + " " + type + " " + value + " " + color; + } + } + + private float baseStroke = 4f; + private Stack axes = new Stack(); + private Stack plots = new Stack(); + + public CoverageGraphLayout baseStroke(float baseStroke) + { + this.baseStroke = baseStroke; + return this; + } + + public CoverageGraphLayout axis() + { + axes.push(new Axis()); + return this; + } + + private void assureAxis() + { + if (axes.isEmpty()) axis(); + } + + public CoverageGraphLayout crop() + { + return crop(5); + } + + public CoverageGraphLayout crop(int marginInPercent) + { + assureAxis(); + axes.peek().crop = marginInPercent; + return this; + } + + public CoverageGraphLayout label(String label) + { + assureAxis(); + axes.peek().label = label; + return this; + } + + public CoverageGraphLayout skipZero() + { + assureAxis(); + axes.peek().skipZero = true; + return this; + } + + public CoverageGraphLayout plot() + { + assureAxis(); + plots.add(new Plot(axes.peek())); + return this; + } + + private void assurePlot() + { + if (plots.isEmpty()) plot(); + } + + public CoverageGraphLayout type(CoverageType type) + { + assurePlot(); + plots.peek().type = type; + return this; + } + + public CoverageGraphLayout value(CoverageValue value) + { + assurePlot(); + plots.peek().value = value; + return this; + } + + public CoverageGraphLayout color(Color color) + { + assurePlot(); + plots.peek().color = color; + return this; + } + + public List getAxes() + { + return Collections.unmodifiableList(axes); + } + + public List getPlots() + { + return Collections.unmodifiableList(plots); + } + + public void apply(JFreeChart chart) + { + final CategoryPlot plot = chart.getCategoryPlot(); + Map axisIds = new HashMap(); + int axisId = 0; + for (Axis axis : axes) + { + LineAndShapeRenderer renderer = new LineAndShapeRenderer(true, false); + renderer.setBaseStroke(new BasicStroke(baseStroke)); + //add axis layout here + plot.setRenderer(axisId, renderer); + axisIds.put(axis, axisId); + axisId++; + } + + for (Plot p : plots) + { + axisId = axisIds.get(p.axis); + int lineIdPerAxis = plot.getDataset(axisId).getRowIndex(p.getMessage()); + LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(axisId); + renderer.setSeriesPaint(lineIdPerAxis, p.color); + renderer.setSeriesItemLabelPaint(lineIdPerAxis, p.color); + renderer.setSeriesFillPaint(lineIdPerAxis, p.color); + //add line layout here + } + + chart.getLegend().setPosition(RectangleEdge.RIGHT); + chart.setBackgroundPaint(Color.white); + // plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0)); + plot.setBackgroundPaint(Color.WHITE); + plot.setOutlinePaint(null); + plot.setRangeGridlinesVisible(true); + plot.setRangeGridlinePaint(Color.black); + plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0)); + // add common layout here + } +} diff --git a/src/main/java/hudson/plugins/jacoco/model/CoverageObject.java b/src/main/java/hudson/plugins/jacoco/model/CoverageObject.java index 639c73fd..fc4273b8 100644 --- a/src/main/java/hudson/plugins/jacoco/model/CoverageObject.java +++ b/src/main/java/hudson/plugins/jacoco/model/CoverageObject.java @@ -3,37 +3,40 @@ import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.Api; -import hudson.plugins.jacoco.Messages; import hudson.plugins.jacoco.Rule; +import hudson.plugins.jacoco.model.CoverageGraphLayout.Axis; +import hudson.plugins.jacoco.model.CoverageGraphLayout.CoverageType; +import hudson.plugins.jacoco.model.CoverageGraphLayout.CoverageValue; +import hudson.plugins.jacoco.model.CoverageGraphLayout.Plot; import hudson.plugins.jacoco.report.AggregatedReport; import hudson.util.ChartUtil; import hudson.util.ChartUtil.NumberOnlyBuildLabel; import hudson.util.DataSetBuilder; import hudson.util.Graph; import hudson.util.ShiftedCategoryAxis; - -import java.awt.BasicStroke; import java.awt.Color; import java.io.IOException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; +import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; - +import java.util.Map; +import java.util.Map.Entry; import org.jacoco.core.analysis.ICoverageNode; 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.axis.ValueAxis; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.renderer.category.LineAndShapeRenderer; -import org.jfree.chart.title.LegendTitle; import org.jfree.data.category.CategoryDataset; -import org.jfree.ui.RectangleEdge; -import org.jfree.ui.RectangleInsets; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; @@ -44,6 +47,7 @@ * Base class of all coverage objects. * * @author Kohsuke Kawaguchi + * @author Martin Heinzerling */ @ExportedBean public abstract class CoverageObject> { @@ -370,78 +374,114 @@ public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException int width = (w != null) ? Integer.valueOf(w) : 500; int height = (h != null) ? Integer.valueOf(h) : 200; - new GraphImpl(this, t, width, height) { + CoverageGraphLayout layout = new CoverageGraphLayout() + .baseStroke(4f) + .axis() + .plot().type(CoverageType.LINE).value(CoverageValue.MISSED).color(Color.RED) + .plot().type(CoverageType.LINE).value(CoverageValue.COVERED).color(Color.GREEN); + createGraph(t, width, height,layout).doPng(req, rsp); + } + + GraphImpl createGraph(final Calendar t, final int width, final int height, final CoverageGraphLayout layout) throws IOException + { + return new GraphImpl(this, t, width, height, layout) + { @Override - protected DataSetBuilder createDataSet(CoverageObject obj) { - DataSetBuilder dsb = new DataSetBuilder(); + protected Map> createDataSetBuilder(CoverageObject obj) + { + Map> builders = new LinkedHashMap>(); + for (Axis axis : layout.getAxes()) + { + builders.put(axis, new DataSetBuilder()); + if (axis.isCrop()) bounds.put(axis, new Bounds()); + } - for (CoverageObject a = obj; a != null; a = a.getPreviousResult()) { + Map last = new HashMap(); + for (CoverageObject a = obj; a != null; a = a.getPreviousResult()) + { NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(a.getBuild()); - /*dsb.add(a.instruction.getPercentageFloat(), Messages.CoverageObject_Legend_Instructions(), label); - dsb.add(a.branch.getPercentageFloat(), Messages.CoverageObject_Legend_Branch(), label); - dsb.add(a.complexity.getPercentageFloat(), Messages.CoverageObject_Legend_Complexity(), label); - dsb.add(a.method.getPercentageFloat(), Messages.CoverageObject_Legend_Method(), label); - dsb.add(a.clazz.getPercentageFloat(), Messages.CoverageObject_Legend_Class(), label);*/ - if (a.line != null) { - dsb.add(a.line.getCovered(), Messages.CoverageObject_Legend_LineCovered(), label); - dsb.add(a.line.getMissed(), Messages.CoverageObject_Legend_LineMissed(), label); - - } else { - dsb.add(0, Messages.CoverageObject_Legend_LineCovered(), label); - dsb.add(0, Messages.CoverageObject_Legend_LineMissed(), label); + for (Plot plot : layout.getPlots()) + { + Number value = plot.getValue(a); + Axis axis = plot.getAxis(); + if (axis.isSkipZero() && (value == null || value.floatValue() == 0f)) value = null; + if (value != null) + { + if (axis.isCrop()) bounds.get(axis).update(value); + last.put(plot, value); + } + else + { + value = last.get(plot); + } + builders.get(axis).add(value, plot.getMessage(), label); } } - - return dsb; + return builders; } - }.doPng(req, rsp); + }; } public Api getApi() { return new Api(this); } - private abstract class GraphImpl extends Graph { + abstract class GraphImpl extends Graph { private CoverageObject obj; + private CoverageGraphLayout layout; + protected Map bounds=new HashMap(); + + protected class Bounds + { + float min=Float.MAX_VALUE; + float max=Float.MIN_VALUE; + + public void update(Number value) + { + float v=value.floatValue(); + if (min>v) min=v; + if (max obj, Calendar timestamp, int defaultW, int defaultH) { + public GraphImpl(CoverageObject obj, Calendar timestamp, int defaultW, int defaultH, CoverageGraphLayout layout) { super(timestamp, defaultW, defaultH); this.obj = obj; + this.layout =layout; } - protected abstract DataSetBuilder createDataSet(CoverageObject obj); + protected abstract Map> createDataSetBuilder(CoverageObject obj); + + public JFreeChart getGraph( ) + { + return createGraph(); + } @Override protected JFreeChart createGraph() { - final CategoryDataset dataset = createDataSet(obj).build(); + Map dataSets = new LinkedHashMap(); + Map> dataSetBuilders = createDataSetBuilder(obj); + for (Entry> e : dataSetBuilders.entrySet()) + { + dataSets.put(e.getKey(), e.getValue().build()); + } + List axes = new ArrayList(dataSets.keySet()); + final JFreeChart chart = ChartFactory.createLineChart( null, // chart title null, // unused - "", // range axis label - dataset, // data + null, // range axis label + dataSets.get(axes.get(0)), // data PlotOrientation.VERTICAL, // orientation true, // include legend true, // tooltips false // urls - ); - - // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... - - final LegendTitle legend = chart.getLegend(); - legend.setPosition(RectangleEdge.RIGHT); - - 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.setRangeGridlinesVisible(true); - plot.setRangeGridlinePaint(Color.black); - CategoryAxis domainAxis = new ShiftedCategoryAxis(null); plot.setDomainAxis(domainAxis); domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); @@ -449,30 +489,31 @@ protected JFreeChart createGraph() { domainAxis.setUpperMargin(0.0); domainAxis.setCategoryMargin(0.0); - final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); - rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); - rangeAxis.setLowerBound(0); - - final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); - - renderer.setSeriesPaint(0, Color.green); - renderer.setSeriesPaint(1, Color.red); - - renderer.setSeriesItemLabelPaint(0, Color.green); - renderer.setSeriesItemLabelPaint(1, Color.red); - - renderer.setSeriesFillPaint(0, Color.green); - renderer.setSeriesFillPaint(1, Color.red); - - renderer.setBaseStroke(new BasicStroke(4.0f)); - //ColorPalette.apply(renderer); - - // crop extra space around the graph - plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0)); - - + int axisId = 0; + for (Axis axis : axes) + { + int di = axisId; + plot.setDataset(di, dataSets.get(axis)); + plot.mapDatasetToRangeAxis(di, axisId); + NumberAxis numberAxis = new NumberAxis(axis.getLabel()); + plot.setRangeAxis(axisId, numberAxis); + numberAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); //TODO + setBounds(axis, numberAxis); + axisId++; + } + + layout.apply(chart); return chart; } + + private void setBounds(Axis a, ValueAxis axis) + { + if (!a.isCrop()) return; + Bounds bounds = this.bounds.get(a); + float border = (bounds.max - bounds.min) / 100 * a.getCrop(); + axis.setUpperBound(bounds.max + border); + axis.setLowerBound(Math.max(0, bounds.min - border)); + } } @Override diff --git a/src/main/resources/hudson/plugins/jacoco/Messages.properties b/src/main/resources/hudson/plugins/jacoco/Messages.properties index 0d45c967..db4d465b 100644 --- a/src/main/resources/hudson/plugins/jacoco/Messages.properties +++ b/src/main/resources/hudson/plugins/jacoco/Messages.properties @@ -9,9 +9,10 @@ BuildAction.Perfect=All coverage targets have been met. BuildAction.DisplayName=Coverage Report ProjectAction.DisplayName=Coverage Trend JacocoPublisher.DisplayName=Record JaCoCo coverage report +CoverageObject.Legend.Covered={0} covered +CoverageObject.Legend.Missed={0} missed CoverageObject.Legend.Class=class -CoverageObject.Legend.LineCovered=lineCovered -CoverageObject.Legend.LineMissed=lineMissed +CoverageObject.Legend.Line=line CoverageObject.Legend.Method=method CoverageObject.Legend.Branch=branch CoverageObject.Legend.Instructions=instructions diff --git a/src/main/resources/hudson/plugins/jacoco/Messages_de.properties b/src/main/resources/hudson/plugins/jacoco/Messages_de.properties index e6544f6f..986bb7a1 100644 --- a/src/main/resources/hudson/plugins/jacoco/Messages_de.properties +++ b/src/main/resources/hudson/plugins/jacoco/Messages_de.properties @@ -1,18 +1,19 @@ BuildAction.Description=Testabdeckung: {0} {1} {2} {3} {4} BuildAction.Classes=Klassen {0} ({1}%). BuildAction.Methods=Methoden {0} ({1}%). -BuildAction.Blocks=Blöcke {0} ({1}%). +BuildAction.Blocks=Blöcke {0} ({1}%). BuildAction.Lines=Zeilen {0} ({1}%). BuildAction.Branches=Branches {0} ({1}%). BuildAction.Instructions=Anweisungen {0} ({1}%). BuildAction.Perfect=Alle Testabdeckungsziele wurden erreicht. BuildAction.DisplayName=Testabdeckung ProjectAction.DisplayName=Trend der Testabdeckung -JacocoPublisher.DisplayName=Veröffentliche die JaCoCo Testabdeckung +JacocoPublisher.DisplayName=Veröffentliche die JaCoCo Testabdeckung CoverageObject.Legend.Class=Klassen -CoverageObject.Legend.LineCovered=Zeilen abgedeckt -CoverageObject.Legend.LineMissed=Zeilen nicht abgedeckt +CoverageObject.Legend.Line=Zeilen +CoverageObject.Legend.Covered={0} abgedeckt +CoverageObject.Legend.Missed={0} nicht abgedeckt CoverageObject.Legend.Method=Methoden CoverageObject.Legend.Branch=Branch CoverageObject.Legend.Instructions=Anweisungen -CoverageObject.Legend.Complexity=Komplexität +CoverageObject.Legend.Complexity=Komplexität diff --git a/src/test/java/hudson/plugins/jacoco/model/CoverageObjectGraphTest.java b/src/test/java/hudson/plugins/jacoco/model/CoverageObjectGraphTest.java new file mode 100644 index 00000000..6ae3a710 --- /dev/null +++ b/src/test/java/hudson/plugins/jacoco/model/CoverageObjectGraphTest.java @@ -0,0 +1,218 @@ +package hudson.plugins.jacoco.model; + +import static org.junit.Assert.assertArrayEquals; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontFormatException; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.GregorianCalendar; +import java.util.Locale; + +import org.apache.commons.io.FileUtils; +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.jfree.chart.ChartUtilities; +import org.jfree.chart.JFreeChart; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import hudson.plugins.jacoco.AbstractJacocoTestBase; +import hudson.plugins.jacoco.model.CoverageGraphLayout.CoverageType; +import hudson.plugins.jacoco.model.CoverageGraphLayout.CoverageValue; + +/** + * @author Martin Heinzerling + */ +public class CoverageObjectGraphTest extends AbstractJacocoTestBase +{ + public static final int WIDTH = 500; + public static final int HEIGHT = 200; + private static Font font; + private IMocksControl ctl; + private Locale localeBackup; + + @BeforeClass + public static void loadFont() throws IOException, FontFormatException + { + // just a free font nobody has on their system, but different enough to default sans-serif, + // that you will see missing system font replacement in the outbut. See #replaceFonts() + InputStream is = new FileInputStream("resources/test/belligerent.ttf"); + font=Font.createFont(Font.TRUETYPE_FONT, is); + } + + @Before + public void setUp() + { + ctl = EasyMock.createControl(); + TestCoverageObject.setEasyMock(ctl); + localeBackup=Locale.getDefault(); + Locale.setDefault(Locale.ENGLISH); + } + + @After + public void tearDown() + { + ctl.verify(); + TestCoverageObject.setEasyMock(null); + Locale.setDefault(localeBackup); + } + + @Test + public void simpleLineCoverage() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout() + /*.baseStroke(4f)*/ + .plot().type(CoverageType.LINE).value(CoverageValue.MISSED).color(Color.RED) + .plot().type(CoverageType.LINE).value(CoverageValue.COVERED).color(Color.GREEN); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "simple.png"); + } + + @Test + public void baseStroke() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout(). + baseStroke(2.0f) + .plot().type(CoverageType.LINE).value(CoverageValue.MISSED).color(Color.RED) + .plot().type(CoverageType.LINE).value(CoverageValue.COVERED).color(Color.GREEN); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "baseStroke.png"); + } + + @Test + public void multipleAccessAndDifferentCoverageType() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout() + .baseStroke(2f) + .axis().label("M") + .plot().type(CoverageType.LINE).value(CoverageValue.MISSED).color(Color.RED) + .axis().label("C") + .plot().type(CoverageType.LINE).value(CoverageValue.COVERED).color(Color.GREEN) + .axis().label("%") + .plot().type(CoverageType.BRANCH).value(CoverageValue.PERCENTAGE).color(Color.BLUE) + .plot().type(CoverageType.LINE).value(CoverageValue.PERCENTAGE).color(Color.YELLOW); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "multiple.png"); + } + + @Test + public void crop5() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout() + .baseStroke(2f) + .axis().crop(5).skipZero() + .plot().type(CoverageType.BRANCH).value(CoverageValue.PERCENTAGE).color(Color.RED); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "crop5.png"); + } + + @Test + public void crop100() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout() + .baseStroke(2f) + .axis().crop(100).skipZero() + .plot().type(CoverageType.BRANCH).value(CoverageValue.PERCENTAGE).color(Color.RED); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "crop100.png"); + } + + @Test + public void skipZero() throws IOException + { + CoverageGraphLayout layout = new CoverageGraphLayout() + .skipZero() + .plot().type(CoverageType.BRANCH).value(CoverageValue.PERCENTAGE).color(Color.RED); + + JFreeChart chart = createTestCoverage().createGraph(new GregorianCalendar(), WIDTH, HEIGHT, layout).getGraph(); + assertGraph(chart, "skipzero.png"); + } + + private TestCoverageObject createTestCoverage() + { + TestCoverageObject t5 = new TestCoverageObject().branch(6, 30).line(5000, 19000); + TestCoverageObject t4 = new TestCoverageObject().branch(6, 0).line(5000, 19000).previous(t5); + TestCoverageObject t3 = new TestCoverageObject().branch(6, 35).line(5000, 19000).previous(t4); + TestCoverageObject t2 = new TestCoverageObject().branch(15, 23).line(10000, 15000).previous(t3); + TestCoverageObject t1 = new TestCoverageObject().branch(27, 13).line(12000, 18000).previous(t2); + TestCoverageObject t0 = new TestCoverageObject().previous(t1); + ctl.replay(); + return t0; + } + + private void assertGraph(JFreeChart chart, String file, boolean writeFile) throws IOException + { + replaceFonts(chart); + if (writeFile) + { + File f = new File(file); + ChartUtilities.saveChartAsPNG(f, chart, WIDTH, HEIGHT); + System.out.println("Stored graph file to " + f.getAbsolutePath()); + } + byte[] expected = FileUtils.readFileToByteArray(new File("resources/test/" + file)); + byte[] actual; + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try + { + ChartUtilities.writeChartAsPNG(out, chart, WIDTH, HEIGHT, null); + actual = out.toByteArray(); + } + finally + { + out.close(); + } + try + { + assertArrayEquals(expected, actual); + } + catch (AssertionError e) + { + File f = new File(file); + ChartUtilities.saveChartAsPNG(f, chart, WIDTH, HEIGHT); + System.err.println("Stored wrong graph file to " + f.getAbsolutePath()); + throw e; + } + } + + private void assertGraph(JFreeChart chart, String file) throws IOException + { + assertGraph(chart, file, !new File("resources/test/" + file).exists()); + } + + private void replaceFonts(JFreeChart chart) + { + int i=0; + while (chart.getLegend(i)!=null) + { + chart.getLegend(i).setItemFont(font.deriveFont(chart.getLegend(i).getItemFont().getStyle(), chart.getLegend(i).getItemFont().getSize())); + i++; + } + i=0; + while (chart.getCategoryPlot().getDomainAxis(i)!=null) + { + chart.getCategoryPlot().getDomainAxis(i).setTickLabelFont(font.deriveFont(chart.getCategoryPlot().getDomainAxis(i).getTickLabelFont().getStyle(), chart.getCategoryPlot().getDomainAxis(i).getTickLabelFont().getSize())); + i++; + } + i=0; + while (chart.getCategoryPlot().getRangeAxis(i)!=null) + { + chart.getCategoryPlot().getRangeAxis(i).setTickLabelFont(font.deriveFont(chart.getCategoryPlot().getRangeAxis(i).getTickLabelFont().getStyle(), chart.getCategoryPlot().getRangeAxis(i).getTickLabelFont().getSize())); + chart.getCategoryPlot().getRangeAxis(i).setLabelFont(font.deriveFont(chart.getCategoryPlot().getRangeAxis(i).getLabelFont().getStyle(), chart.getCategoryPlot().getRangeAxis(i).getLabelFont().getSize())); + i++; + } + } + +} diff --git a/src/test/java/hudson/plugins/jacoco/model/TestCoverageObject.java b/src/test/java/hudson/plugins/jacoco/model/TestCoverageObject.java new file mode 100644 index 00000000..f4543f85 --- /dev/null +++ b/src/test/java/hudson/plugins/jacoco/model/TestCoverageObject.java @@ -0,0 +1,98 @@ +package hudson.plugins.jacoco.model; + +import hudson.model.AbstractBuild; +import hudson.model.FreeStyleBuild; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.easymock.IMocksControl; + +/** + * @author Martin Heinzerling + */ +public class TestCoverageObject extends CoverageObject +{ + private TestCoverageObject previous = null; + private AbstractBuild build = null; + private int buildNumber = 1; + private static IMocksControl mocksControl; + + public static void setEasyMock(IMocksControl mocksControl) + { + TestCoverageObject.mocksControl = mocksControl; + } + + public TestCoverageObject() + { + super(); + build = mocksControl.createMock("build", FreeStyleBuild.class); + EasyMock.expect(build.getDisplayName()).andAnswer(new IAnswer() + { + public String answer() throws Throwable + { + return "#" + buildNumber; + } + }).anyTimes(); + } + + @Override + public AbstractBuild getBuild() + { + return build; + } + + @Override + public TestCoverageObject getPreviousResult() + { + return previous; + } + + public TestCoverageObject clazz(int missed, int covered) + { + clazz = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject method(int missed, int covered) + { + method = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject line(int missed, int covered) + { + line = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject complexity(int missed, int covered) + { + complexity = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject instruction(int missed, int covered) + { + instruction = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject branch(int missed, int covered) + { + branch = new Coverage(missed, covered); + return this; + } + + public TestCoverageObject previous(TestCoverageObject previous) + { + this.previous = previous; + int i = 1; + TestCoverageObject t = this; + while (t != null) + { + t.buildNumber = i; + i++; + t = t.previous; + } + return this; + } +}