Rename 'tests/unit-tests' directory to 'tests/unit'

Change-Id: I78ea29938259fac288781bed12fb2399ac7eba26
diff --git a/tests/unit/util/backports.t.cpp b/tests/unit/util/backports.t.cpp
new file mode 100644
index 0000000..7a124e0
--- /dev/null
+++ b/tests/unit/util/backports.t.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/backports.hpp"
+
+#include "boost-test.hpp"
+#include <numeric>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestBackports)
+
+BOOST_AUTO_TEST_CASE(Clamp)
+{
+  int x = clamp(5, 1, 10);
+  BOOST_CHECK_EQUAL(x, 5);
+
+  x = clamp(-5, 1, 10);
+  BOOST_CHECK_EQUAL(x, 1);
+
+  x = clamp(15, 1, 10);
+  BOOST_CHECK_EQUAL(x, 10);
+
+  x = clamp(5, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 5);
+
+  x = clamp(-5, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 1);
+
+  x = clamp(15, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 10);
+}
+
+BOOST_AUTO_TEST_CASE(OstreamJoiner)
+{
+  boost::test_tools::output_test_stream os;
+
+  auto joiner1 = ostream_joiner<char>(os, ' ');
+  auto joiner2 = make_ostream_joiner(os, ' ');
+  static_assert(std::is_same<decltype(joiner1), decltype(joiner2)>::value, "");
+
+  std::vector<int> v(5);
+  std::iota(v.begin(), v.end(), 1);
+  std::copy(v.begin(), v.end(), joiner2);
+  BOOST_CHECK(os.is_equal("1 2 3 4 5"));
+
+  auto joiner3 = make_ostream_joiner(os, "...");
+  std::copy(v.begin(), v.end(), joiner3);
+  BOOST_CHECK(os.is_equal("1...2...3...4...5"));
+
+  joiner3 = "one";
+  BOOST_CHECK(os.is_equal("one"));
+  joiner3 = "two";
+  BOOST_CHECK(os.is_equal("...two"));
+  ++joiner3 = "three";
+  BOOST_CHECK(os.is_equal("...three"));
+  joiner3++ = "four";
+  BOOST_CHECK(os.is_equal("...four"));
+
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, ""));
+  BOOST_CHECK(os.is_equal("12345"));
+
+  std::string delimiter("_");
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, delimiter));
+  BOOST_CHECK(os.is_equal("1_2_3_4_5"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBackports
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/concepts.t.cpp b/tests/unit/util/concepts.t.cpp
new file mode 100644
index 0000000..3141ec5
--- /dev/null
+++ b/tests/unit/util/concepts.t.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#include "util/concepts.hpp"
+
+namespace ndn {
+namespace tests {
+
+class WireEncodableType
+{
+public:
+  const Block&
+  wireEncode();
+};
+BOOST_CONCEPT_ASSERT((WireEncodable<WireEncodableType>));
+
+class WireEncodableType2
+{
+public:
+  Block
+  wireEncode();
+};
+BOOST_CONCEPT_ASSERT((WireEncodable<WireEncodableType2>));
+
+class WireDecodableType
+{
+public:
+  explicit
+  WireDecodableType(const Block& wire);
+
+  void
+  wireDecode(const Block& wire);
+};
+BOOST_CONCEPT_ASSERT((WireDecodable<WireDecodableType>));
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/config-file-home/.ndn/client.conf b/tests/unit/util/config-file-home/.ndn/client.conf
new file mode 100644
index 0000000..ba9f623
--- /dev/null
+++ b/tests/unit/util/config-file-home/.ndn/client.conf
@@ -0,0 +1,2 @@
+a=/path/to/nowhere
+b=some-othervalue.01
diff --git a/tests/unit/util/config-file-malformed-home/.ndn/client.conf b/tests/unit/util/config-file-malformed-home/.ndn/client.conf
new file mode 100644
index 0000000..7898192
--- /dev/null
+++ b/tests/unit/util/config-file-malformed-home/.ndn/client.conf
@@ -0,0 +1 @@
+a
diff --git a/tests/unit/util/config-file.t.cpp b/tests/unit/util/config-file.t.cpp
new file mode 100644
index 0000000..658018b
--- /dev/null
+++ b/tests/unit/util/config-file.t.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/config-file.hpp"
+#include "../test-home-env-saver.hpp"
+
+#include "boost-test.hpp"
+
+#include <cstdlib>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestConfigFile, TestHomeEnvSaver)
+
+BOOST_AUTO_TEST_CASE(Parse)
+{
+  namespace fs = boost::filesystem;
+
+  setenv("TEST_HOME", "tests/unit/util/config-file-home", 1);
+
+  fs::path homePath(fs::absolute(std::getenv("TEST_HOME")));
+  homePath /= ".ndn/client.conf";
+
+  ConfigFile config;
+  BOOST_REQUIRE_EQUAL(config.getPath(), homePath);
+
+  const ConfigFile::Parsed& parsed = config.getParsedConfiguration();
+  BOOST_CHECK_EQUAL(parsed.get<std::string>("a"), "/path/to/nowhere");
+  BOOST_CHECK_EQUAL(parsed.get<std::string>("b"), "some-othervalue.01");
+}
+
+BOOST_AUTO_TEST_CASE(ParseEmptyPath)
+{
+  setenv("TEST_HOME", "tests/unit/util/does/not/exist", 1);
+
+  BOOST_CHECK_NO_THROW(ConfigFile config);
+}
+
+BOOST_AUTO_TEST_CASE(ParseMalformed)
+{
+  setenv("TEST_HOME", "tests/unit/util/config-file-malformed-home", 1);
+
+  BOOST_CHECK_THROW(ConfigFile config, ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestConfigFile
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/dummy-client-face.t.cpp b/tests/unit/util/dummy-client-face.t.cpp
new file mode 100644
index 0000000..aed8cd6
--- /dev/null
+++ b/tests/unit/util/dummy-client-face.t.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "../identity-management-time-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestDummyClientFace, ndn::tests::IdentityManagementTimeFixture)
+
+BOOST_AUTO_TEST_CASE(ProcessEventsOverride)
+{
+  bool isOverrideInvoked = false;
+  auto override = [&] (time::milliseconds timeout) {
+    isOverrideInvoked = true;
+    BOOST_CHECK_EQUAL(timeout, 200_ms);
+  };
+
+  DummyClientFace face(io, {false, false, override});
+  face.processEvents(200_ms);
+  BOOST_CHECK(isOverrideInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(BroadcastLink)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+  face1.linkTo(face2);
+
+  int nFace1Interest = 0;
+  int nFace2Interest = 0;
+  face1.setInterestFilter("/face1",
+                          [&] (const InterestFilter&, const Interest& interest) {
+                            BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face1/data");
+                            nFace1Interest++;
+                            face1.put(ndn::tests::makeNack(interest, lp::NackReason::NO_ROUTE));
+                          }, nullptr, nullptr);
+  face2.setInterestFilter("/face2",
+                          [&] (const InterestFilter&, const Interest& interest) {
+                            BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face2/data");
+                            nFace2Interest++;
+                            face2.put(*ndn::tests::makeData("/face2/data"));
+                            return;
+                          }, nullptr, nullptr);
+
+  advanceClocks(25_ms, 4);
+
+  int nFace1Data = 0;
+  int nFace2Nack = 0;
+  face1.expressInterest(*makeInterest("/face2/data"),
+                        [&] (const Interest& i, const Data& d) {
+                          BOOST_CHECK_EQUAL(d.getName().toUri(), "/face2/data");
+                          nFace1Data++;
+                        }, nullptr, nullptr);
+  face2.expressInterest(*makeInterest("/face1/data"),
+                        [&] (const Interest& i, const Data& d) {
+                          BOOST_CHECK(false);
+                        },
+                        [&] (const Interest& i, const lp::Nack& n) {
+                          BOOST_CHECK_EQUAL(n.getInterest().getName().toUri(), "/face1/data");
+                          nFace2Nack++;
+                        }, nullptr);
+
+  advanceClocks(10_ms, 100);
+
+  BOOST_CHECK_EQUAL(nFace1Data, 1);
+  BOOST_CHECK_EQUAL(nFace2Nack, 1);
+  BOOST_CHECK_EQUAL(nFace1Interest, 1);
+  BOOST_CHECK_EQUAL(nFace2Interest, 1);
+}
+
+BOOST_AUTO_TEST_CASE(BroadcastLinkDestroy)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+
+  face1.linkTo(face2);
+  face2.unlink();
+  BOOST_CHECK(face1.m_bcastLink == nullptr);
+
+  DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+  face1.linkTo(face2);
+  face3.linkTo(face1);
+  face2.unlink();
+  BOOST_CHECK(face1.m_bcastLink != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(AlreadyLinkException)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face4(io, m_keyChain, DummyClientFace::Options{true, true});
+
+  face1.linkTo(face2);
+  face3.linkTo(face4);
+
+  BOOST_CHECK_THROW(face2.linkTo(face3), DummyClientFace::AlreadyLinkedError);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDummyClientFace
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/indented-stream.t.cpp b/tests/unit/util/indented-stream.t.cpp
new file mode 100644
index 0000000..e450c4a
--- /dev/null
+++ b/tests/unit/util/indented-stream.t.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/indented-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestIndentedStream)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  output_test_stream os;
+
+  os << "Hello" << std::endl;
+  {
+    IndentedStream os1(os, " [prefix] ");
+    os1 << "," << "\n";
+    {
+      IndentedStream os2(os1, " [another prefix] ");
+      os2 << "World!" << "\n";
+    }
+  }
+
+  BOOST_CHECK(os.is_equal("Hello\n"
+                          " [prefix] ,\n"
+                          " [prefix]  [another prefix] World!\n"
+                          ));
+}
+
+BOOST_AUTO_TEST_CASE(BasicWithFlushes) // Bug #2723
+{
+  output_test_stream os;
+
+  os << "Hello" << std::endl;
+  {
+    IndentedStream os1(os, " [prefix] ");
+    os1 << "," << std::endl;
+    {
+      IndentedStream os2(os1, " [another prefix] ");
+      os2 << "World!" << std::endl;
+    }
+  }
+
+  BOOST_CHECK(os.is_equal("Hello\n"
+                          " [prefix] ,\n"
+                          " [prefix]  [another prefix] World!\n"
+                          ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIndentedStream
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/io.t.cpp b/tests/unit/util/io.t.cpp
new file mode 100644
index 0000000..1b46bca
--- /dev/null
+++ b/tests/unit/util/io.t.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/io.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace tests {
+
+class IoFixture
+{
+protected:
+  IoFixture()
+    : filepath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) /= "TestIo")
+    , filename(filepath.string())
+  {
+    boost::filesystem::create_directories(filepath.parent_path());
+  }
+
+  ~IoFixture()
+  {
+    boost::system::error_code ec;
+    boost::filesystem::remove(filepath, ec); // ignore error
+  }
+
+  /** \brief create a directory at filename, so that it's neither readable nor writable as a file
+   */
+  void
+  mkdir() const
+  {
+    boost::filesystem::create_directory(filepath);
+  }
+
+  template<typename Container, typename CharT = typename Container::value_type>
+  Container
+  readFile() const
+  {
+    Container container;
+    std::ifstream fs(filename, std::ios_base::binary);
+    char ch;
+    while (fs.get(ch)) {
+      container.push_back(static_cast<CharT>(ch));
+    }
+    return container;
+  }
+
+  template<typename Container, typename CharT = typename Container::value_type>
+  void
+  writeFile(const Container& content) const
+  {
+    std::ofstream fs(filename, std::ios_base::binary);
+    for (CharT ch : content) {
+      fs.put(static_cast<char>(ch));
+    }
+    fs.close();
+    BOOST_REQUIRE_MESSAGE(fs, "error writing file");
+  }
+
+protected:
+  const boost::filesystem::path filepath;
+  const std::string filename;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestIo, IoFixture)
+
+class EncodableType
+{
+public:
+  Block
+  wireEncode() const
+  {
+    if (shouldThrow) {
+      BOOST_THROW_EXCEPTION(tlv::Error("encode error"));
+    }
+
+    // block will be 0xAA, 0x01, 0xDD
+    return makeNonNegativeIntegerBlock(0xAA, 0xDD);
+  }
+
+public:
+  bool shouldThrow = false;
+};
+
+template<bool SHOULD_THROW = false>
+class DecodableTypeTpl
+{
+public:
+  DecodableTypeTpl() = default;
+
+  explicit
+  DecodableTypeTpl(const Block& block)
+  {
+    this->wireDecode(block);
+  }
+
+  void
+  wireDecode(const Block& block)
+  {
+    if (m_shouldThrow) {
+      BOOST_THROW_EXCEPTION(tlv::Error("decode error"));
+    }
+
+    // block must be 0xBB, 0x01, 0xEE
+    BOOST_CHECK_EQUAL(block.type(), 0xBB);
+    BOOST_REQUIRE_EQUAL(block.value_size(), 1);
+    BOOST_CHECK_EQUAL(block.value()[0], 0xEE);
+  }
+
+private:
+  bool m_shouldThrow = SHOULD_THROW;
+};
+
+typedef DecodableTypeTpl<false> DecodableType;
+typedef DecodableTypeTpl<true> DecodableTypeThrow;
+
+BOOST_AUTO_TEST_CASE(LoadNoEncoding)
+{
+  this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::NO_ENCODING);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64)
+{
+  this->writeFile<std::string>("uwHu\n"); // printf '\xBB\x01\xEE' | base64
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64Newline64)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAA\n");
+  // printf '\x08\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | base64
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64Newline32)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAA\n");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64NewlineEnd)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64NoNewline)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadHex)
+{
+  this->writeFile<std::string>("BB01EE");
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::HEX);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadException)
+{
+  this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
+  shared_ptr<DecodableTypeThrow> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableTypeThrow>(filename, io::NO_ENCODING));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadNotHex)
+{
+  this->writeFile<std::string>("not-hex");
+  shared_ptr<DecodableType> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::HEX));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadFileNotReadable)
+{
+  shared_ptr<DecodableType> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::NO_ENCODING));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(SaveNoEncoding)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::NO_ENCODING));
+  auto content = this->readFile<std::vector<uint8_t>>();
+  uint8_t expected[] = {0xAA, 0x01, 0xDD};
+  BOOST_CHECK_EQUAL_COLLECTIONS(content.begin(), content.end(),
+                                expected, expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(SaveBase64)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::BASE64));
+  auto content = this->readFile<std::string>();
+  BOOST_CHECK_EQUAL(content, "qgHd\n"); // printf '\xAA\x01\xDD' | base64
+}
+
+BOOST_AUTO_TEST_CASE(SaveHex)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::HEX));
+  auto content = this->readFile<std::string>();
+  BOOST_CHECK_EQUAL(content, "AA01DD");
+}
+
+BOOST_AUTO_TEST_CASE(SaveException)
+{
+  EncodableType encoded;
+  encoded.shouldThrow = true;
+  BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SaveFileNotWritable)
+{
+  this->mkdir();
+  EncodableType encoded;
+  encoded.shouldThrow = true;
+  BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
+}
+
+class IdCertFixture : public IoFixture
+                    , public IdentityManagementFixture
+{
+};
+
+BOOST_FIXTURE_TEST_CASE(IdCert, IdCertFixture)
+{
+  auto identity = addIdentity("/TestIo/IdCert", RsaKeyParams());
+  const auto& cert = identity.getDefaultKey().getDefaultCertificate();
+  io::save(cert, filename);
+
+  auto readCert = io::load<security::v2::Certificate>(filename);
+
+  BOOST_REQUIRE(readCert != nullptr);
+  BOOST_CHECK_EQUAL(cert.getName(), readCert->getName());
+
+  this->writeFile<std::string>("");
+  readCert = io::load<security::v2::Certificate>(filename);
+  BOOST_REQUIRE(readCert == nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIo
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/log-filter-module.cpp b/tests/unit/util/log-filter-module.cpp
new file mode 100644
index 0000000..cf3b628
--- /dev/null
+++ b/tests/unit/util/log-filter-module.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(fm.FilterModule);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromFilterModule()
+{
+  NDN_LOG_TRACE("traceFM");
+  NDN_LOG_DEBUG("debugFM");
+  NDN_LOG_INFO("infoFM");
+  NDN_LOG_WARN("warnFM");
+  NDN_LOG_ERROR("errorFM");
+  NDN_LOG_FATAL("fatalFM");
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
+
diff --git a/tests/unit/util/log-module1.cpp b/tests/unit/util/log-module1.cpp
new file mode 100644
index 0000000..2099a75
--- /dev/null
+++ b/tests/unit/util/log-module1.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(Module1);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromModule1()
+{
+  NDN_LOG_TRACE("trace" << 1);
+  NDN_LOG_DEBUG("debug" << 1);
+  NDN_LOG_INFO("info" << 1);
+  NDN_LOG_WARN("warn" << 1);
+  NDN_LOG_ERROR("error" << 1);
+  NDN_LOG_FATAL("fatal" << 1);
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/log-module2.cpp b/tests/unit/util/log-module2.cpp
new file mode 100644
index 0000000..273165d
--- /dev/null
+++ b/tests/unit/util/log-module2.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(Module2);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromModule2()
+{
+  NDN_LOG_TRACE("trace" << 2);
+  NDN_LOG_DEBUG("debug" << 2);
+  NDN_LOG_INFO("info" << 2);
+  NDN_LOG_WARN("warn" << 2);
+  NDN_LOG_ERROR("error" << 2);
+  NDN_LOG_FATAL("fatal" << 2);
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/logger.t.cpp b/tests/unit/util/logger.t.cpp
new file mode 100644
index 0000000..cc48425
--- /dev/null
+++ b/tests/unit/util/logger.t.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/logger.hpp"
+#include "util/logging.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestLogger)
+
+BOOST_AUTO_TEST_CASE(LegalAlphanumeric)
+{
+  Logger logger("ndnTest123");
+  auto mNames = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(mNames.count("ndnTest123"), 1);
+  BOOST_CHECK(logger.isLevelEnabled(LogLevel::NONE));
+  Logging::get().removeLogger(logger);
+}
+
+BOOST_AUTO_TEST_CASE(AllLegalSymbols)
+{
+  Logger logger("ndn.~#%.test_<check>1-2-3");
+  auto mNames = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(mNames.count("ndn.~#%.test_<check>1-2-3"), 1);
+  BOOST_CHECK(logger.isLevelEnabled(LogLevel::NONE));
+  Logging::get().removeLogger(logger);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyLogger)
+{
+  BOOST_CHECK_THROW(Logger logger(""), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidSymbol)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn.test.*"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(StartsWithPeriod)
+{
+  BOOST_CHECK_THROW(Logger logger(".ndn.test"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(EndsWithPeriod)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn.test."), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(ConsecutivePeriods)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn..test"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLogger
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/logging.t.cpp b/tests/unit/util/logging.t.cpp
new file mode 100644
index 0000000..b431a05
--- /dev/null
+++ b/tests/unit/util/logging.t.cpp
@@ -0,0 +1,652 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/logging.hpp"
+#include "util/logger.hpp"
+
+#include "../unit-test-time-fixture.hpp"
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+NDN_LOG_INIT(ndn.util.tests.Logging);
+
+void
+logFromModule1();
+
+void
+logFromModule2();
+
+void
+logFromFilterModule();
+
+static void
+logFromNewLogger(const char* moduleName)
+{
+  Logger logger(moduleName);
+  auto ndn_cxx_getLogger = [&logger] () -> Logger& { return logger; };
+
+  NDN_LOG_TRACE("trace" << moduleName);
+  NDN_LOG_DEBUG("debug" << moduleName);
+  NDN_LOG_INFO("info" << moduleName);
+  NDN_LOG_WARN("warn" << moduleName);
+  NDN_LOG_ERROR("error" << moduleName);
+  NDN_LOG_FATAL("fatal" << moduleName);
+
+  BOOST_CHECK(Logging::get().removeLogger(logger));
+}
+
+namespace ns1 {
+
+NDN_LOG_INIT(ndn.util.tests.ns1);
+
+static void
+logFromNamespace1()
+{
+  NDN_LOG_INFO("hello world from ns1");
+}
+
+} // namespace ns1
+
+namespace ns2 {
+
+NDN_LOG_INIT(ndn.util.tests.ns2);
+
+static void
+logFromNamespace2()
+{
+  NDN_LOG_INFO("hi there from ns2");
+}
+
+} // namespace ns2
+
+class ClassWithLogger
+{
+public:
+  void
+  logFromConstMemberFunction() const
+  {
+    NDN_LOG_INFO("const member function");
+  }
+
+  static void
+  logFromStaticMemberFunction()
+  {
+    NDN_LOG_INFO("static member function");
+  }
+
+private:
+  NDN_LOG_MEMBER_DECL();
+};
+
+NDN_LOG_MEMBER_INIT(ClassWithLogger, ndn.util.tests.ClassWithLogger);
+
+template<class T, class U>
+class ClassTemplateWithLogger
+{
+public:
+  void
+  logFromMemberFunction()
+  {
+    NDN_LOG_INFO("class template non-static member function");
+  }
+
+  static void
+  logFromStaticMemberFunction()
+  {
+    NDN_LOG_INFO("class template static member function");
+  }
+
+private:
+  NDN_LOG_MEMBER_DECL();
+};
+
+// Technically this declaration is not necessary in this case,
+// but we want to test that the macro expands to well-formed code
+NDN_LOG_MEMBER_DECL_SPECIALIZED((ClassTemplateWithLogger<int, double>));
+
+NDN_LOG_MEMBER_INIT_SPECIALIZED((ClassTemplateWithLogger<int, double>), ndn.util.tests.Specialized1);
+NDN_LOG_MEMBER_INIT_SPECIALIZED((ClassTemplateWithLogger<int, std::string>), ndn.util.tests.Specialized2);
+
+const time::microseconds LOG_SYSTIME(1468108800311239LL);
+const std::string LOG_SYSTIME_STR("1468108800.311239");
+
+class LoggingFixture : public ndn::tests::UnitTestTimeFixture
+{
+protected:
+  LoggingFixture()
+    : m_oldEnabledLevel(Logging::get().getLevels())
+    , m_oldDestination(Logging::get().getDestination())
+  {
+    this->systemClock->setNow(LOG_SYSTIME);
+    Logging::get().resetLevels();
+    Logging::setDestination(os);
+  }
+
+  ~LoggingFixture()
+  {
+    Logging::get().setLevelImpl(m_oldEnabledLevel);
+    Logging::setDestination(m_oldDestination);
+  }
+
+protected:
+  boost::test_tools::output_test_stream os;
+
+private:
+  std::unordered_map<std::string, LogLevel> m_oldEnabledLevel;
+  shared_ptr<std::ostream> m_oldDestination;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestLogging, LoggingFixture)
+
+BOOST_AUTO_TEST_CASE(GetLoggerNames)
+{
+  // check that all names are registered from the start even if
+  // logger instances are lazily created on first use
+  auto n = Logging::getLoggerNames().size();
+  NDN_LOG_TRACE("GetLoggerNames");
+  auto names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.size(), n);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Logging"), 1);
+}
+
+BOOST_AUTO_TEST_SUITE(Severity)
+
+BOOST_AUTO_TEST_CASE(None)
+{
+  Logging::setLevel("Module1", LogLevel::NONE);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  Logging::setLevel("Module1", LogLevel::ERROR);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Warn)
+{
+  Logging::setLevel("Module1", LogLevel::WARN);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Info)
+{
+  Logging::setLevel("Module1", LogLevel::INFO);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Debug)
+{
+  Logging::setLevel("Module1", LogLevel::DEBUG);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Trace)
+{
+  Logging::setLevel("Module1", LogLevel::TRACE);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " TRACE: [Module1] trace1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(All)
+{
+  Logging::setLevel("Module1", LogLevel::ALL);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " TRACE: [Module1] trace1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Severity
+
+BOOST_AUTO_TEST_CASE(NamespaceLogger)
+{
+  Logging::setLevel("ndn.util.tests.ns1", LogLevel::INFO);
+  Logging::setLevel("ndn.util.tests.ns2", LogLevel::DEBUG);
+
+  const auto& levels = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(levels.size(), 2);
+  BOOST_CHECK_EQUAL(levels.at("ndn.util.tests.ns1"), LogLevel::INFO);
+  BOOST_CHECK_EQUAL(levels.at("ndn.util.tests.ns2"), LogLevel::DEBUG);
+
+  const auto& names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ns1"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ns2"), 1);
+
+  ns1::logFromNamespace1();
+  ns2::logFromNamespace2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ns1] hello world from ns1\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ns2] hi there from ns2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(MemberLogger)
+{
+  Logging::setLevel("ndn.util.tests.ClassWithLogger", LogLevel::INFO);
+  Logging::setLevel("ndn.util.tests.Specialized1", LogLevel::INFO);
+  // ndn.util.tests.Specialized2 is not enabled
+
+  const auto& names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ClassWithLogger"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Specialized1"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Specialized2"), 1);
+
+  ClassWithLogger::logFromStaticMemberFunction();
+  ClassWithLogger{}.logFromConstMemberFunction();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ClassWithLogger] static member function\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ClassWithLogger] const member function\n"
+    ));
+
+  ClassTemplateWithLogger<int, double>::logFromStaticMemberFunction();
+  ClassTemplateWithLogger<int, double>{}.logFromMemberFunction();
+  ClassTemplateWithLogger<int, std::string>::logFromStaticMemberFunction();
+  ClassTemplateWithLogger<int, std::string>{}.logFromMemberFunction();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.Specialized1] class template static member function\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.Specialized1] class template non-static member function\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SameNameLoggers)
+{
+  Logging::setLevel("Module1", LogLevel::WARN);
+  logFromModule1();
+  logFromNewLogger("Module1");
+
+  BOOST_CHECK_EQUAL(Logging::getLoggerNames().count("Module1"), 1);
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warnModule1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] errorModule1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatalModule1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(LateRegistration)
+{
+  Logging::setLevel("Module3", LogLevel::DEBUG);
+  logFromNewLogger("Module3");
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " DEBUG: [Module3] debugModule3\n" +
+    LOG_SYSTIME_STR + " INFO: [Module3] infoModule3\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module3] warnModule3\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module3] errorModule3\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module3] fatalModule3\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE(DefaultSeverity)
+
+BOOST_AUTO_TEST_CASE(Unset)
+{
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(NoOverride)
+{
+  Logging::setLevel("*", LogLevel::WARN);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Override)
+{
+  Logging::setLevel("*", LogLevel::WARN);
+  Logging::setLevel("Module2", LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // DefaultSeverity
+
+BOOST_AUTO_TEST_SUITE(SeverityConfig)
+
+BOOST_AUTO_TEST_CASE(SetEmpty)
+{
+  Logging::setLevel("");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 0);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetDefault)
+{
+  Logging::setLevel("*=WARN");
+  const auto& prefixMap = Logging::get().getLevels();
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetModule)
+{
+  Logging::setLevel("Module1=ERROR");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module1"), LogLevel::ERROR);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetOverride)
+{
+  Logging::setLevel("*=WARN:Module2=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module2"), LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetTwice)
+{
+  Logging::setLevel("*=WARN");
+  Logging::setLevel("Module2=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module2"), LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  Logging::setLevel("Module2=DEBUG");
+  Logging::setLevel("*=ERROR");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::ERROR);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Malformed)
+{
+  BOOST_CHECK_THROW(Logging::setLevel("Module1=INVALID-LEVEL"), std::invalid_argument);
+  BOOST_CHECK_THROW(Logging::setLevel("Module1-MISSING-EQUAL-SIGN"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(SetFilter)
+{
+  Logging::setLevel("*=FATAL:fm.*=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::FATAL);
+  // "name.*" is treated as "name." internally
+  BOOST_CHECK_EQUAL(prefixMap.at("fm."), LogLevel::DEBUG);
+  logFromModule1();
+  logFromFilterModule();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [fm.FilterModule] debugFM\n" +
+    LOG_SYSTIME_STR + " INFO: [fm.FilterModule] infoFM\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.FilterModule] warnFM\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.FilterModule] errorFM\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.FilterModule] fatalFM\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetOverrideFilter)
+{
+  Logging::setLevel("*=FATAL:fm.FilterModule=DEBUG");
+  Logging::setLevel("fm.*", LogLevel::INFO);
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::FATAL);
+  // "name.*" is treated as "name." internally
+  BOOST_CHECK_EQUAL(prefixMap.at("fm."), LogLevel::INFO);
+  logFromModule1();
+  logFromFilterModule();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " INFO: [fm.FilterModule] infoFM\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.FilterModule] warnFM\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.FilterModule] errorFM\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.FilterModule] fatalFM\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(FindPrefixRule)
+{
+  Logging::setLevel("*=FATAL:fm.a.*=ERROR:fm.a.b=INFO");
+  logFromNewLogger("fm.a.b");
+  logFromNewLogger("fm.a.b.c");
+  logFromNewLogger("fm.a.b.d");
+  logFromNewLogger("fm.b");
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [fm.a.b] infofm.a.b\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.a.b] warnfm.a.b\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b] errorfm.a.b\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b] fatalfm.a.b\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b.c] errorfm.a.b.c\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b.c] fatalfm.a.b.c\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b.d] errorfm.a.b.d\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b.d] fatalfm.a.b.d\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.b] fatalfm.b\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SeverityConfig
+
+BOOST_AUTO_TEST_CASE(ChangeDestination)
+{
+  using boost::test_tools::output_test_stream;
+
+  logFromModule1();
+
+  auto os2 = make_shared<output_test_stream>();
+  Logging::setDestination(os2);
+  weak_ptr<output_test_stream> os2weak(os2);
+  os2.reset();
+
+  logFromModule2();
+
+  Logging::flush();
+  os2 = os2weak.lock();
+  BOOST_REQUIRE(os2 != nullptr);
+
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+  BOOST_CHECK(os2->is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+
+  os2.reset();
+  Logging::setDestination(os);
+  BOOST_CHECK(os2weak.expired());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLogging
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/notification-stream.t.cpp b/tests/unit/util/notification-stream.t.cpp
new file mode 100644
index 0000000..3721091
--- /dev/null
+++ b/tests/unit/util/notification-stream.t.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#include "util/notification-stream.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "simple-notification.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestNotificationStream, ndn::tests::IdentityManagementTimeFixture)
+
+BOOST_AUTO_TEST_CASE(Post)
+{
+  DummyClientFace face(io, m_keyChain);
+  util::NotificationStream<SimpleNotification> notificationStream(face,
+    "/localhost/nfd/NotificationStreamTest", m_keyChain);
+
+  SimpleNotification event1("msg1");
+  notificationStream.postNotification(event1);
+
+  advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), "/localhost/nfd/NotificationStreamTest/%FE%00");
+  SimpleNotification decoded1;
+  BOOST_CHECK_NO_THROW(decoded1.wireDecode(face.sentData[0].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded1.getMessage(), "msg1");
+
+  SimpleNotification event2("msg2");
+  notificationStream.postNotification(event2);
+
+  advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentData[1].getName(), "/localhost/nfd/NotificationStreamTest/%FE%01");
+  SimpleNotification decoded2;
+  BOOST_CHECK_NO_THROW(decoded2.wireDecode(face.sentData[1].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded2.getMessage(), "msg2");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNotificationStream
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/notification-subscriber.t.cpp b/tests/unit/util/notification-subscriber.t.cpp
new file mode 100644
index 0000000..3fe3fd6
--- /dev/null
+++ b/tests/unit/util/notification-subscriber.t.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#include "util/notification-subscriber.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "simple-notification.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+class NotificationSubscriberFixture : public IdentityManagementTimeFixture
+{
+public:
+  NotificationSubscriberFixture()
+    : streamPrefix("ndn:/NotificationSubscriberTest")
+    , subscriberFace(io, m_keyChain)
+    , subscriber(subscriberFace, streamPrefix, 1_s)
+    , nextSendNotificationNo(0)
+  {
+  }
+
+  /** \brief deliver one notification to subscriber
+   */
+  void
+  deliverNotification(const std::string& msg)
+  {
+    SimpleNotification notification(msg);
+
+    Name dataName = streamPrefix;
+    dataName.appendSequenceNumber(nextSendNotificationNo);
+    Data data(dataName);
+    data.setContent(notification.wireEncode());
+    data.setFreshnessPeriod(1_s);
+    m_keyChain.sign(data);
+
+    lastDeliveredSeqNo = nextSendNotificationNo;
+    lastNotification.setMessage("");
+    ++nextSendNotificationNo;
+    subscriberFace.receive(data);
+  }
+
+  /** \brief deliver a Nack to subscriber
+   */
+  void
+  deliverNack(const Interest& interest, const lp::NackReason& reason)
+  {
+    lp::Nack nack = makeNack(interest, reason);
+    subscriberFace.receive(nack);
+  }
+
+  void
+  afterNotification(const SimpleNotification& notification)
+  {
+    lastNotification = notification;
+  }
+
+  void
+  afterNack(const lp::Nack& nack)
+  {
+    lastNack = nack;
+  }
+
+  void
+  afterTimeout()
+  {
+    hasTimeout = true;
+  }
+
+  void
+  afterDecodeError(const Data& data)
+  {
+    lastDecodeErrorData = data;
+  }
+
+  void
+  connectHandlers()
+  {
+    notificationConn = subscriber.onNotification.connect(
+      bind(&NotificationSubscriberFixture::afterNotification, this, _1));
+    nackConn = subscriber.onNack.connect(
+      bind(&NotificationSubscriberFixture::afterNack, this, _1));
+    subscriber.onTimeout.connect(
+      bind(&NotificationSubscriberFixture::afterTimeout, this));
+    subscriber.onDecodeError.connect(
+      bind(&NotificationSubscriberFixture::afterDecodeError, this, _1));
+  }
+
+  void
+  disconnectHandlers()
+  {
+    notificationConn.disconnect();
+    nackConn.disconnect();
+  }
+
+  /** \return true if subscriberFace has an initial request (first sent Interest)
+   */
+  bool
+  hasInitialRequest() const
+  {
+    if (subscriberFace.sentInterests.empty())
+      return false;
+
+    const Interest& interest = subscriberFace.sentInterests[0];
+    return interest.getName() == streamPrefix &&
+           interest.getChildSelector() == 1 &&
+           interest.getMustBeFresh() &&
+           interest.getInterestLifetime() == subscriber.getInterestLifetime();
+  }
+
+  /** \return sequence number of the continuation request sent from subscriberFace
+   *          or 0 if there's no such request as sole sent Interest
+   */
+  uint64_t
+  getRequestSeqNo() const
+  {
+    if (subscriberFace.sentInterests.size() != 1)
+      return 0;
+
+    const Interest& interest = subscriberFace.sentInterests[0];
+    const Name& name = interest.getName();
+    if (streamPrefix.isPrefixOf(name) &&
+        name.size() == streamPrefix.size() + 1 &&
+        interest.getInterestLifetime() == subscriber.getInterestLifetime())
+      return name[-1].toSequenceNumber();
+    else
+      return 0;
+  }
+
+protected:
+  Name streamPrefix;
+  DummyClientFace subscriberFace;
+  util::NotificationSubscriber<SimpleNotification> subscriber;
+  util::signal::Connection notificationConn;
+  util::signal::Connection nackConn;
+  uint64_t nextSendNotificationNo;
+  uint64_t lastDeliveredSeqNo;
+  SimpleNotification lastNotification;
+  lp::Nack lastNack;
+  bool hasTimeout;
+  Data lastDecodeErrorData;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestNotificationSubscriber, NotificationSubscriberFixture)
+
+BOOST_AUTO_TEST_CASE(StartStop)
+{
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  // has no effect because onNotification has no handler
+  subscriber.start();
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  this->connectHandlers();
+  subscriber.start();
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), true);
+  advanceClocks(1_ms);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  subscriberFace.sentInterests.clear();
+  this->disconnectHandlers();
+  this->deliverNotification("n1");
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Notifications)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  // respond to initial request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n1");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+
+  // respond to continuation request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n2");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n2");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  // send the first Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  Interest interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(300_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send the second Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(301_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(200_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send a notification to initial request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+
+  // send a Nack to subsequent request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(300_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  advanceClocks(subscriber.getInterestLifetime(), 2);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(hasTimeout, true);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n1");
+}
+
+BOOST_AUTO_TEST_CASE(SequenceError)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  Name wrongName = streamPrefix;
+  wrongName.append("%07%07");
+  Data wrongData(wrongName);
+  m_keyChain.sign(wrongData);
+  subscriberFace.receive(wrongData);
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  advanceClocks(1_ms);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(lastDecodeErrorData.getName(), wrongName);
+  BOOST_CHECK(this->hasInitialRequest());
+}
+
+BOOST_AUTO_TEST_CASE(PayloadError)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  this->deliverNotification("\x07n4");
+  advanceClocks(1_ms);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK(this->hasInitialRequest());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNotificationSubscriber
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/placeholders.t.cpp b/tests/unit/util/placeholders.t.cpp
new file mode 100644
index 0000000..0dc8c70
--- /dev/null
+++ b/tests/unit/util/placeholders.t.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+// Bug 2109 test case
+
+// interest.hpp includes common.hpp; common.hpp shouldn't be used from external program
+#include "interest.hpp"
+
+// util/config-file.hpp indirectly includes <boost/property_tree/ptree.hpp>
+// which in turn imports Boost placeholders
+#include "util/config-file.hpp"
+
+// It's intentional to write "using namespace",
+// to simulate an external program linked against ndn-cxx.
+using namespace ndn;
+
+void
+placeholdersTestFunction(int i)
+{
+}
+
+int
+placeholdersTestMain()
+{
+  auto f = std::bind(&placeholdersTestFunction, _1);
+  f(1);
+  return 0;
+}
diff --git a/tests/unit/util/placeholders2.t.cpp b/tests/unit/util/placeholders2.t.cpp
new file mode 100644
index 0000000..187daa9
--- /dev/null
+++ b/tests/unit/util/placeholders2.t.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+// Bug 2109 test case
+
+// interest.hpp includes common.hpp; common.hpp shouldn't be used from external program
+#include "interest.hpp"
+
+#include <boost/bind.hpp>
+
+void
+placeholders2TestFunction(int i)
+{
+}
+
+int
+placeholders2TestMain()
+{
+  auto f = boost::bind(&placeholders2TestFunction, _1);
+  f(1);
+  return 0;
+}
diff --git a/tests/unit/util/random.t.cpp b/tests/unit/util/random.t.cpp
new file mode 100644
index 0000000..e830f7b
--- /dev/null
+++ b/tests/unit/util/random.t.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/random.hpp"
+#include "security/detail/openssl.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/mpl/vector.hpp>
+#include <cmath>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestRandom)
+
+class PseudoRandomWord32
+{
+public:
+  static uint32_t
+  generate()
+  {
+    return random::generateWord32();
+  }
+};
+
+class PseudoRandomWord64
+{
+public:
+  static uint64_t
+  generate()
+  {
+    return random::generateWord64();
+  }
+};
+
+class SecureRandomWord32
+{
+public:
+  static uint32_t
+  generate()
+  {
+    return random::generateSecureWord32();
+  }
+};
+
+class SecureRandomWord64
+{
+public:
+  static uint64_t
+  generate()
+  {
+    return random::generateSecureWord64();
+  }
+};
+
+typedef boost::mpl::vector<PseudoRandomWord32,
+                           PseudoRandomWord64,
+                           SecureRandomWord32,
+                           SecureRandomWord64> RandomGenerators;
+
+
+static double
+getDeviation(const std::vector<uint32_t>& counts, size_t size)
+{
+  // Kolmogorov-Smirnov Goodness-of-Fit Test
+  // http://www.itl.nist.gov/div898/handbook/eda/section3/eda35g.htm
+
+  std::vector<double> edf(counts.size(), 0.0);
+  double probability = 0.0;
+  for (size_t i = 0; i < counts.size(); i++) {
+    probability += 1.0 * counts[i] / size;
+    edf[i] = probability;
+  }
+
+  double t = 0.0;
+  for (size_t i = 0; i < counts.size(); i++) {
+    t = std::max(t, std::abs(edf[i] - (i * 1.0 / counts.size())));
+  }
+
+  return t;
+}
+
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(GoodnessOfFit, RandomGenerator, RandomGenerators)
+{
+  const size_t MAX_BINS = 32;
+  const uint32_t MAX_ITERATIONS = 35;
+
+  std::vector<uint32_t> counts(MAX_BINS, 0);
+
+  for (uint32_t i = 0; i < MAX_ITERATIONS; i++) {
+    counts[RandomGenerator::generate() % MAX_BINS]++;
+  }
+
+  // Check if it is uniform distribution with confidence 0.95
+  // http://dlc.erieri.com/onlinetextbook/index.cfm?fuseaction=textbook.appendix&FileName=Table7
+  BOOST_WARN_LE(getDeviation(counts, MAX_ITERATIONS), 0.230);
+}
+
+BOOST_AUTO_TEST_CASE(GenerateRandomBytes)
+{
+  // Kolmogorov-Smirnov Goodness-of-Fit Test
+  // http://www.itl.nist.gov/div898/handbook/eda/section3/eda35g.htm
+
+  uint8_t buf[1024] = {0};
+  random::generateSecureBytes(buf, sizeof(buf));
+
+  std::vector<uint32_t> counts(256, 0);
+
+  for (size_t i = 0; i < sizeof(buf); i++) {
+    counts[buf[i]]++;
+  }
+
+  // Check if it is uniform distribution with confidence 0.95
+  // http://dlc.erieri.com/onlinetextbook/index.cfm?fuseaction=textbook.appendix&FileName=Table7
+  BOOST_WARN_LE(getDeviation(counts, sizeof(buf)), 0.230);
+}
+
+// This fixture uses OpenSSL routines to set a dummy random generator that always fails
+class FailRandMethodFixture
+{
+public:
+  FailRandMethodFixture()
+    : m_dummyRandMethod{&FailRandMethodFixture::seed,
+                        &FailRandMethodFixture::bytes,
+                        &FailRandMethodFixture::cleanup,
+                        &FailRandMethodFixture::add,
+                        &FailRandMethodFixture::pseudorand,
+                        &FailRandMethodFixture::status}
+  {
+    m_origRandMethod = RAND_get_rand_method();
+    RAND_set_rand_method(&m_dummyRandMethod);
+  }
+
+  ~FailRandMethodFixture()
+  {
+    RAND_set_rand_method(m_origRandMethod);
+  }
+
+private: // RAND_METHOD callbacks
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL
+  static void
+  seed(const void* buf, int num)
+  {
+  }
+#else
+  static int
+  seed(const void* buf, int num)
+  {
+    return 0;
+  }
+#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
+
+  static int
+  bytes(unsigned char* buf, int num)
+  {
+    return 0;
+  }
+
+  static void
+  cleanup()
+  {
+  }
+
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL
+  static void
+  add(const void* buf, int num, double entropy)
+  {
+  }
+#else
+  static int
+  add(const void* buf, int num, double entropy)
+  {
+    return 0;
+  }
+#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
+
+  static int
+  pseudorand(unsigned char* buf, int num)
+  {
+    return 0;
+  }
+
+  static int
+  status()
+  {
+    return 0;
+  }
+
+private:
+  const RAND_METHOD* m_origRandMethod;
+  RAND_METHOD m_dummyRandMethod;
+};
+
+BOOST_FIXTURE_TEST_CASE(Error, FailRandMethodFixture)
+{
+  uint8_t buf[1024] = {0};
+  BOOST_CHECK_THROW(random::generateSecureBytes(buf, sizeof(buf)), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRandom
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/regex.t.cpp b/tests/unit/util/regex.t.cpp
new file mode 100644
index 0000000..777defb
--- /dev/null
+++ b/tests/unit/util/regex.t.cpp
@@ -0,0 +1,463 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ *
+ * @author Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>
+ */
+
+#include "util/regex.hpp"
+#include "util/regex/regex-backref-manager.hpp"
+#include "util/regex/regex-backref-matcher.hpp"
+#include "util/regex/regex-component-matcher.hpp"
+#include "util/regex/regex-component-set-matcher.hpp"
+#include "util/regex/regex-pattern-list-matcher.hpp"
+#include "util/regex/regex-repeat-matcher.hpp"
+#include "util/regex/regex-top-matcher.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+using std::string;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestRegex)
+
+BOOST_AUTO_TEST_CASE(ComponentMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexComponentMatcher> cm = make_shared<RegexComponentMatcher>("a", backRef);
+  bool res = cm->match(Name("/a/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentMatcher>("a", backRef);
+  res = cm->match(Name("/a/b/"), 1, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentMatcher>("(c+)\\.(cd)", backRef);
+  res = cm->match(Name("/ccc.cd/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("ccc.cd"));
+
+  BOOST_REQUIRE_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("ccc"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("cd"));
+}
+
+BOOST_AUTO_TEST_CASE(ComponentSetMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexComponentSetMatcher> cm = make_shared<RegexComponentSetMatcher>("<a>", backRef);
+  bool res = cm->match(Name("/a/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/"), 1, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentSetMatcher>("[<a><b><c>]", backRef);
+  res = cm->match(Name("/a/b/d"), 1, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/d"), 2, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentSetMatcher>("[^<a><b><c>]", backRef);
+  res = cm->match(Name("/b/d"), 1, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("d"));
+}
+
+BOOST_AUTO_TEST_CASE(RepeatMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexRepeatMatcher> cm = make_shared<RegexRepeatMatcher>("[<a><b>]*", backRef, 8);
+  bool res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]+", backRef, 8);
+  res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<.*>*", backRef, 4);
+  res = cm->match(Name("/a/b/c/d/e/f/"), 0, 6);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[4].toUri(), string("e"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[5].toUri(), string("f"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<>*", backRef, 2);
+  res = cm->match(Name("/a/b/c/d/e/f/"), 0, 6);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[4].toUri(), string("e"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[5].toUri(), string("f"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{3}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/d/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/d/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{2,3}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{2,}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{,2}", backRef, 8);
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexBackrefMatcher> cm = make_shared<RegexBackrefMatcher>("(<a><b>)", backRef);
+  backRef->pushRef(static_pointer_cast<RegexMatcher>(cm));
+  cm->lateCompile();
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 1);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexBackrefMatcher>("(<a>(<b>))", backRef);
+  backRef->pushRef(cm);
+  cm->lateCompile();
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcherAdvanced)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexRepeatMatcher> cm = make_shared<RegexRepeatMatcher>("([<a><b>])+", backRef, 10);
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 1);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcherAdvanced2)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexPatternListMatcher> cm = make_shared<RegexPatternListMatcher>("(<a>(<b>))<c>", backRef);
+  bool res = cm->match(Name("/a/b/c"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(PatternListMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexPatternListMatcher> cm = make_shared<RegexPatternListMatcher>("<a>[<a><b>]", backRef);
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a>", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a>", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a><>*", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+}
+
+BOOST_AUTO_TEST_CASE(TopMatcher)
+{
+  shared_ptr<RegexTopMatcher> cm = make_shared<RegexTopMatcher>("^<a><b><c>");
+  bool res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("<b><c><d>$");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("^<a><b><c><d>$");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  res = cm->match(Name("/a/b/c/d/e"));
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  cm = make_shared<RegexTopMatcher>("<a><b><c><d>");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("<b><c>");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+}
+
+BOOST_AUTO_TEST_CASE(TopMatcherAdvanced)
+{
+  shared_ptr<Regex> cm = make_shared<Regex>("^(<.*>*)<.*>");
+  bool res = cm->match(Name("/n/a/b/c"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/n/a/b/"));
+
+  cm = make_shared<Regex>("^(<.*>*)<.*><c>(<.*>)<.*>");
+  res = cm->match(Name("/n/a/b/c/d/e/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand("\\1\\2"), Name("/n/a/d/"));
+
+  cm = make_shared<Regex>("(<.*>*)<.*>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/n/a/b/"));
+
+  cm = make_shared<Regex>("<.*>(<.*>*)<.*>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/a/b/"));
+
+  cm = make_shared<Regex>("<a>(<>*)<>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/b/"));
+
+  cm = make_shared<Regex>("^<ndn><(.*)\\.(.*)><DNS>(<>*)<>");
+  res = cm->match(Name("/ndn/ucla.edu/DNS/yingdi/mac/ksk-1/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand("<ndn>\\2\\1\\3"), Name("/ndn/edu/ucla/yingdi/mac/"));
+
+  cm = make_shared<Regex>("^<ndn><(.*)\\.(.*)><DNS>(<>*)<>", "<ndn>\\2\\1\\3");
+  res = cm->match(Name("/ndn/ucla.edu/DNS/yingdi/mac/ksk-1/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand(), Name("/ndn/edu/ucla/yingdi/mac/"));
+}
+
+BOOST_AUTO_TEST_CASE(RegexBackrefManagerMemoryLeak)
+{
+  auto re = make_unique<Regex>("^(<>)$");
+
+  weak_ptr<RegexPatternListMatcher> m1(re->m_primaryMatcher);
+  weak_ptr<RegexPatternListMatcher> m2(re->m_secondaryMatcher);
+  weak_ptr<RegexBackrefManager> b1(re->m_primaryBackrefManager);
+  weak_ptr<RegexBackrefManager> b2(re->m_secondaryBackrefManager);
+
+  re.reset();
+
+  BOOST_CHECK_EQUAL(m1.use_count(), 0);
+  BOOST_CHECK_EQUAL(m2.use_count(), 0);
+  BOOST_CHECK_EQUAL(b1.use_count(), 0);
+  BOOST_CHECK_EQUAL(b2.use_count(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRegex
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/scheduler.t.cpp b/tests/unit/util/scheduler.t.cpp
new file mode 100644
index 0000000..05380f3
--- /dev/null
+++ b/tests/unit/util/scheduler.t.cpp
@@ -0,0 +1,417 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/scheduler.hpp"
+#include "util/scheduler-scoped-event-id.hpp"
+
+#include "boost-test.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace util {
+namespace scheduler {
+namespace tests {
+
+using namespace ndn::tests;
+
+class SchedulerFixture : public UnitTestTimeFixture
+{
+public:
+  SchedulerFixture()
+    : scheduler(io)
+  {
+  }
+
+public:
+  Scheduler scheduler;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestScheduler, SchedulerFixture)
+
+BOOST_AUTO_TEST_SUITE(General)
+
+BOOST_AUTO_TEST_CASE(Events)
+{
+  size_t count1 = 0;
+  size_t count2 = 0;
+
+  scheduler.scheduleEvent(500_ms, [&] {
+      ++count1;
+      BOOST_CHECK_EQUAL(count2, 1);
+    });
+
+  EventId i = scheduler.scheduleEvent(1_s, [&] {
+      BOOST_ERROR("This event should not have been fired");
+    });
+  scheduler.cancelEvent(i);
+
+  scheduler.scheduleEvent(250_ms, [&] {
+      BOOST_CHECK_EQUAL(count1, 0);
+      ++count2;
+    });
+
+  i = scheduler.scheduleEvent(50_ms, [&] {
+      BOOST_ERROR("This event should not have been fired");
+    });
+  scheduler.cancelEvent(i);
+
+  advanceClocks(25_ms, 1000_ms);
+  BOOST_CHECK_EQUAL(count1, 1);
+  BOOST_CHECK_EQUAL(count2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(CallbackException)
+{
+  class MyException : public std::exception
+  {
+  };
+  scheduler.scheduleEvent(10_ms, [] { BOOST_THROW_EXCEPTION(MyException()); });
+
+  bool isCallbackInvoked = false;
+  scheduler.scheduleEvent(20_ms, [&isCallbackInvoked] { isCallbackInvoked = true; });
+
+  BOOST_CHECK_THROW(this->advanceClocks(6_ms, 2), MyException);
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(CancelEmptyEvent)
+{
+  EventId i;
+  scheduler.cancelEvent(i);
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE(SelfCancel)
+{
+  EventId selfEventId;
+  selfEventId = scheduler.scheduleEvent(100_ms, [&] {
+      scheduler.cancelEvent(selfEventId);
+    });
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(100_ms, 10));
+}
+
+class SelfRescheduleFixture : public SchedulerFixture
+{
+public:
+  SelfRescheduleFixture()
+    : count(0)
+  {
+  }
+
+  void
+  reschedule()
+  {
+    EventId eventId = scheduler.scheduleEvent(100_ms,
+                                              bind(&SelfRescheduleFixture::reschedule, this));
+    scheduler.cancelEvent(selfEventId);
+    selfEventId = eventId;
+
+    if (count < 5)
+      count++;
+    else
+      scheduler.cancelEvent(selfEventId);
+  }
+
+  void
+  reschedule2()
+  {
+    scheduler.cancelEvent(selfEventId);
+
+    if (count < 5)  {
+      selfEventId = scheduler.scheduleEvent(100_ms,
+                                            bind(&SelfRescheduleFixture::reschedule2, this));
+      count++;
+    }
+  }
+
+  void
+  reschedule3()
+  {
+    scheduler.cancelEvent(selfEventId);
+
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+  }
+
+public:
+  EventId selfEventId;
+  size_t count;
+};
+
+BOOST_FIXTURE_TEST_CASE(Reschedule, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 5);
+}
+
+BOOST_FIXTURE_TEST_CASE(Reschedule2, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule2, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 5);
+}
+
+BOOST_FIXTURE_TEST_CASE(Reschedule3, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule3, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 6);
+}
+
+class CancelAllFixture : public SchedulerFixture
+{
+public:
+  CancelAllFixture()
+    : count(0)
+  {
+  }
+
+  void
+  event()
+  {
+    ++count;
+
+    scheduler.scheduleEvent(1_s, [&] { event(); });
+  }
+
+public:
+  uint32_t count;
+};
+
+BOOST_FIXTURE_TEST_CASE(CancelAll, CancelAllFixture)
+{
+  scheduler.scheduleEvent(500_ms, [&] { scheduler.cancelAllEvents(); });
+
+  scheduler.scheduleEvent(1_s, [&] { event(); });
+
+  scheduler.scheduleEvent(3_s, [] {
+      BOOST_ERROR("This event should have been cancelled" );
+    });
+
+  advanceClocks(100_ms, 100);
+
+  BOOST_CHECK_EQUAL(count, 0);
+}
+
+BOOST_AUTO_TEST_CASE(CancelAllWithScopedEventId) // Bug 3691
+{
+  Scheduler sched(io);
+  ScopedEventId eid(sched);
+  eid = sched.scheduleEvent(10_ms, []{});
+  sched.cancelAllEvents();
+  eid.cancel(); // should not crash
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // General
+
+BOOST_AUTO_TEST_SUITE(EventId)
+
+using scheduler::EventId;
+
+BOOST_AUTO_TEST_CASE(ConstructEmpty)
+{
+  EventId eid;
+  eid = nullptr;
+  EventId eid2(nullptr);
+
+  BOOST_CHECK(!eid && !eid2);
+}
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  EventId eid, eid2;
+  BOOST_CHECK_EQUAL(eid == eid2, true);
+  BOOST_CHECK_EQUAL(eid != eid2, false);
+  BOOST_CHECK_EQUAL(eid == nullptr, true);
+  BOOST_CHECK_EQUAL(eid != nullptr, false);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(eid == eid2, false);
+  BOOST_CHECK_EQUAL(eid != eid2, true);
+  BOOST_CHECK_EQUAL(eid == nullptr, false);
+  BOOST_CHECK_EQUAL(eid != nullptr, true);
+
+  eid2 = eid;
+  BOOST_CHECK_EQUAL(eid, eid2);
+  BOOST_CHECK_EQUAL(eid != eid2, false);
+
+  eid2 = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(eid == eid2, false);
+  BOOST_CHECK_NE(eid, eid2);
+}
+
+BOOST_AUTO_TEST_CASE(Valid)
+{
+  EventId eid;
+  BOOST_CHECK_EQUAL(static_cast<bool>(eid), false);
+  BOOST_CHECK_EQUAL(!eid, true);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(static_cast<bool>(eid), true);
+  BOOST_CHECK_EQUAL(!eid, false);
+
+  EventId eid2 = eid;
+  scheduler.cancelEvent(eid2);
+  BOOST_CHECK(!eid);
+  BOOST_CHECK(!eid2);
+}
+
+BOOST_AUTO_TEST_CASE(DuringCallback)
+{
+  EventId eid;
+  EventId eid2 = scheduler.scheduleEvent(20_ms, []{});
+
+  bool isCallbackInvoked = false;
+  eid = scheduler.scheduleEvent(10_ms, [this, &eid, &eid2, &isCallbackInvoked] {
+    isCallbackInvoked = true;
+
+    // eid is "expired" during callback execution
+    BOOST_CHECK(!eid);
+    BOOST_CHECK(eid == nullptr);
+    BOOST_CHECK_NE(eid, eid2);
+
+    scheduler.cancelEvent(eid2);
+    BOOST_CHECK_EQUAL(eid, eid2);
+  });
+
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  bool isCallbackInvoked = false;
+  EventId eid = scheduler.scheduleEvent(10_ms, [&isCallbackInvoked] { isCallbackInvoked = true; });
+  eid.reset();
+  BOOST_CHECK(!eid);
+  BOOST_CHECK(eid == nullptr);
+
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  std::string nullString = boost::lexical_cast<std::string>(shared_ptr<int>());
+
+  EventId eid;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(eid), nullString);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_TEST_MESSAGE("eid=" << eid);
+  BOOST_CHECK_NE(boost::lexical_cast<std::string>(eid), nullString);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // EventId
+
+BOOST_AUTO_TEST_SUITE(ScopedEventId)
+
+using scheduler::ScopedEventId;
+
+BOOST_AUTO_TEST_CASE(Destruct)
+{
+  int hit = 0;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 0);
+}
+
+BOOST_AUTO_TEST_CASE(Assign)
+{
+  int hit1 = 0, hit2 = 0;
+  ScopedEventId se1(scheduler);
+  se1 = scheduler.scheduleEvent(10_ms, [&] { ++hit1; });
+  se1 = scheduler.scheduleEvent(10_ms, [&] { ++hit2; });
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit1, 0);
+  BOOST_CHECK_EQUAL(hit2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+  int hit = 0;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se.release();
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Move)
+{
+  int hit = 0;
+  unique_ptr<ScopedEventId> se2;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se2 = make_unique<ScopedEventId>(std::move(se)); // move constructor
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 1);
+
+  ScopedEventId se3(scheduler);
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se3 = std::move(se); // move assignment
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ScopedEventId
+
+BOOST_AUTO_TEST_SUITE_END() // TestScheduler
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace scheduler
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/segment-fetcher.t.cpp b/tests/unit/util/segment-fetcher.t.cpp
new file mode 100644
index 0000000..d52fa08
--- /dev/null
+++ b/tests/unit/util/segment-fetcher.t.cpp
@@ -0,0 +1,863 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/segment-fetcher.hpp"
+
+#include "data.hpp"
+#include "lp/nack.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "dummy-validator.hpp"
+#include "make-interest-data.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+#include <set>
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+class Fixture : public IdentityManagementTimeFixture
+{
+public:
+  Fixture()
+    : face(io, m_keyChain)
+  {
+  }
+
+  static shared_ptr<Data>
+  makeDataSegment(const Name& baseName, uint64_t segment, bool isFinal)
+  {
+    const uint8_t buffer[] = "Hello, world!";
+
+    auto data = make_shared<Data>(Name(baseName).appendSegment(segment));
+    data->setContent(buffer, sizeof(buffer));
+    if (isFinal) {
+      data->setFinalBlock(data->getName()[-1]);
+    }
+
+    return signData(data);
+  }
+
+  void
+  onError(uint32_t errorCode)
+  {
+    ++nErrors;
+    lastError = errorCode;
+  }
+
+  void
+  onComplete(ConstBufferPtr data)
+  {
+    ++nCompletions;
+    dataSize = data->size();
+    dataBuf = data;
+  }
+
+  void
+  nackLastInterest(lp::NackReason nackReason)
+  {
+    const Interest& lastInterest = face.sentInterests.back();
+    lp::Nack nack = makeNack(lastInterest, nackReason);
+    face.receive(nack);
+    advanceClocks(10_ms);
+  }
+
+  void
+  connectSignals(const shared_ptr<SegmentFetcher>& fetcher)
+  {
+    fetcher->onComplete.connect(bind(&Fixture::onComplete, this, _1));
+    fetcher->onError.connect(bind(&Fixture::onError, this, _1));
+  }
+
+  void
+  onInterest(const Interest& interest)
+  {
+    if (interest.getName().get(-1).isSegment()) {
+      if (segmentsToDropOrNack.size() > 0 &&
+          interest.getName().get(-1).toSegment() == segmentsToDropOrNack.front()) {
+        segmentsToDropOrNack.pop();
+        if (sendNackInsteadOfDropping) {
+          lp::Nack nack = makeNack(interest, nackReason);
+          face.receive(nack);
+        }
+        return;
+      }
+
+      auto data = makeDataSegment("/hello/world/version0",
+                                  interest.getName().get(-1).toSegment(),
+                                  interest.getName().get(-1).toSegment() == nSegments - 1);
+      face.receive(*data);
+
+      uniqSegmentsSent.insert(interest.getName().get(-1).toSegment());
+      if (uniqSegmentsSent.size() == nSegments) {
+        io.stop();
+      }
+    }
+    else {
+      if (segmentsToDropOrNack.size() > 0 &&
+          segmentsToDropOrNack.front() == 0) {
+        segmentsToDropOrNack.pop();
+        if (sendNackInsteadOfDropping) {
+          lp::Nack nack = makeNack(interest, nackReason);
+          face.receive(nack);
+        }
+        return;
+      }
+
+      auto data = makeDataSegment("/hello/world/version0", defaultSegmentToSend, nSegments == 1);
+      face.receive(*data);
+      uniqSegmentsSent.insert(defaultSegmentToSend);
+    }
+  }
+
+public:
+  DummyClientFace face;
+  std::set<uint64_t> uniqSegmentsSent;
+
+  int nErrors = 0;
+  uint32_t lastError = 0;
+  int nCompletions = 0;
+  size_t dataSize = 0;
+  ConstBufferPtr dataBuf;
+
+  // number of segments in fetched object
+  uint64_t nSegments = 0;
+  std::queue<uint64_t> segmentsToDropOrNack;
+  bool sendNackInsteadOfDropping = false;
+  lp::NackReason nackReason = lp::NackReason::NONE;
+  // segment that is sent in response to an Interest w/o a segment component in its name
+  uint64_t defaultSegmentToSend = 0;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestSegmentFetcher, Fixture)
+
+BOOST_AUTO_TEST_CASE(InvalidOptions)
+{
+  SegmentFetcher::Options options;
+  options.mdCoef = 1.5;
+  DummyValidator acceptValidator;
+  BOOST_CHECK_THROW(SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options),
+                    std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(ExceedMaxTimeout)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentTimedOut = 0;
+  SegmentFetcher::Options options;
+  options.maxTimeout = 100_ms;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator, options);
+  connectSignals(fetcher);
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  advanceClocks(1_ms);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  const Interest& interest = face.sentInterests[0];
+  BOOST_CHECK_EQUAL(interest.getName(), "/hello/world");
+  BOOST_CHECK_EQUAL(interest.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(interest.getCanBePrefix(), true);
+
+  advanceClocks(98_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+
+  advanceClocks(1_ms, 2);
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::INTEREST_TIMEOUT));
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 1);
+}
+
+BOOST_AUTO_TEST_CASE(BasicSingleSegment)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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);
+
+  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_AUTO_TEST_CASE(ConstantCwnd)
+{
+  SegmentFetcher::Options options;
+  options.useConstantCwnd = true;
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator, options);
+  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(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 1);
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 2);
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 4);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 3);
+  face.receive(*makeDataSegment("/hello/world/version0", 3, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4);
+  nackLastInterest(lp::NackReason::CONGESTION);
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 6);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4);
+  face.receive(*makeDataSegment("/hello/world/version0", 4, true));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+}
+
+BOOST_AUTO_TEST_CASE(BasicMultipleSegments)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  sendNackInsteadOfDropping = false;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(FirstSegmentNotZero)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  sendNackInsteadOfDropping = false;
+  defaultSegmentToSend = 47;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(WindowSize)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+
+  advanceClocks(10_ms); // T+10ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, std::numeric_limits<double>::max());
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nReceived, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  double oldCwnd = fetcher->m_cwnd;
+  double oldSsthresh = fetcher->m_ssthresh;
+  uint64_t oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+
+  advanceClocks(10_ms); //T+20ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  // +2 below because m_nextSegmentNum will be incremented in the receive callback if segment 0 is
+  // the first received
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 2);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 14);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+
+  advanceClocks(10_ms); //T+30ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 28);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 2);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+
+  advanceClocks(10_ms); //T+40ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 3 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldSsthresh = fetcher->m_ssthresh;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+  size_t oldSentInterestsSize = face.sentInterests.size();
+
+  nackLastInterest(lp::NackReason::CONGESTION); //T+50ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 20_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  // The Nacked segment will remain in pendingSegments, so the size of the structure doesn't change
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize);
+
+  advanceClocks(10_ms); //T+60ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 30_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize);
+
+  // Properly end test case
+  lp::Nack nack = makeNack(face.sentInterests[face.sentInterests.size() - 2], lp::NackReason::NO_ROUTE);
+  face.receive(nack);
+  advanceClocks(10_ms); //T+70ms
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::NACK_ERROR));
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+}
+
+BOOST_AUTO_TEST_CASE(MissingSegmentNum)
+{
+  DummyValidator acceptValidator;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+
+  advanceClocks(10_ms);
+
+  const uint8_t buffer[] = "Hello, world!";
+  auto data = makeData("/hello/world/version0/no-segment");
+  data->setContent(buffer, sizeof(buffer));
+
+  face.receive(*data);
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::DATA_HAS_NO_SEGMENT));
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+}
+
+BOOST_AUTO_TEST_CASE(MoreSegmentsThanNSegments)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 3, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  auto data4 = makeDataSegment("/hello/world/version0", 4, false);
+  data4->setFinalBlock(name::Component::fromSegment(2));
+  face.receive(*data4);
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 3);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(DuplicateNack)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  segmentsToDropOrNack.push(0);
+  segmentsToDropOrNack.push(200);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::DUPLICATE;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionNack)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  segmentsToDropOrNack.push(0);
+  segmentsToDropOrNack.push(200);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::CONGESTION;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(OtherNackReason)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  segmentsToDropOrNack.push(0);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::NO_ROUTE;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  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; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  DummyValidator validator;
+  validator.getPolicy().setResultCallback([] (const Name& name) {
+      return name.at(-1).toSegment() % 2 == 0;
+    });
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             validator);
+  connectSignals(fetcher);
+
+  auto data1 = makeDataSegment("/hello/world", 0, false);
+  auto data2 = makeDataSegment("/hello/world", 1, true);
+
+  size_t nRecvSegments = 0;
+  fetcher->afterSegmentReceived.connect([&nRecvSegments] (const Data& receivedSegment) {
+      ++nRecvSegments;
+    });
+
+  size_t nValidatedSegments = 0;
+  fetcher->afterSegmentValidated.connect([&nValidatedSegments] (const Data& validatedSegment) {
+      ++nValidatedSegments;
+    });
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms);
+
+  face.receive(*data1);
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms);
+
+  face.receive(*data2);
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 200_ms);
+  BOOST_CHECK_EQUAL(nRecvSegments, 2);
+  BOOST_CHECK_EQUAL(nValidatedSegments, 1);
+  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
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/sha256.t.cpp b/tests/unit/util/sha256.t.cpp
new file mode 100644
index 0000000..094f5f6
--- /dev/null
+++ b/tests/unit/util/sha256.t.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/sha256.hpp"
+#include "util/string-helper.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/endian/conversion.hpp>
+#include <sstream>
+
+namespace ndn {
+namespace util {
+namespace test {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestSha256)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  const uint8_t input[] = {0x01, 0x02, 0x03, 0x04};
+  auto expected = fromHex("9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a");
+
+  Sha256 statefulSha256;
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), true);
+
+  statefulSha256.update(input, 1);
+  statefulSha256.update(input + 1, 1);
+  statefulSha256.update(input + 2, 1);
+  statefulSha256.update(input + 3, 1);
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+  BOOST_CHECK_EQUAL(digest->size(), Sha256::DIGEST_SIZE);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(ConstructFromStream)
+{
+  const std::string input = "Hello, world!";
+  auto expected = fromHex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3");
+
+  std::istringstream is(input);
+  Sha256 sha(is);
+  BOOST_CHECK_EQUAL(sha.empty(), false);
+  BOOST_CHECK_EQUAL(sha.toString(), "315F5BDB76D078C43B8AC0064E4A0164612B1FCE77C869345BFC94C75894EDD3");
+
+  ConstBufferPtr digest = sha.computeDigest();
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  const uint8_t origin[] = {0x01, 0x02, 0x03, 0x04};
+
+  Sha256 digest1;
+  digest1.update(origin, sizeof(origin));
+  digest1.computeDigest();
+
+  Sha256 digest2;
+  digest2.update(origin, 1);
+  digest2.update(origin + 1, 1);
+  digest2.update(origin + 2, 1);
+  digest2.update(origin + 3, 1);
+  digest2.computeDigest();
+
+  BOOST_CHECK_EQUAL(digest1 == digest2, true);
+  BOOST_CHECK_EQUAL(digest1 != digest2, false);
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorSha256)
+{
+  auto expected = fromHex("d7bd34bfe44a18d2aa755a344fe3e6b06ed0473772e6dfce16ac71ba0b0a241c");
+
+  Sha256 innerDigest;
+  innerDigest << "TEST";
+
+  Sha256 statefulSha256;
+  statefulSha256 << innerDigest;
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorString)
+{
+  const std::string input = "Hello, world!";
+  auto expected = fromHex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3");
+
+  Sha256 statefulSha256;
+  statefulSha256 << input;
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorBlock)
+{
+  const uint8_t input[] = {
+    0x16, 0x1b, // SignatureInfo
+      0x1b, 0x01, // SignatureType
+        0x01, // Sha256WithRsa
+      0x1c, 0x16, // KeyLocator
+        0x07, 0x14, // Name
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x03,
+            0x6b, 0x65, 0x79,
+          0x08, 0x07,
+            0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+  };
+  auto expected = fromHex("b372edfd4d6a4db2cfeaeead6c34fdee9b9e759f7b8d799cf8067e39e7f2886c");
+
+  Sha256 statefulSha256;
+  statefulSha256 << Block{input, sizeof(input)};
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorUint64t)
+{
+  const uint64_t input[] = {1, 2, 3, 4};
+  auto expected = fromHex("7236c00c170036c6de133a878210ddd58567aa1d0619a0f70f69e38ae6f916e9");
+
+  Sha256 statefulSha256;
+  for (size_t i = 0; i < sizeof(input) / sizeof(uint64_t); ++i) {
+    statefulSha256 << boost::endian::native_to_big(input[i]);
+  }
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  Sha256 sha;
+  BOOST_CHECK_EQUAL(sha.empty(), true);
+
+  sha << 42;
+  BOOST_CHECK_EQUAL(sha.empty(), false);
+
+  sha.computeDigest(); // finalize
+  sha.reset();
+  BOOST_CHECK_EQUAL(sha.empty(), true);
+  BOOST_CHECK_NO_THROW(sha << 42);
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  Sha256 sha;
+  sha << 42;
+  sha.computeDigest(); // finalize
+  BOOST_CHECK_THROW(sha << 42, Sha256::Error);
+}
+
+BOOST_AUTO_TEST_CASE(StaticComputeDigest)
+{
+  const uint8_t input[] = {0x01, 0x02, 0x03, 0x04};
+  auto expected = fromHex("9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a");
+
+  ConstBufferPtr digest = Sha256::computeDigest(input, sizeof(input));
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  const uint8_t origin[] = {0x94, 0xEE, 0x05, 0x93, 0x35, 0xE5, 0x87, 0xE5,
+                            0x01, 0xCC, 0x4B, 0xF9, 0x06, 0x13, 0xE0, 0x81,
+                            0x4F, 0x00, 0xA7, 0xB0, 0x8B, 0xC7, 0xC6, 0x48,
+                            0xFD, 0x86, 0x5A, 0x2A, 0xF6, 0xA2, 0x2C, 0xC2};
+  std::string expected = toHex(origin, sizeof(origin));
+
+  Sha256 digest;
+  digest << "TEST";
+  std::ostringstream os;
+  os << digest;
+  BOOST_CHECK_EQUAL(os.str(), expected);
+  BOOST_CHECK_EQUAL(digest.toString(), expected);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSha256
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace test
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/signal.t.cpp b/tests/unit/util/signal.t.cpp
new file mode 100644
index 0000000..c319f64
--- /dev/null
+++ b/tests/unit/util/signal.t.cpp
@@ -0,0 +1,450 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/signal.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace signal {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestSignal)
+
+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);
+}
+
+class SignalOwner1
+{
+public:
+  Signal<SignalOwner1, int> sig;
+
+protected:
+  DECLARE_SIGNAL_EMIT(sig)
+};
+
+class SignalEmitter1 : public SignalOwner1
+{
+public:
+  void
+  emitTestSignal()
+  {
+    this->emitSignal(sig, 8106);
+  }
+};
+
+BOOST_AUTO_TEST_CASE(OneArgument)
+{
+  SignalEmitter1 se;
+
+  int hit = 0;
+  se.sig.connect([&hit] (int a) {
+    ++hit;
+    BOOST_CHECK_EQUAL(a, 8106);
+  });
+  se.emitTestSignal();
+
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(TwoArguments)
+{
+  Signal<std::remove_pointer_t<decltype(this)>, 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_t<decltype(this)>, RefObject> sig;
+  sig.connect([] (RefObject) {});
+  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_t<decltype(this)>, RefObject> sig;
+  sig.connect([] (const RefObject&) {});
+  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; });
+  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
+
+  BOOST_CHECK_NO_THROW(c2.disconnect());
+  BOOST_CHECK_NO_THROW(c1.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(ManualDisconnectDestructed)
+{
+  auto so = make_unique<SignalOwner0>();
+
+  int hit = 0;
+  Connection connection = so->sig.connect([&hit] { ++hit; });
+
+  so->emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  BOOST_CHECK_EQUAL(connection.isConnected(), true);
+  so.reset(); // destruct Signal
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
+  BOOST_CHECK_NO_THROW(connection.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnect)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+    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; });
+  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
+  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
+  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)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    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
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectMove)
+{
+  SignalOwner0 so;
+  int hit = 0;
+
+  unique_ptr<ScopedConnection> sc2;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+    sc2 = make_unique<ScopedConnection>(std::move(sc)); // move constructor
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    BOOST_CHECK_EQUAL(sc2->isConnected(), true);
+
+    // sc goes out of scope, but without disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+  sc2.reset();
+
+  ScopedConnection sc3;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 3); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+    BOOST_CHECK_EQUAL(sc3.isConnected(), false);
+
+    sc3 = std::move(sc); // move assignment
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    BOOST_CHECK_EQUAL(sc3.isConnected(), true);
+
+    // sc goes out of scope, but without disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 4); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShot)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  so.sig.connectSingleShot([&hit] { ++hit; });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShotDisconnected)
+{
+  SignalOwner0 so;
+
+  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
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShot1)
+{
+  SignalEmitter1 se;
+
+  int hit = 0;
+  se.sig.connectSingleShot([&hit] (int) { ++hit; });
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler not 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;
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
+  connection = so.sig.connect([&so, &connection, &hit] {
+    ++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
+  });
+
+  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);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ThrowInHandler)
+{
+  SignalOwner0 so;
+
+  struct HandlerError : public std::exception
+  {
+  };
+
+  int hit = 0;
+  so.sig.connect([&] {
+    ++hit;
+    BOOST_THROW_EXCEPTION(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_SUITE_END() // TestSignal
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace signal
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/simple-notification.hpp b/tests/unit/util/simple-notification.hpp
new file mode 100644
index 0000000..9e0d4ed
--- /dev/null
+++ b/tests/unit/util/simple-notification.hpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#ifndef NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
+#define NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
+
+#include "common.hpp"
+
+#include "encoding/encoding-buffer.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+class SimpleNotification
+{
+public:
+  SimpleNotification() = default;
+
+  explicit
+  SimpleNotification(const Block& block)
+  {
+    wireDecode(block);
+  }
+
+  SimpleNotification(const std::string& message)
+    : m_message(message)
+  {
+  }
+
+  Block
+  wireEncode() const
+  {
+    ndn::EncodingBuffer buffer;
+    buffer.prependByteArrayBlock(0x8888,
+                                 reinterpret_cast<const uint8_t*>(m_message.c_str()),
+                                 m_message.size());
+    return buffer.block();
+  }
+
+  void
+  wireDecode(const Block& block)
+  {
+    m_message.assign(reinterpret_cast<const char*>(block.value()),
+                     block.value_size());
+
+    // error for testing
+    if (!m_message.empty() && m_message[0] == '\x07')
+      BOOST_THROW_EXCEPTION(tlv::Error("0x07 error"));
+  }
+
+  const std::string&
+  getMessage() const
+  {
+    return m_message;
+  }
+
+  void
+  setMessage(const std::string& message)
+  {
+    m_message = message;
+  }
+
+private:
+  std::string m_message;
+};
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
diff --git a/tests/unit/util/sqlite3-statement.t.cpp b/tests/unit/util/sqlite3-statement.t.cpp
new file mode 100644
index 0000000..13171c5
--- /dev/null
+++ b/tests/unit/util/sqlite3-statement.t.cpp
@@ -0,0 +1,158 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/sqlite3-statement.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/filesystem.hpp>
+#include <cstring>
+#include <sqlite3.h>
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+class Sqlite3StatementTestFixture
+{
+public:
+  Sqlite3StatementTestFixture()
+    : m_path(boost::filesystem::path(UNIT_TEST_CONFIG_PATH))
+  {
+    boost::filesystem::create_directories(m_path);
+    int result = sqlite3_open_v2((m_path / "sqlite3-statement.db").string().c_str(), &db,
+                                 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+#ifdef NDN_CXX_DISABLE_SQLITE3_FS_LOCKING
+                                 "unix-dotfile"
+#else
+                                 nullptr
+#endif
+                                 );
+
+    if (result != SQLITE_OK) {
+      BOOST_FAIL("Sqlite3 database cannot be opened/created: " + m_path.string());
+    }
+  }
+
+  ~Sqlite3StatementTestFixture()
+  {
+    sqlite3_close(db);
+    boost::filesystem::remove_all(m_path);
+  }
+
+private:
+  boost::filesystem::path m_path;
+
+public:
+  sqlite3* db;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestSqlite3Statement, Sqlite3StatementTestFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  // create table
+  BOOST_CHECK_NO_THROW(Sqlite3Statement(db, "CREATE TABLE test (t1 int, t2 text)").step());
+
+  // insert data into table
+  BOOST_CHECK_NO_THROW(Sqlite3Statement(db, "INSERT INTO test VALUES (1, 'test1')").step());
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (2, ?)");
+    stmt.bind(1, "test2", std::strlen("test2"), SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (3, ?)");
+    stmt.bind(1, "test3", SQLITE_TRANSIENT);
+    stmt.step();
+  }
+
+  Block block(100);
+  block.encode();
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (4, ?)");
+    stmt.bind(1, block, SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (5, ?)");
+    stmt.bind(1, reinterpret_cast<const void*>(block.wire()), block.size(), SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (?, ?)");
+    stmt.bind(1, 6);
+    stmt.bind(2, "test", SQLITE_TRANSIENT);
+    stmt.step();
+  }
+
+  // check content of the table
+
+  {
+    Sqlite3Statement stmt(db, "SELECT count(*) FROM test");
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 6);
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_DONE);
+  }
+
+  {
+    Sqlite3Statement stmt(db, "SELECT t1, t2 FROM test ORDER BY t1");
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 1);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test1");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 2);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test2");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 3);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test3");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 4);
+
+    Block newBlock = stmt.getBlock(1);
+    BOOST_CHECK_EQUAL(newBlock.type(), 100);
+    BOOST_CHECK_EQUAL(newBlock, block);
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 5);
+    BOOST_CHECK_EQUAL(stmt.getSize(1), block.size());
+    BOOST_CHECK_EQUAL_COLLECTIONS(block.begin(), block.end(),
+                                  stmt.getBlob(1), stmt.getBlob(1) + stmt.getSize(1));
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_DONE);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSqlite3Statement
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/string-helper.t.cpp b/tests/unit/util/string-helper.t.cpp
new file mode 100644
index 0000000..0456a35
--- /dev/null
+++ b/tests/unit/util/string-helper.t.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/string-helper.hpp"
+#include "encoding/buffer.hpp"
+
+#include "boost-test.hpp"
+
+#include <cctype>
+#include <cstring>
+
+namespace ndn {
+namespace util {
+namespace test {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestStringHelper)
+
+BOOST_AUTO_TEST_CASE(PrintHex)
+{
+  output_test_stream os;
+
+  printHex(os, 0);
+  BOOST_CHECK(os.is_equal("0x0"));
+
+  printHex(os, 42);
+  BOOST_CHECK(os.is_equal("0x2a"));
+
+  printHex(os, 2748, true);
+  BOOST_CHECK(os.is_equal("0xABC"));
+
+  printHex(os, static_cast<uint64_t>(-1));
+  BOOST_CHECK(os.is_equal("0xffffffffffffffff"));
+
+  printHex(os, ~0U, true);
+  BOOST_CHECK(os.is_equal("0xFFFFFFFF"));
+
+  printHex(os, ~0ULL, true);
+  BOOST_CHECK(os.is_equal("0xFFFFFFFFFFFFFFFF"));
+}
+
+BOOST_AUTO_TEST_CASE(AsHex)
+{
+  using ndn::AsHex;
+  output_test_stream os;
+
+  os << AsHex{0};
+  BOOST_CHECK(os.is_equal("0x0"));
+
+  os << AsHex{42};
+  BOOST_CHECK(os.is_equal("0x2a"));
+
+  os << std::uppercase << AsHex{~0U};
+  BOOST_CHECK(os.is_equal("0xFFFFFFFF"));
+
+  os << std::nouppercase << AsHex{~0U};
+  BOOST_CHECK(os.is_equal("0xffffffff"));
+}
+
+BOOST_AUTO_TEST_CASE(ToHex)
+{
+  std::string test = "Hello, world!";
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size()),
+                    "48656C6C6F2C20776F726C6421");
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size(), false),
+                    "48656c6c6f2c20776f726c6421");
+  BOOST_CHECK_EQUAL(toHex(nullptr, 0), "");
+
+  Buffer buffer(test.data(), test.size());
+  BOOST_CHECK_EQUAL(toHex(buffer, false),  "48656c6c6f2c20776f726c6421");
+  BOOST_CHECK_EQUAL(toHex(Buffer{}), "");
+}
+
+BOOST_AUTO_TEST_CASE(FromHex)
+{
+  BOOST_CHECK(*fromHex("") == Buffer{});
+  BOOST_CHECK(*fromHex("48656c6c6f2c20776f726c6421") ==
+              (std::vector<uint8_t>{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20,
+                                    0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21}));
+  BOOST_CHECK(*fromHex("012a3Bc4defAB5CdEF") ==
+              (std::vector<uint8_t>{0x01, 0x2a, 0x3b, 0xc4, 0xde,
+                                    0xfa, 0xb5, 0xcd, 0xef}));
+
+  BOOST_CHECK_THROW(fromHex("1"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("zz"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("00az"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("1234z"), StringHelperError);
+}
+
+BOOST_AUTO_TEST_CASE(ToHexChar)
+{
+  static const std::vector<std::pair<unsigned int, char>> hexMap{
+    {0, '0'}, {1, '1'},  {2, '2'},  {3, '3'},  {4, '4'},  {5, '5'},  {6, '6'},  {7, '7'},
+    {8, '8'}, {9, '9'}, {10, 'A'}, {11, 'B'}, {12, 'C'}, {13, 'D'}, {14, 'E'}, {15, 'F'}
+  };
+
+  for (const auto& i : hexMap) {
+    BOOST_CHECK_EQUAL(toHexChar(i.first), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 16), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 32), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 240), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first, false), std::tolower(static_cast<unsigned char>(i.second)));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(FromHexChar)
+{
+  // for (int ch = 0; ch <= std::numeric_limits<uint8_t>::max(); ++ch) {
+  //   std::cout << "{0x" << std::hex << ch << ", "
+  //             << std::dec << fromHexChar(static_cast<char>(ch)) << "}, ";
+  //   if (ch % 8 == 7)
+  //     std::cout << std::endl;
+  // }
+  static const std::vector<std::pair<char, int>> hexMap{
+    {0x0, -1}, {0x1, -1}, {0x2, -1}, {0x3, -1}, {0x4, -1}, {0x5, -1}, {0x6, -1}, {0x7, -1},
+    {0x8, -1}, {0x9, -1}, {0xa, -1}, {0xb, -1}, {0xc, -1}, {0xd, -1}, {0xe, -1}, {0xf, -1},
+    {0x10, -1}, {0x11, -1}, {0x12, -1}, {0x13, -1}, {0x14, -1}, {0x15, -1}, {0x16, -1}, {0x17, -1},
+    {0x18, -1}, {0x19, -1}, {0x1a, -1}, {0x1b, -1}, {0x1c, -1}, {0x1d, -1}, {0x1e, -1}, {0x1f, -1},
+    {0x20, -1}, {0x21, -1}, {0x22, -1}, {0x23, -1}, {0x24, -1}, {0x25, -1}, {0x26, -1}, {0x27, -1},
+    {0x28, -1}, {0x29, -1}, {0x2a, -1}, {0x2b, -1}, {0x2c, -1}, {0x2d, -1}, {0x2e, -1}, {0x2f, -1},
+    {0x30, 0}, {0x31, 1}, {0x32, 2}, {0x33, 3}, {0x34, 4}, {0x35, 5}, {0x36, 6}, {0x37, 7},
+    {0x38, 8}, {0x39, 9}, {0x3a, -1}, {0x3b, -1}, {0x3c, -1}, {0x3d, -1}, {0x3e, -1}, {0x3f, -1},
+    {0x40, -1}, {0x41, 10}, {0x42, 11}, {0x43, 12}, {0x44, 13}, {0x45, 14}, {0x46, 15}, {0x47, -1},
+    {0x48, -1}, {0x49, -1}, {0x4a, -1}, {0x4b, -1}, {0x4c, -1}, {0x4d, -1}, {0x4e, -1}, {0x4f, -1},
+    {0x50, -1}, {0x51, -1}, {0x52, -1}, {0x53, -1}, {0x54, -1}, {0x55, -1}, {0x56, -1}, {0x57, -1},
+    {0x58, -1}, {0x59, -1}, {0x5a, -1}, {0x5b, -1}, {0x5c, -1}, {0x5d, -1}, {0x5e, -1}, {0x5f, -1},
+    {0x60, -1}, {0x61, 10}, {0x62, 11}, {0x63, 12}, {0x64, 13}, {0x65, 14}, {0x66, 15}, {0x67, -1},
+    {0x68, -1}, {0x69, -1}, {0x6a, -1}, {0x6b, -1}, {0x6c, -1}, {0x6d, -1}, {0x6e, -1}, {0x6f, -1},
+    {0x70, -1}, {0x71, -1}, {0x72, -1}, {0x73, -1}, {0x74, -1}, {0x75, -1}, {0x76, -1}, {0x77, -1},
+    {0x78, -1}, {0x79, -1}, {0x7a, -1}, {0x7b, -1}, {0x7c, -1}, {0x7d, -1}, {0x7e, -1}, {0x7f, -1},
+    {0x80, -1}, {0x81, -1}, {0x82, -1}, {0x83, -1}, {0x84, -1}, {0x85, -1}, {0x86, -1}, {0x87, -1},
+    {0x88, -1}, {0x89, -1}, {0x8a, -1}, {0x8b, -1}, {0x8c, -1}, {0x8d, -1}, {0x8e, -1}, {0x8f, -1},
+    {0x90, -1}, {0x91, -1}, {0x92, -1}, {0x93, -1}, {0x94, -1}, {0x95, -1}, {0x96, -1}, {0x97, -1},
+    {0x98, -1}, {0x99, -1}, {0x9a, -1}, {0x9b, -1}, {0x9c, -1}, {0x9d, -1}, {0x9e, -1}, {0x9f, -1},
+    {0xa0, -1}, {0xa1, -1}, {0xa2, -1}, {0xa3, -1}, {0xa4, -1}, {0xa5, -1}, {0xa6, -1}, {0xa7, -1},
+    {0xa8, -1}, {0xa9, -1}, {0xaa, -1}, {0xab, -1}, {0xac, -1}, {0xad, -1}, {0xae, -1}, {0xaf, -1},
+    {0xb0, -1}, {0xb1, -1}, {0xb2, -1}, {0xb3, -1}, {0xb4, -1}, {0xb5, -1}, {0xb6, -1}, {0xb7, -1},
+    {0xb8, -1}, {0xb9, -1}, {0xba, -1}, {0xbb, -1}, {0xbc, -1}, {0xbd, -1}, {0xbe, -1}, {0xbf, -1},
+    {0xc0, -1}, {0xc1, -1}, {0xc2, -1}, {0xc3, -1}, {0xc4, -1}, {0xc5, -1}, {0xc6, -1}, {0xc7, -1},
+    {0xc8, -1}, {0xc9, -1}, {0xca, -1}, {0xcb, -1}, {0xcc, -1}, {0xcd, -1}, {0xce, -1}, {0xcf, -1},
+    {0xd0, -1}, {0xd1, -1}, {0xd2, -1}, {0xd3, -1}, {0xd4, -1}, {0xd5, -1}, {0xd6, -1}, {0xd7, -1},
+    {0xd8, -1}, {0xd9, -1}, {0xda, -1}, {0xdb, -1}, {0xdc, -1}, {0xdd, -1}, {0xde, -1}, {0xdf, -1},
+    {0xe0, -1}, {0xe1, -1}, {0xe2, -1}, {0xe3, -1}, {0xe4, -1}, {0xe5, -1}, {0xe6, -1}, {0xe7, -1},
+    {0xe8, -1}, {0xe9, -1}, {0xea, -1}, {0xeb, -1}, {0xec, -1}, {0xed, -1}, {0xee, -1}, {0xef, -1},
+    {0xf0, -1}, {0xf1, -1}, {0xf2, -1}, {0xf3, -1}, {0xf4, -1}, {0xf5, -1}, {0xf6, -1}, {0xf7, -1},
+    {0xf8, -1}, {0xf9, -1}, {0xfa, -1}, {0xfb, -1}, {0xfc, -1}, {0xfd, -1}, {0xfe, -1}, {0xff, -1}
+  };
+
+  for (const auto& item : hexMap) {
+    BOOST_CHECK_EQUAL(fromHexChar(item.first), item.second);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(Escape)
+{
+  BOOST_CHECK_EQUAL(escape(""), "");
+  BOOST_CHECK_EQUAL(escape("foo42"), "foo42");
+  BOOST_CHECK_EQUAL(escape("foo%bar"), "foo%25bar");
+  BOOST_CHECK_EQUAL(escape("lower UPPER"), "lower%20UPPER");
+  BOOST_CHECK_EQUAL(escape("-._~"), "-._~");
+  BOOST_CHECK_EQUAL(escape(":/?#[]@"), "%3A%2F%3F%23%5B%5D%40");
+
+  output_test_stream os;
+  const char str[] = "\x01\x2a\x3b\xc4\xde\xfa\xb5\xcd\xef";
+  escape(os, str, std::strlen(str));
+  BOOST_CHECK(os.is_equal("%01%2A%3B%C4%DE%FA%B5%CD%EF"));
+}
+
+BOOST_AUTO_TEST_CASE(Unescape)
+{
+  BOOST_CHECK_EQUAL(unescape(""), "");
+  BOOST_CHECK_EQUAL(unescape("Hello%01, world!%AA  "), "Hello\x01, world!\xAA  ");
+  BOOST_CHECK_EQUAL(unescape("Bad %ZZ (not a hex value)"), "Bad %ZZ (not a hex value)");
+  BOOST_CHECK_EQUAL(unescape("Bad %a (should be two hex chars)"), "Bad %a (should be two hex chars)");
+  BOOST_CHECK_EQUAL(unescape("Bad %a"), "Bad %a");
+
+  output_test_stream os;
+  const char str[] = "%01%2a%3B%c4%de%fA%B5%Cd%EF";
+  unescape(os, str, std::strlen(str));
+  BOOST_CHECK(os.is_equal("\x01\x2a\x3b\xc4\xde\xfa\xb5\xcd\xef"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStringHelper
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace test
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/time-unit-test-clock.t.cpp b/tests/unit/util/time-unit-test-clock.t.cpp
new file mode 100644
index 0000000..dd8346a
--- /dev/null
+++ b/tests/unit/util/time-unit-test-clock.t.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/time-unit-test-clock.hpp"
+#include "util/scheduler.hpp"
+
+#include "boost-test.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <thread>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestTimeUnitTestClock, UnitTestTimeFixture)
+
+BOOST_AUTO_TEST_CASE(SystemClock)
+{
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  steadyClock->advance(1_day);
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  systemClock->advance(1_day);
+  BOOST_CHECK_GT(time::system_clock::now().time_since_epoch(),
+                 time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  time::system_clock::TimePoint referenceTime =
+    time::fromUnixTimestamp(time::milliseconds(1390966967032LL));
+  BOOST_CHECK_GT(time::system_clock::now(), referenceTime);
+
+  systemClock->setNow(referenceTime.time_since_epoch());
+  BOOST_CHECK_EQUAL(time::system_clock::now(), referenceTime);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(time::system_clock::now()),
+                    "1390966967032000000 nanoseconds since unit test beginning");
+
+  BOOST_CHECK_EQUAL(time::toIsoString(referenceTime), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(time::toString(referenceTime), "2014-01-29 03:42:47");
+  BOOST_CHECK_EQUAL(time::toString(referenceTime), "2014-01-29 03:42:47");
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(time::toString(referenceTime, "%Y. gada %d. %B",
+  //                                  std::locale("lv_LV.UTF-8")),
+  //                   "2014. gada 29. Janvāris");
+
+  BOOST_CHECK_EQUAL(time::toString(referenceTime, "%Y -- %d -- %B",
+                                   std::locale("C")),
+                    "2014 -- 29 -- January");
+
+  BOOST_CHECK_EQUAL(time::fromIsoString("20140129T034247.032000"), referenceTime);
+  BOOST_CHECK_EQUAL(time::fromIsoString("20140129T034247.032000Z"), referenceTime);
+  BOOST_CHECK_EQUAL(time::fromString("2014-01-29 03:42:47"),
+                    time::fromUnixTimestamp(1390966967_s));
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(time::fromString("2014. gada 29. Janvāris", "%Y. gada %d. %B",
+  //                                    std::locale("lv_LV.UTF-8")),
+  //                   time::fromUnixTimestamp(1390953600_s));
+
+  BOOST_CHECK_EQUAL(time::fromString("2014 -- 29 -- January", "%Y -- %d -- %B",
+                                     std::locale("C")),
+                    time::fromUnixTimestamp(1390953600_s));
+}
+
+BOOST_AUTO_TEST_CASE(SteadyClock)
+{
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  systemClock->advance(36500_days);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  steadyClock->advance(100_ns);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 100_ns);
+
+  steadyClock->advance(100_us);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 100100_ns);
+
+  steadyClock->setNow(1_ms);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 1000000_ns);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(time::steady_clock::now()),
+                    "1000000 nanoseconds since unit test beginning");
+}
+
+BOOST_AUTO_TEST_CASE(Scheduler)
+{
+  ndn::Scheduler scheduler(io);
+
+  bool hasFired = false;
+  scheduler.scheduleEvent(100_s, [&] { hasFired = true; });
+
+  io.poll();
+  BOOST_CHECK_EQUAL(hasFired, false);
+
+  steadyClock->advance(100_s);
+
+  io.poll();
+  BOOST_CHECK_EQUAL(hasFired, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTimeUnitTestClock
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/time.t.cpp b/tests/unit/util/time.t.cpp
new file mode 100644
index 0000000..a60e2d9
--- /dev/null
+++ b/tests/unit/util/time.t.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "util/time.hpp"
+
+#include "boost-test.hpp"
+
+#include <thread>
+
+namespace ndn {
+namespace time {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestTime)
+
+BOOST_AUTO_TEST_CASE(SystemClock)
+{
+  system_clock::TimePoint value = system_clock::now();
+  system_clock::TimePoint referenceTime = fromUnixTimestamp(milliseconds(1390966967032LL));
+
+  BOOST_CHECK_GT(value, referenceTime);
+
+  BOOST_CHECK_EQUAL(toIsoString(referenceTime), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(toString(referenceTime), "2014-01-29 03:42:47");
+  BOOST_CHECK_EQUAL(toString(referenceTime), "2014-01-29 03:42:47");
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(toString(referenceTime, "%Y. gada %d. %B", std::locale("lv_LV.UTF-8")),
+  //                   "2014. gada 29. Janvāris");
+
+  BOOST_CHECK_EQUAL(toString(referenceTime, "%Y -- %d -- %B", std::locale("C")),
+                    "2014 -- 29 -- January");
+
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), referenceTime);
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000Z"), referenceTime);
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47"), fromUnixTimestamp(1390966967_s));
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(fromString("2014. gada 29. Janvāris", "%Y. gada %d. %B", std::locale("lv_LV.UTF-8")),
+  //                   fromUnixTimestamp(1390953600_s));
+
+  BOOST_CHECK_EQUAL(fromString("2014 -- 29 -- January", "%Y -- %d -- %B", std::locale("C")),
+                    fromUnixTimestamp(1390953600_s));
+}
+
+BOOST_AUTO_TEST_CASE(SteadyClock)
+{
+  steady_clock::TimePoint oldValue = steady_clock::now();
+  std::this_thread::sleep_for(std::chrono::milliseconds(1));
+  steady_clock::TimePoint newValue = steady_clock::now();
+  BOOST_CHECK_GT(newValue, oldValue);
+}
+
+BOOST_AUTO_TEST_CASE(Abs)
+{
+  BOOST_CHECK_EQUAL(abs(nanoseconds(24422)), nanoseconds(24422));
+  BOOST_CHECK_EQUAL(abs(microseconds(0)), microseconds(0));
+  BOOST_CHECK_EQUAL(abs(milliseconds(-15583)), milliseconds(15583));
+}
+
+BOOST_AUTO_TEST_CASE(LargeDates)
+{
+  auto value = fromUnixTimestamp(1390966967032_ms);
+  BOOST_CHECK_EQUAL(toIsoString(value), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2014-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47.032000", "%Y-%m-%d %H:%M:%S%F"), value);
+
+  value += 36524_days;
+  BOOST_CHECK_EQUAL(toIsoString(value), "21140129T034247.032000");
+  BOOST_CHECK_EQUAL(fromIsoString("21140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2114-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2114-01-29 03:42:47.03200", "%Y-%m-%d %H:%M:%S%F"), value);
+}
+
+BOOST_AUTO_TEST_CASE(Literals)
+{
+  BOOST_CHECK_EQUAL(42_s, seconds(42));
+
+  BOOST_CHECK_EQUAL(1_day, 24_h);
+  BOOST_CHECK_EQUAL(2_days, 48_h);
+  BOOST_CHECK_EQUAL(0.5_day, 12_h);
+  BOOST_CHECK_EQUAL(.5_days, 12_h);
+
+  BOOST_CHECK_EQUAL(1_h, 60_min);
+  BOOST_CHECK_EQUAL(0.5_h, 30_min);
+
+  BOOST_CHECK_EQUAL(1_min, 60_s);
+  BOOST_CHECK_EQUAL(0.5_min, 30_s);
+
+  BOOST_CHECK_EQUAL(1_s, 1000_ms);
+  BOOST_CHECK_EQUAL(0.5_s, 500_ms);
+
+  BOOST_CHECK_EQUAL(1_ms, 1000_us);
+  BOOST_CHECK_EQUAL(0.5_ms, 500_us);
+
+  BOOST_CHECK_EQUAL(1_us, 1000_ns);
+  BOOST_CHECK_EQUAL(0.5_us, 500_ns);
+
+  BOOST_CHECK_EQUAL(1_ns, nanoseconds(1));
+  BOOST_CHECK_EQUAL(5.5_ns, 0.0055_us);
+}
+
+BOOST_AUTO_TEST_CASE(Year2038)
+{
+  auto year2042 = fromIsoString("20420101T000001.042000");
+  auto year2010 = fromIsoString("20100101T000001.042000");
+
+  BOOST_CHECK_EQUAL(to_string(year2010), "1262304001042000000 nanoseconds since Jan 1, 1970");
+  BOOST_CHECK_EQUAL(to_string(year2042), "2272147201042000000 nanoseconds since Jan 1, 1970");
+  BOOST_CHECK_GT(year2042, year2010);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTime
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace time
+} // namespace ndn