face: handle initially non-running Ethernet interfaces

refs #5096

Change-Id: I27cc913cc2478baa8dfaad08e67bd1b9703c43f4
diff --git a/tests/daemon/face/ethernet-fixture.hpp b/tests/daemon/face/ethernet-fixture.hpp
index 1898d50..0c98c72 100644
--- a/tests/daemon/face/ethernet-fixture.hpp
+++ b/tests/daemon/face/ethernet-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -58,17 +58,35 @@
       }
     }
     if (!netifs.empty()) {
-      netif = const_pointer_cast<ndn::net::NetworkInterface>(netifs.front());
+      defaultNetif = const_pointer_cast<ndn::net::NetworkInterface>(netifs.front());
     }
   }
 
+  /** \brief returns the first running interface
+   */
+  shared_ptr<ndn::net::NetworkInterface>
+  getRunningNetif() const
+  {
+    for (const auto& netif : netifs) {
+      if (netif->getState() == ndn::net::InterfaceState::RUNNING) {
+        return const_pointer_cast<ndn::net::NetworkInterface>(netif);
+      }
+    }
+
+    return nullptr;
+  }
+
   /** \brief create a UnicastEthernetTransport
    */
   void
-  initializeUnicast(ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+  initializeUnicast(shared_ptr<ndn::net::NetworkInterface> netif = nullptr,
+                    ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                     ethernet::Address remoteAddr = {0x00, 0x00, 0x5e, 0x00, 0x53, 0x5e})
   {
-    BOOST_ASSERT(netif != nullptr);
+    if (!netif) {
+      netif = defaultNetif;
+    }
+
     localEp = netif->getName();
     remoteEp = remoteAddr;
     transport = make_unique<UnicastEthernetTransport>(*netif, remoteEp, persistency, 2_s);
@@ -77,10 +95,14 @@
   /** \brief create a MulticastEthernetTransport
    */
   void
-  initializeMulticast(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS,
+  initializeMulticast(shared_ptr<ndn::net::NetworkInterface> netif = nullptr,
+                      ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS,
                       ethernet::Address mcastGroup = {0x01, 0x00, 0x5e, 0x90, 0x10, 0x5e})
   {
-    BOOST_ASSERT(netif != nullptr);
+    if (!netif) {
+      netif = defaultNetif;
+    }
+
     localEp = netif->getName();
     remoteEp = mcastGroup;
     transport = make_unique<MulticastEthernetTransport>(*netif, remoteEp, linkType);
@@ -93,7 +115,7 @@
    */
   std::vector<shared_ptr<const ndn::net::NetworkInterface>> netifs;
 
-  shared_ptr<ndn::net::NetworkInterface> netif;
+  shared_ptr<ndn::net::NetworkInterface> defaultNetif;
   unique_ptr<EthernetTransport> transport;
   std::string localEp;
   ethernet::Address remoteEp;
@@ -108,6 +130,15 @@
     } \
   } while (false)
 
+#define SKIP_IF_NO_RUNNING_ETHERNET_NETIF() \
+  do { \
+    if (!this->getRunningNetif()) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require a running " \
+                                "EthernetTransport-capable network interface"); \
+      return; \
+    } \
+  } while (false)
+
 } // namespace tests
 } // namespace face
 } // namespace nfd
diff --git a/tests/daemon/face/multicast-ethernet-transport.t.cpp b/tests/daemon/face/multicast-ethernet-transport.t.cpp
index 4e0df3c..84dead4 100644
--- a/tests/daemon/face/multicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/multicast-ethernet-transport.t.cpp
@@ -63,12 +63,12 @@
 
 BOOST_AUTO_TEST_CASE(NetifStateChange)
 {
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  initializeMulticast();
-  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+  SKIP_IF_NO_RUNNING_ETHERNET_NETIF();
+  auto netif = getRunningNetif();
+  initializeMulticast(netif);
 
-  // simulate 'ip link set IFNAME down'
-  getScheduler().schedule(10_ms, [this] { netif->setState(ndn::net::InterfaceState::DOWN); });
+  // Simulate setting interface administratively down
+  getScheduler().schedule(10_ms, [netif] { netif->setState(ndn::net::InterfaceState::DOWN); });
   transport->afterStateChange.connectSingleShot([this] (auto oldState, auto newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
     BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
@@ -77,9 +77,9 @@
   BOOST_CHECK_EQUAL(limitedIo.run(1, 1_s), LimitedIo::EXCEED_OPS);
   BOOST_CHECK_EQUAL(transport->getState(), TransportState::DOWN);
 
-  // simulate 'ip link set IFNAME up'
-  getScheduler().schedule(10_ms, [this] { netif->setState(ndn::net::InterfaceState::NO_CARRIER); });
-  getScheduler().schedule(80_ms, [this] { netif->setState(ndn::net::InterfaceState::RUNNING); });
+  // Simulate setting interface administratively up
+  getScheduler().schedule(10_ms, [netif] { netif->setState(ndn::net::InterfaceState::NO_CARRIER); });
+  getScheduler().schedule(80_ms, [netif] { netif->setState(ndn::net::InterfaceState::RUNNING); });
   transport->afterStateChange.connectSingleShot([this] (auto oldState, auto newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::DOWN);
     BOOST_CHECK_EQUAL(newState, TransportState::UP);
@@ -93,25 +93,25 @@
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
   initializeMulticast();
-  BOOST_CHECK_EQUAL(transport->getMtu(), netif->getMtu());
+  BOOST_CHECK_EQUAL(transport->getMtu(), defaultNetif->getMtu());
 
   // netif changes MTU from initial value to 1024
-  netif->setMtu(1024);
+  defaultNetif->setMtu(1024);
   BOOST_CHECK_EQUAL(transport->getMtu(), 1024);
 
   // netif changes MTU from 1024 to 4000
-  netif->setMtu(4000);
+  defaultNetif->setMtu(4000);
   BOOST_CHECK_EQUAL(transport->getMtu(), 4000);
 
   // netif changes MTU from 4000 to 0
-  netif->setMtu(0);
+  defaultNetif->setMtu(0);
   BOOST_CHECK_EQUAL(transport->getMtu(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(Close)
 {
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  initializeMulticast();
+  SKIP_IF_NO_RUNNING_ETHERNET_NETIF();
+  initializeMulticast(getRunningNetif());
 
   transport->afterStateChange.connectSingleShot([] (auto oldState, auto newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
diff --git a/tests/daemon/face/unicast-ethernet-transport.t.cpp b/tests/daemon/face/unicast-ethernet-transport.t.cpp
index 634a119..79a0f38 100644
--- a/tests/daemon/face/unicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/unicast-ethernet-transport.t.cpp
@@ -27,6 +27,8 @@
 
 #include "ethernet-fixture.hpp"
 
+#include "common/global.hpp"
+
 namespace nfd {
 namespace face {
 namespace tests {
@@ -58,29 +60,57 @@
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), true);
 }
 
+BOOST_AUTO_TEST_CASE(NetifStateChange)
+{
+  SKIP_IF_NO_RUNNING_ETHERNET_NETIF();
+  auto netif = getRunningNetif();
+  initializeUnicast(netif);
+
+  // Simulate setting interface administratively down
+  getScheduler().schedule(10_ms, [netif] { netif->setState(ndn::net::InterfaceState::DOWN); });
+  transport->afterStateChange.connectSingleShot([this] (auto oldState, auto newState) {
+    BOOST_CHECK_EQUAL(oldState, TransportState::UP);
+    BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
+    this->limitedIo.afterOp();
+  });
+  BOOST_CHECK_EQUAL(limitedIo.run(1, 1_s), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::DOWN);
+
+  // Simulate setting interface administratively up
+  getScheduler().schedule(10_ms, [netif] { netif->setState(ndn::net::InterfaceState::NO_CARRIER); });
+  getScheduler().schedule(80_ms, [netif] { netif->setState(ndn::net::InterfaceState::RUNNING); });
+  transport->afterStateChange.connectSingleShot([this] (auto oldState, auto newState) {
+    BOOST_CHECK_EQUAL(oldState, TransportState::DOWN);
+    BOOST_CHECK_EQUAL(newState, TransportState::UP);
+    this->limitedIo.afterOp();
+  });
+  BOOST_CHECK_EQUAL(limitedIo.run(1, 1_s), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+}
+
 BOOST_AUTO_TEST_CASE(NetifMtuChange)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
   initializeUnicast();
-  BOOST_CHECK_EQUAL(transport->getMtu(), netif->getMtu());
+  BOOST_CHECK_EQUAL(transport->getMtu(), defaultNetif->getMtu());
 
   // netif changes MTU from initial value to 1024
-  netif->setMtu(1024);
+  defaultNetif->setMtu(1024);
   BOOST_CHECK_EQUAL(transport->getMtu(), 1024);
 
   // netif changes MTU from 1024 to 4000
-  netif->setMtu(4000);
+  defaultNetif->setMtu(4000);
   BOOST_CHECK_EQUAL(transport->getMtu(), 4000);
 
   // netif changes MTU from 4000 to 0
-  netif->setMtu(0);
+  defaultNetif->setMtu(0);
   BOOST_CHECK_EQUAL(transport->getMtu(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(ExpirationTime)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  initializeUnicast(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  initializeUnicast(nullptr, ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
   BOOST_CHECK_NE(transport->getExpirationTime(), time::steady_clock::TimePoint::max());
 
   transport->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
@@ -92,8 +122,8 @@
 
 BOOST_AUTO_TEST_CASE(Close)
 {
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  initializeUnicast();
+  SKIP_IF_NO_RUNNING_ETHERNET_NETIF();
+  initializeUnicast(getRunningNetif());
 
   transport->afterStateChange.connectSingleShot([] (auto oldState, auto newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
@@ -113,8 +143,8 @@
 
 BOOST_AUTO_TEST_CASE(IdleClose)
 {
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  initializeUnicast(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  SKIP_IF_NO_RUNNING_ETHERNET_NETIF();
+  initializeUnicast(getRunningNetif(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
 
   int nStateChanges = 0;
   transport->afterStateChange.connect(