001: package abbot.script;
002:
003: import java.util.Map;
004: import javax.swing.tree.TreePath;
005:
006: import abbot.*;
007: import abbot.i18n.Strings;
008: import abbot.tester.ComponentTester;
009: import abbot.util.ExtendedComparator;
010:
011: /** Encapsulate an assertion (or a wait). Usage:<br>
012: * <blockquote><code>
013: * <assert method="[!]assertXXX" args="..." [class="..."]><br>
014: * <assert method="[!](get|is|has)XXX" component="component_id" value="..."><br>
015: * <assert method="[!]XXX" args="..." class="..."><br>
016: * <br>
017: * <wait ... [timeout="..."] [pollInterval="..."]><br>
018: * </code></blockquote>
019: * The first example above invokes a core assertion provided by the
020: * {@link abbot.tester.ComponentTester} class; the class tag is required for
021: * assertions based on a class derived from
022: * {@link abbot.tester.ComponentTester}; the class tag indicates the
023: * {@link java.awt.Component} class, not the Tester class (the appropriate
024: * tester class will be derived automatically).<p>
025: * The second format indicates a property check on the given component, and an
026: * expected value must be provided; the method name must start with "is",
027: * "get", or "has". Finally, any arbitrary static boolean method may be used
028: * in the assertion; you must specify the class and arguments.<p>
029: * You can invert the sense of either form of assertion by inserting a '!'
030: * character before the method name, or adding an <code>invert="true"</code>
031: * attribute.
032: * <p>
033: * The default timeout for a wait is ten seconds; the default poll interval
034: * (sleep time between checking the assertion) is 1/10 second. Both may be
035: * set as XML attributes (<code>pollInterval</code> and <code>timeout</code>).
036: */
037:
038: public class Assert extends PropertyCall {
039: /** Default interval between checking the assertion in a wait. */
040: public static final int DEFAULT_INTERVAL = 100;
041: /** Default timeout before a wait will indicate failure. */
042: public static final int DEFAULT_TIMEOUT = 10000;
043:
044: private static final String ASSERT_USAGE = "<assert component=... method=... value=... [invert=true]/>\n"
045: + "<assert method=[!]... [class=...]/>";
046:
047: private static final String WAIT_USAGE = "<wait component=... method=... value=... [invert=true] "
048: + "[timeout=...] [pollInterval=...]/>\n"
049: + "<wait method=[!]... [class=...] [timeout=...] [pollInterval=...]/>";
050:
051: private String expectedResult = "true";
052: private boolean invert;
053: private boolean wait;
054:
055: private long interval = DEFAULT_INTERVAL;
056: private long timeout = DEFAULT_TIMEOUT;
057:
058: /** Construct an assert step from XML. */
059: public Assert(Resolver resolver, Map attributes) {
060: super (resolver, patchAttributes(attributes));
061: wait = attributes.get(TAG_WAIT) != null;
062: String to = (String) attributes.get(TAG_TIMEOUT);
063: if (to != null) {
064: try {
065: timeout = Integer.parseInt(to);
066: } catch (NumberFormatException exc) {
067: }
068: }
069: String pi = (String) attributes.get(TAG_POLL_INTERVAL);
070: if (pi != null) {
071: try {
072: interval = Integer.parseInt(pi);
073: } catch (NumberFormatException exc) {
074: }
075: }
076: init(Boolean.valueOf((String) attributes.get(TAG_INVERT))
077: .booleanValue(), (String) attributes.get(TAG_VALUE));
078: }
079:
080: /** Assertion provided by the ComponentTester class, or an arbitrary
081: static call.
082: */
083: public Assert(Resolver resolver, String desc,
084: String targetClassName, String methodName, String[] args,
085: String expectedResult, boolean invert) {
086: super (resolver, desc, targetClassName != null ? targetClassName
087: : ComponentTester.class.getName(), methodName, args);
088: init(invert, expectedResult);
089: }
090:
091: /** Assertion provided by a ComponentTester subclass which operates on a
092: * Component subclass.
093: */
094: public Assert(Resolver resolver, String desc, String methodName,
095: String[] args, Class testedClass, String expectedResult,
096: boolean invert) {
097: super (resolver, desc, testedClass.getName(), methodName, args);
098: init(invert, expectedResult);
099: }
100:
101: /** Property assertion on Component subclass. */
102: public Assert(Resolver resolver, String desc, String methodName,
103: String componentID, String expectedResult, boolean invert) {
104: super (resolver, desc, methodName, componentID);
105: init(invert, expectedResult);
106: }
107:
108: /** The canonical form for a boolean assertion is to return true, and set
109: * the invert flag if necessary.
110: */
111: private void init(boolean inverted, String value) {
112: if ("false".equals(value)) {
113: inverted = !inverted;
114: value = "true";
115: }
116: expectedResult = value != null ? value : "true";
117: invert = inverted;
118: }
119:
120: /** Changes the behavior of this step between failing if the condition is
121: not met and waiting for the condition to be met.
122: @param wait If true, this step returns from its {@link #runStep()}
123: method only when its condition is met, throwing a
124: {@link WaitTimedOutError} if the condition is not met within the
125: timeout interval.
126: @see #setPollInterval(long)
127: @see #getPollInterval()
128: @see #setTimeout(long)
129: @see #getTimeout()
130: */
131: public void setWait(boolean wait) {
132: this .wait = wait;
133: }
134:
135: public boolean isWait() {
136: return wait;
137: }
138:
139: public void setPollInterval(long interval) {
140: this .interval = interval;
141: }
142:
143: public long getPollInterval() {
144: return interval;
145: }
146:
147: public void setTimeout(long timeout) {
148: this .timeout = timeout;
149: }
150:
151: public long getTimeout() {
152: return timeout;
153: }
154:
155: public String getExpectedResult() {
156: return expectedResult;
157: }
158:
159: public void setExpectedResult(String result) {
160: init(invert, result);
161: }
162:
163: public boolean isInverted() {
164: return invert;
165: }
166:
167: public void setInverted(boolean invert) {
168: init(invert, expectedResult);
169: }
170:
171: /** Strip inversion from the method name. */
172: private static Map patchAttributes(Map map) {
173: String method = (String) map.get(TAG_METHOD);
174: if (method != null && method.startsWith("!")) {
175: map.put(TAG_METHOD, method.substring(1));
176: map.put(TAG_INVERT, "true");
177: }
178: // If no class is specified, default to ComponentTester
179: String cls = (String) map.get(TAG_CLASS);
180: if (cls == null) {
181: map.put(TAG_CLASS, "abbot.tester.ComponentTester");
182: }
183: return map;
184: }
185:
186: public String getXMLTag() {
187: return wait ? TAG_WAIT : TAG_ASSERT;
188: }
189:
190: public String getUsage() {
191: return wait ? WAIT_USAGE : ASSERT_USAGE;
192: }
193:
194: public String getDefaultDescription() {
195: String mname = getMethodName();
196: // assert/is/get doesn't really add any information, so drop it
197: if (mname.startsWith("assert"))
198: mname = mname.substring(6);
199: else if (mname.startsWith("get") || mname.startsWith("has"))
200: mname = mname.substring(3);
201: else if (mname.startsWith("is"))
202: mname = mname.substring(2);
203: // FIXME this is cruft; really only need i18n for wait for X/assert X
204: // [!][$.]m [==|!= v]
205: String expression = mname + getArgumentsDescription();
206: if (getComponentID() != null)
207: expression = "${" + getComponentID() + "}." + expression;
208: if (invert && "true".equals(expectedResult))
209: expression = "!" + expression;
210: if (!"true".equals(expectedResult)) {
211: expression += invert ? " != " : " == ";
212: expression += expectedResult;
213: }
214: if (wait && timeout != DEFAULT_TIMEOUT) {
215: expression += " "
216: + Strings
217: .get(
218: (timeout > 5000 ? "wait.seconds"
219: : "wait.milliseconds"),
220: new Object[] { String
221: .valueOf(timeout > 5000 ? timeout / 1000
222: : timeout) });
223: }
224: return Strings.get((wait ? "wait.desc" : "assert.desc"),
225: new Object[] { expression });
226: }
227:
228: public Map getAttributes() {
229: Map map = super .getAttributes();
230: if (invert) {
231: map.put(TAG_INVERT, "true");
232: }
233: if (!expectedResult.equalsIgnoreCase("true")) {
234: map.put(TAG_VALUE, expectedResult);
235: }
236: if (timeout != DEFAULT_TIMEOUT)
237: map.put(TAG_TIMEOUT, String.valueOf(timeout));
238: if (interval != DEFAULT_INTERVAL)
239: map.put(TAG_POLL_INTERVAL, String.valueOf(interval));
240: return map;
241: }
242:
243: /** Check the assertion. */
244: protected void evaluateAssertion() throws Throwable {
245: Object expected, actual;
246: Class type = getMethod().getReturnType();
247: boolean compareStrings = false;
248:
249: try {
250: expected = ArgumentParser.eval(getResolver(),
251: expectedResult, type);
252: } catch (IllegalArgumentException iae) {
253: // If we can't convert the string to the expected type,
254: // match the string against the result's toString method instead
255: expected = expectedResult;
256: compareStrings = true;
257: }
258:
259: actual = invoke();
260:
261: // Special-case TreePaths; we want to string-compare the underlying
262: // objects rather than the TreePaths themselves.
263: // If this comes up again we'll look for a generalized solution.
264: if (expected instanceof TreePath && actual instanceof TreePath) {
265: expected = ArgumentParser.toString(((TreePath) expected)
266: .getPath());
267: actual = ArgumentParser.toString(((TreePath) actual)
268: .getPath());
269: }
270:
271: if (compareStrings) {
272: actual = ArgumentParser.toString(actual);
273: }
274:
275: if (invert) {
276: assertNotEquals(toString(), expected, actual);
277: } else {
278: assertEquals(toString(), expected, actual);
279: }
280: }
281:
282: /** Use our own comparison, to get the extended array comparisons. */
283: private void assertEquals(String message, Object expected,
284: Object actual) {
285: if (!ExtendedComparator.equals(expected, actual)) {
286: String msg = Strings.get("assert.comparison_failed",
287: new Object[] {
288: (message != null ? message + " " : ""),
289: ArgumentParser.toString(actual) });
290: throw new AssertionFailedError(msg, this );
291: }
292: }
293:
294: /** Use our own comparison, to get the extended array comparisons. */
295: private void assertNotEquals(String message, Object expected,
296: Object actual) {
297: if (ExtendedComparator.equals(expected, actual)) {
298: String msg = message != null ? message : "";
299: throw new AssertionFailedError(msg, this );
300: }
301: }
302:
303: /** Run this step. */
304: protected void runStep() throws Throwable {
305: if (!wait) {
306: evaluateAssertion();
307: } else {
308: long now = System.currentTimeMillis();
309: long remaining = timeout;
310: while (remaining > 0) {
311: long start = now;
312: try {
313: try {
314: evaluateAssertion();
315: return;
316: } catch (AssertionFailedError exc) {
317: // keep waiting
318: Log.debug(exc);
319: }
320: Thread.sleep(interval);
321: } catch (InterruptedException ie) {
322: }
323: now = System.currentTimeMillis();
324: remaining -= now - start;
325: }
326: throw new WaitTimedOutError(Strings
327: .get("wait.timed_out", new Object[] {
328: String.valueOf(timeout), toString() }));
329: }
330: }
331:
332: }
|