// Modified or written by Object Mentor, Inc. for inclusion with FitNesse.
// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.package fit;
package fit;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import fit.exception.CouldNotParseFitFailureException;
import fit.exception.FitFailureException;
import fit.exception.FitMatcherException;
// TODO-RcM Figure out how to make me smaller.
public class Fixture {
public Map<String, Object> summary = new HashMap<String, Object>();
public Counts counts = new Counts();
public FixtureListener listener = new NullFixtureListener();
protected String[] args;
private static final Map<String, Object> symbols = new HashMap<String, Object>();
private static boolean forcedAbort = false; //Semaphores
public static void setForcedAbort(boolean state) {
forcedAbort = state;
} //Semaphores
protected Class<?> getTargetClass() {
return getClass();
}
public class RunTime {
long start = System.currentTimeMillis();
long elapsed = 0;
public String toString() {
elapsed = System.currentTimeMillis() - start;
if (elapsed > 600000) {
return d(3600000) + ":" + d(600000) + d(60000) + ":" + d(10000) + d(1000);
} else {
return d(60000) + ":" + d(10000) + d(1000) + "." + d(100) + d(10);
}
}
String d(long scale) {
long report = elapsed / scale;
elapsed -= report * scale;
return Long.toString(report);
}
}
// Traversal //////////////////////////
/* Altered by Rick to dispatch on the first Fixture */
public void doTables(Parse tables) {
summary.put("run date", new Date());
summary.put("run elapsed time", new RunTime());
if (tables != null) {
Parse heading = tables.at(0, 0, 0);
if (heading != null) {
try {
Fixture fixture = getLinkedFixtureWithArgs(tables);
fixture.listener = listener;
fixture.interpretTables(tables);
} catch (Throwable e) {
exception(heading, e);
interpretFollowingTables(tables);
}
}
}
listener.tablesFinished(counts);
ClearSymbols();
SemaphoreFixture.ClearSemaphores(); //Semaphores: clear all at end
}
public static void ClearSymbols() {
symbols.clear();
}
/* Added by Rick to allow a dispatch into DoFixture */
protected void interpretTables(Parse tables) {
try { // Don't create the first fixture again, because creation may do something important.
getArgsForTable(tables); // get them again for the new fixture object
doTable(tables);
} catch (Exception ex) {
exception(tables.at(0, 0, 0), ex);
listener.tableFinished(tables);
return;
}
interpretFollowingTables(tables);
}
/* Added by Rick */
private void interpretFollowingTables(Parse tables) {
listener.tableFinished(tables);
tables = tables.more;
while (tables != null) {
Parse heading = tables.at(0, 0, 0);
if (forcedAbort) ignore(heading); //Semaphores: ignore on failed lock
else if (heading != null) {
try {
Fixture fixture = getLinkedFixtureWithArgs(tables);
fixture.doTable(tables);
} catch (Throwable e) {
exception(heading, e);
}
}
listener.tableFinished(tables);
tables = tables.more;
}
}
/* Added by Rick */
protected Fixture getLinkedFixtureWithArgs(Parse tables) throws Throwable {
Parse header = tables.at(0, 0, 0);
Fixture fixture = loadFixture(header.text());
fixture.counts = counts;
fixture.summary = summary;
fixture.getArgsForTable(tables);
return fixture;
}
public static Fixture loadFixture(String fixtureName) throws Throwable {
return FixtureLoader.instance().disgraceThenLoad(fixtureName);
}
public void getArgsForTable(Parse table) {
List<String> argumentList = new ArrayList<String>();
Parse parameters = table.parts.parts.more;
for (; parameters != null; parameters = parameters.more) {
argumentList.add(Parse.unescape(parameters.body));
}
args = argumentList.toArray(new String[argumentList.size()]);
}
public void doTable(Parse table) {
doRows(table.parts.more);
}
public void doRows(Parse rows) {
while (rows != null) {
Parse more = rows.more;
doRow(rows);
rows = more;
}
}
public void doRow(Parse row) {
doCells(row.parts);
}
public void doCells(Parse cells) {
for (int i = 0; cells != null; i++) {
try {
doCell(cells, i);
} catch (Exception e) {
exception(cells, e);
}
cells = cells.more;
}
}
public void doCell(Parse cell, int columnNumber) {
ignore(cell);
}
// Annotation ///////////////////////////////
public void right(Parse cell) {
cell.addToTag(" class=\"pass\"");
counts.right++;
}
public void wrong(Parse cell) {
cell.addToTag(" class=\"fail\"");
counts.wrong++;
}
public void wrong(Parse cell, String actual) {
wrong(cell);
cell.addToBody(label("expected") + "<hr>" + escape(actual) + label("actual"));
}
public void ignore(Parse cell) {
cell.addToTag(" class=\"ignore\"");
counts.ignores++;
}
public void exception(Parse cell, Throwable exception) {
while (exception.getClass().equals(InvocationTargetException.class)) {
exception = ((InvocationTargetException) exception).getTargetException();
}
if (isFriendlyException(exception)) {
cell.addToBody("<hr/>" + label(exception.getMessage()));
} else {
final StringWriter buf = new StringWriter();
exception.printStackTrace(new PrintWriter(buf));
cell.addToBody("<hr><pre><div class=\"fit_stacktrace\">" + (buf.toString()) + "</div></pre>");
}
cell.addToTag(" class=\"error\"");
counts.exceptions++;
}
public boolean isFriendlyException(Throwable exception) {
return exception instanceof FitFailureException;
}
// Utility //////////////////////////////////
public String counts() {
return counts.toString();
}
public static String label(String string) {
return " <span class=\"fit_label\">" + string + "</span>";
}
public static String gray(String string) {
return " <span class=\"fit_grey\">" + string + "</span>";
}
public static String escape(String string) {
return escape(escape(string, '&', "&"), '<', "<");
}
public static String escape(String string, char from, String to) {
int i = -1;
while ((i = string.indexOf(from, i + 1)) >= 0) {
if (i == 0) {
string = to + string.substring(1);
} else if (i == string.length()) {
string = string.substring(0, i) + to;
} else {
string = string.substring(0, i) + to + string.substring(i + 1);
}
}
return string;
}
public static String camel(String name) {
StringBuffer b = new StringBuffer(name.length());
StringTokenizer t = new StringTokenizer(name);
b.append(t.nextToken());
while (t.hasMoreTokens()) {
String token = t.nextToken();
b.append(token.substring(0, 1).toUpperCase()); // replace spaces with
// camelCase
b.append(token.substring(1));
}
return b.toString();
}
public Object parse(String s, Class<?> type) throws Exception {
if (type.equals(String.class)) {
if (s.toLowerCase().equals("null"))
return null;
else if (s.toLowerCase().equals("blank"))
return "";
else
return s;
} else if (type.equals(Date.class)) {
return DateFormat.getDateInstance(DateFormat.SHORT).parse(s);
} else if (hasParseMethod(type)) {
return callParseMethod(type, s);
} else {
throw new CouldNotParseFitFailureException(s, type.getName());
}
}
public void check(Parse cell, TypeAdapter a) {
String text = cell.text();
if (text.equals(""))
handleBlankCell(cell, a);
else if (a == null)
ignore(cell);
else if (text.equals("error"))
handleErrorInCell(a, cell);
else
compareCellToResult(a, cell);
}
private void compareCellToResult(TypeAdapter a, Parse cell) {
new CellComparator().compareCellToResult(a, cell);
}
public void handleBlankCell(Parse cell, TypeAdapter a) {
try {
cell.addToBody(gray(a.toString(a.get())));
} catch (Exception e) {
cell.addToBody(gray("error"));
}
}
private void handleErrorInCell(TypeAdapter a, Parse cell) {
try {
Object result = a.invoke();
wrong(cell, a.toString(result));
} catch (IllegalAccessException e) {
exception(cell, e);
} catch (Exception e) {
right(cell);
}
}
public String[] getArgs() {
return Arrays.copyOf(args, args.length);
}
public static void setSymbol(String name, Object value) {
symbols.put(name, (value == null) ? "null" : value);
}
public static Object getSymbol(String name) {
return symbols.get(name);
}
public static boolean hasSymbol(String name) {
return symbols.containsKey(name);
}
public static boolean hasParseMethod(Class<?> type) {
try {
type.getMethod("parse", new Class<?>[]
{String.class});
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
public static Object callParseMethod(Class<?> type, String s) throws Exception {
Method parseMethod = type.getMethod("parse", new Class<?>[]
{String.class});
Object o = parseMethod.invoke(null, new Object[]
{s});
return o;
}
// TODO-RcM I might be moving out of here. Can you help me find a home of my
// own?
private class CellComparator {
private Object result = null;
private Object expected = null;
private TypeAdapter typeAdapter;
private Parse cell;
private void compareCellToResult(TypeAdapter a, Parse theCell) {
typeAdapter = a;
cell = theCell;
try {
result = typeAdapter.get();
expected = parseCell();
if (expected instanceof Unparseable)
tryRelationalMatch();
else
compare();
} catch (Exception e) {
exception(cell, e);
}
}
private void compare() {
if (typeAdapter.equals(expected, result)) {
right(cell);
} else {
wrong(cell, typeAdapter.toString(result));
}
}
private Object parseCell() {
try {
return typeAdapter.isRegex ? cell.text() : typeAdapter.parse(cell.text());
}
// Ignore parse exceptions, print non-parse exceptions,
// return null so that compareCellToResult tries relational matching.
catch (NumberFormatException e) {
} catch (ParseException e) {
} catch (Exception e) {
e.printStackTrace();
}
return new Unparseable();
}
private void tryRelationalMatch() {
Class<?> adapterType = typeAdapter.type;
FitFailureException cantParseException = new CouldNotParseFitFailureException(cell.text(), adapterType
.getName());
if (result != null) {
FitMatcher matcher = new FitMatcher(cell.text(), result);
try {
if (matcher.matches())
right(cell);
else
wrong(cell);
cell.body = matcher.message();
} catch (FitMatcherException fme) {
exception(cell, cantParseException);
} catch (Exception e) {
exception(cell, e);
}
} else {
// TODO-RcM Is this always accurate?
exception(cell, cantParseException);
}
}
}
private class Unparseable {
}
}