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