/*******************************************************************************
* Copyright (c) 2011 The OpenNMS Group, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************/
package org.rrd4j.graph;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.gt;
import static org.easymock.EasyMock.lt;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.same;
import static org.easymock.EasyMock.verify;
import java.awt.FontFormatException;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.rrd4j.ConsolFun;
import org.rrd4j.DsType;
import org.rrd4j.core.RrdDb;
import org.rrd4j.core.RrdDef;
import org.rrd4j.core.Sample;
import org.rrd4j.core.Util;
/**
* The form of these tests is fairly generic, using a Mock ImageWorker to see if the expected
* sort of calls were made. We generally don't check co-ordinates. Rather, we check for a certain
* number of major grid lines, with the right string labels, and a certain number of minor lines.
* Presumably if there are the right number of lines they'll probably be around the right locations
* This will test the actual complex behaviour of ValueAxis which is working out how many lines to
* draw and at what major values. Subtleties like the actual pixel values are generally less important.
* If we find a specific bug with placement of the grid lines, then we can writes tests to check
* for specific co-ordinates, knowing exactly why we're checking
*
* @author cmiskell
*
*/
public class ValueAxisTest extends DummyGraph {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
private ValueAxis valueAxis;
private String jrbFileName;
private final long startTime = 1;
@Before
public void setup() throws IOException {
jrbFileName = testFolder.newFile("test-value-axis.rrd").getCanonicalPath();
}
private void createGaugeRrd(int rowCount) throws IOException {
RrdDef def = new RrdDef(jrbFileName);
def.setStartTime(startTime);
def.setStep(60);
def.addDatasource("testvalue", DsType.GAUGE, 120, Double.NaN, Double.NaN);
def.addArchive("RRA:AVERAGE:0.5:1:"+rowCount);
//Create the empty rrd. Other code may open and append data
RrdDb rrd = new RrdDb(def);
rrd.close();
}
//Cannot be called until the RRD has been populated; wait
private void prepareGraph() throws IOException {
graphDef = new RrdGraphDef();
graphDef.datasource("testvalue", jrbFileName, "testvalue", ConsolFun.AVERAGE);
graphDef.area("testvalue", Util.parseColor("#FF0000"), "TestValue");
graphDef.setStartTime(startTime);
graphDef.setEndTime(startTime + (60*60*24));
graphDef.setLocale(Locale.US);
//There's only a couple of methods of ImageWorker that we actually care about in this test.
// More to the point, we want the rest to work as normal (like getFontHeight, getFontAscent etc)
imageWorker = createMockBuilder(ImageWorker.class)
.addMockedMethod("drawLine")
.addMockedMethod("drawString")
.createStrictMock(); //Order is important!
buildGraph();
valueAxis = new ValueAxis(imageParameters, imageWorker, graphDef, graphMapper);
}
public void checkForBasicGraph() {
expectMajorGridLine(" 0.0");
expectMinorGridLines(4);
expectMajorGridLine(" 0.5");
expectMinorGridLines(4);
expectMajorGridLine(" 1.0");
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
private void expectMajorGridLine(String label) {
//We don't care what y value is used for any of the gridlines; the fact that they're there
// is good enough. Our expectations for any given graph will be a certain set of grid lines
// in a certain order, which pretty much implies y values. If we find a bug where that
// assumption is wrong, a more specific test can be written to find that.
//The x-values of the lines are important though, to be sure they've been drawn correctly
//Note the use of "same" for the strokes; in RrdGraphConstants, these are both BasicStroke(1)
// so we want to be sure exactly the same object was used
int quarterX = imageParameters.xgif/4;
int midX = imageParameters.xgif/2;
int threeQuartersX = quarterX*3;
imageWorker.drawString(eq(label), lt(quarterX), anyInt(), eq(graphDef.getFont(RrdGraphDef.FONTTAG_LEGEND)), eq(RrdGraphDef.DEFAULT_FONT_COLOR));
//Horizontal tick on the left
imageWorker.drawLine(lt(quarterX), anyInt(), lt(midX), anyInt(), eq(RrdGraphDef.DEFAULT_MGRID_COLOR), same(RrdGraphDef.TICK_STROKE));
//Horizontal tick on the right
imageWorker.drawLine(gt(threeQuartersX), anyInt(), gt(threeQuartersX), anyInt(), eq(RrdGraphDef.DEFAULT_MGRID_COLOR), same(RrdGraphDef.TICK_STROKE));
//Line in between the ticks (but overlapping a bit)
imageWorker.drawLine(lt(quarterX), anyInt(), gt(midX),anyInt(), eq(RrdGraphDef.DEFAULT_MGRID_COLOR), same(RrdGraphDef.GRID_STROKE));
}
private void expectMinorGridLines(int count) {
//Note the use of "same" for the strokes; in RrdGraphConstants, these are both BasicStroke(1)
// so we want to be sure exactly the same object was used
int quarterX = imageParameters.xgif/4;
int midX = quarterX*2;
int threeQuartersX = quarterX*3;
for(int i=0; i<count; i++) {
imageWorker.drawLine(lt(quarterX), anyInt(), lt(quarterX), anyInt(), eq(RrdGraphDef.DEFAULT_GRID_COLOR), same(RrdGraphDef.TICK_STROKE));
imageWorker.drawLine(gt(threeQuartersX), anyInt(), gt(threeQuartersX), anyInt(), eq(RrdGraphDef.DEFAULT_GRID_COLOR), same(RrdGraphDef.TICK_STROKE));
imageWorker.drawLine(lt(quarterX), anyInt(), gt(midX), anyInt(), eq(RrdGraphDef.DEFAULT_GRID_COLOR), same(RrdGraphDef.GRID_STROKE));
}
}
@Test
public void testBasicEmptyRrd() throws IOException, FontFormatException {
createGaugeRrd(100);
prepareGraph();
checkForBasicGraph();
}
@Test
public void testOneEntryInRrd() throws IOException, FontFormatException {
createGaugeRrd(100);
RrdDb rrd = new RrdDb(jrbFileName);
long nowSeconds = new Date().getTime();
long fiveMinutesAgo = nowSeconds - (5 * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(fiveMinutesAgo+":10");
rrd.close();
prepareGraph();
checkForBasicGraph();
}
@Test
public void testTwoEntriesInRrd() throws IOException, FontFormatException {
createGaugeRrd(100);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<2; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp+":100");
}
rrd.close();
prepareGraph();
expectMajorGridLine(" 90");
expectMinorGridLines(1);
expectMajorGridLine(" 100");
expectMinorGridLines(1);
expectMajorGridLine(" 110");
expectMinorGridLines(1);
expectMajorGridLine(" 120");
expectMinorGridLines(1);
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
@Test
public void testEntriesZeroTo100InRrd() throws IOException, FontFormatException {
createGaugeRrd(105); //Make sure all entries are recorded (5 is just a buffer for consolidation)
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<100; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + i);
}
rrd.close();
prepareGraph();
expectMinorGridLines(4);
expectMajorGridLine(" 50");
expectMinorGridLines(4);
expectMajorGridLine(" 100");
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
@Test
public void testEntriesNeg50To100InRrd() throws IOException, FontFormatException {
createGaugeRrd(155);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<150; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + (i -50));
}
rrd.close();
prepareGraph();
expectMajorGridLine(" -50");
expectMinorGridLines(4);
expectMajorGridLine(" 0");
expectMinorGridLines(4);
expectMajorGridLine(" 50");
expectMinorGridLines(4);
expectMajorGridLine(" 100");
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
@Test
public void testEntriesNeg55To105InRrd() throws IOException, FontFormatException {
createGaugeRrd(165);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<160; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + (i -55));
}
rrd.close();
prepareGraph();
/**
* Prior to JRB-12 fix, this was the behaviour. Note the lack of a decent negative label
expectMinorGridLines(3);
expectMajorGridLine(" 0");
expectMinorGridLines(4);
expectMajorGridLine(" 100");
expectMinorGridLines(1);
*/
//New behaviour is better; no minor grid lines, which is interesting, but much better representation
expectMajorGridLine(" -50");
expectMajorGridLine(" 0");
expectMajorGridLine(" 50");
expectMajorGridLine(" 100");
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
@Test
public void testEntriesNeg50To0InRrd() throws IOException, FontFormatException {
createGaugeRrd(100);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<50; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + (i -50));
}
rrd.close();
prepareGraph();
expectMinorGridLines(2);
expectMajorGridLine(" -40");
expectMinorGridLines(3);
expectMajorGridLine(" -20");
expectMinorGridLines(3);
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
/**
* Test specifically for JRB-12 (http://issues.opennms.org/browse/JRB-12)
* In the original, when the values go from -80 to 90 on a default height graph
* (i.e. limited pixels available for X-axis labelling),ValueAxis gets all confused
* and decides it can only display "0" on the X-axis (there's not enough pixels
* for more labels, and none of the Y-label factorings available work well enough
* @throws FontFormatException
*/
@Test
public void testEntriesNeg80To90InRrd() throws IOException, FontFormatException {
createGaugeRrd(180);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<170; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + (i -80));
}
rrd.close();
prepareGraph();
/**
* Original behaviour; a single major X-axis label (0) only.
expectMinorGridLines(4);
expectMajorGridLine(" 0");
expectMinorGridLines(4);
*/
//New behaviour post JRB-12 fix:
expectMajorGridLine(" -50");
expectMajorGridLine(" 0");
expectMajorGridLine(" 50");
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
/**
* Test specifically for JRB-12 (http://issues.opennms.org/browse/JRB-12)
* Related to testEntriesNeg80To90InRrd, except in the original code
* this produced sensible labelling. Implemented to check that the
* changes don't break the sanity.
* @throws FontFormatException
*/
@Test
public void testEntriesNeg80To80InRrd() throws IOException, FontFormatException {
createGaugeRrd(180);
RrdDb rrd = new RrdDb(jrbFileName);
for(int i=0; i<160; i++) {
long timestamp = startTime + 1 + (i * 60);
Sample sample = rrd.createSample();
sample.setAndUpdate(timestamp + ":" + (i -80));
}
rrd.close();
prepareGraph();
// Original
expectMinorGridLines(3);
expectMajorGridLine(" -50");
expectMinorGridLines(4);
expectMajorGridLine(" 0");
expectMinorGridLines(4);
expectMajorGridLine(" 50");
expectMinorGridLines(3);
replay(imageWorker);
valueAxis.draw();
//Validate the calls to the imageWorker
verify(imageWorker);
}
}