001: package jimm.datavision.test;
002:
003: import jimm.datavision.*;
004: import jimm.datavision.field.*;
005: import jimm.datavision.layout.CharSepLE;
006: import jimm.datavision.test.mock.source.MockDataSource;
007: import java.io.*;
008: import java.util.Date;
009: import java.text.DecimalFormat;
010: import java.text.SimpleDateFormat;
011: import junit.framework.TestCase;
012: import junit.framework.TestSuite;
013: import junit.framework.Test;
014:
015: /**
016: * Reads a report from an XML file, runs it, and verifies the output. Uses
017: * the {}@link CharSepLE} layout engine to produce a tab-delimited output
018: * file.
019: * <p>
020: * These tests are tightly coupled with the contents of the files
021: * <code>test.xml</code> and <code>test_parameters.xml</code> and the data
022: * generated by the {@link MockDataSource}.
023: *
024: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
025: */
026: public class ReportRunTest extends TestCase {
027:
028: protected static final File EXAMPLE_REPORT = new File(AllTests
029: .testDataFile("test.xml"));
030: protected static final File PARAM_INPUT_FILE = new File(AllTests
031: .testDataFile("test_parameters.xml"));
032: protected static final File OUT_FILE = new File(System
033: .getProperty("java.io.tmpdir"),
034: "datavision_report_run_test_out.txt");
035: // This must match the format string for the report.date field in
036: // EXAMPLE_REPORT.
037: protected static final String REPORT_DATE_FORMAT = "yyyy-MM-dd";
038: // This must be an alphabetically sorted list of office names from the
039: // offices table.
040: static final String OFFICES[] = { "Chicago", "New Jersey",
041: "New York" };
042: // The value of parameter one, which is at the beginning of the
043: // report header.
044: protected static final String STRING_PARAM_VALUE = "Chicago";
045: // The value of the report.title special field, which appears
046: // in the report header.
047: protected static final String REPORT_TITLE = "Example Report";
048:
049: protected Report report;
050: protected DecimalFormat dollarFormatter;
051: protected DecimalFormat lastColFormatter;
052: protected SimpleDateFormat titleDateFormatter;
053: protected int reportRowNumber;
054: protected int officeRowNumber;
055: protected int postDateRowNumber;
056:
057: public static Test suite() {
058: return new TestSuite(ReportRunTest.class);
059: }
060:
061: public ReportRunTest(String name) {
062: super (name);
063: }
064:
065: public void setUp() throws Exception {
066: dollarFormatter = new DecimalFormat("$#,###.00");
067: lastColFormatter = new DecimalFormat("#,###.##");
068: titleDateFormatter = new SimpleDateFormat(REPORT_DATE_FORMAT);
069:
070: report = new Report();
071: report.setDataSource(new MockDataSource(report));
072: reportRowNumber = officeRowNumber = postDateRowNumber = 1;
073:
074: report.read(EXAMPLE_REPORT);
075:
076: if (report.hasParameterFields())
077: report.setParameterXMLInput(PARAM_INPUT_FILE);
078:
079: OUT_FILE.deleteOnExit();
080: PrintWriter out = new PrintWriter(new FileWriter(OUT_FILE));
081: report.setLayoutEngine(new CharSepLE(out, '\t'));
082: }
083:
084: public void tearDown() {
085: if (OUT_FILE.exists())
086: OUT_FILE.delete();
087: }
088:
089: public void testReportRun() throws IOException,
090: FileNotFoundException {
091: // Run report in this thread, not a separate one. Running the
092: // report closes the output stream.
093: report.runReport();
094:
095: // Open the output and look for various things.
096: BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
097:
098: expectHeaders(in);
099:
100: // Each of the office groups. checkOfficeGroup() returns the total
101: // dollar amount (really the total id number amount, but that is proved
102: // to be the same thing within checkDetailLine()).
103: int total = 0;
104: for (int i = 0; i < OFFICES.length; ++i)
105: total += checkOfficeGroup(in, OFFICES[i]);
106:
107: // The grand total.
108: String line = in.readLine();
109: assertNotNull(line);
110: if (line.startsWith("Page "))
111: assertNotNull(line = in.readLine());
112:
113: assertEquals("Grand Total:\t" + dollarFormatter.format(total)
114: + "\t" + (reportRowNumber - 1) + "\t"
115: + (reportRowNumber - 1), line);
116:
117: // The final page number.
118: assertNotNull(line = in.readLine());
119: assertEquals("Page ", line.substring(0, 5));
120:
121: // Make sure we are at the end of the file.
122: assertNull(in.readLine());
123:
124: in.close();
125: }
126:
127: protected void expectHeaders(BufferedReader in) throws IOException {
128: String line;
129:
130: // Line 1: report title and formatted date.
131: // since that will most definitely be different.
132: assertNotNull(line = in.readLine());
133: assertEquals(0, line.indexOf(STRING_PARAM_VALUE + '\t'
134: + REPORT_TITLE + '\t'
135: + titleDateFormatter.format(new Date())));
136:
137: // Line 2: the page header.
138: assertNotNull(line = in.readLine());
139: assertEquals(0, line.indexOf("Job #\tTitle\tHourly Rate"));
140: }
141:
142: /**
143: * Checks a group (header plus detail lines) and returns the total
144: * of the dollar amounts in the group.
145: *
146: * @param in the input reader
147: * @param officeName the group name
148: * @return the total dollar amount in the group
149: */
150: protected int checkOfficeGroup(BufferedReader in, String officeName)
151: throws IOException {
152: String line;
153: int aggregate = 0;
154:
155: officeRowNumber = 1;
156:
157: // The office name. Skip page delimiters ("Page" at foot and
158: // page headers at top).
159: assertNotNull(line = in.readLine());
160: while (line.startsWith("Page ") || line.startsWith("Job #\t"))
161: assertNotNull(line = in.readLine());
162: assertEquals(officeName, line);
163:
164: Object postDateGroupEnd;
165: while ((postDateGroupEnd = checkPostDateGroup(in)) instanceof Integer)
166: aggregate += ((Integer) postDateGroupEnd).intValue();
167: line = (String) postDateGroupEnd;
168:
169: assertEquals("Total:\t" + dollarFormatter.format(aggregate)
170: + "\t" + (reportRowNumber - 1) + "\t"
171: + (officeRowNumber - 1), line);
172:
173: return aggregate;
174: }
175:
176: /**
177: * Checks a subgroup (header plus detail lines) and returns the total
178: * of the dollar amounts in the group. If the next report section is
179: * not a subgroup, it will be a group total line; we return the line
180: *
181: * @param in the input reader
182: * @return either an Integer containing the total or the next line read
183: * from the report
184: */
185: protected Object checkPostDateGroup(BufferedReader in)
186: throws IOException {
187: int aggregate = 0;
188: String line;
189:
190: postDateRowNumber = 1;
191:
192: // Read either post date or next (super)group name. Return false if we
193: // see a non-date group name; that means we're done. Skip page
194: // delimiters ("Page" at foot and page headers at top).
195: while (true) {
196: assertNotNull(line = in.readLine());
197: if (line.startsWith("Page ") || line.startsWith("Job #\t"))
198: continue;
199: if (line.startsWith("Total:"))
200: return line;
201:
202: // Primitive date check
203: assertTrue(Character.isDigit(line.charAt(0))
204: && line.length() == 10 && line.charAt(4) == '-'
205: && line.charAt(7) == '-');
206: break;
207: }
208:
209: // The detail lines. Collect the id number from the beginning of each
210: // detail line. It is the same as the dollar amount value. Add that
211: // amount to the aggregate.
212: while (true) {
213: assertNotNull(line = in.readLine());
214: if (line.startsWith("Post Date Total:"))
215: break;
216: if (line.startsWith("Page ") || line.startsWith("Job #\t"))
217: continue;
218:
219: aggregate += checkDetailLine(line);
220: }
221:
222: assertEquals("Post Date Total:\t"
223: + dollarFormatter.format(aggregate) + "\t"
224: + (reportRowNumber - 1) + "\t"
225: + (postDateRowNumber - 1), line);
226:
227: return new Integer(aggregate);
228: }
229:
230: /**
231: * Checks the format of a detail line and returns the integer id found
232: * at the beginning of line. This is the same as the dollar amount.
233: *
234: * @param line a detail line
235: * @return the id number
236: */
237: protected int checkDetailLine(String line) {
238: // Read job id from the beginning of the line.
239: int id = Integer
240: .parseInt(line.substring(0, line.indexOf("\t")));
241:
242: // The description, at random, may be repeated ("job 3 job 3"). We
243: // check for the first full description.
244: String str = "" + id
245: + "\tThis is the short description of job " + id;
246: assertTrue("line does not start with \"" + str + "\"", line
247: .startsWith(str));
248:
249: // Check for the remaining columns at the end of the line. We are also
250: // assuring that the dollar amount is the same as the id number.
251: if (id == 0) {
252: assertEquals(
253: "0\tThis is the short description of job 0\t\t\t\t"
254: + reportRowNumber + "\t"
255: + postDateRowNumber, line);
256: } else {
257: str = "\t" + (id * 100) + "\t" + dollarFormatter.format(id)
258: + "\t" + lastColFormatter.format(id) + "\t"
259: + reportRowNumber + "\t" + postDateRowNumber;
260: assertTrue("line \"" + line + "\" does not end with \""
261: + str, line.endsWith(str));
262: }
263:
264: ++reportRowNumber;
265: ++officeRowNumber;
266: ++postDateRowNumber;
267:
268: return id;
269: }
270:
271: // Performs everything the old testWhereClause() did, so I removed that one.
272: public void testParamInWhereClause() throws IOException,
273: FileNotFoundException {
274: // Set where clause and parameter value file
275: report.getDataSource().getQuery().setEditableWhereClause(
276: "office.name = {?String Param}");
277: report.setParameterXMLInput(PARAM_INPUT_FILE);
278:
279: // Run report in this thread, not a separate one. Running the
280: // report closes the output stream.
281: Date now = new Date(); // Get date as near report run time as possible
282: report.runReport();
283:
284: // Open the output and look for various things.
285: BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
286: String line;
287:
288: // Line 1. Look for report title and the date (ignore the time,
289: // since that will most definitely be different.
290: assertNotNull(line = in.readLine());
291:
292: // There is an infinitessimally small chance the next assertion will
293: // fail even though the report is correct. Imagine the date "now"
294: // retrieved above is created just before midnight. Further imagine
295: // that the report.date special field's value isn't retrieved until
296: // just after midnight. Not terribly likely, but I'm in a CYA mood.
297: assertEquals(0, line.indexOf(STRING_PARAM_VALUE + '\t'
298: + REPORT_TITLE + '\t' + titleDateFormatter.format(now)));
299:
300: // Line 2: the page header.
301: assertNotNull(line = in.readLine());
302: assertEquals(0, line.indexOf("Job #\tTitle\tHourly Rate"));
303:
304: // The single office group we asked for. checkOfficeGroup() returns
305: // the total dollar amount (really the total id number amount, but
306: // that is proved to be be the same thing within checkDetailLine()).
307: int total = checkOfficeGroup(in, STRING_PARAM_VALUE);
308:
309: // The grand total.
310: assertNotNull(line = in.readLine());
311: if (line.startsWith("Page "))
312: assertNotNull(line = in.readLine());
313:
314: assertEquals("Grand Total:\t" + dollarFormatter.format(total)
315: + "\t" + (reportRowNumber - 1) + "\t"
316: + (reportRowNumber - 1), line);
317:
318: in.close();
319: }
320:
321: // We used to throw an exception if there were no records returned
322: // by the query but there was a column field in the page header or
323: // report header.
324: public void testNoRecords() {
325: try {
326: // Add a column field to the report header
327: Section pageHeader = report
328: .getFirstSectionByArea(SectionArea.PAGE_HEADER);
329: assertNotNull(pageHeader);
330: ColumnField f = new ColumnField(null, report, pageHeader,
331: "office.name", true);
332: pageHeader.addField(f);
333:
334: // Create a query that returns 0 records
335: report.getDataSource().getQuery().setEditableWhereClause(
336: "1 = 2");
337:
338: // Run the report. We shouldn't throw an exception.
339: report.runReport();
340: } catch (Exception e) {
341: fail("should not throw an exception just 'cause there are no records");
342: }
343: }
344:
345: public void testUseReportValue() throws Exception {
346: Formula f = report.findFormula("1");
347: f
348: .setEditableExpression("val = $report.value('jobs.hourly rate'); val.nil? ? nil : val / 100.0");
349:
350: // Run report in this thread, not a separate one. Running the
351: // report closes the output stream.
352: report.runReport();
353:
354: // Open the output and look for various things.
355: BufferedReader in = new BufferedReader(new FileReader(OUT_FILE));
356:
357: expectHeaders(in);
358:
359: // Each of the office groups. checkOfficeGroup() returns the total
360: // dollar amount (really the total id number amount, but that is proved
361: // to be the same thing within checkDetailLine()).
362: int total = 0;
363: for (int i = 0; i < OFFICES.length; ++i)
364: total += checkOfficeGroup(in, OFFICES[i]);
365:
366: // The grand total.
367: String line = in.readLine();
368: assertNotNull(line);
369: if (line.startsWith("Page "))
370: assertNotNull(line = in.readLine());
371:
372: assertEquals("Grand Total:\t" + dollarFormatter.format(total)
373: + "\t" + (reportRowNumber - 1) + "\t"
374: + (reportRowNumber - 1), line);
375:
376: // The final page number.
377: assertNotNull(line = in.readLine());
378: assertEquals("Page ", line.substring(0, 5));
379:
380: // Make sure we are at the end of the file.
381: assertNull(in.readLine());
382:
383: in.close();
384: }
385:
386: public static void main(String[] args) {
387: junit.textui.TestRunner.run(suite());
388: System.exit(0);
389: }
390:
391: }
|