summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarun Gupta <tarun1995gupta@gmail.com>2015-07-21 11:29:03 (GMT)
committerNick Hudson <nick.hudson@isode.com>2015-08-03 09:51:55 (GMT)
commit32ef37b9059e21de19209a9a1ab4ef2564051918 (patch)
treeb30affbab2d833e50369d5f81d0188eecc7bf1e5
parent7c1715ada34e6ebfd82a22f67a7346f85d4a3158 (diff)
downloadstroke-32ef37b9059e21de19209a9a1ab4ef2564051918.zip
stroke-32ef37b9059e21de19209a9a1ab4ef2564051918.tar.bz2
Add tests for EventLoop.
Adds SingleThreadedEventLoop. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: Tests for EventLoop and SimpleEventLoop passes. Change-Id: Ifda63a328e0adfb2da0eb2a1038805042ed0f6fb
-rw-r--r--src/com/isode/stroke/eventloop/SingleThreadedEventLoop.java108
-rw-r--r--test/com/isode/stroke/eventloop/EventLoopTest.java137
-rw-r--r--test/com/isode/stroke/eventloop/SimpleEventLoopTest.java88
3 files changed, 333 insertions, 0 deletions
diff --git a/src/com/isode/stroke/eventloop/SingleThreadedEventLoop.java b/src/com/isode/stroke/eventloop/SingleThreadedEventLoop.java
new file mode 100644
index 0000000..bf32b3c
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/SingleThreadedEventLoop.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010 Soren Dreijer
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.eventloop;
+
+import com.isode.stroke.eventloop.EventLoop;
+import java.util.Vector;
+import java.util.Collection;
+import java.util.ArrayList;
+
+// DESCRIPTION:
+//
+// All interaction with Swiften should happen on the same thread, such as the main GUI thread,
+// since the library isn't thread-safe.
+// For applications that don't have a main loop, such as WPF and MFC applications, we need a
+// different approach to process events from Swiften.
+//
+// The SingleThreadedEventLoop class implements an event loop that can be used from such applications.
+//
+// USAGE:
+//
+// Spawn a new thread in the desired framework and call SingleThreadedEventLoop::waitForEvents(). The method
+// blocks until a new event has arrived at which time it'll return, or until the wait is canceled
+// at which time it throws EventLoopCanceledException.
+//
+// When a new event has arrived and SingleThreadedEventLoop::waitForEvents() returns, the caller should then
+// call SingleThreadedEventLoop::handleEvents() on the main GUI thread. For WPF applications, for instance,
+// the Dispatcher class can be used to execute the call on the GUI thread.
+//
+public class SingleThreadedEventLoop extends EventLoop {
+
+ public class EventLoopCanceledException extends Exception {
+ public EventLoopCanceledException(String message) {
+ super(message);
+ }
+ }
+
+ private boolean shouldShutDown_;
+ private Vector<Event> events_ = new Vector<Event>();
+ private Object eventsMutex_ = new Object();
+
+ public SingleThreadedEventLoop() {
+ shouldShutDown_ = false;
+ }
+
+ /**
+ * Blocks while waiting for new events and returns when new events are available.
+ * @throws EventLoopCanceledException when the wait is canceled.
+ */
+ public void waitForEvents() throws EventLoopCanceledException {
+ synchronized(eventsMutex_) {
+ while (events_.isEmpty() && !shouldShutDown_) {
+ try {
+ eventsMutex_.wait();
+ } catch (InterruptedException e) {
+
+ }
+ }
+ }
+ if (shouldShutDown_) {
+ throw new EventLoopCanceledException("");
+ }
+ }
+
+ public void handleEvents() {
+ // Make a copy of the list of events so we don't block any threads that post
+ // events while we process them.
+ Vector<Event> events = new Vector<Event>();
+ synchronized(eventsMutex_) {
+ swapCollectionContents(events, events_);
+ }
+
+ // Loop through all the events and handle them
+ for(Event event : events) {
+ handleEvent(event);
+ }
+ }
+
+ static <T> void swapCollectionContents(Collection<T> coll1, Collection<T> coll2) {
+ Collection<T> temp = new ArrayList<T>(coll1);
+ coll1.clear();
+ coll1.addAll(coll2);
+ coll2.clear();
+ coll2.addAll(temp);
+ }
+
+ public void stop() {
+ synchronized(eventsMutex_) {
+ shouldShutDown_ = true;
+ eventsMutex_.notifyAll();
+ }
+ }
+
+ public void post(final Event event) {
+ synchronized(eventsMutex_) {
+ events_.add(event);
+ eventsMutex_.notifyAll();
+ }
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/eventloop/EventLoopTest.java b/test/com/isode/stroke/eventloop/EventLoopTest.java
new file mode 100644
index 0000000..09f3d6e
--- /dev/null
+++ b/test/com/isode/stroke/eventloop/EventLoopTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.eventloop;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.Before;
+import com.isode.stroke.eventloop.EventOwner;
+import com.isode.stroke.eventloop.SimpleEventLoop;
+import com.isode.stroke.eventloop.DummyEventLoop;
+import java.util.Vector;
+
+public class EventLoopTest {
+
+ private Vector<Integer> events_ = new Vector<Integer>();
+
+ private class MyEventOwner implements EventOwner {};
+
+ private void logEvent(int i) {
+ events_.add(i);
+ }
+
+ private void runEventLoop(DummyEventLoop loop, MyEventOwner eventOwner) {
+ loop.processEvents();
+ assertEquals(0, events_.size());
+ loop.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(1);
+ }
+ }, eventOwner);
+ }
+
+ @Before
+ public void setUp() {
+ events_.clear();
+ }
+
+ @Test
+ public void testPost() {
+ SimpleEventLoop testling = new SimpleEventLoop();
+
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(1);
+ }
+ });
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(2);
+ }
+ });
+ testling.stop();
+ testling.run();
+
+ assertEquals(Integer.valueOf(1), events_.get(0));
+ assertEquals(Integer.valueOf(2), events_.get(1));
+ }
+
+ @Test
+ public void testRemove() {
+ SimpleEventLoop testling = new SimpleEventLoop();
+ MyEventOwner eventOwner1 = new MyEventOwner();
+ MyEventOwner eventOwner2 = new MyEventOwner();
+
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(1);
+ }
+ }, eventOwner1);
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(2);
+ }
+ }, eventOwner2);
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(3);
+ }
+ }, eventOwner1);
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(4);
+ }
+ }, eventOwner2);
+ testling.removeEventsFromOwner(eventOwner2);
+ testling.stop();
+ testling.run();
+
+ assertEquals(2, events_.size());
+ assertEquals(Integer.valueOf(1), events_.get(0));
+ assertEquals(Integer.valueOf(3), events_.get(1));
+ }
+
+ @Test
+ public void testHandleEvent_Recursive() {
+ final DummyEventLoop testling = new DummyEventLoop();
+ final MyEventOwner eventOwner = new MyEventOwner();
+
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ runEventLoop(testling, eventOwner);
+ }
+ }, eventOwner);
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ logEvent(0);
+ }
+ }, eventOwner);
+ testling.processEvents();
+
+ assertEquals(2, events_.size());
+ assertEquals(Integer.valueOf(0), events_.get(0));
+ assertEquals(Integer.valueOf(1), events_.get(1));
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/eventloop/SimpleEventLoopTest.java b/test/com/isode/stroke/eventloop/SimpleEventLoopTest.java
new file mode 100644
index 0000000..497c994
--- /dev/null
+++ b/test/com/isode/stroke/eventloop/SimpleEventLoopTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.eventloop;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.Before;
+import com.isode.stroke.eventloop.SimpleEventLoop;
+
+public class SimpleEventLoopTest {
+
+ private void runIncrementingThread(SimpleEventLoop loop) {
+ for (int i = 0; i < 10; ++i) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+
+ }
+ loop.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ incrementCounter();
+ }
+ });
+ }
+ loop.stop();
+ }
+
+ private void incrementCounter() {
+ counter_++;
+ }
+
+ private void incrementCounterAndStop(SimpleEventLoop loop) {
+ counter_++;
+ loop.stop();
+ }
+
+ private int counter_;
+
+ @Before
+ public void setUp() {
+ counter_ = 0;
+ }
+
+ @Test
+ // FIXME: Temporarily disabling run, because it generates a "vector
+ // iterator not incrementable" on XP
+ public void testRun() {
+ final SimpleEventLoop testling = new SimpleEventLoop();
+ Thread d = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIncrementingThread(testling);
+ }
+ });
+ d.start();
+ testling.run();
+
+ assertEquals(10, counter_);
+ }
+
+ @Test
+ public void testPostFromMainThread() {
+ final SimpleEventLoop testling = new SimpleEventLoop();
+ testling.postEvent(new Event.Callback() {
+ @Override
+ public void run() {
+ incrementCounterAndStop(testling);
+ }
+ });
+ testling.run();
+
+ assertEquals(1, counter_);
+ }
+} \ No newline at end of file