package freenet; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.jws.WebParam; import org.w3c.dom.NodeList; import com.gargoylesoftware.htmlunit.AjaxController; import com.gargoylesoftware.htmlunit.TopLevelWindow; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebRequestSettings; import com.gargoylesoftware.htmlunit.WebWindow; import com.gargoylesoftware.htmlunit.html.HtmlPage; import freenet.support.Base64; public class PushTester { public static final String TEST_URL_PREFIX = "http://127.0.0.1:8888"; public static final String TEST_URL = TEST_URL_PREFIX + "/pushtester"; public static final boolean stress = false; public static final boolean continous = true; private List<Method> getAllTestMethods() { List<Method> methods = new ArrayList<Method>(); for (Method m : getClass().getMethods()) { if (m.getName().startsWith("test") && m.getParameterTypes().length == 0) { methods.add(m); } } return methods; } private Method getMethodWithOnlyAnnotation() { for (Method m : getAllTestMethods()) { if (m.isAnnotationPresent(Only.class)) { return m; } } return null; } private List<Method> getAllIntegrationTests() { List<Method> methods = new ArrayList<Method>(); for (Method m : getAllTestMethods()) { if (m.isAnnotationPresent(TestType.class) && m.getAnnotation(TestType.class).value() == Type.INTEGRATION) { methods.add(m); } } return methods; } private List<Method> getAllAcceptanceTests() { List<Method> methods = new ArrayList<Method>(); for (Method m : getAllTestMethods()) { if (m.isAnnotationPresent(TestType.class) && m.getAnnotation(TestType.class).value() == Type.ACCEPTANCE) { methods.add(m); } } return methods; } private void runTests(List<Method> tests) throws Exception { for (Method m : tests) { String testName = String.valueOf(m.getName().charAt(4)).toLowerCase().concat(m.getName().substring(5)); int secondsLasts = m.isAnnotationPresent(SecondsLong.class) ? m.getAnnotation(SecondsLong.class).value() : -1; System.out.println("Testing: " + testName + (secondsLasts != -1 ? " Lasts: " + secondsLasts + " seconds" : "")); PrintStream err = System.err; PrintStream out = System.out; try { MemoryOutputStream mos = new MemoryOutputStream(); System.setOut(new PrintStream(mos)); System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { } })); Countdown countdown = null; if (secondsLasts != -1) { countdown = new Countdown(secondsLasts, out); } try { try { m.invoke(this); } finally { System.setErr(err); } } catch (Exception e) { if (countdown != null) { countdown.cancelTimer(); } out.println("FAILED! msg:" + e.getCause().getMessage()); e.printStackTrace(); out.println(mos.getVal()); throw new Exception("Test failed"); } out.println("Passed"); } finally { System.setErr(err); System.setOut(out); } } } private class MemoryOutputStream extends OutputStream { StringBuilder sb = new StringBuilder(); @Override public void write(int b) throws IOException { sb.append(new String(new byte[] { (byte) b })); } public String getVal() { return sb.toString(); } } public static void main(String[] args) { if (stress) { for (int i = 0; i < 4; i++) { new Thread() { public void run() { new PushTester().startTesting(true); }; }.start(); } } else { boolean runned = false; boolean error = false; while ((continous || !runned) && !error) { error = !new PushTester().startTesting(false); runned = true; } } } private boolean startTesting(boolean stress) { Method only = getMethodWithOnlyAnnotation(); if (only != null) { try { only.invoke(this); } catch (Exception e) { e.printStackTrace(); return false; } return true; } if (stress) { List<Method> methods = getAllTestMethods(); Collections.shuffle(methods); try { runTests(methods); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Integrational tests:"); try { runTests(getAllIntegrationTests()); } catch (Exception e) { e.printStackTrace(); return false; } System.out.println("\nAcceptance tests:"); try { runTests(getAllAcceptanceTests()); } catch (Exception e) { e.printStackTrace(); return false; } } return true; } @SecondsLong(43) @TestType(Type.INTEGRATION) public void testKeepalive() throws Exception { WebClient c = new WebClient(); String requestId = ((HtmlPage) c.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); c.closeAllWindows(); if (c.getPage(TEST_URL_PREFIX + "/keepalive/?requestId=" + requestId).getWebResponse().getContentAsString().startsWith("SUCCESS") == false) { throw new Exception("Initial keepalive should be successfull"); } Thread.sleep(43000); if (c.getPage(TEST_URL_PREFIX + "/keepalive/?requestId=" + requestId).getWebResponse().getContentAsString().startsWith("FAILURE") == false) { throw new Exception("Timeouted keepalive should have failed"); } c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId); } @SecondsLong(4) @TestType(Type.ACCEPTANCE) public void testPushing() throws Exception { WebClient c = new WebClient(); WebWindow w = new TopLevelWindow("1", c); c.setCurrentWindow(w); HtmlPage p = c.getPage(TEST_URL); Thread.sleep(4000); int current = Integer.parseInt(p.getElementById("content").getFirstChild().getFirstChild().getTextContent().trim()); if (current < 3 || current > 5) { throw new Exception("The value is not in the expected interval:[3,5]. The current value:" + current); } c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + p.getElementById("requestId").getAttribute("value")); } @SecondsLong(12) @TestType(Type.ACCEPTANCE) public void testConnectionSharing() throws Exception { WebClient c = new WebClient(); WebWindow w1 = new TopLevelWindow("1", c); WebWindow w2 = new TopLevelWindow("2", c); c.setCurrentWindow(w1); String requestId1 = ((HtmlPage) c.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); Thread.sleep(1000); c.setCurrentWindow(w2); String requestId2 = ((HtmlPage) c.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); Thread.sleep(500); enableDebug(w1); enableDebug(w2); Thread.sleep(10000); System.out.println(getLogForWindows(w1,w2)); int current = Integer.parseInt(((HtmlPage) w1.getEnclosedPage()).getElementById("content").getFirstChild().getFirstChild().getTextContent().trim()); if (current < 9 || current > 12) { throw new Exception("The value is not in the expected interval:[9,12] for Window 1. The current value:" + current); } current = Integer.parseInt(((HtmlPage) w2.getEnclosedPage()).getElementById("content").getFirstChild().getFirstChild().getTextContent().trim()); if (current < 9 || current > 12) { throw new Exception("The value is not in the expected interval:[9,12] for Window 2. The current value:" + current); } if (getLogForWindows(w2).contains("pushnotifications") || getLogForWindows(w1).contains("pushnotifications") == false) { throw new Exception("Window 2 is making permanent requests or Window 1 don't"); } c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId1); c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId2); } @Only @SecondsLong(25) @TestType(Type.ACCEPTANCE) public void testFailover() throws Exception { WebClient c = new WebClient(); WebWindow w1 = new TopLevelWindow("1", c); WebWindow w2 = new TopLevelWindow("2", c); c.setCurrentWindow(w1); c.getPage(TEST_URL); Thread.sleep(500); enableDebug(w1); Thread.sleep(500); c.setCurrentWindow(w2); c.getPage(TEST_URL); Thread.sleep(500); enableDebug(w2); Thread.sleep(4000); c.deregisterWebWindow(w1); Thread.sleep(20000); System.out.println(getLogForWindows(w1,w2)); int current = Integer.parseInt(((HtmlPage) w2.getEnclosedPage()).getElementById("content").getFirstChild().getFirstChild().getTextContent().trim()); if (current < 23 || current > 25) { throw new Exception("The value is not in the expected interval:[23,25] for Window 2. The current value:" + current); } c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + ((HtmlPage) w2.getEnclosedPage()).getElementById("requestId").getAttribute("value")); } /** Tests that the failing pages' notifications are removed nicely. */ @SecondsLong(42) @TestType(Type.INTEGRATION) public void testCleanerNotificationRemoval() throws Exception { WebClient c1 = new WebClient(); WebClient c2 = new WebClient(); String requestId1 = ((HtmlPage) c1.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); String requestId2 = ((HtmlPage) c2.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); System.out.println("Pages got"); c1.closeAllWindows(); c2.closeAllWindows(); System.out.println("Windows closed"); if (c1.getPage(TEST_URL_PREFIX + "/pushnotifications/?requestId=" + requestId1).getWebResponse().getContentAsString().startsWith("SUCCESS") == false) { throw new Exception("There should be a notification!"); } System.out.println("Notifications working"); for (int i = 0; i < 21; i++) { Thread.sleep(2000); System.out.println("Sending keepalive:" + i); if (c1.getPage(TEST_URL_PREFIX + "/keepalive/?requestId=" + requestId1).getWebResponse().getContentAsString().startsWith("SUCCESS") == false) { throw new Exception("Keepalive should be successful!"); } } System.out.println("All keepalives sent"); for (int i = 0; i < 20; i++) { System.out.println("Getting notification:" + i); if (new String(Base64.decodeStandard(c1.getPage(TEST_URL_PREFIX + "/pushnotifications/?requestId=" + requestId1).getWebResponse().getContentAsString().split("[:]")[1])).compareTo(requestId1) != 0) { throw new Exception("Only the first page should have notifications!"); } } c1.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId1); c2.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId2); } @SecondsLong(0) @TestType(Type.INTEGRATION) public void testLeaving() throws Exception { WebClient c = new WebClient(); String requestId = ((HtmlPage) c.getPage(TEST_URL)).getElementById("requestId").getAttribute("value"); if (c.getPage(TEST_URL_PREFIX + "/keepalive/?requestId=" + requestId).getWebResponse().getContentAsString().startsWith("SUCCESS") == false) { throw new Exception("Initial keepalive should be successfull"); } if (c.getPage(TEST_URL_PREFIX + "/leaving/?requestId=" + requestId).getWebResponse().getContentAsString().startsWith("SUCCESS") == false) { throw new Exception("Leaving should always be successfull"); } if (c.getPage(TEST_URL_PREFIX + "/keepalive/?requestId=" + requestId).getWebResponse().getContentAsString().startsWith("FAILURE") == false) { throw new Exception("Keepalive should fail after leaving"); } } public static String getLogForWindows(WebWindow... windows) { List<String> log = new ArrayList<String>(); for (WebWindow w : windows) { NodeList logElements = ((HtmlPage) w.getEnclosedPage()).getElementById("log").getElementsByTagName("div"); for (int i = 0; i < logElements.getLength(); i++) { String msg = logElements.item(i).getTextContent() + "\n"; msg = msg.substring(0, msg.indexOf("}") + 1).concat("{" + w.getName() + "}").concat(msg.substring(msg.indexOf("}") + 1)); log.add(msg); } } Collections.sort(log); StringBuilder sb = new StringBuilder(); for (String s : log) { sb.append(s); } return sb.toString(); } public static void logToWindow(WebWindow window, String msg) { ((HtmlPage) window.getEnclosedPage()).executeJavaScript("window.log(\"" + msg.replace("\"", "\\\"") + "\");"); } public static void enableDebug(WebWindow window) { ((HtmlPage) window.getEnclosedPage()).executeJavaScript("window.enableDebug();"); } private class Countdown extends TimerTask { private int status; private PrintStream out; private Timer timer = new Timer(true); public Countdown(int from, PrintStream out) { status = from; this.out = out; timer.scheduleAtFixedRate(this, 0, 1000); } @Override public void run() { out.print((status--) + ".."); if (status < 0) { cancel(); } } public void cancelTimer() { timer.cancel(); } } private class LoggerStream extends OutputStream { @Override public void write(int b) throws IOException { // TODO Auto-generated method stub } } }