util: improve io::load and io::save test coverage

refs #3741

Change-Id: Iefb4ab1c627550fe9a8cf9b8675a796a03132d70
diff --git a/tests/unit-tests/util/io.t.cpp b/tests/unit-tests/util/io.t.cpp
index b8d0347..9d6ec03 100644
--- a/tests/unit-tests/util/io.t.cpp
+++ b/tests/unit-tests/util/io.t.cpp
@@ -21,33 +21,246 @@
 
 #include "util/io.hpp"
 #include "security/key-chain.hpp"
-#include "identity-management-fixture.hpp"
 
 #include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include <boost/filesystem.hpp>
+#include <fstream>
 
 namespace ndn {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(UtilIo, IdentityManagementFixture)
-
-BOOST_AUTO_TEST_CASE(Basic)
+class IoFixture
 {
-  Name identity("/TestIO/Basic");
+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:
+  class Error : public tlv::Error
+  {
+  public:
+    Error()
+      : tlv::Error("encode error")
+    {
+    }
+  };
+
+  Block
+  wireEncode() const
+  {
+    if (shouldThrow) {
+      BOOST_THROW_EXCEPTION(Error());
+    }
+
+    // block will be 0xAA, 0x01, 0xDD
+    return makeNonNegativeIntegerBlock(0xAA, 0xDD);
+  }
+
+public:
+  bool shouldThrow = false;
+};
+
+class DecodableType
+{
+public:
+  class Error : public tlv::Error
+  {
+  public:
+    Error()
+      : tlv::Error("decode error")
+    {
+    }
+  };
+
+  void
+  wireDecode(const Block& block) const
+  {
+    if (shouldThrow) {
+      BOOST_THROW_EXCEPTION(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);
+  }
+
+public:
+  bool shouldThrow = false;
+};
+
+class DecodableTypeThrow : public DecodableType
+{
+public:
+  DecodableTypeThrow()
+  {
+    this->shouldThrow = true;
+  }
+};
+
+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::BASE_64);
+  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)
+{
+  this->mkdir();
+  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::BASE_64));
+  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)
+{
+  Name identity("/TestIo/IdCert");
   identity.appendVersion();
   BOOST_REQUIRE(addIdentity(identity, RsaKeyParams()));
   Name certName = m_keyChain.getDefaultCertificateNameForIdentity(identity);
   shared_ptr<IdentityCertificate> idCert;
   BOOST_REQUIRE_NO_THROW(idCert = m_keyChain.getCertificate(certName));
 
-  std::string file("/tmp/TestIO-Basic");
-  io::save(*idCert, file);
-  shared_ptr<IdentityCertificate> readCert = io::load<IdentityCertificate>(file);
+  io::save(*idCert, filename);
+  shared_ptr<IdentityCertificate> readCert = io::load<IdentityCertificate>(filename);
 
-  BOOST_CHECK(static_cast<bool>(readCert));
-  BOOST_CHECK(idCert->getName() == readCert->getName());
+  BOOST_CHECK(readCert != nullptr);
+  BOOST_CHECK_EQUAL(idCert->getName(), readCert->getName());
 }
 
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END() // TestIo
+BOOST_AUTO_TEST_SUITE_END() // Util
 
 } // namespace tests
 } // namespace ndn