package xapi.test.junit; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.BodyElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.VerticalAlign; import com.google.gwt.reflect.client.ConstPool; import com.google.gwt.reflect.shared.JsMemberPool; import com.google.gwt.reflect.shared.ReflectUtil; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.Timer; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import javax.inject.Provider; import xapi.annotation.compile.Resource; import xapi.gwtc.api.Gwtc; import xapi.util.X_Debug; @Gwtc( includeGwtXml={ @Resource("org.junit.JUnit4") } , debug = true , includeSource = "" ) public class JUnitUi { private static final String TEST_RESULTS = "test.result"; private final Map<Method, Provider<Object>> tests = new LinkedHashMap<Method, Provider<Object>>(); private final Map<Class<?>, Method[]> testClasses = new LinkedHashMap<Class<?>, Method[]>(); public void onModuleLoad() { final String module = GWT.getModuleName(), host = GWT.getHostPageBaseURL().replace("/" + module, ""); // print("<a href='#' onclick=\"" + "window.__gwt_bookmarklet_params = " + "{server_url:'" + host // + "', " + "module_name:'" + module + "'}; " + "var s = document.createElement('script'); " // + "s.src = 'http://localhost:1337/dev_mode_on.js'; " // + "document.getElementsByTagName('head')[0].appendChild(s); " + "return true;" // + "\">Recompile</a>", null); print("<style>" + "h3 {" + "margin-bottom: 5px; padding-right: 15px;" + "}" + ".results {" + "color: grey;" + " margin-bottom: 5px;" + " text-align: center;" + " width: 350px;" + "}" + ".success {" + "color: green;" + "}" + ".fail {" + "color: red;" + "}" + "</style>", null); GWT.runAsync(JUnitUi.class, new RunAsyncCallback() { @Override public void onSuccess() { try { String.class.getMethod("equals", Object.class).invoke("!", "!"); } catch (final Exception e) { print("Basic string reflection not working; " + "expect failures...", e); } // Do not change the order of the following calls unless you also // update the initial-load-sequence defined in CompileSizeTest.gwt.xml loadTests(false); loadAllTests(); ConstPool.loadConstPool(new Callback<ConstPool, Throwable>() { @Override public void onSuccess(final ConstPool result) { for (final JsMemberPool<?> m : result.getAllReflectionData()) { try { final Class<?> c = m.getType(); if (!testClasses.containsKey(c)) { addTests(c); } } catch (final Throwable e) { print("Error adding tests", e); } } loadTests(true); } @Override public void onFailure(final Throwable caught) { print("Error loading ConstPool", caught); } }); } @Override public void onFailure(final Throwable reason) { print("Error loading TestEntryPoint", reason); } }); } protected void loadAllTests() { } protected void loadTests(final boolean forReal) { GWT.runAsync(JUnit4Runner.class, new RunAsyncCallback() { @Override public void onSuccess() { if (forReal) { displayTests(); runTests(); } } @Override public void onFailure(final Throwable reason) { } }); } public void addTests(final Class<?> cls) throws Throwable { final Method[] allTests = JUnit4Runner.findTests(cls); final Provider<Object> provider = new Provider<Object> () { @Override public Object get() { try { return cls.newInstance(); } catch(final Exception e) { throw X_Debug.rethrow(e); } } }; if (allTests.length > 0) { testClasses.put(cls, allTests); for (final Method method : allTests) { tests.put(method, provider); } } } private void displayTests() { final BodyElement body = Document.get().getBody(); for (final Class<?> c : testClasses.keySet()) { final DivElement div = Document.get().createDivElement(); div.getStyle().setDisplay(Display.INLINE_BLOCK); div.getStyle().setVerticalAlign(VerticalAlign.TOP); div.getStyle().setMarginRight(2, Unit.EM); div.getStyle().setProperty("maxHeight", "400px"); div.getStyle().setOverflowY(Overflow.AUTO); StringBuilder b = new StringBuilder(); final String id = toId(c); b.append("<h3><a id='" + id + "' href='#run:" + id + "'>").append(c.getName()).append( "</a></h3>").append("<div class='results' id='" + TEST_RESULTS + id + "'> </div>"); try { final String path = c.getProtectionDomain().getCodeSource().getLocation().getPath(); b.append("<sup><a href='file://" + path + "'>").append(path).append("</a></sup>"); } catch (final Exception ignored) { } div.setInnerHTML(b.toString()); for (final Method m : testClasses.get(c)) { final String methodId = m.getName() + c.hashCode(); b = new StringBuilder(); b.append("<pre>"); b.append("<a href='javascript:'>"); b.append(m.getName()); b.append("</a>"); b.append('('); b.append(ReflectUtil.joinClasses(", ", m.getParameterTypes())); b.append(')'); b.append("</pre>"); b.append("<div id='" + methodId + "'> </div>"); final Element el = Document.get().createDivElement().cast(); el.setInnerHTML(b.toString()); DOM.setEventListener(el, new EventListener() { @Override public void onBrowserEvent(final Event event) { if (event.getTypeInt() == Event.ONCLICK) { runTest(m); } } }); DOM.sinkEvents(el, Event.ONCLICK); div.appendChild(el); } body.appendChild(div); final Element anchor = Document.get().getElementById(id).cast(); DOM.setEventListener(anchor, new EventListener() { @Override public void onBrowserEvent(final Event event) { final Map<Method, Boolean> res = testResults.get(c); for (final Method m : res.keySet().toArray(new Method[res.size()])) { res.put(m, null); } updateTestClass(c); for (final Method m : testClasses.get(c)) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { runTest(m); } }); } } }); DOM.sinkEvents(anchor, Event.ONCLICK); } } private native void log(Object o) /*-{ $wnd.console && $wnd.console.log(o); }-*/; private String toId(final Class<?> c) { return c.getName().replace('.', '_'); } Map<Class<?>, Map<Method, Boolean>> testResults = new LinkedHashMap<Class<?>, Map<Method, Boolean>>(); private void runTests() { int delay = 1; testResults.clear(); for (final Method method : tests.keySet()) { Map<Method, Boolean> results = testResults.get(method.getDeclaringClass()); if (results == null) { results = new HashMap<Method, Boolean>(); testResults.put(method.getDeclaringClass(), results); } results.put(method, null); new Timer() { @Override public void run() { runTest(method); } }.schedule(delay += 5); } for (final Class<?> testClass : testResults.keySet()) { updateTestClass(testClass); } } private void updateTestClass(final Class<?> cls) { final String id = toId(cls); final Element el = DOM.getElementById(TEST_RESULTS + id); final Map<Method, Boolean> results = testResults.get(cls); int success = 0, fail = 0; final int total = results.size(); for (final Entry<Method, Boolean> e : results.entrySet()) { if (e.getValue() != null) { if (e.getValue()) { success++; } else { fail++; } } } final StringBuilder b = new StringBuilder("<span class='success'>Passed: ").append(success).append("/").append( total).append("</span>; ").append("<span"); if (fail > 0) { b.append(" class='fail'"); } b.append(">Failed: ").append(fail).append("/").append(total); el.setInnerHTML(b.toString()); } protected void runTest(final Method m) { final String id = m.getName() + m.getDeclaringClass().hashCode(); final com.google.gwt.dom.client.Element el = Document.get().getElementById(id); el.setInnerHTML(""); final Map<Method, Boolean> results = testResults.get(m.getDeclaringClass()); try { JUnit4Runner.runTest(tests.get(m), m); results.put(m, true); debug(el, "<div style='color:green'>" + m.getName() + " passes!</div>", null); } catch (Throwable e) { results.put(m, false); final String error = m.getDeclaringClass().getName() + "." + m.getName() + " failed"; while (e.getClass() == RuntimeException.class && e.getCause() != null) { e = e.getCause(); } debug(el, error, e); try { // Move the element up to the top of the results list. final com.google.gwt.dom.client.Element result = el.getParentElement(); final com.google.gwt.dom.client.Element parent = result.getParentElement(); parent.insertAfter(result, parent.getChild(2)); } catch (final Exception ignored) { } if (e instanceof Error) { throw (Error) e; } if (e instanceof RuntimeException) { throw (Error) e; } throw new AssertionError(error); } finally { updateTestClass(m.getDeclaringClass()); } } public void print(final String string, final Throwable e) { final DivElement el = Document.get().createDivElement(); debug(el, string, e); Document.get().getBody().appendChild(el); } private void debug(final com.google.gwt.dom.client.Element el, final String string, Throwable e) { final StringBuilder b = new StringBuilder(); b.append(string); b.append('\n'); b.append("<pre style='color:red;'>"); while (e != null) { b.append(e); b.append('\n'); for (final StackTraceElement trace : e.getStackTrace()) { b.append('\t').append(trace.getClassName()).append('.').append(trace.getMethodName()) .append(' ').append(trace.getFileName()).append(':').append(trace.getLineNumber()) .append('\n'); } e = e.getCause(); } b.append("</pre>"); el.setInnerHTML(b.toString()); } }