package abbot.script.swt; import java.lang.reflect.Array; import java.util.HashMap; import org.eclipse.swt.widgets.Widget; import abbot.AssertionFailedError; import abbot.Log; import abbot.WaitTimedOutError; import abbot.i18n.Strings; import abbot.script.InvalidScriptException; import abbot.swt.Resolver; import abbot.tester.swt.WidgetTester; import abbot.util.ExtendedComparator; /** Encapsulate an assertion (or a wait). Usage:<br> * <blockquote><code> * <assert method="[!]assertXXX" args="..." [class="..."]><br> * <assert method="[!](get|is|has)XXX" widget="widget_id" value="..."><br> * <assert method="[!]XXX" args="..." class="..."><br> * <br> * <wait ... [timeout="..."] [pollInterval="..."]><br> * </code></blockquote> * In the first example above, the class tag is required for assertions based on * a class derived from ComponentTester; the class tag indicates the Component * class, not the Tester class (the appropriate tester class will be derived * automatically). * The second format indicates a property check on the given widget, and an * expected value must be provided; the method name must start with "is", * "get", or "has". Finally, any arbitrary static boolean method may be used * in the assertion; you must specify the class and arguments.<p> * You can invert the sense of either form of assertion by inserting a '!' * character before the method name, or adding an <code>invert="true"</code> * attribute. * <p> * The default timeout for a wait is ten seconds; the default poll interval * (sleep time between checking the assertion) is 1/10 second. */ // FIXME we'd also like to be able to do things like getWidth() < 100 public class Assert extends PropertyCall { public static final String copyright = "Licensed Materials -- Property of IBM\n"+ "(c) Copyright International Business Machines Corporation, 2003\nUS Government "+ "Users Restricted Rights - Use, duplication or disclosure restricted by GSA "+ "ADP Schedule Contract with IBM Corp."; /** Default interval between checking the assertion in a wait. */ public static final int DEFAULT_INTERVAL = 100; /** Default timeout before a wait will indicate failure. */ public static final int DEFAULT_TIMEOUT = 10000; private static final String ASSERT_USAGE = "<assert widget=... method=... value=... [invert=true]/>\n" + "<assert method=[!]... [class=...]/>"; private static final String WAIT_USAGE = "<wait widget=... method=... value=... [invert=true] " + "[timeout=...] [pollInterval=...]/>\n" + "<wait method=[!]... [class=...] [timeout=...] [pollInterval=...]/>"; private String expectedResult = "true"; private boolean invert = false; private boolean wait = false; private long interval = DEFAULT_INTERVAL; private long timeout = DEFAULT_TIMEOUT; /** Construct an assert step from XML. */ public Assert(Resolver resolver, HashMap attributes) { super(resolver, patchAttributes(attributes)); wait = attributes.get(TAG_WAIT) != null; String to = (String)attributes.get(TAG_TIMEOUT); if (to != null) { try { timeout = Integer.parseInt(to); } catch(NumberFormatException exc) { } } String pi = (String)attributes.get(TAG_POLL_INTERVAL); if (pi != null) { try { interval = Integer.parseInt(pi); } catch(NumberFormatException exc) { } } init(Boolean.valueOf((String)attributes.get(TAG_INVERT)). booleanValue(), (String)attributes.get(TAG_VALUE)); } /** Assertion provided by the WidgetTester class. */ public Assert(Resolver resolver, String desc, String methodName, String[] args, String expectedResult, boolean invert) { super(resolver, desc, WidgetTester.class.getName(), methodName, args); init(invert, expectedResult); } /** Assertion provided by a WidgetTester subclass which operates on a * Widget subclass. */ public Assert(Resolver resolver, String desc, String methodName, String[] args, Class testedClass, String expectedResult, boolean invert) { super(resolver, desc, testedClass.getName(), methodName, args); init(invert, expectedResult); } /** Property assertion on Widget subclass. */ public Assert(Resolver resolver, String desc, String methodName, String[] args, String widgetID, String expectedResult, boolean invert) { super(resolver, desc, methodName, args, widgetID); init(invert, expectedResult); } /** The canonical form for a boolean assertion is to return true, and set * the invert flag if necessary. */ private void init(boolean inverted, String value) { if ("false".equals(value)) { inverted = !inverted; value = "true"; } expectedResult = value != null ? value : "true"; invert = inverted; // Accommodate deprecated usage; Assert steps previously saved only // the Widget subclass of the target or of the first argument to a // WidgetTester static method. That was bad. try { Class cls = getTargetClass(); Log.debug("Target class is " + cls.getName()); if (Widget.class.isAssignableFrom(cls)) { try { resolveMethod(getMethodName(), cls, null); } catch(IllegalArgumentException iae) { Log.debug("Attempting to repair usage, method " + getMethodName()); // Method doesn't exist on this class; if it exists on a // WidgetTester, fix things up. Otherwise, mark it as // an error. WidgetTester tester = WidgetTester.getTester(cls); try { resolveMethod(getMethodName(), tester.getClass(), null); setTargetClassName(tester.getClass().getName()); // old usage set the widget id; it is included in // the arg list, and widget ID now means the method // target *only* setWidgetID(null); } catch(IllegalArgumentException e) { // Leave things alone and let it fail when it is run Log.debug(e); } } } } catch(InvalidScriptException ise) { setScriptError(ise); } // End deprecated usage handling } public void setWait(boolean wait) { this.wait = wait; } public boolean isWait() { return wait; } public void setPollInterval(long interval) { this.interval = interval; } public long getPollInterval() { return interval; } public void setTimeout(long timeout) { this.timeout = timeout; } public long getTimeout() { return timeout; } public String getExpectedResult() { return expectedResult; } public void setExpectedResult(String result) { init(invert, result); } public boolean isInverted() { return invert; } public void setInverted(boolean invert) { init(invert, expectedResult); } /** Strip inversion from the method name. */ private static HashMap patchAttributes(HashMap map) { String method = (String)map.get(TAG_METHOD); if (method != null && method.startsWith("!")) { map.put(TAG_METHOD, method.substring(1)); map.put(TAG_INVERT, "true"); } // If no class is specified, defualt to WidgetTester String cls = (String)map.get(TAG_CLASS); if (cls == null) { map.put(TAG_CLASS, "abbot.swt.tester.WidgetTester"); } return map; } public String getXMLTag() { return wait ? TAG_WAIT : TAG_ASSERT; } public String getUsage() { return wait ? WAIT_USAGE : ASSERT_USAGE; } protected String getDefaultDescription() { String mname = getMethodName(); // assert/is/get doesn't really add any information, so drop it if (mname.startsWith("assert")) mname = mname.substring(6); else if (mname.startsWith("get")) mname = mname.substring(3); else if (mname.startsWith("is")) mname = mname.substring(2); // [!][$.]m [==|!= v] String desc = Strings.get((wait ? "WaitDesc" : "AssertDesc"), new Object[] { ((invert && "true".equals(expectedResult)) ? "!" : ""), (getWidgetID() != null ? ("${" + getWidgetID() + "}.") : ""), mname + "(" + getEncodedArguments() + ") ", (expectedResult.equals("true") ? "" : ((invert ? Strings.get("NotEquals") : Strings.get("Equals"))) + " "), (expectedResult.equals("true") ? "" : expectedResult), (wait && timeout != DEFAULT_TIMEOUT ? Strings.get((timeout > 5000 ? "Seconds" : "Milliseconds"), new Object[] { String.valueOf(timeout > 5000 ? timeout/1000 : timeout) }) : "") }); return desc; } public HashMap getAttributes() { HashMap map = super.getAttributes(); if (invert) { map.put(TAG_INVERT, "true"); } if (!expectedResult.equalsIgnoreCase("true")) { map.put(TAG_VALUE, expectedResult); } if (timeout != DEFAULT_TIMEOUT) map.put(TAG_TIMEOUT, String.valueOf(timeout)); if (interval != DEFAULT_INTERVAL) map.put(TAG_POLL_INTERVAL, String.valueOf(interval)); return map; } /** Check the assertion. This is exported so that it can be used by * derivatives of Assert (e.g. Wait). */ protected void doCheck() throws Throwable { Object expected, actual; boolean matchStrings = false; // If we can't convert the string to the expected type, // match the string against the result's toString method instead try { expected = ArgumentParser.eval(getResolver(), expectedResult, getMethod().getReturnType()); } catch(IllegalArgumentException iae) { expected = expectedResult; matchStrings = true; } actual = invoke(); if (matchStrings) actual = actual.toString(); if (invert) { assertNotEquals(this.toString(), expected, actual); } else { assertEquals(this.toString(), expected, actual); } } /** Print out arrays by individual element. */ protected String toString(Object obj) { if (obj.getClass().isArray()) { String str = "["; String comma = ""; for (int i=0;i < Array.getLength(obj);i++) { str += comma + toString(Array.get(obj, i)); comma = ","; } return str + "]"; } return obj.toString(); } /** Use our own comparison, to get the extended array comparisons. */ private void assertEquals(String message, Object expected, Object actual) { if (!ExtendedComparator.equals(expected, actual)) { String msg = Strings.get("ComparisonFailed", new Object[] { (message != null ? message + " " : ""), toString(actual) }); throw new AssertionFailedError(msg); } } /** Use our own comparison, to get the extended array comparisons. */ private void assertNotEquals(String message, Object expected, Object actual) { if (ExtendedComparator.equals(expected, actual)) { String msg = message != null ? message : ""; throw new AssertionFailedError(msg); } } /** Run this step. */ protected void runStep() throws Throwable { if (wait) { long now = System.currentTimeMillis(); long remaining = timeout; String error = toString(); while (remaining > 0) { long start = now; try { try { doCheck(); return; } catch(AssertionFailedError exc) { // keep waiting error = exc.getMessage(); } Thread.sleep(interval); } catch(InterruptedException ie) { } now = System.currentTimeMillis(); remaining -= now - start; } throw new WaitTimedOutError(Strings.get("WaitTimedOut", new Object[] { String.valueOf(timeout), toString() })); } else { doCheck(); } } }