package jimm.datavision.test;
import jimm.datavision.*;
import jimm.datavision.field.*;
import jimm.datavision.layout.CharSepLE;
import jimm.datavision.test.mock.source.MockDataSource;
import java.io.*;
import java.util.Date;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.framework.Test;
/**
* Reads a report from an XML file, runs it, and verifies the output. Uses
* the {}@link CharSepLE} layout engine to produce a tab-delimited output
* file.
* <p>
* These tests are tightly coupled with the contents of the files
* <code>test.xml</code> and <code>test_parameters.xml</code> and the data
* generated by the {@link MockDataSource}.
*
* @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
*/
public class ReportRunTest extends TestCase {
protected static final File EXAMPLE_REPORT =
new File(AllTests.testDataFile("test.xml"));
protected static final File PARAM_INPUT_FILE =
new File(AllTests.testDataFile("test_parameters.xml"));
protected static final File OUT_FILE =
new File(System.getProperty("java.io.tmpdir"),
"datavision_report_run_test_out.txt");
// This must match the format string for the report.date field in
// EXAMPLE_REPORT.
protected static final String REPORT_DATE_FORMAT = "yyyy-MM-dd";
// This must be an alphabetically sorted list of office names from the
// offices table.
static final String OFFICES[] = {
"Chicago", "New Jersey", "New York"
};
// The value of parameter one, which is at the beginning of the
// report header.
protected static final String STRING_PARAM_VALUE = "Chicago";
// The value of the report.title special field, which appears
// in the report header.
protected static final String REPORT_TITLE = "Example Report";
protected Report report;
protected DecimalFormat dollarFormatter;
protected DecimalFormat lastColFormatter;
protected SimpleDateFormat titleDateFormatter;
protected int reportRowNumber;
protected int officeRowNumber;
protected int postDateRowNumber;
public static Test suite() {
return new TestSuite(ReportRunTest.class);
}
public ReportRunTest(String name) {
super(name);
}
public void setUp() throws Exception {
dollarFormatter = new DecimalFormat("$#,###.00");
lastColFormatter = new DecimalFormat("#,###.##");
titleDateFormatter = new SimpleDateFormat(REPORT_DATE_FORMAT);
report = new Report();
report.setDataSource(new MockDataSource(report));
reportRowNumber = officeRowNumber = postDateRowNumber = 1;
report.read(EXAMPLE_REPORT);
if (report.hasParameterFields())
report.setParameterXMLInput(PARAM_INPUT_FILE);
OUT_FILE.deleteOnExit();
PrintWriter out = new PrintWriter(new FileWriter(OUT_FILE));
report.setLayoutEngine(new CharSepLE(out, '\t'));
}
public void tearDown() {
if (OUT_FILE.exists())
OUT_FILE.delete();
}
public void testReportRun() throws IOException, FileNotFoundException {
// Run report in this thread, not a separate one. Running the
// report closes the output stream.
report.runReport();
// Open the output and look for various things.
BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
expectHeaders(in);
// Each of the office groups. checkOfficeGroup() returns the total
// dollar amount (really the total id number amount, but that is proved
// to be the same thing within checkDetailLine()).
int total = 0;
for (int i = 0; i < OFFICES.length; ++i)
total += checkOfficeGroup(in, OFFICES[i]);
// The grand total.
String line = in.readLine();
assertNotNull(line);
if (line.startsWith("Page "))
assertNotNull(line = in.readLine());
assertEquals("Grand Total:\t" + dollarFormatter.format(total)
+ "\t" + (reportRowNumber - 1)
+ "\t" + (reportRowNumber - 1),
line);
// The final page number.
assertNotNull(line = in.readLine());
assertEquals("Page ", line.substring(0, 5));
// Make sure we are at the end of the file.
assertNull(in.readLine());
in.close();
}
protected void expectHeaders(BufferedReader in) throws IOException {
String line;
// Line 1: report title and formatted date.
// since that will most definitely be different.
assertNotNull(line = in.readLine());
assertEquals(0, line.indexOf(STRING_PARAM_VALUE + '\t'
+ REPORT_TITLE + '\t'
+ titleDateFormatter.format(new Date())));
// Line 2: the page header.
assertNotNull(line = in.readLine());
assertEquals(0, line.indexOf("Job #\tTitle\tHourly Rate"));
}
/**
* Checks a group (header plus detail lines) and returns the total
* of the dollar amounts in the group.
*
* @param in the input reader
* @param officeName the group name
* @return the total dollar amount in the group
*/
protected int checkOfficeGroup(BufferedReader in, String officeName)
throws IOException
{
String line;
int aggregate = 0;
officeRowNumber = 1;
// The office name. Skip page delimiters ("Page" at foot and
// page headers at top).
assertNotNull(line = in.readLine());
while (line.startsWith("Page ") || line.startsWith("Job #\t"))
assertNotNull(line = in.readLine());
assertEquals(officeName, line);
Object postDateGroupEnd;
while ((postDateGroupEnd = checkPostDateGroup(in)) instanceof Integer)
aggregate += ((Integer)postDateGroupEnd).intValue();
line = (String)postDateGroupEnd;
assertEquals("Total:\t" + dollarFormatter.format(aggregate)
+ "\t" + (reportRowNumber - 1)
+ "\t" + (officeRowNumber - 1),
line);
return aggregate;
}
/**
* Checks a subgroup (header plus detail lines) and returns the total
* of the dollar amounts in the group. If the next report section is
* not a subgroup, it will be a group total line; we return the line
*
* @param in the input reader
* @return either an Integer containing the total or the next line read
* from the report
*/
protected Object checkPostDateGroup(BufferedReader in) throws IOException {
int aggregate = 0;
String line;
postDateRowNumber = 1;
// Read either post date or next (super)group name. Return false if we
// see a non-date group name; that means we're done. Skip page
// delimiters ("Page" at foot and page headers at top).
while (true) {
assertNotNull(line = in.readLine());
if (line.startsWith("Page ") || line.startsWith("Job #\t"))
continue;
if (line.startsWith("Total:"))
return line;
// Primitive date check
assertTrue(Character.isDigit(line.charAt(0)) && line.length() == 10
&& line.charAt(4) == '-' && line.charAt(7) == '-');
break;
}
// The detail lines. Collect the id number from the beginning of each
// detail line. It is the same as the dollar amount value. Add that
// amount to the aggregate.
while (true) {
assertNotNull(line = in.readLine());
if (line.startsWith("Post Date Total:"))
break;
if (line.startsWith("Page ") || line.startsWith("Job #\t"))
continue;
aggregate += checkDetailLine(line);
}
assertEquals("Post Date Total:\t" + dollarFormatter.format(aggregate)
+ "\t" + (reportRowNumber - 1)
+ "\t" + (postDateRowNumber - 1),
line);
return new Integer(aggregate);
}
/**
* Checks the format of a detail line and returns the integer id found
* at the beginning of line. This is the same as the dollar amount.
*
* @param line a detail line
* @return the id number
*/
protected int checkDetailLine(String line) {
// Read job id from the beginning of the line.
int id = Integer.parseInt(line.substring(0, line.indexOf("\t")));
// The description, at random, may be repeated ("job 3 job 3"). We
// check for the first full description.
String str = "" + id + "\tThis is the short description of job " + id;
assertTrue("line does not start with \"" + str + "\"",
line.startsWith(str));
// Check for the remaining columns at the end of the line. We are also
// assuring that the dollar amount is the same as the id number.
if (id == 0) {
assertEquals("0\tThis is the short description of job 0\t\t\t\t"
+ reportRowNumber + "\t" + postDateRowNumber,
line);
}
else {
str = "\t" + (id * 100)
+ "\t" + dollarFormatter.format(id)
+ "\t" + lastColFormatter.format(id)
+ "\t" + reportRowNumber
+ "\t" + postDateRowNumber;
assertTrue("line \"" + line + "\" does not end with \"" + str,
line.endsWith(str));
}
++reportRowNumber;
++officeRowNumber;
++postDateRowNumber;
return id;
}
// Performs everything the old testWhereClause() did, so I removed that one.
public void testParamInWhereClause() throws IOException, FileNotFoundException
{
// Set where clause and parameter value file
report.getDataSource().getQuery()
.setEditableWhereClause("office.name = {?String Param}");
report.setParameterXMLInput(PARAM_INPUT_FILE);
// Run report in this thread, not a separate one. Running the
// report closes the output stream.
Date now = new Date(); // Get date as near report run time as possible
report.runReport();
// Open the output and look for various things.
BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
String line;
// Line 1. Look for report title and the date (ignore the time,
// since that will most definitely be different.
assertNotNull(line = in.readLine());
// There is an infinitessimally small chance the next assertion will
// fail even though the report is correct. Imagine the date "now"
// retrieved above is created just before midnight. Further imagine
// that the report.date special field's value isn't retrieved until
// just after midnight. Not terribly likely, but I'm in a CYA mood.
assertEquals(0, line.indexOf(STRING_PARAM_VALUE + '\t'
+ REPORT_TITLE + '\t'
+ titleDateFormatter.format(now)));
// Line 2: the page header.
assertNotNull(line = in.readLine());
assertEquals(0, line.indexOf("Job #\tTitle\tHourly Rate"));
// The single office group we asked for. checkOfficeGroup() returns
// the total dollar amount (really the total id number amount, but
// that is proved to be be the same thing within checkDetailLine()).
int total = checkOfficeGroup(in, STRING_PARAM_VALUE);
// The grand total.
assertNotNull(line = in.readLine());
if (line.startsWith("Page "))
assertNotNull(line = in.readLine());
assertEquals("Grand Total:\t" + dollarFormatter.format(total)
+ "\t" + (reportRowNumber - 1)
+ "\t" + (reportRowNumber - 1),
line);
in.close();
}
// We used to throw an exception if there were no records returned
// by the query but there was a column field in the page header or
// report header.
public void testNoRecords() {
try {
// Add a column field to the report header
Section pageHeader =
report.getFirstSectionByArea(SectionArea.PAGE_HEADER);
assertNotNull(pageHeader);
ColumnField f = new ColumnField(null, report, pageHeader,
"office.name", true);
pageHeader.addField(f);
// Create a query that returns 0 records
report.getDataSource().getQuery().setEditableWhereClause("1 = 2");
// Run the report. We shouldn't throw an exception.
report.runReport();
}
catch (Exception e) {
fail("should not throw an exception just 'cause there are no records");
}
}
public void testUseReportValue() throws Exception {
Formula f = report.findFormula("1");
f.setEditableExpression("val = $report.value('jobs.hourly rate'); val.nil? ? nil : val / 100.0");
// Run report in this thread, not a separate one. Running the
// report closes the output stream.
report.runReport();
// Open the output and look for various things.
BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
expectHeaders(in);
// Each of the office groups. checkOfficeGroup() returns the total
// dollar amount (really the total id number amount, but that is proved
// to be the same thing within checkDetailLine()).
int total = 0;
for (int i = 0; i < OFFICES.length; ++i)
total += checkOfficeGroup(in, OFFICES[i]);
// The grand total.
String line = in.readLine();
assertNotNull(line);
if (line.startsWith("Page "))
assertNotNull(line = in.readLine());
assertEquals("Grand Total:\t" + dollarFormatter.format(total)
+ "\t" + (reportRowNumber - 1)
+ "\t" + (reportRowNumber - 1),
line);
// The final page number.
assertNotNull(line = in.readLine());
assertEquals("Page ", line.substring(0, 5));
// Make sure we are at the end of the file.
assertNull(in.readLine());
in.close();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
System.exit(0);
}
}