changeset 695:8515c529e29c

Add accidentally not included files
author Adam Domurad <adomurad@redhat.com>
date Thu, 25 Apr 2013 17:05:31 -0400
parents 142217481a51
children 0e170a31770d
files ChangeLog tests/netx/unit/sun/applet/PluginAppletViewerTest.java tests/test-extensions/net/sourceforge/jnlp/AsyncCall.java tests/test-extensions/sun/applet/mock/PluginPipeMock.java
diffstat 4 files changed, 477 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Apr 25 10:53:44 2013 -0400
+++ b/ChangeLog	Thu Apr 25 17:05:31 2013 -0400
@@ -1,3 +1,8 @@
+2013-04-25  Adam Domurad  <adomurad@redhat.com>
+
+	Add accidentally not included files from "Tests & test extensions for
+	mocking the plugin input & output pipes."
+
 2013-04-25  Adam Domurad  <adomurad@redhat.com>
 
 	Fix a dead-lock that can cause (namely) Firefox to hang.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/sun/applet/PluginAppletViewerTest.java	Thu Apr 25 17:05:31 2013 -0400
@@ -0,0 +1,249 @@
+package sun.applet;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.Callable;
+
+import net.sourceforge.jnlp.AsyncCall;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import sun.applet.mock.PluginPipeMock;
+
+public class PluginAppletViewerTest {
+
+    /**************************************************************************
+     *                          Test setup                                    *
+     **************************************************************************/
+
+    ThreadGroup spawnedForTestThreadGroup; // Set up before each test
+    PluginPipeMock pipeMock; // Set up before each test
+
+    /* By providing custom implementations of the input stream & output stream used by PluginStreamHandler,
+     * we are able to mock the C++-side of the plugin. We do this by sending the messages the Java-side expects
+     * to receive. Additionally, we able to test that the Java-side sends the correct requests.
+     * See PluginPipeMock for more details.
+     */
+    private void installPipeMock() {
+        AppletSecurityContextManager.addContext(0, new PluginAppletSecurityContext(0, false /* no security */));
+
+        pipeMock = new PluginPipeMock();
+
+        PluginStreamHandler streamHandler = new PluginStreamHandler(pipeMock.getResponseInputStream(), pipeMock.getRequestOutputStream());
+        PluginAppletViewer.setStreamhandler(streamHandler);
+        PluginAppletViewer.setPluginCallRequestFactory(new PluginCallRequestFactory());
+
+        streamHandler.startProcessing();
+    }
+
+    /* Call installPipeMock, wrapping the threads it creates in a ThreadGroup.
+     * This allows us to stop the message handling threads we spawn, while normally
+     * this would be difficult as they are meant to be alive at all times.
+     */
+    @Before
+    public void setupMockedMessageHandling() throws Exception {
+        spawnedForTestThreadGroup = new ThreadGroup("PluginAppletViewerTestThreadGroup") {
+            public void uncaughtException(Thread t, Throwable e) {
+                // Silent death for plugin message handler threads
+            }
+        };
+        // Do set-up in a thread so we can pass along our thread-group, used for clean-up.
+        Thread initThread = new Thread(spawnedForTestThreadGroup, "InstallPipeMockThread") {
+            @Override
+            public void run() {
+                installPipeMock();
+            }
+        };
+        initThread.start();
+        initThread.join();
+    }
+
+    @After
+    @SuppressWarnings("deprecation") // 'stop' must be used, 'interrupt' is too gentle.
+    public void cleanUpMessageHandlingThreads() throws Exception {
+        spawnedForTestThreadGroup.stop();
+    }
+
+    /**************************************************************************
+     *                          Test cases                                    *
+     *   A PluginStreamHandler is installed for each, see 'installPipeMock'.  *
+     **************************************************************************/
+
+    @Test
+    public void testJavascriptCall() throws Exception {
+        /* JS call parameters */
+        final int jsObjectID = 0;
+        final String callName = "testfunction";
+        final Object[] arguments = { "testargument", 1 }; // Arbitrary objects
+
+        AsyncCall<Object> call = AsyncCall.startWithTimeOut(new Callable<Object>() {
+            public Object call() {
+                return PluginAppletViewer.call(jsObjectID, callName, arguments);
+            }
+        });
+
+        String message = pipeMock.getNextRequest();
+        Object expectedReturn = new Object();
+        pipeMock.sendResponse("context 0 reference " 
+                + parseAndCheckJSCall(message, jsObjectID, callName, arguments)
+                + " JavaScriptCall " + storeObject(expectedReturn));
+
+        assertEquals(expectedReturn, call.join());
+    }
+
+    @Test
+    public void testJavascriptEval() throws Exception {
+        /* JS eval parameters */
+        final int jsObjectID = 0;
+        final String callName = "testfunction";
+
+        AsyncCall<Object> call = AsyncCall.startWithTimeOut(new Callable<Object>() {
+            public Object call() {
+                return PluginAppletViewer.eval(jsObjectID, callName);
+            }
+        });
+
+        String message = pipeMock.getNextRequest();
+        Object expectedReturn = new Object();
+        pipeMock.sendResponse("context 0 reference " 
+                + parseAndCheckJSEval(message, jsObjectID, callName)
+                + " JavaScriptEval " + storeObject(expectedReturn));
+
+        assertEquals(expectedReturn, call.join());
+    }
+
+    @Test
+    public void testJavascriptFinalize() throws Exception {
+        final int jsObjectID = 0;
+        AsyncCall<Void> call = AsyncCall.startWithTimeOut(new Callable<Void>() {
+            public Void call() {
+                PluginAppletViewer.JavaScriptFinalize(jsObjectID);
+                return null;
+            }
+        });
+
+        String message = pipeMock.getNextRequest();
+        pipeMock.sendResponse("context 0 reference "
+                + parseAndCheckJSFinalize(message, jsObjectID) 
+                + " JavaScriptFinalize ");
+
+        call.join();
+    }
+
+    @Test
+    public void testJavascriptToString() throws Exception {
+        final int jsObjectID = 0;
+        AsyncCall<String> call = AsyncCall.startWithTimeOut(new Callable<String>() {
+            public String call() {
+                return PluginAppletViewer.javascriptToString(jsObjectID);
+            }
+        });
+
+        String message = pipeMock.getNextRequest();
+
+        String expectedReturn = "testreturn";
+        pipeMock.sendResponse("context 0 reference " 
+                + parseAndCheckJSToString(message, jsObjectID)
+                + " JavaScriptToString " + storeObject(expectedReturn));
+
+        assertEquals(expectedReturn, call.join());
+    }
+
+   /**************************************************************************
+    *                          Test utilities                                *
+    **************************************************************************/
+
+    /*
+     * Helpers for manipulating the object mapping using to refer to objects in
+     * the plugin
+     */
+    private static Object getStoredObject(int id) {
+        return PluginObjectStore.getInstance().getObject(id);
+    }
+
+    private static int storeObject(Object obj) {
+        PluginObjectStore.getInstance().reference(obj);
+        return PluginObjectStore.getInstance().getIdentifier(obj);
+    }
+
+    /*
+     * Asserts that the message is a valid javascript request and returns the
+     * reference number
+     */
+    private static int parseAndCheckJSMessage(String message, int messageLength,
+            String messageType, int contextObjectID) {
+        System.out.println(message);
+        String[] parts = message.split(" ");
+        assertEquals(messageLength, parts.length);
+
+        assertEquals("instance", parts[0]);
+        assertEquals("0", parts[1]); // JSCall's are prefixed with a dummy '0' instance
+        assertEquals("reference", parts[2]);
+        int reference = Integer.parseInt(parts[3]);
+        assertEquals(messageType, parts[4]);
+
+        assertEquals(contextObjectID, Integer.parseInt(parts[5]));
+        return reference;
+    }
+
+    /*
+     * Asserts that the message is a valid javascript request and returns the
+     * reference number
+     */
+    private static int parseAndCheckJSMessage(String message,
+            String messageType, int contextObjectID, String stringArg,
+            Object[] arguments) {
+        int expectedLength = 7 + arguments.length;
+        int reference = parseAndCheckJSMessage(message, expectedLength, messageType, contextObjectID);
+
+        String[] parts = message.split(" ");
+        assertEquals(stringArg, getStoredObject(Integer.parseInt(parts[6])));
+
+        for (int i = 0; i < arguments.length; i++) {
+            int objectID = Integer.parseInt(parts[7+i]);
+            assertEquals(arguments[i], getStoredObject(objectID));
+        }
+
+        return reference;
+    }
+
+    /*
+     * Asserts that the message is a valid javascript method call request, and
+     * returns the reference number
+     */
+    public static int parseAndCheckJSCall(String message, int contextObjectID,
+            String callName, Object[] arguments) {
+        return parseAndCheckJSMessage(message, "Call", contextObjectID,
+                callName, arguments);
+    }
+
+    /*
+     * Asserts that the message is a valid javascript Eval request, and returns
+     * the reference number
+     */
+    public static int parseAndCheckJSEval(String message, int contextObjectID,
+            String evalString) {
+        return parseAndCheckJSMessage(message, "Eval", contextObjectID,
+                evalString, new Object[] {});
+    }
+
+    /*
+     * Asserts that the message is a valid javascript Finalize request, and returns
+     * the reference number
+     */
+    public static int parseAndCheckJSFinalize(String message, int contextObjectID) {
+        int expectedLength = 6;
+        return parseAndCheckJSMessage(message, expectedLength, "Finalize", contextObjectID);
+    }
+
+    /*
+     * Asserts that the message is a valid javascript ToString request, and returns
+     * the reference number
+     */
+    public static int parseAndCheckJSToString(String message, int contextObjectID) {
+        int expectedLength = 6;
+        return parseAndCheckJSMessage(message, expectedLength, "ToString", contextObjectID);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extensions/net/sourceforge/jnlp/AsyncCall.java	Thu Apr 25 17:05:31 2013 -0400
@@ -0,0 +1,102 @@
+package net.sourceforge.jnlp;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A call that runs on a separate thread, with an optional timeout. It takes a runnable and allows
+ * joining. 
+ * 
+ * On join, throws any exceptions that occurred within the call, or a TimeOutException if
+ * it did not finish. Returns the value from the call.
+ */
+public class AsyncCall<T> {
+    static public class TimeOutException extends RuntimeException  {
+        public TimeOutException() {
+            super("Call did not finish within the allocated time.");
+        }
+    }
+
+    private Thread handler;
+    private Callable<T> callable;
+    private long timeout;
+    private T callResult;
+
+    /* Captures exception from async call */
+    private Exception asyncException = null;
+
+    /* Create an AsyncCall with a given time-out */
+    public AsyncCall(Callable<T> callable, long timeout) {
+        this.callable = callable;
+        this.timeout = timeout;
+        this.handler = new HandlerThread();
+    }
+
+    /* Create an AsyncCall with (effectively) no time-out */
+    public AsyncCall(Callable<T> call) {
+        this(call, Long.MAX_VALUE);
+    }
+
+    /* Chains construction + start for convenience */
+    public static <T> AsyncCall<T> startWithTimeOut(Callable<T> callable, long timeout) {
+        AsyncCall<T> asyncCall = new AsyncCall<T>(callable, timeout);
+        asyncCall.start();
+        return asyncCall;
+    }
+
+    /* Chains construction + start for convenience */
+    public static <T> AsyncCall<T> startWithTimeOut(Callable<T> callable) {
+        return startWithTimeOut(callable, 1000); // Default timeout of 1 second
+    }
+
+    public void start() {
+        this.handler.start();
+    }
+
+    // Rethrows exceptions from handler thread, and throws TimeOutException in case of time-out.
+    public T join() throws Exception {
+        handler.join();
+        if (asyncException != null) {
+            throw asyncException;
+        }
+        return callResult;
+    }
+
+    /* The handler thread is responsible for timing-out the Callable thread.
+     * The resulting thread */
+    private class HandlerThread extends Thread {
+        @Override
+        public void run() {
+            Thread thread = new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        /* Capture result of the call */
+                        callResult = callable.call();
+                    } catch (Exception e) {
+                        /* In case of exception, capture for re-throw */
+                        asyncException = e;
+                    }
+                    handler.interrupt(); // Finish early
+                }
+            };
+
+            thread.start();
+
+            try {
+                Thread.sleep(timeout);
+            } catch (InterruptedException e) {
+                // Finish early
+                return;
+            }
+
+            if (thread.isAlive()) {
+                asyncException = new TimeOutException();
+            }
+
+            // Make sure the thread is finished
+            while (thread.isAlive()) {
+                thread.interrupt();
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extensions/sun/applet/mock/PluginPipeMock.java	Thu Apr 25 17:05:31 2013 -0400
@@ -0,0 +1,121 @@
+package sun.applet.mock;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Helper for getting an input & output stream for use with PluginStreamHandler.
+ * Provides a convenient way of reading the Java requests and sending mocked
+ * plugin responses.
+ * 
+ * The handling of these requests should be done on a different thread from the
+ * tested method, as icedtea-web will block waiting for a reply after sending a
+ * request.
+ */
+public class PluginPipeMock {
+    private ResponseInputPipeMock responseInputStream = new ResponseInputPipeMock();
+    private RequestOutputPipeMock requestOutputStream = new RequestOutputPipeMock();
+
+    /*
+     * A queue of mocked responses that are sent as replies to icedtea-web
+     * Java-side requests.
+     */
+    private BlockingQueue<String> mockedResponseQueue = new LinkedBlockingQueue<String>();
+
+    /*
+     * A queue of actual (ie, not mocked) requests that come from methods
+     * under test.
+     */
+    private BlockingQueue<String> requestQueue = new LinkedBlockingQueue<String>();
+
+    public InputStream getResponseInputStream() {
+        return responseInputStream;
+    }
+
+    public OutputStream getRequestOutputStream() {
+        return requestOutputStream;
+    }
+
+    public String getNextRequest() {
+        try {
+            return requestQueue.take();
+        } catch (InterruptedException e) {
+            // Nothing to do
+            return null;
+        }
+    }
+
+    public void sendResponse(String response) {
+        try {
+            mockedResponseQueue.put(response);
+        } catch (InterruptedException e) {
+            // Nothing to do
+        }
+    }
+
+    /**
+     * Queues mocked responses and sends them as replies to icedtea-web. A
+     * synchronized message queue is read from. Blocks until it gets the next
+     * message.
+     */
+    private class ResponseInputPipeMock extends InputStream {
+        private StringReader reader = null;
+
+        @Override
+        public int read() throws IOException {
+            try {
+                while (true) {
+                    if (reader == null) {
+                        reader = new StringReader(mockedResponseQueue.take() + '\n');
+                    }
+                    int chr = reader.read();
+                    if (chr == -1) {
+                        reader = null;
+                        continue;
+                    }
+                    return chr;
+                }
+            } catch (InterruptedException e) {
+                // Nothing to do
+                return -1;
+            }
+        }
+
+        /* Necessary for correct behaviour with BufferedReader! */
+        @Override
+        public int read(byte b[], int off, int len) throws IOException {
+            if (len == 0) {
+                return 0;
+            }
+            b[off] = (byte) read();
+            return 1;
+        }
+    }
+
+    /**
+     * Outputs requests from icedtea-web as a stream of lines. A synchronized
+     * message queue is written to.
+     */
+    private class RequestOutputPipeMock extends OutputStream {
+        private StringBuilder lineBuffer = new StringBuilder();
+
+        @Override
+        public synchronized void write(int b) throws IOException {
+            try {
+                char chr = (char) b;
+                if (chr == '\0') {
+                    requestQueue.put(lineBuffer.toString());
+                    lineBuffer.setLength(0);
+                } else {
+                    lineBuffer.append((char) b);
+                }
+            } catch (InterruptedException e) {
+                // Nothing to do
+            }
+        }
+    }
+}