Browse Source

harness support for message details, extra source locations

Completely new (clearer?) message-checking code.
tags/mostlyLastEclipse2xTree_20040112
wisberg 20 years ago
parent
commit
50567fc1b7

+ 295
- 279
testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java View File

@@ -30,298 +30,314 @@ import org.aspectj.testing.util.BridgeUtil;
import org.aspectj.testing.util.Diffs;
import org.aspectj.util.LangUtil;


/**
* Handle messages during test and calculate differences
* between expected and actual messages.
*/
public class AjcMessageHandler extends MessageHandler {
public class AjcMessageHandler extends MessageHandler {

/** Comparator for enclosed IMessage diffs */
public static final Comparator COMP_IMessage
= BridgeUtil.Comparators.MEDIUM_IMessage;
/** Comparator for enclosed File diffs */
public static final Comparator COMP_File
= BridgeUtil.Comparators.WEAK_File;
/** Comparator for enclosed IMessage diffs */
public static final Comparator COMP_IMessage =
BridgeUtil.Comparators.MEDIUM_IMessage;

/** unmodifiable list of IMessage messages of any type */
private final List expectedMessagesAsList;
/** IMessageHolder variant of expectedMessagesAsList */
private final IMessageHolder expectedMessages;
/** number of messages FAIL or worse */
private final int numExpectedFailed;
/** Comparator for enclosed File diffs */
public static final Comparator COMP_File = BridgeUtil.Comparators.WEAK_File;

/** true if there were no error or worse messages expected */
private final boolean expectingCommandTrue;
/** unmodifiable list of IMessage messages of any type */
private final List expectedMessagesAsList;

/** unmodifiable list of File expected to be recompiled */
private final List expectedRecompiled; // Unused now, but reinstate when supported
/** if true, ignore warnings when calculating diffs and passed() */
private final boolean ignoreWarnings;
/** list of File actually recompiled */
private List actualRecompiled;
/** cache expected/actual diffs, nullify if any new message */
private transient CompilerDiffs diffs;
AjcMessageHandler(IMessageHolder expectedMessages) {
this(expectedMessages, false);
}
/**
* @param messages the (constant) IMessageHolder with expected messages
*/
AjcMessageHandler(IMessageHolder expectedMessages, boolean ignoreWarnings) {
LangUtil.throwIaxIfNull(messages, "messages");
this.expectedMessages = expectedMessages;
expectedMessagesAsList = expectedMessages.getUnmodifiableListView();
expectedRecompiled = Collections.EMPTY_LIST;
this.ignoreWarnings = ignoreWarnings;
int fails = 0;
int errors = 0;
for (Iterator iter = expectedMessagesAsList.iterator(); iter.hasNext();) {
IMessage m = (IMessage) iter.next();
IMessage.Kind kind = m.getKind();
if (IMessage.FAIL.isSameOrLessThan(kind)) {
fails++;
} else if (m.isError()) {
errors++;
}
}
expectingCommandTrue = (0 == (errors + fails));
numExpectedFailed = fails;
}
/** clear out any actual values to be re-run */
public void init() {
super.init();
actualRecompiled = null;
diffs = null;
}
/** IMessageHolder variant of expectedMessagesAsList */
private final IMessageHolder expectedMessages;

/**
* Return true if we have this kind of
* message for the same line and store all messages.
* @see bridge.tools.impl.ErrorHandlerAdapter#doShowMessage(IMessage)
* @return true if message handled (processing should abort)
*/
public boolean handleMessage(IMessage message) {
if (null == message) {
throw new IllegalArgumentException("null message");
}
super.handleMessage(message);
return expecting(message);
}
/**
* Set the actual files recompiled.
* @param List of File recompiled - may be null; adopted but not modified
* @throws IllegalStateException if they have been set already.
*/
public void setRecompiled(List list) {
if (null != actualRecompiled) {
throw new IllegalStateException("actual recompiled already set");
}
this.actualRecompiled = LangUtil.safeList(list);
}
/** Generate differences between expected and actual errors and warnings */
public CompilerDiffs getCompilerDiffs() {
if (null == diffs) {
final List expected;
final List actual;
if (!ignoreWarnings) {
expected = expectedMessages.getUnmodifiableListView();
actual = this.getUnmodifiableListView();
} else {
expected = Arrays.asList(
expectedMessages.getMessages(IMessage.ERROR, true));
actual = Arrays.asList(
this.getMessages(IMessage.ERROR, true));
}
// we ignore unexpected info messages,
// but we do test for expected ones
Diffs messages = new Diffs(
"message",
expected,
actual,
COMP_IMessage,
Diffs.ACCEPT_ALL,
CompilerDiffs.SKIP_UNEXPECTED_INFO);
Diffs recompiled = new Diffs(
"recompiled",
expectedRecompiled,
actualRecompiled,
COMP_File);
diffs = new CompilerDiffs(messages, recompiled);
}
return diffs;
}
/**
* Get the (current) result of this run,
* ignoring differences in warnings on request.
* Note it may return passed (true) when there are expected error messages.
* @return false
* if there are any fail or abort messages,
* or if the expected errors, warnings, or recompiled do not match actual.
*/
public boolean passed() {
return !getCompilerDiffs().different;
}
/** @return true if we are expecting the command to fail - i.e., any expected errors */
public boolean expectingCommandTrue() {
return expectingCommandTrue;
}
/**
* Report results to a handler,
* adding all messages
* and creating fail messages for each diff.
*/
public void report(IMessageHandler handler) {
if (null == handler) {
MessageUtil.debug(this, "report got null handler");
}
// Report all messages except expected fail+ messages,
// which will cause the reported-to handler client to gack.
// XXX need some verbose way to report even expected fail+
final boolean fastFail = false; // do all messages
if (0 == numExpectedFailed) {
MessageUtil.handleAll(handler, this, fastFail);
} else {
IMessage[] ra = getMessagesWithoutExpectedFails();
MessageUtil.handleAll(handler, ra, fastFail);
}
/** number of messages FAIL or worse */
private final int numExpectedFailed;

CompilerDiffs diffs = getCompilerDiffs();
if (diffs.different) {
diffs.messages.report(handler, IMessage.FAIL);
diffs.recompiled.report(handler, IMessage.FAIL);
}
}
/** @return String consisting of differences and any other messages */
public String toString() {
CompilerDiffs diffs = getCompilerDiffs();
StringBuffer sb = new StringBuffer(super.toString());
final String EOL = "\n";
sb.append(EOL);
render(sb, " unexpected message ", EOL, diffs.messages.unexpected);
render(sb, " missing message ", EOL, diffs.messages.missing);
render(sb, " fail ", EOL, getList(IMessage.FAIL));
render(sb, " abort ", EOL, getList(IMessage.ABORT));
render(sb, " info ", EOL, getList(IMessage.INFO));
return sb.toString(); // XXX cache toString
}
/** true if there were no error or worse messages expected */
private final boolean expectingCommandTrue;

/**
* Check if the message was expected, and clear diffs if not.
* @return true if we expect a message of this kind with this line number
*/
private boolean expecting(IMessage message) {
boolean match = false;
if (null != message) {
for (Iterator iter = expectedMessagesAsList.iterator();
iter.hasNext();
) {
// amc - we have to compare against all messages to consume multiple
// text matches on same line. Return true if any matches.
if (0 == COMP_IMessage.compare(message, iter.next())) {
match = true;
}
}
}
if (!match) {
diffs = null;
}
return match;
}
/** unmodifiable list of File expected to be recompiled */
private final List expectedRecompiled;
// Unused now, but reinstate when supported

private IMessage[] getMessagesWithoutExpectedFails() {
IMessage[] result = super.getMessages(null, true);
// remove all expected fail+ (COSTLY)
ArrayList list = new ArrayList();
int leftToFilter = numExpectedFailed;
for (int i = 0; i < result.length; i++) {
if ((0 == leftToFilter)
|| !IMessage.FAIL.isSameOrLessThan(result[i].getKind())) {
list.add(result[i]);
} else {
// see if this failure was expected
if (expectedMessagesHasMatchFor(result[i])) {
leftToFilter--; // ok, don't add
} else {
list.add(result[i]);
/** if true, ignore warnings when calculating diffs and passed() */
private final boolean ignoreWarnings;

/** list of File actually recompiled */
private List actualRecompiled;

/** cache expected/actual diffs, nullify if any new message */
private transient CompilerDiffs diffs;

AjcMessageHandler(IMessageHolder expectedMessages) {
this(expectedMessages, false);
}
/**
* @param messages the (constant) IMessageHolder with expected messages
*/
AjcMessageHandler(
IMessageHolder expectedMessages,
boolean ignoreWarnings) {
LangUtil.throwIaxIfNull(messages, "messages");
this.expectedMessages = expectedMessages;
expectedMessagesAsList = expectedMessages.getUnmodifiableListView();
expectedRecompiled = Collections.EMPTY_LIST;
this.ignoreWarnings = ignoreWarnings;
int fails = 0;
int errors = 0;
for (Iterator iter = expectedMessagesAsList.iterator();
iter.hasNext();
) {
IMessage m = (IMessage) iter.next();
IMessage.Kind kind = m.getKind();
if (IMessage.FAIL.isSameOrLessThan(kind)) {
fails++;
} else if (m.isError()) {
errors++;
}
}
expectingCommandTrue = (0 == (errors + fails));
numExpectedFailed = fails;
}

/** clear out any actual values to be re-run */
public void init() {
super.init();
actualRecompiled = null;
diffs = null;
}

/**
* Return true if we have this kind of
* message for the same line and store all messages.
* @see bridge.tools.impl.ErrorHandlerAdapter#doShowMessage(IMessage)
* @return true if message handled (processing should abort)
*/
public boolean handleMessage(IMessage message) {
if (null == message) {
throw new IllegalArgumentException("null message");
}
super.handleMessage(message);
return expecting(message);
}

/**
* Set the actual files recompiled.
* @param List of File recompiled - may be null; adopted but not modified
* @throws IllegalStateException if they have been set already.
*/
public void setRecompiled(List list) {
if (null != actualRecompiled) {
throw new IllegalStateException("actual recompiled already set");
}
this.actualRecompiled = LangUtil.safeList(list);
}

/** Generate differences between expected and actual errors and warnings */
public CompilerDiffs getCompilerDiffs() {
if (null == diffs) {
final List expected;
final List actual;
if (!ignoreWarnings) {
expected = expectedMessages.getUnmodifiableListView();
actual = this.getUnmodifiableListView();
} else {
expected =
Arrays.asList(
expectedMessages.getMessages(IMessage.ERROR, true));
actual = Arrays.asList(this.getMessages(IMessage.ERROR, true));
}
// we ignore unexpected info messages,
// but we do test for expected ones
final Diffs messages;
boolean usingNew = true; // XXX extract old API's after shake-out period
if (usingNew) {
final IMessage.Kind[] NOSKIPS = new IMessage.Kind[0];
IMessage.Kind[] skipActual = new IMessage.Kind[] { IMessage.INFO };
int expectedInfo
= MessageUtil.numMessages(expected, IMessage.INFO, false);
if (0 < expectedInfo) {
// fyi, when expecting any info messages, have to expect all
skipActual = NOSKIPS;
}
messages = Diffs.makeDiffs(
"message",
(IMessage[]) expected.toArray(new IMessage[0]),
(IMessage[]) actual.toArray(new IMessage[0]),
NOSKIPS,
skipActual);
} else {
messages = Diffs.makeDiffs(
"message",
expected,
actual,
COMP_IMessage,
Diffs.ACCEPT_ALL,
CompilerDiffs.SKIP_UNEXPECTED_INFO);
}
}
result = (IMessage[]) list.toArray(new IMessage[0]);
return result;
}
Diffs recompiled =
Diffs.makeDiffs(
"recompiled",
expectedRecompiled,
actualRecompiled,
COMP_File);
diffs = new CompilerDiffs(messages, recompiled);
}
return diffs;
}

/**
* @param actual the actual IMessage to seek a match for in expected messages
* @return true if actual message is matched in the expected messages
*/
private boolean expectedMessagesHasMatchFor(IMessage actual) {
for (Iterator iter = expectedMessagesAsList.iterator();
iter.hasNext();) {
IMessage expected = (IMessage) iter.next();
if (0 == COMP_IMessage.compare(expected, actual)) {
return true;
}
}
return false;
}
/** @return immutable list of a given kind - use null for all kinds */
private List getList(IMessage.Kind kind) {
if ((null == kind) || (0 == numMessages(kind, IMessageHolder.EQUAL))) {
return Collections.EMPTY_LIST;
}
return Arrays.asList(getMessages(kind, IMessageHolder.EQUAL));
}
/** @return "" if no items or {prefix}{item}{suffix}... otherwise */
private void render( // LangUtil instead?
StringBuffer result,
String prefix,
String suffix,
List items) {
if ((null != items)) {
for (Iterator iter = items.iterator(); iter.hasNext();) {
result.append(prefix + iter.next() + suffix);
}
}
}
/** compiler results for errors, warnings, and recompiled files */
public static class CompilerDiffs {
/** Skip info messages when reporting unexpected messages */
static final Diffs.Filter SKIP_UNEXPECTED_INFO
= new Diffs.Filter(){
public boolean accept(Object o) {
return ((o instanceof IMessage)
&& !((IMessage)o).isInfo());
}
};
public final Diffs messages;
public final Diffs recompiled;
public final boolean different;
public CompilerDiffs(
Diffs messages,
Diffs recompiled) {
this.recompiled = recompiled;
this.messages = messages;
different = (messages.different || recompiled.different);
}
}
/**
* Get the (current) result of this run,
* ignoring differences in warnings on request.
* Note it may return passed (true) when there are expected error messages.
* @return false
* if there are any fail or abort messages,
* or if the expected errors, warnings, or recompiled do not match actual.
*/
public boolean passed() {
return !getCompilerDiffs().different;
}

/** @return true if we are expecting the command to fail - i.e., any expected errors */
public boolean expectingCommandTrue() {
return expectingCommandTrue;
}

/**
* Report results to a handler,
* adding all messages
* and creating fail messages for each diff.
*/
public void report(IMessageHandler handler) {
if (null == handler) {
MessageUtil.debug(this, "report got null handler");
}
// Report all messages except expected fail+ messages,
// which will cause the reported-to handler client to gack.
// XXX need some verbose way to report even expected fail+
final boolean fastFail = false; // do all messages
if (0 == numExpectedFailed) {
MessageUtil.handleAll(handler, this, fastFail);
} else {
IMessage[] ra = getMessagesWithoutExpectedFails();
MessageUtil.handleAll(handler, ra, fastFail);
}

CompilerDiffs diffs = getCompilerDiffs();
if (diffs.different) {
diffs.messages.report(handler, IMessage.FAIL);
diffs.recompiled.report(handler, IMessage.FAIL);
}
}

/** @return String consisting of differences and any other messages */
public String toString() {
CompilerDiffs diffs = getCompilerDiffs();
StringBuffer sb = new StringBuffer(super.toString());
final String EOL = "\n";
sb.append(EOL);
render(sb, " unexpected message ", EOL, diffs.messages.unexpected);
render(sb, " missing message ", EOL, diffs.messages.missing);
render(sb, " fail ", EOL, getList(IMessage.FAIL));
render(sb, " abort ", EOL, getList(IMessage.ABORT));
render(sb, " info ", EOL, getList(IMessage.INFO));
return sb.toString(); // XXX cache toString
}

/**
* Check if the message was expected, and clear diffs if not.
* @return true if we expect a message of this kind with this line number
*/
private boolean expecting(IMessage message) {
boolean match = false;
if (null != message) {
for (Iterator iter = expectedMessagesAsList.iterator();
iter.hasNext();
) {
// amc - we have to compare against all messages to consume multiple
// text matches on same line. Return true if any matches.
if (0 == COMP_IMessage.compare(message, iter.next())) {
match = true;
}
}
}
if (!match) {
diffs = null;
}
return match;
}

private IMessage[] getMessagesWithoutExpectedFails() {
IMessage[] result = super.getMessages(null, true);
// remove all expected fail+ (COSTLY)
ArrayList list = new ArrayList();
int leftToFilter = numExpectedFailed;
for (int i = 0; i < result.length; i++) {
if ((0 == leftToFilter)
|| !IMessage.FAIL.isSameOrLessThan(result[i].getKind())) {
list.add(result[i]);
} else {
// see if this failure was expected
if (expectedMessagesHasMatchFor(result[i])) {
leftToFilter--; // ok, don't add
} else {
list.add(result[i]);
}
}
}
result = (IMessage[]) list.toArray(new IMessage[0]);
return result;
}

/**
* @param actual the actual IMessage to seek a match for in expected messages
* @return true if actual message is matched in the expected messages
*/
private boolean expectedMessagesHasMatchFor(IMessage actual) {
for (Iterator iter = expectedMessagesAsList.iterator();
iter.hasNext();
) {
IMessage expected = (IMessage) iter.next();
if (0 == COMP_IMessage.compare(expected, actual)) {
return true;
}
}
return false;
}

/** @return immutable list of a given kind - use null for all kinds */
private List getList(IMessage.Kind kind) {
if ((null == kind) || (0 == numMessages(kind, IMessageHolder.EQUAL))) {
return Collections.EMPTY_LIST;
}
return Arrays.asList(getMessages(kind, IMessageHolder.EQUAL));
}

/** @return "" if no items or {prefix}{item}{suffix}... otherwise */
private void render(// LangUtil instead?
StringBuffer result, String prefix, String suffix, List items) {
if ((null != items)) {
for (Iterator iter = items.iterator(); iter.hasNext();) {
result.append(prefix + iter.next() + suffix);
}
}
}

/** compiler results for errors, warnings, and recompiled files */
public static class CompilerDiffs {
/** Skip info messages when reporting unexpected messages */
static final Diffs.Filter SKIP_UNEXPECTED_INFO = new Diffs.Filter() {
public boolean accept(Object o) {
return ((o instanceof IMessage) && !((IMessage) o).isInfo());
}
};
public final Diffs messages;
public final Diffs recompiled;
public final boolean different;

public CompilerDiffs(Diffs messages, Diffs recompiled) {
this.recompiled = recompiled;
this.messages = messages;
different = (messages.different || recompiled.different);
}
}
}

+ 2
- 1
testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java View File

@@ -24,6 +24,7 @@ import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.bridge.IMessage.Kind;
import org.aspectj.testing.util.BridgeUtil;
@@ -368,7 +369,7 @@ public class FlatSuiteReader implements SFileReader.Maker {
abortOnError,
System.err);
} catch (IOException e) {
IMessage m = Message.fail("reading " + suiteFile, e);
IMessage m = MessageUtil.fail("reading " + suiteFile, e);
throw new AbortException(m);
}

+ 509
- 103
testing/src/org/aspectj/testing/util/Diffs.java View File

@@ -13,6 +13,7 @@

package org.aspectj.testing.util;

import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
@@ -22,120 +23,525 @@ import java.util.List;

import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.FileUtil;
import org.aspectj.util.LangUtil;

/** result struct for expected/actual diffs for Collection */
/**
* Result struct for expected/actual diffs for Collection
*/
public class Diffs {

public static final Filter ACCEPT_ALL = new Filter() {
public boolean accept(Object o) {
return true;
}
};
// XXX List -> Collection b/c comparator orders
public static final Diffs NONE
= new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST);
/** name of the thing being diffed - used only for reporting */
public final String label;
/** immutable List */
public final List missing;
/**
* Compare IMessage.Kind based on kind priority.
*/
public static final Comparator KIND_PRIORITY = new Comparator() {
/**
* Compare IMessage.Kind based on kind priority.
* @throws NullPointerException if anything is null
*/
public int compare(Object lhs, Object rhs) {
return ((IMessage.Kind) lhs).compareTo((IMessage.Kind) rhs);
}
};
/**
* Sort ISourceLocation based on line, file path.
*/
public static final Comparator SORT_SOURCELOC = new Comparator() {
/**
* Compare ISourceLocation based on line, file path.
* @throws NullPointerException if anything is null
*/
public int compare(Object lhs, Object rhs) {
ISourceLocation l = (ISourceLocation) lhs;
ISourceLocation r = (ISourceLocation) rhs;
int result = getLine(l) - getLine(r);
if (0 != result) {
return result;
}
String lp = getSourceFile(l).getPath();
String rp = getSourceFile(r).getPath();
return lp.compareTo(rp);
}
};

/** immutable List */
public final List unexpected;
/**
* Compare IMessages based on kind and source location line (only).
*/
public static final Comparator MESSAGE_LINEKIND = new Comparator() {
/**
* Compare IMessages based on kind and source location line (only).
* @throws NullPointerException if anything is null
*/
public int compare(Object lhs, Object rhs) {
IMessage lm = (IMessage) lhs;
IMessage rm = (IMessage) rhs;
ISourceLocation ls = (lm == null ? null : lm.getSourceLocation());
ISourceLocation rs = (rm == null ? null : rm.getSourceLocation());
int left = (ls == null ? -1 : ls.getLine());
int right = (rs == null ? -1 : rs.getLine());
int result = left - right;
if (0 == result) {
result = lm.getKind().compareTo(rm.getKind());
}
return result;
}
};
public static final Filter ACCEPT_ALL = new Filter() {
public boolean accept(Object o) {
return true;
}
};
// // XXX List -> Collection b/c comparator orders
// public static final Diffs NONE
// = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST);

/** true if there are any missing or unexpected */
public final boolean different;
private Diffs(String label, List missing, List unexpected) {
this.label = label;
this.missing = missing;
this.unexpected = unexpected;
different = ((0 != this.missing.size())
|| (0 != this.unexpected.size()));
}
public static Diffs makeDiffs(
String label,
List expected,
List actual,
Comparator comparator) {
return makeDiffs(
label,
expected,
actual,
comparator,
ACCEPT_ALL,
ACCEPT_ALL);
}

public Diffs(
String label,
List expected,
List actual,
Comparator comparator) {
this(label, expected, actual, comparator, ACCEPT_ALL, ACCEPT_ALL);
}
public static Diffs makeDiffs(
String label,
IMessage[] expected,
IMessage[] actual) {
return makeDiffs(label, expected, actual, null, null);
}
public Diffs(
String label,
List expected,
List actual,
Comparator comparator,
Filter missingFilter,
Filter unexpectedFilter) {
label = label.trim();
if (null == label) {
label = ": ";
} else if (!label.endsWith(":")) {
label += ": ";
}
this.label = " " + label;
ArrayList miss = new ArrayList();
ArrayList unexpect = new ArrayList();
org.aspectj.testing.util.LangUtil.makeSoftDiffs(expected, actual, miss, unexpect, comparator);
if (null != missingFilter) {
for (ListIterator iter = miss.listIterator(); iter.hasNext();) {
if (!missingFilter.accept(iter.next())) {
iter.remove();
}
}
}
missing = Collections.unmodifiableList(miss);
if (null != unexpectedFilter) {
for (ListIterator iter = unexpect.listIterator(); iter.hasNext();) {
if (!unexpectedFilter.accept(iter.next())) {
iter.remove();
}
}
private static int getLine(ISourceLocation loc) {
int result = -1;
if (null != loc) {
result = loc.getLine();
}
unexpected = Collections.unmodifiableList(unexpect);
different = ((0 != this.missing.size())
|| (0 != this.unexpected.size()));
return result;
}
/**
* Report missing and extra items to handler.
* For each item in missing or unexpected, this creates a {kind} IMessage with
* the text "{missing|unexpected} {label}: {message}"
* where {message} is the result of
* <code>MessageUtil.renderMessage(IMessage)</code>.
* @param handler where the messages go - not null
* @param kind the kind of message to construct - not null
* @param label the prefix for the message text - if null, "" used
* @see MessageUtil#renderMessage(IMessage)
*/
public void report(IMessageHandler handler, IMessage.Kind kind) {
LangUtil.throwIaxIfNull(handler, "handler");
LangUtil.throwIaxIfNull(kind, "kind");
if (different) {
for (Iterator iter = missing.iterator(); iter.hasNext();) {
String s = MessageUtil.renderMessage((IMessage) iter.next());
MessageUtil.fail(handler, "missing " + label + s);
}
for (Iterator iter = unexpected.iterator(); iter.hasNext();) {
String s = MessageUtil.renderMessage((IMessage) iter.next());
MessageUtil.fail(handler, "unexpected " + label + s);
}
private static int getLine(IMessage message) {
int result = -1;
if ((null != message)) {
result = getLine(message.getSourceLocation());
}
return result;
}
/** @return "{label}: (unexpected={#}, missing={#})" */
public String toString() {
return label + "(unexpected=" + unexpected.size()
+ ", missing=" + missing.size() + ")";
}
public static interface Filter {
/** @return true to keep input in list of messages */
boolean accept(Object input);

private static File getSourceFile(ISourceLocation loc) {
File result = ISourceLocation.NO_FILE;
if (null != loc) {
result = loc.getSourceFile();
}
return result;
}
}

public static Diffs makeDiffs(
String label,
IMessage[] expected,
IMessage[] actual,
IMessage.Kind[] ignoreExpectedKinds,
IMessage.Kind[] ignoreActualKinds) {
ArrayList exp = getExcept(expected, ignoreExpectedKinds);
ArrayList act = getExcept(actual, ignoreActualKinds);

ArrayList missing = new ArrayList();
List unexpected = new ArrayList();

if (LangUtil.isEmpty(expected)) {
unexpected.addAll(act);
} else if (LangUtil.isEmpty(actual)) {
missing.addAll(exp);
} else {
ListIterator expectedIterator = exp.listIterator();
int lastLine = Integer.MIN_VALUE + 1;
ArrayList expectedFound = new ArrayList();
ArrayList expectedForLine = new ArrayList();
for (ListIterator iter = act.listIterator(); iter.hasNext();) {
IMessage actualMessage = (IMessage) iter.next();
int actualLine = getLine(actualMessage);
if (actualLine != lastLine) {
// new line - get all messages expected for it
if (lastLine > actualLine) {
throw new Error("sort error");
}
lastLine = actualLine;
expectedForLine.clear();
while (expectedIterator.hasNext()) {
IMessage curExpected =
(IMessage) expectedIterator.next();
int curExpectedLine = getLine(curExpected);
if (actualLine == curExpectedLine) {
expectedForLine.add(curExpected);
} else {
expectedIterator.previous();
break;
}
}
}
// now check actual against all expected on that line
boolean found = false;
IMessage expectedMessage = null;
for (Iterator iterator = expectedForLine.iterator();
!found && iterator.hasNext();
) {
expectedMessage = (IMessage) iterator.next();
found = expectingMessage(expectedMessage, actualMessage);
}
if (found) {
iter.remove();
if (expectedFound.contains(expectedMessage)) {
// XXX warn: expected message matched two actual
} else {
expectedFound.add(expectedMessage);
}
} else {
// unexpected: any actual result not found
unexpected.add(actualMessage);
}
}
// missing: all expected results not found
exp.removeAll(expectedFound);
missing.addAll(exp);
}
return new Diffs(label, missing, unexpected);
}

public static Diffs makeDiffs(
String label,
List expected,
List actual,
Comparator comparator,
Filter missingFilter,
Filter unexpectedFilter) {
label = label.trim();
if (null == label) {
label = ": ";
} else if (!label.endsWith(":")) {
label += ": ";
}
final String thisLabel = " " + label;
ArrayList miss = new ArrayList();
ArrayList unexpect = new ArrayList();

org.aspectj.testing.util.LangUtil.makeSoftDiffs(
expected,
actual,
miss,
unexpect,
comparator);
if (null != missingFilter) {
for (ListIterator iter = miss.listIterator(); iter.hasNext();) {
if (!missingFilter.accept(iter.next())) {
iter.remove();
}
}
}
if (null != unexpectedFilter) {
for (ListIterator iter = unexpect.listIterator();
iter.hasNext();
) {
if (!unexpectedFilter.accept(iter.next())) {
iter.remove();
}
}
}
return new Diffs(thisLabel, miss, unexpect);
}

// /**
// * Shift over elements in sink if they are of one of the specified kinds.
// * @param sink the IMessage[] to shift elements from
// * @param kinds
// * @return length of sink after shifting
// * (same as input length if nothing shifted)
// */
// public static int removeKinds(IMessage[] sink, IMessage.Kind[] kinds) {
// if (LangUtil.isEmpty(kinds)) {
// return sink.length;
// } else if (LangUtil.isEmpty(sink)) {
// return 0;
// }
// int from = -1;
// int to = -1;
// for (int j = 0; j < sink.length; j++) {
// from++;
// if (null == sink[j]) {
// continue;
// }
// boolean remove = false;
// for (int i = 0; !remove && (i < kinds.length); i++) {
// IMessage.Kind kind = kinds[i];
// if (null == kind) {
// continue;
// }
// if (0 == kind.compareTo(sink[j].getKind())) {
// remove = true;
// }
// }
// if (!remove) {
// to++;
// if (to != from) {
// sink[to] = sink[from];
// }
// }
// }
// return to+1;
// }

/**
* @param expected the File from the expected source location
* @param actual the File from the actual source location
* @return true if exp is ISourceLocation.NO_FILE
* or exp path is a suffix of the actual path
* (after using FileUtil.weakNormalize(..) on both)
*/
static boolean expectingFile(File expected, File actual) {
if (null == expected) {
return (null == actual);
} else if (null == actual) {
return false;
}
if (expected != ISourceLocation.NO_FILE) {
String expPath = FileUtil.weakNormalize(expected.getPath());
String actPath = FileUtil.weakNormalize(actual.getPath());
if (!actPath.endsWith(expPath)) {
return false;
}
}
return true;
}

/**
* Soft comparison for expected message will not check a corresponding
* element in the actual message unless defined in the expected message.
* <pre>
* message
* kind must match (constant/priority)
* message only requires substring
* thrown ignored
* column ignored
* endline ignored
* details only requires substring
* sourceLocation
* line must match, unless expected < 0
* file ignored if ISourceLocation.NOFILE
* matches if expected is a suffix of actual
* after changing any \ to /
* extraSourceLocation[]
* if any are defined in expected, then there
* must be exactly the actual elements as are
* defined in expected (so it is an error to
* not define all if you define any)
* <pre>
* @param expected
* @param actual
* @return true if we are expecting the line, kind, file, message,
* details, and any extra source locations.
* (ignores column/endline, thrown) XXX
*/
static boolean expectingMessage(IMessage expected, IMessage actual) {
if (null == expected) {
return (null == actual);
} else if (null == actual) {
return false;
}
if (0 != expected.getKind().compareTo(actual.getKind())) {
return false;
}
if (!expectingSourceLocation(expected.getSourceLocation(),
actual.getSourceLocation())) {
return false;
}
if (!expectingText(expected.getMessage(), actual.getMessage())) {
return false;
}
if (!expectingText(expected.getDetails(), actual.getDetails())) {
return false;
}
ISourceLocation[] esl =
(ISourceLocation[]) expected.getExtraSourceLocations().toArray(
new ISourceLocation[0]);
ISourceLocation[] asl =
(ISourceLocation[]) actual.getExtraSourceLocations().toArray(
new ISourceLocation[0]);

Arrays.sort(esl, SORT_SOURCELOC);
Arrays.sort(asl, SORT_SOURCELOC);
if (!expectingSourceLocations(esl, asl)) {
return false;
}
return true;
}

/**
* This returns true if no ISourceLocation are specified
* (i.e., it ignored any extra source locations if no expectations stated).
* XXX need const like NO_FILE.
* @param expected the sorted ISourceLocation[] expected
* @param expected the actual sorted ISourceLocation[]
* @return true if any expected element is expected by the corresponding actual element.
*/
static boolean expectingSourceLocations(
ISourceLocation[] expected,
ISourceLocation[] actual) {
if (LangUtil.isEmpty(expected)) {
return true;
} else if (LangUtil.isEmpty(actual)) {
return false;
} else if (actual.length != expected.length) {
return false;
}
for (int i = 0; i < actual.length; i++) {
if (!expectingSourceLocation(expected[i], actual[i])) {
return false;
}
}

return true;
}

/**
* @param expected
* @param actual
* @return true if any expected line/file matches the actual line/file,
* accepting a substring as a file match
*/
static boolean expectingSourceLocation(
ISourceLocation expected,
ISourceLocation actual) {
int eline = getLine(expected);
int aline = getLine(actual);
if ((-1 < eline) && (eline != aline)) {
return false;
}
if (!expectingFile(getSourceFile(expected), getSourceFile(actual))) {
return false;
}
return true;
}

/**
* @param expected the String in the expected message
* @param actual the String in the actual message
* @return true if both are null or actual contains expected
*/
static boolean expectingText(String expected, String actual) {
if (null == expected) {
return true; // no expectations
} else if (null == actual) {
return false; // expected something
} else {
return (-1 != actual.indexOf(expected));
}
}

private static ArrayList getExcept(
IMessage[] source,
IMessage.Kind[] skip) {
ArrayList sink = new ArrayList();
if (LangUtil.isEmpty(source)) {
return sink;
}

if (LangUtil.isEmpty(skip)) {
sink.addAll(Arrays.asList(source));
Collections.sort(sink, MESSAGE_LINEKIND);
return sink;
}
for (int i = 0; i < source.length; i++) {
IMessage message = source[i];
IMessage.Kind mkind = message.getKind();
boolean skipping = false;
for (int j = 0; !skipping && (j < skip.length); j++) {
if (0 == mkind.compareTo(skip[j])) {
skipping = true;
}
}
if (!skipping) {
sink.add(message);
}
}
Collections.sort(sink, MESSAGE_LINEKIND);
return sink;
}

private static List harden(List list) {
return (
LangUtil.isEmpty(list)
? Collections.EMPTY_LIST
: Collections.unmodifiableList(list));
}

/** name of the thing being diffed - used only for reporting */
public final String label;

/** immutable List */
public final List missing;

/** immutable List */
public final List unexpected;

/** true if there are any missing or unexpected */
public final boolean different;

/**
* Struct-constructor stores these values,
* wrapping the lists as unmodifiable.
* @param label the String label for these diffs
* @param missing the List of missing elements
* @param unexpected the List of unexpected elements
*/
public Diffs(String label, List missing, List unexpected) {
this.label = label;
this.missing = harden(missing);
this.unexpected = harden(unexpected);
different =
((0 != this.missing.size()) || (0 != this.unexpected.size()));
}

/**
* Report missing and extra items to handler.
* For each item in missing or unexpected, this creates a {kind} IMessage with
* the text "{missing|unexpected} {label}: {message}"
* where {message} is the result of
* <code>MessageUtil.renderMessage(IMessage)</code>.
* @param handler where the messages go - not null
* @param kind the kind of message to construct - not null
* @param label the prefix for the message text - if null, "" used
* @see MessageUtil#renderMessage(IMessage)
*/
public void report(IMessageHandler handler, IMessage.Kind kind) {
LangUtil.throwIaxIfNull(handler, "handler");
LangUtil.throwIaxIfNull(kind, "kind");
if (different) {
for (Iterator iter = missing.iterator(); iter.hasNext();) {
String s = MessageUtil.renderMessage((IMessage) iter.next());
MessageUtil.fail(handler, "missing " + label + ": " + s);
}
for (Iterator iter = unexpected.iterator(); iter.hasNext();) {
String s = MessageUtil.renderMessage((IMessage) iter.next());
MessageUtil.fail(handler, "unexpected " + label + ": " + s);
}
}
}

/** @return "{label}: (unexpected={#}, missing={#})" */
public String toString() {
return label
+ "(unexpected="
+ unexpected.size()
+ ", missing="
+ missing.size()
+ ")";
}
public static interface Filter {
/** @return true to keep input in list of messages */
boolean accept(Object input);
}
}

+ 1
- 1
testing/src/org/aspectj/testing/util/FileUtil.java View File

@@ -193,7 +193,7 @@ public class FileUtil {
unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector)));
// report any unexpected changes
return new Diffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER);
return Diffs.makeDiffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER);
}



+ 1
- 2
testing/src/org/aspectj/testing/util/LangUtil.java View File

@@ -270,8 +270,7 @@ public class LangUtil {
return ((null == s) || (0 == s.length()));
}



/**
* Throw IllegalArgumentException if any component in input array
* is null or (if superType is not null) not assignable to superType.

+ 2
- 2
testing/src/org/aspectj/testing/util/TestDiffs.java View File

@@ -80,13 +80,13 @@ public class TestDiffs { // XXX pretty dumb implementation
reading = actual;
act = TestDiffs.readTestResults(actual, actual.getPath());
Diffs tests = new Diffs("tests", exp, act, TestResult.BY_NAME);
Diffs tests = Diffs.makeDiffs("tests", exp, act, TestResult.BY_NAME);
// remove missing/unexpected (removed, added) tests from results
// otherwise, unexpected-[pass|fail] look like [fixes|broken]
ArrayList expResults = trimByName(exp, tests.missing);
ArrayList actResults = trimByName(act, tests.unexpected);
Diffs results = new Diffs("results", expResults, actResults, TestResult.BY_PASSNAME);
Diffs results = Diffs.makeDiffs("results", expResults, actResults, TestResult.BY_PASSNAME);

// broken tests show up in results as unexpected-fail or missing-pass
// fixed tests show up in results as unexpected-pass or missing-fail

+ 18
- 82
testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java View File

@@ -52,6 +52,13 @@ public class AjcSpecXmlReader {
* - update any client writers referring to the DOCTYPE, as necessary.
* - the parent IXmlWriter should delegate to the child component
* as IXmlWriter (or write the subelement itself)
*
* Debugging
* - use logLevel = 2 for tracing
* - common mistakes
* - dtd has to match input
* - no rule defined (or misdefined) so element ignored
* - property read-only (?)
*/
private static final String EOL = "\n";
@@ -63,82 +70,6 @@ public class AjcSpecXmlReader {
public static final String DOCTYPE = "<!DOCTYPE "
+ AjcTest.Suite.Spec.XMLNAME + " SYSTEM \"" + DTD_PATH + "\">";

/** xml leader */
public static final String FILE_LEADER
= "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";

/**
* @deprecated
* @return a String suitable as an inlined DOCTYPE statement
*/
public static String inlineDocType() {
return "<!DOCTYPE "
+ AjcTest.Suite.Spec.XMLNAME
+ " ["
+ AjcSpecXmlReader.getDocType()
+ EOL + " ]>";
}

/**
* @deprecated
* @return the elements of a document type as a String,
* using EOL as a line delimiter
*/
public static String getDocType() {
if (true) {
throw new Error("XXX using ajcTestSuite.dtd");
}
StringBuffer r = new StringBuffer();
final String suiteX = AjcTest.Suite.Spec.XMLNAME;
final String ajctestX = AjcTest.Spec.XMLNAME;
final String compileX = CompilerRun.Spec.XMLNAME;
final String inccompileX = IncCompilerRun.Spec.XMLNAME;
final String runX = JavaRun.Spec.XMLNAME;
final String dirchangesX = DirChanges.Spec.XMLNAME;
final String messageX = SoftMessage.XMLNAME;

r.append(EOL + " <!ELEMENT " + suiteX + " (" + ajctestX + "+)>");
r.append(EOL + " <!ATTLIST " + suiteX + " suiteDir CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + ajctestX + " (" + compileX + ", (" + compileX + " | " + inccompileX + " | " + runX + ")*)>");
r.append(EOL + " <!ATTLIST " + ajctestX + " title CDATA #REQUIRED >");
r.append(EOL + " <!ATTLIST " + ajctestX + " dir CDATA #REQUIRED >");
r.append(EOL + " <!ATTLIST " + ajctestX + " pr CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + ajctestX + " keywords CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + compileX + " (" + dirchangesX + "*,file*," + messageX + "*)>"); // deprecate file?
r.append(EOL + " <!ATTLIST " + compileX + " staging CDATA #IMPLIED >"); // if precursor to incremental
r.append(EOL + " <!ATTLIST " + compileX + " files CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + compileX + " options CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + inccompileX + " (" + dirchangesX + "*," + messageX + "*)>"); // add file* if not deprecated
r.append(EOL + " <!ATTLIST " + inccompileX + " tag CDATA #REQUIRED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + runX + " (" + dirchangesX + "*," + messageX + "*)>");
r.append(EOL + " <!ATTLIST " + runX + " class CDATA #REQUIRED >");
r.append(EOL + " <!ATTLIST " + runX + " skipTester CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + runX + " options CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT file (#PCDATA)>"); // deprecate?
r.append(EOL + " <!ATTLIST file path CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + messageX + " (#PCDATA)>");
r.append(EOL + " <!ATTLIST " + messageX + " kind (error | warning | info | Xlint) #REQUIRED >");
r.append(EOL + " <!ATTLIST " + messageX + " line CDATA #REQUIRED >");
r.append(EOL + " <!ATTLIST " + messageX + " text CDATA #IMPLIED >"); // but Message requires non-null...
r.append(EOL + " <!ATTLIST " + messageX + " file CDATA #IMPLIED >");
r.append(EOL + "");
r.append(EOL + " <!ELEMENT " + dirchangesX + " (#PCDATA)>");
r.append(EOL + " <!ATTLIST " + dirchangesX + " dirToken (classes | run) #IMPLIED >");
r.append(EOL + " <!ATTLIST " + dirchangesX + " defaultSuffix (.class) #IMPLIED >");
r.append(EOL + " <!ATTLIST " + dirchangesX + " added CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + dirchangesX + " removed CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + dirchangesX + " updated CDATA #IMPLIED >");
r.append(EOL + " <!ATTLIST " + dirchangesX + " unchanged CDATA #IMPLIED >");
r.append(EOL + "");
return r.toString();
}

private static final AjcSpecXmlReader ME
= new AjcSpecXmlReader();
@@ -162,7 +93,7 @@ public class AjcSpecXmlReader {
try {
out.println("<!-- document type for ajc test suite - see "
+ AjcSpecXmlReader.class.getName() + " -->");
out.println(getDocType());
//out.println(getDocType());
} finally {
out.close();
}
@@ -170,7 +101,8 @@ public class AjcSpecXmlReader {

private static final String[] LOG = new String[] {"info", "debug", "trace" };
private int logLevel;
// XXX logLevel n>0 causes JUnit tests to fail!
private int logLevel = 0; // use 2 for tracing
private AjcSpecXmlReader() {}

@@ -292,7 +224,7 @@ public class AjcSpecXmlReader {
final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME;
final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME;
final String messageX = "*/" + SoftMessage.XMLNAME;
final String messageSrcLocX = messageX + "/source-location";
final String messageSrcLocX = messageX + "/" +SoftSourceLocation.XMLNAME;

// ---- each sub-element needs to be created
// handle messages the same at any level
@@ -323,9 +255,10 @@ public class AjcSpecXmlReader {
new String[] { "className", "javaVersion", "skipTester"});
digester.addSetProperties(dirchangesX);
digester.addSetProperties(messageX);
digester.addSetProperties(messageSrcLocX);
digester.addSetProperties(messageSrcLocX, "line", "lineAsString");
digester.addSetProperties(messageX, "kind", "kindAsString");
digester.addSetProperties(messageX, "line", "lineAsString");
//digester.addSetProperties(messageX, "details", "details");
// only file subelement of compile uses text as path... XXX vestigial
digester.addCallMethod(compileX + "/file", "setFile", 0);

@@ -340,7 +273,9 @@ public class AjcSpecXmlReader {
digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName());
digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName());
digester.addSetNext(messageX, "addMessage", IMessage.class.getName());
digester.addSetNext(messageSrcLocX, "setSourceLocation", ISourceLocation.class.getName());
// setSourceLocation is for the inline variant
// addSourceLocation is for the extra
digester.addSetNext(messageSrcLocX, "addSourceLocation", ISourceLocation.class.getName());
digester.addSetNext(dirchangesX, "addDirChanges", DirChanges.Spec.class.getName());
// can set parent, but prefer to have "knows-about" flow down only...
@@ -372,7 +307,7 @@ public class AjcSpecXmlReader {
new BProps(AbstractRunSpec.WrapFile.class,
new String[] { "path"}),
new BProps(SoftMessage.class,
new String[] { "kindAsString", "lineAsString", "text", "file"})
new String[] { "kindAsString", "lineAsString", "text", "details", "file"})
// mapped from { "kind", "line", ...}
};
}
@@ -430,6 +365,7 @@ public class AjcSpecXmlReader {
m.setSourceLocation((ISourceLocation) null);
m.setText((String) null);
m.setKindAsString((String) null);
m.setDetails((String) null);
SoftSourceLocation sl = new SoftSourceLocation();
sl.setFile((String) null);

+ 287
- 249
testing/src/org/aspectj/testing/xml/SoftMessage.java View File

@@ -11,10 +11,10 @@
* Xerox/PARC initial implementation
* ******************************************************************/


package org.aspectj.testing.xml;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -26,261 +26,294 @@ import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.util.LangUtil;


/**
* Implement messages.
* This implementation is immutable if ISourceLocation is immutable.
* This implementation is immutable if ISourceLocation is immutable,
* except for adding source locations.
*/
public class SoftMessage implements IMessage { // XXX mutable dup of Message
public static String XMLNAME = "message";
public static final File NO_FILE = ISourceLocation.NO_FILE;
private String message;
private IMessage.Kind kind;
private Throwable thrown;
private ISourceLocation sourceLocation;
private String details;
public class SoftMessage implements IMessage {
public static String XMLNAME = "message";
public static final File NO_FILE = ISourceLocation.NO_FILE;
private String message;
private IMessage.Kind kind;
private Throwable thrown;
private ISourceLocation sourceLocation;
private String details;
private final ArrayList extraSourceLocations = new ArrayList();

//private ISourceLocation pseudoSourceLocation; // set directly
// collapse enclosed source location for shorter, property-based xml
private String file;
private int line = Integer.MAX_VALUE;
/** convenience for constructing failure messages */
public static SoftMessage fail(String message, Throwable thrown) {
return new SoftMessage(message, IMessage.FAIL, thrown, null);
}

/**
* Print messages.
* @param messages List of IMessage
*/
public static void writeXml(XMLWriter out, IMessageHolder messages) {
if ((null == out) || (null == messages)
|| (0 == messages.numMessages(null, true))) {
return;
}
List list = messages.getUnmodifiableListView();
for (Iterator iter = list.iterator(); iter.hasNext();) {
writeXml(out, (IMessage) iter.next());
}
}

/**
* Print messages.
* @param messages IMessage[]
*/
public static void writeXml(XMLWriter out, IMessage[] messages) {
if ((null == out) || (null == messages)) {
return;
}
for (int i = 0; i < messages.length; i++) {
writeXml(out, messages[i]);
}
}

/** print message as an element
* @throws IllegalArgumentException if message.getThrown() is not null
*/
public static void writeXml(XMLWriter out, IMessage message) { // XXX short form only, no files
if ((null == out) || (null == message)) {
return;
}
Throwable thrown = message.getThrown();
if (null != thrown) {
String m = "unable to write " + message + " thrown not permitted";
throw new IllegalArgumentException(m);
}
final String elementName = XMLNAME;
out.startElement(elementName, false);
out.printAttribute("kind", message.getKind().toString());
String value = message.getMessage();
if (null != value) {
out.printAttribute("message", value);
}
ISourceLocation sl = message.getSourceLocation();
if (null != sl) {
out.endAttributes();
SoftSourceLocation.writeXml(out, sl);
}
out.endElement(elementName);
}


public SoftMessage() {} // XXX programmatic only
/**
* Create a (compiler) error or warning message
* @param message the String used as the underlying message
* @param sourceLocation the ISourceLocation, if any, associated with this message
* @param isError if true, use IMessage.ERROR; else use IMessage.WARNING
*/
public SoftMessage(String message, ISourceLocation location, boolean isError) {
this(message, (isError ? IMessage.ERROR : IMessage.WARNING), null,
location);
}
/**
* Create a message, handling null values for message and kind
* if thrown is not null.
* @param message the String used as the underlying message
* @param kind the IMessage.Kind of message - not null
* @param thrown the Throwable, if any, associated with this message
* @param sourceLocation the ISourceLocation, if any, associated with this message
* @throws IllegalArgumentException if message is null and
* thrown is null or has a null message, or if kind is null
* and thrown is null.
*/
public SoftMessage(String message, IMessage.Kind kind, Throwable thrown,
ISourceLocation sourceLocation) {
this.message = message;
this.kind = kind;
this.thrown = thrown;
this.sourceLocation = sourceLocation;
if (null == message) {
if (null != thrown) {
message = thrown.getMessage();
}
if (null == message) {
throw new IllegalArgumentException("null message");
// collapse enclosed source location for shorter, property-based xml
private String file;
private int line = Integer.MAX_VALUE;

/** convenience for constructing failure messages */
public static SoftMessage fail(String message, Throwable thrown) {
return new SoftMessage(message, IMessage.FAIL, thrown, null);
}

/**
* Print messages.
* @param messages List of IMessage
*/
public static void writeXml(XMLWriter out, IMessageHolder messages) {
if ((null == out)
|| (null == messages)
|| (0 == messages.numMessages(null, true))) {
return;
}
List list = messages.getUnmodifiableListView();
for (Iterator iter = list.iterator(); iter.hasNext();) {
writeXml(out, (IMessage) iter.next());
}
}

/**
* Print messages.
* @param messages IMessage[]
*/
public static void writeXml(XMLWriter out, IMessage[] messages) {
if ((null == out) || (null == messages)) {
return;
}
for (int i = 0; i < messages.length; i++) {
writeXml(out, messages[i]);
}
}

/** print message as an element
* XXX has to sync with ajcTests.dtd
* @throws IllegalArgumentException if message.getThrown() is not null
*/
public static void writeXml(
XMLWriter out,
IMessage message) { // XXX short form only, no files
if ((null == out) || (null == message)) {
return;
}
Throwable thrown = message.getThrown();
if (null != thrown) {
String m = "unable to write " + message + " thrown not permitted";
throw new IllegalArgumentException(m);
}
final String elementName = XMLNAME;
out.startElement(elementName, false);
out.printAttribute("kind", message.getKind().toString());
String value = message.getMessage();
if (null != value) {
value = XMLWriter.attributeValue(value);
out.printAttribute("message", value);
}
value = message.getDetails();
if (null != value) {
value = XMLWriter.attributeValue(value);
out.printAttribute("details", value);
}
ISourceLocation sl = message.getSourceLocation();
if (null != sl) {
int line = sl.getLine();
if (-1 < line) {
out.printAttribute("line", "" + line);
}
File file = sl.getSourceFile();
if ((null != file) && !ISourceLocation.NO_FILE.equals(file)) {
value = XMLWriter.attributeValue(file.getPath());
out.printAttribute("file", value);
}
}
List extras = message.getExtraSourceLocations();
if (!LangUtil.isEmpty(extras)) {
out.endAttributes();
for (Iterator iter = extras.iterator(); iter.hasNext();) {
ISourceLocation element = (ISourceLocation) iter.next();
SoftSourceLocation.writeXml(out, sl);
}
}
if (null == kind) {
throw new IllegalArgumentException("null kind");
}
}
/** @return the kind of this message */
public IMessage.Kind getKind() {
return kind;
}

/** @return true if kind == IMessage.ERROR */
public boolean isError() {
return kind == IMessage.ERROR;
}
/** @return true if kind == IMessage.WARNING */
public boolean isWarning() {
return kind == IMessage.WARNING;
}

/** @return true if kind == IMessage.DEBUG */
public boolean isDebug() {
return kind == IMessage.DEBUG;
}

/**
* @return true if kind == IMessage.INFO
*/
public boolean isInfo() {
return kind == IMessage.INFO;
}
/** @return true if kind == IMessage.ABORT */
public boolean isAbort() {
return kind == IMessage.ABORT;
}
/**
* @return true if kind == IMessage.FAIL
*/
public boolean isFailed() {
return kind == IMessage.FAIL;
}
/** @return non-null String with simple message */
final public String getMessage() {
return message;
}
/** @return Throwable associated with this message, or null if none */
final public Throwable getThrown() {
return thrown;
}

/**
* This returns any ISourceLocation set or a mock-up
* if file and/or line were set.
* @return ISourceLocation associated with this message,
* a mock-up if file or line is available, or null if none
*/
final public ISourceLocation getSourceLocation() {
if ((null == sourceLocation)
&& ((null != file) || (line != Integer.MAX_VALUE))) {
File f = (null == file ? NO_FILE : new File(file));
int line = (this.line == Integer.MAX_VALUE ? 0 : this.line);
sourceLocation = new SourceLocation(f, line);
}
return sourceLocation;
}
/** set the kind of this message */
public void setMessageKind(IMessage.Kind kind) {
this.kind = (null == kind ? IMessage.ERROR : kind);
}


/** set the file for the underlying source location of this message
* @throws IllegalStateException if source location was set directly
* or indirectly by calling getSourceLocation after setting
* file or line.
*/
public void setFile(String path) {
LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(path), "empty path");
if (null != sourceLocation) {
throw new IllegalStateException("cannot set line after creating source location");
}
this.file = path;
}

/** set the kind of this message */
public void setKindAsString(String kind) {
setMessageKind(MessageUtil.getKind(kind));
}

public void setSourceLocation(ISourceLocation sourceLocation) {
this.sourceLocation = sourceLocation;
}
/**
* Set the line for the underlying source location.
* @throws IllegalStateException if source location was set directly
* or indirectly by calling getSourceLocation after setting
* file or line.
*/
public void setLineAsString(String line) {
if (null != sourceLocation) {
throw new IllegalStateException("cannot set line after creating source location");
}
this.line = Integer.valueOf(line).intValue();
SourceLocation.validLine(this.line);
}

public void setText(String text) {
this.message = (null == text ? "" : text);
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append(getKind().toString());
String messageString = getMessage();
if (!LangUtil.isEmpty(messageString)) {
result.append(messageString);
}
out.endElement(elementName);
}

public SoftMessage() {
} // XXX programmatic only

/**
* Create a (compiler) error or warning message
* @param message the String used as the underlying message
* @param sourceLocation the ISourceLocation, if any, associated with this message
* @param isError if true, use IMessage.ERROR; else use IMessage.WARNING
*/
public SoftMessage(
String message,
ISourceLocation location,
boolean isError) {
this(
message,
(isError ? IMessage.ERROR : IMessage.WARNING),
null,
location);
}

/**
* Create a message, handling null values for message and kind
* if thrown is not null.
* @param message the String used as the underlying message
* @param kind the IMessage.Kind of message - not null
* @param thrown the Throwable, if any, associated with this message
* @param sourceLocation the ISourceLocation, if any, associated with this message
* @throws IllegalArgumentException if message is null and
* thrown is null or has a null message, or if kind is null
* and thrown is null.
*/
public SoftMessage(
String message,
IMessage.Kind kind,
Throwable thrown,
ISourceLocation sourceLocation) {
this.message = message;
this.kind = kind;
this.thrown = thrown;
this.sourceLocation = sourceLocation;
if (null == message) {
if (null != thrown) {
message = thrown.getMessage();
}
if (null == message) {
throw new IllegalArgumentException("null message");
}
}
if (null == kind) {
throw new IllegalArgumentException("null kind");
}
}

/** @return the kind of this message */
public IMessage.Kind getKind() {
return kind;
}

/** @return true if kind == IMessage.ERROR */
public boolean isError() {
return kind == IMessage.ERROR;
}

/** @return true if kind == IMessage.WARNING */
public boolean isWarning() {
return kind == IMessage.WARNING;
}

/** @return true if kind == IMessage.DEBUG */
public boolean isDebug() {
return kind == IMessage.DEBUG;
}

/**
* @return true if kind == IMessage.INFO
*/
public boolean isInfo() {
return kind == IMessage.INFO;
}

/** @return true if kind == IMessage.ABORT */
public boolean isAbort() {
return kind == IMessage.ABORT;
}

/**
* @return true if kind == IMessage.FAIL
*/
public boolean isFailed() {
return kind == IMessage.FAIL;
}

/** @return non-null String with simple message */
final public String getMessage() {
return message;
}

/** @return Throwable associated with this message, or null if none */
final public Throwable getThrown() {
return thrown;
}

/**
* This returns any ISourceLocation set or a mock-up
* if file and/or line were set.
* @return ISourceLocation associated with this message,
* a mock-up if file or line is available, or null if none
*/
final public ISourceLocation getSourceLocation() {
if ((null == sourceLocation)
&& ((null != file) || (line != Integer.MAX_VALUE))) {
File f = (null == file ? NO_FILE : new File(file));
int line = (this.line == Integer.MAX_VALUE ? 0 : this.line);
sourceLocation = new SourceLocation(f, line);
}
return sourceLocation;
}

/** set the kind of this message */
public void setMessageKind(IMessage.Kind kind) {
this.kind = (null == kind ? IMessage.ERROR : kind);
}

/** set the file for the underlying source location of this message
* @throws IllegalStateException if source location was set directly
* or indirectly by calling getSourceLocation after setting
* file or line.
*/
public void setFile(String path) {
LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(path), "empty path");
if (null != sourceLocation) {
throw new IllegalStateException("cannot set line after creating source location");
}
this.file = path;
}

/** set the kind of this message */
public void setKindAsString(String kind) {
setMessageKind(MessageUtil.getKind(kind));
}

public void setSourceLocation(ISourceLocation sourceLocation) {
this.sourceLocation = sourceLocation;
}

/**
* Set the line for the underlying source location.
* @throws IllegalStateException if source location was set directly
* or indirectly by calling getSourceLocation after setting
* file or line.
*/
public void setLineAsString(String line) {
if (null != sourceLocation) {
throw new IllegalStateException("cannot set line after creating source location");
}
this.line = Integer.valueOf(line).intValue();
SourceLocation.validLine(this.line);
}

public void setText(String text) {
this.message = (null == text ? "" : text);
}

public String toString() {
StringBuffer result = new StringBuffer();

result.append(null == getKind() ? "<null kind>" : getKind().toString());

String messageString = getMessage();
if (!LangUtil.isEmpty(messageString)) {
result.append(messageString);
}

ISourceLocation loc = getSourceLocation();
if ((null != loc) && (loc != ISourceLocation.NO_FILE)) {
result.append(" at " + loc);
}
if (null != thrown) {
result.append(" -- " + LangUtil.renderExceptionShort(thrown));
}
return result.toString();
}

ISourceLocation loc = getSourceLocation();
if ((null != loc) && (loc != ISourceLocation.NO_FILE)) {
result.append(" at " + loc);
}
if (null != thrown) {
result.append(" -- " + LangUtil.renderExceptionShort(thrown));
}
return result.toString();
}
public String getDetails() {
return details;
}
@@ -293,6 +326,11 @@ public class SoftMessage implements IMessage { // XXX mutable dup of Message
* @see org.aspectj.bridge.IMessage#getExtraSourceLocations()
*/
public List getExtraSourceLocations() {
return Collections.EMPTY_LIST;
return extraSourceLocations;
}
public void addSourceLocation(ISourceLocation location) {
if (null != location) {
extraSourceLocations.add(location);
}
}
}

+ 21
- 18
testing/src/org/aspectj/testing/xml/SoftSourceLocation.java View File

@@ -1,6 +1,7 @@
/* *******************************************************************
* Copyright (c) 1999-2001 Xerox Corporation,
* 2002 Palo Alto Research Center, Incorporated (PARC).
* 2002 Palo Alto Research Center, Incorporated (PARC),
* 2004 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Common Public License v1.0
@@ -9,6 +10,7 @@
*
* Contributors:
* Xerox/PARC initial implementation
* Wes Isberg 2004 updates
* ******************************************************************/

package org.aspectj.testing.xml;
@@ -20,17 +22,13 @@ import org.aspectj.bridge.ISourceLocation;
import org.aspectj.util.LangUtil;

/**
* Immutable source location.
* This guarantees that the source file is not null
* and that the numeric values are positive and line <= endLine.
* @see org.aspectj.lang.reflect.SourceLocation
* @see org.aspectj.compiler.base.parser.SourceInfo
* @see org.aspectj.tools.ide.SourceLine
* @see org.aspectj.testing.harness.ErrorLine
* A mutable ISourceLocation for XML initialization of tests.
* This does not support reading/writing of the attributes
* column, context, or endline.
*/
public class SoftSourceLocation implements ISourceLocation { // XXX endLine?
public static final File NONE = new File("SoftSourceLocation.NONE");
public static final String XMLNAME = "source-location";
public class SoftSourceLocation implements ISourceLocation {
public static final File NONE = ISourceLocation.NO_FILE;
public static final String XMLNAME = "source";

/**
* Write an ISourceLocation as XML element to an XMLWriter sink.
@@ -44,17 +42,16 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine?
final String elementName = XMLNAME;
out.startElement(elementName, false);
out.printAttribute("line", "" + sl.getLine());
out.printAttribute("column", "" + sl.getColumn());
out.printAttribute("endLine", "" + sl.getEndLine());
// other attributes not supported
File file = sl.getSourceFile();
if (null != file) {
out.printAttribute("sourceFile", file.getPath());
out.printAttribute("file", file.getPath());
}
out.endElement(elementName);
}

private File sourceFile;
private int line;
private int line = -1; // required for no-line comparisons to work
private int column;
private int endLine;
private String context;
@@ -83,13 +80,20 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine?
public void setFile(String sourceFile) {
this.sourceFile = new File(sourceFile);
}
public void setLine(String line) {
setLineAsString(line);
}

public void setLine(String line) {
public void setLineAsString(String line) {
this.line = convert(line);
if (0 == endLine) {
endLine = this.line;
}
}
public String getLineAsString() {
return ""+line;
}
public void setColumn(String column) {
this.column = convert(column);
@@ -115,7 +119,6 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine?
public String toString() {
return (null == context ? "" : context + LangUtil.EOL)
+ getSourceFile().getPath()
+ ":" + getLine()
+ ":" + getColumn();
+ ":" + getLine() ;
}
}

Loading…
Cancel
Save