001: // Modified or written by Object Mentor, Inc. for inclusion with FitNesse.
002: // Copyright (c) 2002 Cunningham & Cunningham, Inc.
003: // Released under the terms of the GNU General Public License version 2 or later.package fit;
004:
005: package fit;
006:
007: import java.io.*;
008: import java.util.*;
009: import java.lang.reflect.*;
010: import java.text.*;
011: import fit.exception.*;
012:
013: // TODO-RcM Figure out how to make me smaller.
014: public class Fixture {
015: public Map<String, Object> summary = new HashMap<String, Object>();
016:
017: public Counts counts = new Counts();
018:
019: public FixtureListener listener = new NullFixtureListener();
020:
021: protected String[] args;
022:
023: private static Map<String, Object> symbols = new HashMap<String, Object>();
024:
025: protected Class getTargetClass() {
026: return getClass();
027: }
028:
029: public class RunTime {
030: long start = System.currentTimeMillis();
031:
032: long elapsed = 0;
033:
034: public String toString() {
035: elapsed = System.currentTimeMillis() - start;
036: if (elapsed > 600000) {
037: return d(3600000) + ":" + d(600000) + d(60000) + ":"
038: + d(10000) + d(1000);
039: } else {
040: return d(60000) + ":" + d(10000) + d(1000) + "."
041: + d(100) + d(10);
042: }
043: }
044:
045: String d(long scale) {
046: long report = elapsed / scale;
047: elapsed -= report * scale;
048: return Long.toString(report);
049: }
050: }
051:
052: // Traversal //////////////////////////
053:
054: /* Altered by Rick to dispatch on the first Fixture */
055: public void doTables(Parse tables) {
056: summary.put("run date", new Date());
057: summary.put("run elapsed time", new RunTime());
058: if (tables != null) {
059: Parse heading = tables.at(0, 0, 0);
060: if (heading != null) {
061: try {
062: Fixture fixture = getLinkedFixtureWithArgs(tables);
063: fixture.listener = listener;
064: fixture.interpretTables(tables);
065: } catch (Throwable e) {
066: exception(heading, e);
067: interpretFollowingTables(tables);
068: }
069: }
070: }
071: listener.tablesFinished(counts);
072: ClearSymbols();
073: }
074:
075: public static void ClearSymbols() {
076: symbols.clear();
077: }
078:
079: /* Added by Rick to allow a dispatch into DoFixture */
080: protected void interpretTables(Parse tables) {
081: try { // Don't create the first fixture again, because creation may do something important.
082: getArgsForTable(tables); // get them again for the new fixture object
083: doTable(tables);
084: } catch (Exception ex) {
085: exception(tables.at(0, 0, 0), ex);
086: listener.tableFinished(tables);
087: return;
088: }
089: interpretFollowingTables(tables);
090: }
091:
092: /* Added by Rick */
093: private void interpretFollowingTables(Parse tables) {
094: listener.tableFinished(tables);
095: tables = tables.more;
096: while (tables != null) {
097: Parse heading = tables.at(0, 0, 0);
098: if (heading != null) {
099: try {
100: Fixture fixture = getLinkedFixtureWithArgs(tables);
101: fixture.doTable(tables);
102: } catch (Throwable e) {
103: exception(heading, e);
104: }
105: }
106: listener.tableFinished(tables);
107: tables = tables.more;
108: }
109: }
110:
111: /* Added by Rick */
112: protected Fixture getLinkedFixtureWithArgs(Parse tables)
113: throws Throwable {
114: Parse header = tables.at(0, 0, 0);
115: Fixture fixture = loadFixture(header.text());
116: fixture.counts = counts;
117: fixture.summary = summary;
118: fixture.getArgsForTable(tables);
119: return fixture;
120: }
121:
122: public static Fixture loadFixture(String fixtureName)
123: throws Throwable {
124: return FixtureLoader.instance().disgraceThenLoad(fixtureName);
125: }
126:
127: void getArgsForTable(Parse table) {
128: List<String> argumentList = new ArrayList<String>();
129: Parse parameters = table.parts.parts.more;
130: for (; parameters != null; parameters = parameters.more)
131: argumentList.add(parameters.text());
132:
133: args = (String[]) argumentList.toArray(new String[0]);
134: }
135:
136: public void doTable(Parse table) {
137: doRows(table.parts.more);
138: }
139:
140: public void doRows(Parse rows) {
141: while (rows != null) {
142: Parse more = rows.more;
143: doRow(rows);
144: rows = more;
145: }
146: }
147:
148: public void doRow(Parse row) {
149: doCells(row.parts);
150: }
151:
152: public void doCells(Parse cells) {
153: for (int i = 0; cells != null; i++) {
154: try {
155: doCell(cells, i);
156: } catch (Exception e) {
157: exception(cells, e);
158: }
159: cells = cells.more;
160: }
161: }
162:
163: public void doCell(Parse cell, int columnNumber) {
164: ignore(cell);
165: }
166:
167: // Annotation ///////////////////////////////
168:
169: public void right(Parse cell) {
170: cell.addToTag(" class=\"pass\"");
171: counts.right++;
172: }
173:
174: public void wrong(Parse cell) {
175: cell.addToTag(" class=\"fail\"");
176: counts.wrong++;
177: }
178:
179: public void wrong(Parse cell, String actual) {
180: wrong(cell);
181: cell.addToBody(label("expected") + "<hr>" + escape(actual)
182: + label("actual"));
183: }
184:
185: public void ignore(Parse cell) {
186: cell.addToTag(" class=\"ignore\"");
187: counts.ignores++;
188: }
189:
190: public void exception(Parse cell, Throwable exception) {
191: while (exception.getClass().equals(
192: InvocationTargetException.class)) {
193: exception = ((InvocationTargetException) exception)
194: .getTargetException();
195: }
196: if (isFriendlyException(exception)) {
197: cell.addToBody("<hr/>" + label(exception.getMessage()));
198: } else {
199: final StringWriter buf = new StringWriter();
200: exception.printStackTrace(new PrintWriter(buf));
201: cell.addToBody("<hr><pre><div class=\"fit_stacktrace\">"
202: + (buf.toString()) + "</div></pre>");
203: }
204: cell.addToTag(" class=\"error\"");
205: counts.exceptions++;
206: }
207:
208: public boolean isFriendlyException(Throwable exception) {
209: return exception instanceof FitFailureException;
210: }
211:
212: // Utility //////////////////////////////////
213:
214: public String counts() {
215: return counts.toString();
216: }
217:
218: public static String label(String string) {
219: return " <span class=\"fit_label\">" + string + "</span>";
220: }
221:
222: public static String gray(String string) {
223: return " <span class=\"fit_grey\">" + string + "</span>";
224: }
225:
226: public static String escape(String string) {
227: return escape(escape(string, '&', "&"), '<', "<");
228: }
229:
230: public static String escape(String string, char from, String to) {
231: int i = -1;
232: while ((i = string.indexOf(from, i + 1)) >= 0) {
233: if (i == 0) {
234: string = to + string.substring(1);
235: } else if (i == string.length()) {
236: string = string.substring(0, i) + to;
237: } else {
238: string = string.substring(0, i) + to
239: + string.substring(i + 1);
240: }
241: }
242: return string;
243: }
244:
245: public static String camel(String name) {
246: StringBuffer b = new StringBuffer(name.length());
247: StringTokenizer t = new StringTokenizer(name);
248: b.append(t.nextToken());
249: while (t.hasMoreTokens()) {
250: String token = t.nextToken();
251: b.append(token.substring(0, 1).toUpperCase()); // replace spaces with
252: // camelCase
253: b.append(token.substring(1));
254: }
255: return b.toString();
256: }
257:
258: public Object parse(String s, Class type) throws Exception {
259: if (type.equals(String.class)) {
260: if (s.toLowerCase().equals("null"))
261: return null;
262: else if (s.toLowerCase().equals("blank"))
263: return "";
264: else
265: return s;
266: } else if (type.equals(Date.class)) {
267: return DateFormat.getDateInstance(DateFormat.SHORT)
268: .parse(s);
269: } else if (hasParseMethod(type)) {
270: return callParseMethod(type, s);
271: } else {
272: throw new CouldNotParseFitFailureException(s, type
273: .getName());
274: }
275: }
276:
277: public void check(Parse cell, TypeAdapter a) {
278: String text = cell.text();
279: if (text.equals(""))
280: handleBlankCell(cell, a);
281: else if (a == null)
282: ignore(cell);
283: else if (text.equals("error"))
284: handleErrorInCell(a, cell);
285: else
286: compareCellToResult(a, cell);
287: }
288:
289: private void compareCellToResult(TypeAdapter a, Parse cell) {
290: new CellComparator().compareCellToResult(a, cell);
291: }
292:
293: public void handleBlankCell(Parse cell, TypeAdapter a) {
294: try {
295: cell.addToBody(gray(a.toString(a.get())));
296: } catch (Exception e) {
297: cell.addToBody(gray("error"));
298: }
299: }
300:
301: private void handleErrorInCell(TypeAdapter a, Parse cell) {
302: try {
303: Object result = a.invoke();
304: wrong(cell, a.toString(result));
305: } catch (IllegalAccessException e) {
306: exception(cell, e);
307: } catch (Exception e) {
308: right(cell);
309: }
310: }
311:
312: public String[] getArgs() {
313: return args;
314: }
315:
316: public static void setSymbol(String name, Object value) {
317: symbols.put(name, value);
318: }
319:
320: public static Object getSymbol(String name) {
321: return symbols.get(name);
322: }
323:
324: public static boolean hasParseMethod(Class type) {
325: try {
326: type.getMethod("parse", new Class[] { String.class });
327: return true;
328: } catch (NoSuchMethodException e) {
329: return false;
330: }
331: }
332:
333: public static Object callParseMethod(Class type, String s)
334: throws Exception {
335: Method parseMethod = type.getMethod("parse",
336: new Class[] { String.class });
337: Object o = parseMethod.invoke(null, new Object[] { s });
338: return o;
339: }
340:
341: // TODO-RcM I might be moving out of here. Can you help me find a home of my
342: // own?
343: private class CellComparator {
344: private Object result = null;
345:
346: private Object expected = null;
347:
348: private TypeAdapter typeAdapter;
349:
350: private Parse cell;
351:
352: private void compareCellToResult(TypeAdapter a, Parse theCell) {
353: typeAdapter = a;
354: cell = theCell;
355:
356: try {
357: result = typeAdapter.get();
358: expected = parseCell();
359: if (expected instanceof Unparseable)
360: tryRelationalMatch();
361: else
362: compare();
363: } catch (Exception e) {
364: exception(cell, e);
365: }
366: }
367:
368: private void compare() {
369: if (typeAdapter.equals(expected, result)) {
370: right(cell);
371: } else {
372: wrong(cell, typeAdapter.toString(result));
373: }
374: }
375:
376: private Object parseCell() {
377: try {
378: return typeAdapter.parse(cell.text());
379: }
380: // Ignore parse exceptions, print non-parse exceptions,
381: // return null so that compareCellToResult tries relational matching.
382: catch (NumberFormatException e) {
383: } catch (ParseException e) {
384: } catch (Exception e) {
385: e.printStackTrace();
386: }
387: return new Unparseable();
388: }
389:
390: private void tryRelationalMatch() {
391: Class adapterType = typeAdapter.type;
392: FitFailureException cantParseException = new CouldNotParseFitFailureException(
393: cell.text(), adapterType.getName());
394: if (result != null) {
395: FitMatcher matcher = new FitMatcher(cell.text(), result);
396: try {
397: if (matcher.matches())
398: right(cell);
399: else
400: wrong(cell);
401: cell.body = matcher.message();
402: } catch (FitMatcherException fme) {
403: exception(cell, cantParseException);
404: } catch (Exception e) {
405: exception(cell, e);
406: }
407: } else {
408: // TODO-RcM Is this always accurate?
409: exception(cell, cantParseException);
410: }
411: }
412: }
413:
414: private class Unparseable {
415: }
416: }
|