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