util: Signal

Signal is an enhanced version of EventEmitter:

* only the owner can emit a signal (aka trigger an event)
* signal connection (aka event subscription) can be disconnected

EventEmitter is deprecated in favor of Signal.

refs #2279

Change-Id: I74ea5fef2e1e9b34776aa04f01170600b171152e
diff --git a/tests/unit-tests/util/signal.cpp b/tests/unit-tests/util/signal.cpp
new file mode 100644
index 0000000..0de06a2
--- /dev/null
+++ b/tests/unit-tests/util/signal.cpp
@@ -0,0 +1,345 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2014 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * This code is actually copied from NFD project (NDN Forwarding Daemon).
+ * We acknowledge the permission of the authors of NFD.
+ */
+
+#include "util/signal.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace signal {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(UtilSignal)
+
+class SignalOwner0
+{
+public:
+  Signal<SignalOwner0> sig;
+
+public:
+  DECLARE_SIGNAL_EMIT(sig)
+
+  bool
+  isSigEmpty()
+  {
+    return sig.isEmpty();
+  }
+};
+
+BOOST_AUTO_TEST_CASE(ZeroSlot)
+{
+  SignalOwner0 so;
+  BOOST_CHECK_NO_THROW(so.emitSignal(sig));
+}
+
+BOOST_AUTO_TEST_CASE(TwoListeners)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0;
+  so.sig.connect([&hit1] { ++hit1; });
+  so.sig.connect([&hit2] { ++hit2; });
+
+  so.emitSignal(sig);
+
+  BOOST_CHECK_EQUAL(hit1, 1);
+  BOOST_CHECK_EQUAL(hit2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(TwoArguments)
+{
+  Signal<std::remove_pointer<decltype(this)>::type, int, int> sig;
+
+  int hit = 0;
+  sig.connect([&hit] (int a, int b) {
+    ++hit;
+    BOOST_CHECK_EQUAL(a, 21);
+    BOOST_CHECK_EQUAL(b, 22);
+  });
+  sig(21, 22);
+
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+class RefObject
+{
+public:
+  RefObject()
+  {
+  }
+
+  RefObject(const RefObject& other)
+  {
+    ++s_copyCount;
+  }
+
+public:
+  static int s_copyCount;
+};
+int RefObject::s_copyCount = 0;
+
+// Signal passes arguments by reference,
+// but it also allows a handler that accept arguments by value
+BOOST_AUTO_TEST_CASE(HandlerByVal)
+{
+  RefObject refObject;
+  RefObject::s_copyCount = 0;
+
+  Signal<std::remove_pointer<decltype(this)>::type, RefObject> sig;
+  sig.connect([] (RefObject ro) {});
+  sig(refObject);
+
+  BOOST_CHECK_EQUAL(RefObject::s_copyCount, 1);
+}
+
+// Signal passes arguments by reference, and no copying
+// is necessary when handler accepts arguments by reference
+BOOST_AUTO_TEST_CASE(HandlerByRef)
+{
+  RefObject refObject;
+  RefObject::s_copyCount = 0;
+
+  Signal<std::remove_pointer<decltype(this)>::type, RefObject> sig;
+  sig.connect([] (const RefObject& ro) {});
+  sig(refObject);
+
+  BOOST_CHECK_EQUAL(RefObject::s_copyCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(ManualDisconnect)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection c1 = so.sig.connect([&hit] { ++hit; });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  Connection c2 = c1; // make a copy
+  c2.disconnect();
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+
+  BOOST_CHECK_NO_THROW(c2.disconnect());
+  BOOST_CHECK_NO_THROW(c1.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(ManualDisconnectDestructed)
+{
+  unique_ptr<SignalOwner0> so(new SignalOwner0());
+
+  int hit = 0;
+  Connection connection = so->sig.connect([&hit] { ++hit; });
+
+  so->emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  so.reset(); // destruct EventEmitter
+  BOOST_CHECK_NO_THROW(connection.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnect)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+    // sc goes out of scope, disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectAssign)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0;
+  ScopedConnection sc = so.sig.connect([&hit1] { ++hit1; });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 called
+
+  sc = so.sig.connect([&hit2] { ++hit2; }); // handler1 is disconnected
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 not called
+  BOOST_CHECK_EQUAL(hit2, 1); // handler2 called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectAssignSame)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection c1 = so.sig.connect([&hit] { ++hit; });
+
+  ScopedConnection sc(c1);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  sc = c1; // assign same connection
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+
+  Connection c2 = c1;
+  sc = c2; // assign a copy of same connection
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 3); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectRelease)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+    sc.release();
+    // sc goes out of scope, but not disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectMove)
+{
+  SignalOwner0 so;
+  unique_ptr<ScopedConnection> sc2;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+    sc2.reset(new ScopedConnection(std::move(sc)));
+
+    // sc goes out of scope, but not disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectInHandler)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0; bool hasHandler2 = false;
+  so.sig.connect([&] {
+    ++hit1;
+    if (!hasHandler2) {
+      so.sig.connect([&] { ++hit2; });
+      hasHandler2 = true;
+    }
+  });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 called
+  BOOST_CHECK_EQUAL(hit2, 0); // handler2 not called
+
+  // new subscription takes effect
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 2); // handler1 called
+  BOOST_CHECK_EQUAL(hit2, 1); // handler2 called
+}
+
+BOOST_AUTO_TEST_CASE(DisconnectSelfInHandler)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection connection;
+  connection = so.sig.connect([&] {
+    ++hit;
+    connection.disconnect();
+    BOOST_CHECK_EQUAL(so.isSigEmpty(), false); // disconnecting hasn't taken effect
+  });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  // disconnecting takes effect
+  BOOST_CHECK_EQUAL(so.isSigEmpty(), true);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ThrowInHandler)
+{
+  SignalOwner0 so;
+
+  struct HandlerError
+  {
+  };
+
+  int hit = 0;
+  so.sig.connect([&] {
+    ++hit;
+    throw HandlerError();
+  });
+
+  BOOST_CHECK_THROW(so.emitSignal(sig), HandlerError);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  BOOST_CHECK_THROW(so.emitSignal(sig), HandlerError);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(DestructInHandler)
+{
+  unique_ptr<SignalOwner0> so(new SignalOwner0());
+
+  int hit = 0;
+  so->sig.connect([&] {
+    ++hit;
+    so.reset();
+  });
+
+  BOOST_CHECK_NO_THROW(so->emitSignal(sig));
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+  BOOST_CHECK(so == nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace signal
+} // namespace util
+} // namespace ndn