diff options
Diffstat (limited to 'testing')
9 files changed, 1136 insertions, 737 deletions
diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java index d01f26ddc..dc19ea2b9 100644 --- a/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java +++ b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java @@ -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); + } + } } diff --git a/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java index 96e15bdc9..cdfe4d542 100644 --- a/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java +++ b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java @@ -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); } diff --git a/testing/src/org/aspectj/testing/util/Diffs.java b/testing/src/org/aspectj/testing/util/Diffs.java index 4e493534f..4c2ff3d39 100644 --- a/testing/src/org/aspectj/testing/util/Diffs.java +++ b/testing/src/org/aspectj/testing/util/Diffs.java @@ -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); + } +} diff --git a/testing/src/org/aspectj/testing/util/FileUtil.java b/testing/src/org/aspectj/testing/util/FileUtil.java index e3ca612d1..b68d7f8b2 100644 --- a/testing/src/org/aspectj/testing/util/FileUtil.java +++ b/testing/src/org/aspectj/testing/util/FileUtil.java @@ -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); } diff --git a/testing/src/org/aspectj/testing/util/LangUtil.java b/testing/src/org/aspectj/testing/util/LangUtil.java index d05fd2edc..c6b4b9ae1 100644 --- a/testing/src/org/aspectj/testing/util/LangUtil.java +++ b/testing/src/org/aspectj/testing/util/LangUtil.java @@ -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. diff --git a/testing/src/org/aspectj/testing/util/TestDiffs.java b/testing/src/org/aspectj/testing/util/TestDiffs.java index f07ed9664..2a69b1634 100644 --- a/testing/src/org/aspectj/testing/util/TestDiffs.java +++ b/testing/src/org/aspectj/testing/util/TestDiffs.java @@ -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 diff --git a/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java index 849c59096..60be24296 100644 --- a/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java +++ b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java @@ -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); diff --git a/testing/src/org/aspectj/testing/xml/SoftMessage.java b/testing/src/org/aspectj/testing/xml/SoftMessage.java index f11e55966..208b155b7 100644 --- a/testing/src/org/aspectj/testing/xml/SoftMessage.java +++ b/testing/src/org/aspectj/testing/xml/SoftMessage.java @@ -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); + } } } diff --git a/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java index 48d88db69..7df8436a0 100644 --- a/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java +++ b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java @@ -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() ; } } |