util: add stop function to SegmentFetcher

refs: #4692

Change-Id: I8b7f0c52ac9dd22ed9a665daaa62ef130eac2e53
diff --git a/tests/unit-tests/util/segment-fetcher.t.cpp b/tests/unit-tests/util/segment-fetcher.t.cpp
index c0cb222..d52fa08 100644
--- a/tests/unit-tests/util/segment-fetcher.t.cpp
+++ b/tests/unit-tests/util/segment-fetcher.t.cpp
@@ -85,7 +85,7 @@
   }
 
   void
-  connectSignals(shared_ptr<SegmentFetcher> fetcher)
+  connectSignals(const shared_ptr<SegmentFetcher>& fetcher)
   {
     fetcher->onComplete.connect(bind(&Fixture::onComplete, this, _1));
     fetcher->onError.connect(bind(&Fixture::onError, this, _1));
@@ -730,6 +730,131 @@
   BOOST_CHECK_EQUAL(nErrors, 1);
 }
 
+BOOST_AUTO_TEST_CASE(Stop)
+{
+  DummyValidator acceptValidator;
+
+  auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  connectSignals(fetcher);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  fetcher->stop();
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  fetcher.reset();
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 0);
+
+  // Make sure we can re-assign w/o any complains from ASan
+  fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  connectSignals(fetcher);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+
+  // Stop from callback
+  bool fetcherStopped = false;
+
+  fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  fetcher->afterSegmentReceived.connect([&fetcher, &fetcherStopped] (const Data& data) {
+                                          fetcherStopped = true;
+                                          fetcher->stop();
+                                        });
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+  BOOST_CHECK(fetcherStopped);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Lifetime)
+{
+  // BasicSingleSegment, but with scoped fetcher
+
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+
+  weak_ptr<SegmentFetcher> weakFetcher;
+  {
+    auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+    weakFetcher = fetcher;
+    connectSignals(fetcher);
+
+    fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+    fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+    fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+    fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+  }
+
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), false);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), true);
+}
+
+BOOST_AUTO_TEST_CASE(OutOfScopeTimeout)
+{
+  DummyValidator acceptValidator;
+  SegmentFetcher::Options options;
+  options.maxTimeout = 3000_ms;
+
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+
+  weak_ptr<SegmentFetcher> weakFetcher;
+  {
+    auto fetcher = SegmentFetcher::start(face, Interest("/localhost/nfd/faces/list"),
+                                         acceptValidator, options);
+    weakFetcher = fetcher;
+    connectSignals(fetcher);
+    fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+    fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+    fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+    fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+  }
+
+  advanceClocks(500_ms, 7);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), true);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 2);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestSegmentFetcher
 BOOST_AUTO_TEST_SUITE_END() // Util