util: add isConnected() in signal::Connection and signal::ScopedConnection

Change-Id: Iddf2a084ccf137a691cd85c54003182acf0e5afd
Refs: #2308
diff --git a/src/util/signal-connection.cpp b/src/util/signal-connection.cpp
index 8660e4d..7c74246 100644
--- a/src/util/signal-connection.cpp
+++ b/src/util/signal-connection.cpp
@@ -46,6 +46,12 @@
 }
 
 bool
+Connection::isConnected() const
+{
+  return !m_disconnect.expired();
+}
+
+bool
 Connection::operator==(const Connection& other) const
 {
   shared_ptr<function<void()>> f1 = m_disconnect.lock();
diff --git a/src/util/signal-connection.hpp b/src/util/signal-connection.hpp
index 1276182..f2ac45e 100644
--- a/src/util/signal-connection.hpp
+++ b/src/util/signal-connection.hpp
@@ -45,6 +45,12 @@
   void
   disconnect();
 
+  /** \brief check if connected to the signal
+   *  \return false if disconnected from the signal
+   */
+  bool
+  isConnected() const;
+
   /** \brief compare for equality
    *
    *  Two connections are equal if they both refer to the same connection that isn't disconnected,
diff --git a/src/util/signal-scoped-connection.cpp b/src/util/signal-scoped-connection.cpp
index ae90c5f..a0f2d1c 100644
--- a/src/util/signal-scoped-connection.cpp
+++ b/src/util/signal-scoped-connection.cpp
@@ -61,6 +61,12 @@
   m_connection.disconnect();
 }
 
+bool
+ScopedConnection::isConnected() const
+{
+  return m_connection.isConnected();
+}
+
 void
 ScopedConnection::release()
 {
diff --git a/src/util/signal-scoped-connection.hpp b/src/util/signal-scoped-connection.hpp
index 9104791..80717e5 100644
--- a/src/util/signal-scoped-connection.hpp
+++ b/src/util/signal-scoped-connection.hpp
@@ -61,6 +61,13 @@
   void
   disconnect();
 
+  /** \brief check if the connection is connected to the signal
+   *  \return false when a default-constructed connection is used, the connection is released,
+   *          or the connection is disconnected
+   */
+  bool
+  isConnected() const;
+
   /** \brief releases the connection so that it won't be disconnected
    *         when this ScopedConnection is destructed
    */
diff --git a/tests/unit-tests/util/signal.cpp b/tests/unit-tests/util/signal.cpp
index f538629..df137fe 100644
--- a/tests/unit-tests/util/signal.cpp
+++ b/tests/unit-tests/util/signal.cpp
@@ -164,12 +164,17 @@
 
   int hit = 0;
   Connection c1 = so.sig.connect([&hit] { ++hit; });
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
 
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 1); // handler called
 
   Connection c2 = c1; // make a copy
+  BOOST_CHECK_EQUAL(c2.isConnected(), true);
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
   c2.disconnect();
+  BOOST_CHECK_EQUAL(c2.isConnected(), false);
+  BOOST_CHECK_EQUAL(c1.isConnected(), false);
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 1); // handler not called
 
@@ -187,7 +192,9 @@
   so->emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 1); // handler called
 
+  BOOST_CHECK_EQUAL(connection.isConnected(), true);
   so.reset(); // destruct EventEmitter
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
   BOOST_CHECK_NO_THROW(connection.disconnect());
 }
 
@@ -199,6 +206,7 @@
   {
     ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
 
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
     so.emitSignal(sig);
     BOOST_CHECK_EQUAL(hit, 1); // handler called
 
@@ -215,11 +223,13 @@
 
   int hit1 = 0, hit2 = 0;
   ScopedConnection sc = so.sig.connect([&hit1] { ++hit1; });
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit1, 1); // handler1 called
 
   sc = so.sig.connect([&hit2] { ++hit2; }); // handler1 is disconnected
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit1, 1); // handler1 not called
@@ -236,15 +246,22 @@
   ScopedConnection sc(c1);
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 1); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
   sc = c1; // assign same connection
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 2); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
   Connection c2 = c1;
   sc = c2; // assign a copy of same connection
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 3); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(c2.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
 }
 
 BOOST_AUTO_TEST_CASE(AutoDisconnectRelease)
@@ -257,8 +274,10 @@
 
     so.emitSignal(sig);
     BOOST_CHECK_EQUAL(hit, 1); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
     sc.release();
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
     // sc goes out of scope, but not disconnecting
   }
 
@@ -277,8 +296,11 @@
 
     so.emitSignal(sig);
     BOOST_CHECK_EQUAL(hit, 1); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
 
     sc2.reset(new ScopedConnection(std::move(sc)));
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    BOOST_CHECK_EQUAL(sc2->isConnected(), true);
 
     // sc goes out of scope, but not disconnecting
   }
@@ -307,7 +329,9 @@
 
   int hit = 0;
   Connection conn = so.sig.connectSingleShot([&hit] { ++hit; });
+  BOOST_CHECK_EQUAL(conn.isConnected(), true);
   conn.disconnect();
+  BOOST_CHECK_EQUAL(conn.isConnected(), false);
 
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 0); // handler not called
@@ -356,9 +380,12 @@
 
   int hit = 0;
   Connection connection;
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
   connection = so.sig.connect(bind([&] (SignalOwner0& so) {
     ++hit;
+    BOOST_CHECK_EQUAL(connection.isConnected(), true);
     connection.disconnect();
+    BOOST_CHECK_EQUAL(connection.isConnected(), false);
     BOOST_CHECK_EQUAL(so.isSigEmpty(), false); // disconnecting hasn't taken effect
   }, ref(so)));
   // Bug 2302: 'so' needs to be bound to the handler;
@@ -366,6 +393,7 @@
 
   so.emitSignal(sig);
   BOOST_CHECK_EQUAL(hit, 1); // handler called
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
 
   // disconnecting takes effect
   BOOST_CHECK_EQUAL(so.isSigEmpty(), true);