You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by jm...@apache.org on 2020/04/13 08:09:42 UTC

[incubator-datasketches-cpp] branch catch2_tests updated: migrate hll, theta to catch2 for testing

This is an automated email from the ASF dual-hosted git repository.

jmalkin pushed a commit to branch catch2_tests
in repository https://gitbox.apache.org/repos/asf/incubator-datasketches-cpp.git


The following commit(s) were added to refs/heads/catch2_tests by this push:
     new 2581b1c  migrate hll, theta to catch2 for testing
2581b1c is described below

commit 2581b1c92f14613ea8cdcf937ff63ea68e3a7f5d
Author: Jon Malkin <jm...@users.noreply.github.com>
AuthorDate: Mon Apr 13 01:09:23 2020 -0700

    migrate hll, theta to catch2 for testing
---
 common/test/CMakeLists.txt             |  36 +-
 cpc/test/CMakeLists.txt                |   7 +-
 fi/test/CMakeLists.txt                 |   7 +-
 hll/test/AuxHashMapTest.cpp            | 124 +++---
 hll/test/CouponHashSetTest.cpp         | 226 +++++-----
 hll/test/CouponListTest.cpp            | 334 +++++++--------
 hll/test/CrossCountingTest.cpp         | 141 +++---
 hll/test/HllArrayTest.cpp              | 357 +++++++---------
 hll/test/HllSketchTest.cpp             | 501 ++++++++++------------
 hll/test/HllUnionTest.cpp              | 707 +++++++++++++++---------------
 hll/test/IsomorphicTest.cpp            | 193 ++++-----
 hll/test/TablesTest.cpp                |  51 +--
 hll/test/ToFromByteArrayTest.cpp       | 271 ++++++------
 kll/test/CMakeLists.txt                |   7 +-
 sampling/test/CMakeLists.txt           |   7 +-
 theta/test/theta_a_not_b_test.cpp      | 403 +++++++++---------
 theta/test/theta_intersection_test.cpp | 406 +++++++++---------
 theta/test/theta_sketch_test.cpp       | 755 ++++++++++++++++-----------------
 theta/test/theta_union_test.cpp        | 157 ++++---
 19 files changed, 2142 insertions(+), 2548 deletions(-)

diff --git a/common/test/CMakeLists.txt b/common/test/CMakeLists.txt
index c75fed6..6ac5066 100644
--- a/common/test/CMakeLists.txt
+++ b/common/test/CMakeLists.txt
@@ -17,38 +17,22 @@
 
 add_library(common_test OBJECT "")
 
-target_link_libraries(common_test ${CPPUNIT_LIBRARY})
-
 set_target_properties(common_test PROPERTIES
   CXX_STANDARD 11
   CXX_STANDARD_REQUIRED YES
 )
-target_include_directories(common_test
-  PUBLIC
-    ${CPPUNIT_INCLUDE_DIR}
-)
 
-target_sources(common_test
-  PRIVATE
-    test_runner.cpp
-    test_type.hpp
-    test_allocator.hpp
-    test_allocator.cpp
+target_include_directories(common
+  INTERFACE
+    ${CMAKE_CURRENT_SOURCE_DIR}
 )
 
-
-add_library(catch_test OBJECT "")
-
-set_target_properties(catch_test PROPERTIES
-  CXX_STANDARD 11
-  CXX_STANDARD_REQUIRED YES
-)
-
-target_sources(catch_test
+target_sources(common_test
+  INTERFACE
+    ${CMAKE_CURRENT_SOURCE_DIR}/catch.hpp  
+    ${CMAKE_CURRENT_SOURCE_DIR}/test_allocator.hpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test_type.hpp
   PRIVATE
-    catch.hpp
-    catch_runner.cpp
-    test_type.hpp
-    test_allocator.hpp
-    test_allocator.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/catch_runner.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test_allocator.cpp
 )
diff --git a/cpc/test/CMakeLists.txt b/cpc/test/CMakeLists.txt
index cc96580..0d31219 100644
--- a/cpc/test/CMakeLists.txt
+++ b/cpc/test/CMakeLists.txt
@@ -17,7 +17,7 @@
 
 add_executable(cpc_test)
 
-target_link_libraries(cpc_test cpc catch_test)
+target_link_libraries(cpc_test cpc common_test)
 
 set_target_properties(cpc_test PROPERTIES
   CXX_STANDARD 11
@@ -41,8 +41,3 @@ target_sources(cpc_test
     cpc_union_test.cpp
     compression_test.cpp
 )
-
-target_include_directories(cpc_test
-  PRIVATE
-    ../../common/test
-)
diff --git a/fi/test/CMakeLists.txt b/fi/test/CMakeLists.txt
index 37d01f0..51c020c 100644
--- a/fi/test/CMakeLists.txt
+++ b/fi/test/CMakeLists.txt
@@ -17,7 +17,7 @@
 
 add_executable(fi_test)
 
-target_link_libraries(fi_test fi catch_test)
+target_link_libraries(fi_test fi common_test)
 
 set_target_properties(fi_test PROPERTIES
   CXX_STANDARD 11
@@ -41,8 +41,3 @@ target_sources(fi_test
     frequent_items_sketch_test.cpp
     frequent_items_sketch_custom_type_test.cpp
 )
-
-target_include_directories(fi_test
-  PRIVATE
-    ../../common/test
-)
diff --git a/hll/test/AuxHashMapTest.cpp b/hll/test/AuxHashMapTest.cpp
index 2ab3fff..827f4ad 100644
--- a/hll/test/AuxHashMapTest.cpp
+++ b/hll/test/AuxHashMapTest.cpp
@@ -17,83 +17,63 @@
  * under the License.
  */
 
-#include "AuxHashMap.hpp"
-
+#include <catch.hpp>
 #include <memory>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-namespace datasketches {
-
-class AuxHashMapTest : public CppUnit::TestFixture {
-
-  // list of values defined at bottom of file
-  static const int nArr[]; // = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
-
-  CPPUNIT_TEST_SUITE(AuxHashMapTest);
-  CPPUNIT_TEST(checkMustReplace);
-  CPPUNIT_TEST(checkGrowSpace);
-  CPPUNIT_TEST(checkExceptionMustFindValueFor);
-  CPPUNIT_TEST(checkExceptionMustAdd);
-  CPPUNIT_TEST_SUITE_END();
-
-  void checkMustReplace() {
-    AuxHashMap<>* map = new AuxHashMap<>(3, 7);
-    map->mustAdd(100, 5);
-    int val = map->mustFindValueFor(100);
-    CPPUNIT_ASSERT_EQUAL(val, 5);
-
-    map->mustReplace(100, 10);
-    val = map->mustFindValueFor(100);
-    CPPUNIT_ASSERT_EQUAL(val, 10);
 
-    CPPUNIT_ASSERT_THROW_MESSAGE("map->mustReplace() should fail",
-                                 map->mustReplace(101, 5), std::invalid_argument);
-
-    delete map;
-  }
+#include "AuxHashMap.hpp"
 
-  void checkGrowSpace() {
-    auto map = std::unique_ptr<AuxHashMap<>, std::function<void(AuxHashMap<>*)>>(
-        AuxHashMap<>::newAuxHashMap(3, 7),
-        AuxHashMap<>::make_deleter()
-        );
-    CPPUNIT_ASSERT_EQUAL(map->getLgAuxArrInts(), 3);
-    for (int i = 1; i <= 7; ++i) {
-      map->mustAdd(i, i);
-    }
-    CPPUNIT_ASSERT_EQUAL(map->getLgAuxArrInts(), 4);
-    auto itr = map->begin(true);
-    int count1 = 0;
-    int count2 = 0;
-    while (itr != map->end()) {
-      ++count2;
-      int pair = *itr;
-      if (pair != 0) { ++count1; }
-      ++itr;
-    }
-    CPPUNIT_ASSERT_EQUAL(count1, 7);
-    CPPUNIT_ASSERT_EQUAL(count2, 16);
-  }
+namespace datasketches {
 
-  void checkExceptionMustFindValueFor() {
-    AuxHashMap<> map(3, 7);
-    map.mustAdd(100, 5);
-    CPPUNIT_ASSERT_THROW_MESSAGE("map.mustFindValueFor() should fail",
-                                 map.mustFindValueFor(101), std::invalid_argument);
+TEST_CASE("aux hash map: check must replace", "[aux_hash_map]") {
+  AuxHashMap<>* map = new AuxHashMap<>(3, 7);
+  map->mustAdd(100, 5);
+  int val = map->mustFindValueFor(100);
+  REQUIRE(val == 5);
+
+  map->mustReplace(100, 10);
+  val = map->mustFindValueFor(100);
+  REQUIRE(val == 10);
+
+  REQUIRE_THROWS_AS(map->mustReplace(101, 5), std::invalid_argument);
+
+  delete map;
+}
+
+TEST_CASE("aux hash map: check grow space", "[aux_hash_map]") {
+  auto map = std::unique_ptr<AuxHashMap<>, std::function<void(AuxHashMap<>*)>>(
+      AuxHashMap<>::newAuxHashMap(3, 7),
+      AuxHashMap<>::make_deleter()
+      );
+  REQUIRE(map->getLgAuxArrInts() == 3);
+  for (int i = 1; i <= 7; ++i) {
+    map->mustAdd(i, i);
   }
-
-  void checkExceptionMustAdd() {
-    AuxHashMap<>* map = AuxHashMap<>::newAuxHashMap(3, 7);
-    map->mustAdd(100, 5);
-    CPPUNIT_ASSERT_THROW_MESSAGE("map->mustAdd() should fail",
-                                 map->mustAdd(100, 6), std::invalid_argument);
-    
-    AuxHashMap<>::make_deleter()(map);
+  REQUIRE(map->getLgAuxArrInts() == 4);
+  auto itr = map->begin(true);
+  int count1 = 0;
+  int count2 = 0;
+  while (itr != map->end()) {
+    ++count2;
+    int pair = *itr;
+    if (pair != 0) { ++count1; }
+    ++itr;
   }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(AuxHashMapTest);
+  REQUIRE(count1 == 7);
+  REQUIRE(count2 == 16);
+}
+
+TEST_CASE("aux hash map: check exception must find value for", "[aux_hash_map]") {
+  AuxHashMap<> map(3, 7);
+  map.mustAdd(100, 5);
+  REQUIRE_THROWS_AS(map.mustFindValueFor(101), std::invalid_argument);
+}
+
+TEST_CASE("aux hash map: check exception must add", "[aux_hash_map]") {
+  AuxHashMap<>* map = AuxHashMap<>::newAuxHashMap(3, 7);
+  map->mustAdd(100, 5);
+  REQUIRE_THROWS_AS(map->mustAdd(100, 6), std::invalid_argument);
+  
+  AuxHashMap<>::make_deleter()(map);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/CouponHashSetTest.cpp b/hll/test/CouponHashSetTest.cpp
index 5ed9e03..76d1b68 100644
--- a/hll/test/CouponHashSetTest.cpp
+++ b/hll/test/CouponHashSetTest.cpp
@@ -21,8 +21,7 @@
 #include "CouponHashSet.hpp"
 #include "HllUtil.hpp"
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 #include <ostream>
 #include <cmath>
 #include <string>
@@ -30,137 +29,102 @@
 
 namespace datasketches {
 
-class CouponHashSetTest : public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(CouponHashSetTest);
-  CPPUNIT_TEST(checkCorruptBytearray);
-  CPPUNIT_TEST(checkCorruptStream);
-  CPPUNIT_TEST_SUITE_END();
-
-  void checkCorruptBytearray() {
-    int lgK = 8;
-    hll_sketch sk1(lgK);
-    for (int i = 0; i < 24; ++i) {
-      sk1.update(i);
-    }
-    auto sketchBytes = sk1.serialize_updatable();
-    uint8_t* bytes = sketchBytes.data();
-    const size_t size = sketchBytes.size();
-
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
-    // fail in HllSketchImpl
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    // fail in CouponHashSet
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 CouponHashSet<>::newSet(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::HASH_SET_PREINTS;
-
-    bytes[HllUtil<>::SER_VER_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
-
-    bytes[HllUtil<>::FAMILY_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
-
-    bytes[HllUtil<>::LG_K_BYTE] = 6;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect too small a lgK for Set mode",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::LG_K_BYTE] = lgK;
-
-    uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
-    bytes[HllUtil<>::MODE_BYTE] = 0x10; // HLL_6, LIST
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::MODE_BYTE] = tmp;
-
-    tmp = bytes[HllUtil<>::LG_ARR_BYTE];
-    bytes[HllUtil<>::LG_ARR_BYTE] = 0;
-    hll_sketch::deserialize(bytes, size);
-    // should work fine despite the corruption
-    bytes[HllUtil<>::LG_ARR_BYTE] = tmp;
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, size - 1),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, 3),
-                                 std::invalid_argument);
+TEST_CASE("coupon hash set: check corrupt bytearray", "[coupon_hash_set]") {
+  int lgK = 8;
+  hll_sketch sk1(lgK);
+  for (int i = 0; i < 24; ++i) {
+    sk1.update(i);
   }
-
-  void checkCorruptStream() {
-    int lgK = 9;
-    hll_sketch sk1(lgK);
-    for (int i = 0; i < 24; ++i) {
-      sk1.update(i);
-    }
-    std::stringstream ss;
-    sk1.serialize_compact(ss);
-
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    // fail in HllSketchImpl
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    // fail in CouponHashSet
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 CouponHashSet<>::newSet(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(HllUtil<>::HASH_SET_PREINTS);
-
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(HllUtil<>::SER_VER);
-
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(HllUtil<>::FAMILY_ID);
-
-    ss.seekg(HllUtil<>::MODE_BYTE);
-    uint8_t tmp = ss.get();
-    ss.seekp(HllUtil<>::MODE_BYTE);
-    ss.put(0x22); // HLL_8, HLL
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::MODE_BYTE);
-    ss.put(tmp);
-
-    ss.seekg(HllUtil<>::LG_ARR_BYTE);
-    tmp = ss.get();
-    ss.seekp(HllUtil<>::LG_ARR_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    hll_sketch::deserialize(ss);
-    // should work fine despite the corruption
-    ss.seekp(HllUtil<>::LG_ARR_BYTE);
-    ss.put(tmp);
+  auto sketchBytes = sk1.serialize_updatable();
+  uint8_t* bytes = sketchBytes.data();
+  const size_t size = sketchBytes.size();
+
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
+  // fail in HllSketchImpl
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  // fail in CouponHashSet
+  REQUIRE_THROWS_AS(CouponHashSet<>::newSet(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::HASH_SET_PREINTS;
+
+  bytes[HllUtil<>::SER_VER_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
+
+  bytes[HllUtil<>::FAMILY_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
+
+  bytes[HllUtil<>::LG_K_BYTE] = 6;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::LG_K_BYTE] = lgK;
+
+  uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
+  bytes[HllUtil<>::MODE_BYTE] = 0x10; // HLL_6, LIST
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::MODE_BYTE] = tmp;
+
+  tmp = bytes[HllUtil<>::LG_ARR_BYTE];
+  bytes[HllUtil<>::LG_ARR_BYTE] = 0;
+  hll_sketch::deserialize(bytes, size);
+  // should work fine despite the corruption
+  bytes[HllUtil<>::LG_ARR_BYTE] = tmp;
+
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size - 1), std::invalid_argument);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, 3), std::invalid_argument);
+}
+
+TEST_CASE("coupon hash set: check corrupt stream", "[coupon_hash_set]") {
+  int lgK = 9;
+  hll_sketch sk1(lgK);
+  for (int i = 0; i < 24; ++i) {
+    sk1.update(i);
   }
-};
+  std::stringstream ss;
+  sk1.serialize_compact(ss);
+
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  // fail in HllSketchImpl
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  // fail in CouponHashSet
+  REQUIRE_THROWS_AS(CouponHashSet<>::newSet(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(HllUtil<>::HASH_SET_PREINTS);
+
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(HllUtil<>::SER_VER);
+
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(HllUtil<>::FAMILY_ID);
+
+  ss.seekg(HllUtil<>::MODE_BYTE);
+  uint8_t tmp = ss.get();
+  ss.seekp(HllUtil<>::MODE_BYTE);
+  ss.put(0x22); // HLL_8, HLL
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::MODE_BYTE);
+  ss.put(tmp);
+
+  ss.seekg(HllUtil<>::LG_ARR_BYTE);
+  tmp = ss.get();
+  ss.seekp(HllUtil<>::LG_ARR_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  hll_sketch::deserialize(ss);
+  // should work fine despite the corruption
+  ss.seekp(HllUtil<>::LG_ARR_BYTE);
+  ss.put(tmp);
+}
 
-CPPUNIT_TEST_SUITE_REGISTRATION(CouponHashSetTest);
 
 } // namespace datasketches
diff --git a/hll/test/CouponListTest.cpp b/hll/test/CouponListTest.cpp
index 5284032..d7d18fc 100644
--- a/hll/test/CouponListTest.cpp
+++ b/hll/test/CouponListTest.cpp
@@ -17,215 +17,165 @@
  * under the License.
  */
 
-#include "hll.hpp"
-#include "CouponList.hpp"
-#include "HllUtil.hpp"
-
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 #include <ostream>
+#include <sstream>
 #include <cmath>
 #include <string>
 #include <exception>
 
-namespace datasketches {
-
-class CouponListTest : public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(CouponListTest);
-  CPPUNIT_TEST(checkIterator);
-  CPPUNIT_TEST(checkDuplicatesAndMisc);
-  CPPUNIT_TEST(checkSerializeDeserialize);
-  CPPUNIT_TEST(checkCorruptBytearrayData);
-  CPPUNIT_TEST(checkCorruptStreamData);
-  CPPUNIT_TEST_SUITE_END();
+#include "hll.hpp"
+#include "CouponList.hpp"
+#include "HllUtil.hpp"
 
-  void println_string(std::string str) {
-    //std::cout << str << std::endl;
-  }
+namespace datasketches {
 
-  void checkIterator() {
-    int lgConfigK = 8;
-    CouponList<> cl(lgConfigK, HLL_4, LIST);
-    for (int i = 1; i <= 8; ++i) { cl.couponUpdate(HllUtil<>::pair(i, i)); } // not hashes but distinct values
-    const int mask = (1 << lgConfigK) - 1;
-    int idx = 0;
-    auto itr = cl.begin(true);
-    while (itr != cl.end()) {
-      int key = HllUtil<>::getLow26(*itr);
-      int val = HllUtil<>::getValue(*itr);
-      int slot = HllUtil<>::getLow26(*itr) & mask;
-      std::ostringstream oss;
-      oss << "Idx: " << idx << ", Key: " << key << ", Val: " << val
-          << ", Slot: " << slot;
-      println_string(oss.str());
-      CPPUNIT_ASSERT_EQUAL(val, idx + 1);
-      ++itr;
-      ++idx;
-    }
+void println_string(std::string str) {
+  //std::cout << str << std::endl;
+}
+
+TEST_CASE("coupon list: check iterator", "[coupon_list]") {
+  int lgConfigK = 8;
+  CouponList<> cl(lgConfigK, HLL_4, LIST);
+  for (int i = 1; i <= 8; ++i) { cl.couponUpdate(HllUtil<>::pair(i, i)); } // not hashes but distinct values
+  const int mask = (1 << lgConfigK) - 1;
+  int idx = 0;
+  auto itr = cl.begin(true);
+  while (itr != cl.end()) {
+    int key = HllUtil<>::getLow26(*itr);
+    int val = HllUtil<>::getValue(*itr);
+    int slot = HllUtil<>::getLow26(*itr) & mask;
+    std::ostringstream oss;
+    oss << "Idx: " << idx << ", Key: " << key << ", Val: " << val
+        << ", Slot: " << slot;
+    println_string(oss.str());
+    REQUIRE(val == idx + 1);
+    ++itr;
+    ++idx;
   }
+}
 
-  void checkDuplicatesAndMisc() {
-    int lgConfigK = 8;
-    hll_sketch sk(lgConfigK);
-
-    for (int i = 1; i <= 7; ++i) {
-      sk.update(i);
-      sk.update(i);
-    }
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_composite_estimate(), 7.0, 7 * 0.1);
-
-    sk.update(8);
-    sk.update(8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_composite_estimate(), 8.0, 8 * 0.1);
-
-    for (int i = 9; i <= 25; ++i) {
-      sk.update(i);
-      sk.update(i);
-    }
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_composite_estimate(), 25.0, 25 * 0.1);
+TEST_CASE("coupon list: check duplicates and misc", "[coupon_list]") {
+  int lgConfigK = 8;
+  hll_sketch sk(lgConfigK);
 
-    double relErr = sk.get_rel_err(true, true, 4, 1);
-    CPPUNIT_ASSERT(relErr < 0.0);
+  for (int i = 1; i <= 7; ++i) {
+    sk.update(i);
+    sk.update(i);
   }
+  REQUIRE(sk.get_composite_estimate() == Approx(7.0).epsilon(0.1));
 
-  std::string dumpAsHex(const char* data, int len) {
-   constexpr uint8_t hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
-                                 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+  sk.update(8);
+  sk.update(8);
+  REQUIRE(sk.get_composite_estimate() == Approx(8.0).epsilon(0.1));
 
-    std::string s(len * 2, ' ');
-    for (int i = 0; i < len; ++i) {
-      s[2 * i]     = hexmap[(data[i] & 0xF0) >> 4];
-      s[2 * i + 1] = hexmap[(data[i] & 0x0F)];
-    }
-    return s;
+  for (int i = 9; i <= 25; ++i) {
+    sk.update(i);
+    sk.update(i);
   }
+  REQUIRE(sk.get_composite_estimate() == Approx(25.0).epsilon(0.1));
 
-  void serializeDeserialize(const int lgK) {
-    hll_sketch sk1(lgK);
-
-    int u = (lgK < 8) ? 7 : (((1 << (lgK - 3))/ 4) * 3);
-    for (int i = 0; i < u; ++i) {
-      sk1.update(i);
-    }
-    double est1 = sk1.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est1, u, u * 1e-4);
-
-    std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
-    sk1.serialize_compact(ss);
-    hll_sketch sk2 = hll_sketch::deserialize(ss);
-    double est2 = sk2.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est2, est1, 0.0);
-
-    ss.str(std::string());
-    ss.clear();
-
-    sk1.serialize_updatable(ss);
-    sk2 = hll_sketch::deserialize(ss);
-    est2 = sk2.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est2, est1, 0.0);
-  }
-  
-  void checkSerializeDeserialize() {
-    serializeDeserialize(7);
-    serializeDeserialize(21);
-  }
+  double relErr = sk.get_rel_err(true, true, 4, 1);
+  REQUIRE(relErr < 0.0);
+}
 
-  void checkCorruptBytearrayData() {
-    int lgK = 6;
-    hll_sketch sk1(lgK);
-    sk1.update(1);
-    sk1.update(2);
-    auto sketchBytes = sk1.serialize_compact();
-    uint8_t* bytes = sketchBytes.data();
-    const size_t size = sketchBytes.size();
-
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 CouponList<>::newList(bytes, size),
-                                 std::invalid_argument);
-
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::LIST_PREINTS;
-
-    bytes[HllUtil<>::SER_VER_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
-
-    bytes[HllUtil<>::FAMILY_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
-
-    uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
-    bytes[HllUtil<>::MODE_BYTE] = 0x01; // HLL_4, SET
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::MODE_BYTE] = tmp;
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, size - 1),
-                                 std::invalid_argument);
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, 3),
-                                 std::invalid_argument);
-  }
+static void serializeDeserialize(const int lgK) {
+  hll_sketch sk1(lgK);
 
-  void checkCorruptStreamData() {
-    int lgK = 6;
-    hll_sketch sk1(lgK);
-    sk1.update(1);
-    sk1.update(2);
-    std::stringstream ss;
-    sk1.serialize_compact(ss);
-
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 CouponList<>::newList(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(HllUtil<>::LIST_PREINTS);
-
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(HllUtil<>::SER_VER);
-
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(HllUtil<>::FAMILY_ID);
-
-    ss.seekp(HllUtil<>::MODE_BYTE);
-    ss.put(0x22); // HLL_8, HLL
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode bytes",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
+  int u = (lgK < 8) ? 7 : (((1 << (lgK - 3))/ 4) * 3);
+  for (int i = 0; i < u; ++i) {
+    sk1.update(i);
   }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(CouponListTest);
+  double est1 = sk1.get_estimate();
+  REQUIRE(est1 == Approx(u).margin(u * 1e-4));
+
+  std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
+  sk1.serialize_compact(ss);
+  hll_sketch sk2 = hll_sketch::deserialize(ss);
+  double est2 = sk2.get_estimate();
+  REQUIRE(est1 == est2);
+
+  ss.str(std::string());
+  ss.clear();
+
+  sk1.serialize_updatable(ss);
+  sk2 = hll_sketch::deserialize(ss);
+  est2 = sk2.get_estimate();
+  REQUIRE(est1 == est2);
+}
+
+TEST_CASE("coupon list: check serialize deserialize", "[coupon_list]") {
+  serializeDeserialize(7);
+  serializeDeserialize(21);
+}
+
+TEST_CASE("coupon list: check corrupt bytearray data", "[coupon_list]") {
+  int lgK = 6;
+  hll_sketch sk1(lgK);
+  sk1.update(1);
+  sk1.update(2);
+  auto sketchBytes = sk1.serialize_compact();
+  uint8_t* bytes = sketchBytes.data();
+  const size_t size = sketchBytes.size();
+
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  REQUIRE_THROWS_AS(CouponList<>::newList(bytes, size), std::invalid_argument);
+
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::LIST_PREINTS;
+
+  bytes[HllUtil<>::SER_VER_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
+
+  bytes[HllUtil<>::FAMILY_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
+
+  uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
+  bytes[HllUtil<>::MODE_BYTE] = 0x01; // HLL_4, SET
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::MODE_BYTE] = tmp;
+
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size - 1), std::invalid_argument);
+
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, 3), std::invalid_argument);
+}
+
+TEST_CASE("coupon list: check corrupt stream data", "[coupon_list]") {
+  int lgK = 6;
+  hll_sketch sk1(lgK);
+  sk1.update(1);
+  sk1.update(2);
+  std::stringstream ss;
+  sk1.serialize_compact(ss);
+
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  REQUIRE_THROWS_AS(CouponList<>::newList(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(HllUtil<>::LIST_PREINTS);
+
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(HllUtil<>::SER_VER);
+
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(HllUtil<>::FAMILY_ID);
+
+  ss.seekp(HllUtil<>::MODE_BYTE);
+  ss.put(0x22); // HLL_8, HLL
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/CrossCountingTest.cpp b/hll/test/CrossCountingTest.cpp
index c46beba..21eb919 100644
--- a/hll/test/CrossCountingTest.cpp
+++ b/hll/test/CrossCountingTest.cpp
@@ -17,88 +17,77 @@
  * under the License.
  */
 
-#include "hll.hpp"
-
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 #include <ostream>
 #include <cmath>
 #include <string>
 
-namespace datasketches {
-
-class CrossCountingTest : public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(CrossCountingTest);
-  CPPUNIT_TEST(crossCountingChecks);
-  CPPUNIT_TEST_SUITE_END();
-
-  hll_sketch buildSketch(const int n, const int lgK, const target_hll_type tgtHllType) {
-    hll_sketch sketch(lgK, tgtHllType);
-    for (int i = 0; i < n; ++i) {
-      sketch.update(i);
-    }
-    return sketch;
-  }
-
-  void crossCountingCheck(const int lgK, const int n) {
-    hll_sketch sk4 = buildSketch(n, lgK, HLL_4);
-    const double est = sk4.get_estimate();
-    const double lb = sk4.get_lower_bound(1);
-    const double ub = sk4.get_upper_bound(1);
-
-    hll_sketch sk6 = buildSketch(n, lgK, HLL_6);
-    CPPUNIT_ASSERT_EQUAL(sk6.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk6.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk6.get_upper_bound(1), ub);
-
-    hll_sketch sk8 = buildSketch(n, lgK, HLL_8);
-    CPPUNIT_ASSERT_EQUAL(sk8.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk8.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk8.get_upper_bound(1), ub);
-
-    // Conversions
-    hll_sketch sk4to6(sk4, HLL_6);
-    CPPUNIT_ASSERT_EQUAL(sk4to6.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk4to6.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk4to6.get_upper_bound(1), ub);
-
-    hll_sketch sk4to8(sk4, HLL_8);
-    CPPUNIT_ASSERT_EQUAL(sk4to8.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk4to8.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk4to8.get_upper_bound(1), ub);
-
-    hll_sketch sk6to4(sk6, HLL_4);
-    CPPUNIT_ASSERT_EQUAL(sk6to4.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk6to4.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk6to4.get_upper_bound(1), ub);
-
-    hll_sketch sk6to8(sk6, HLL_8);
-    CPPUNIT_ASSERT_EQUAL(sk6to8.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk6to8.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk6to8.get_upper_bound(1), ub);
-
-    hll_sketch sk8to4(sk8, HLL_4);
-    CPPUNIT_ASSERT_EQUAL(sk8to4.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk8to4.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk8to4.get_upper_bound(1), ub);
+#include "hll.hpp"
 
-    hll_sketch sk8to6(sk8, HLL_6);
-    CPPUNIT_ASSERT_EQUAL(sk8to6.get_estimate(), est);
-    CPPUNIT_ASSERT_EQUAL(sk8to6.get_lower_bound(1), lb);
-    CPPUNIT_ASSERT_EQUAL(sk8to6.get_upper_bound(1), ub);
-  }
+namespace datasketches {
 
-  void crossCountingChecks() {
-    crossCountingCheck(4, 100);
-    crossCountingCheck(4, 10000);
-    crossCountingCheck(12, 7);
-    crossCountingCheck(12, 384);
-    crossCountingCheck(12, 10000);
+static hll_sketch buildSketch(const int n, const int lgK, const target_hll_type tgtHllType) {
+  hll_sketch sketch(lgK, tgtHllType);
+  for (int i = 0; i < n; ++i) {
+    sketch.update(i);
   }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(CrossCountingTest);
+  return sketch;
+}
+
+static void crossCountingCheck(const int lgK, const int n) {
+  hll_sketch sk4 = buildSketch(n, lgK, HLL_4);
+  const double est = sk4.get_estimate();
+  const double lb = sk4.get_lower_bound(1);
+  const double ub = sk4.get_upper_bound(1);
+
+  hll_sketch sk6 = buildSketch(n, lgK, HLL_6);
+  REQUIRE(sk6.get_estimate() == est);
+  REQUIRE(sk6.get_lower_bound(1) == lb);
+  REQUIRE(sk6.get_upper_bound(1) == ub);
+
+  hll_sketch sk8 = buildSketch(n, lgK, HLL_8);
+  REQUIRE(sk8.get_estimate() == est);
+  REQUIRE(sk8.get_lower_bound(1) == lb);
+  REQUIRE(sk8.get_upper_bound(1) == ub);
+
+  // Conversions
+  hll_sketch sk4to6(sk4, HLL_6);
+  REQUIRE(sk4to6.get_estimate() == est);
+  REQUIRE(sk4to6.get_lower_bound(1) == lb);
+  REQUIRE(sk4to6.get_upper_bound(1) == ub);
+
+  hll_sketch sk4to8(sk4, HLL_8);
+  REQUIRE(sk4to8.get_estimate() == est);
+  REQUIRE(sk4to8.get_lower_bound(1) == lb);
+  REQUIRE(sk4to8.get_upper_bound(1) == ub);
+
+  hll_sketch sk6to4(sk6, HLL_4);
+  REQUIRE(sk6to4.get_estimate() == est);
+  REQUIRE(sk6to4.get_lower_bound(1) == lb);
+  REQUIRE(sk6to4.get_upper_bound(1) == ub);
+
+  hll_sketch sk6to8(sk6, HLL_8);
+  REQUIRE(sk6to8.get_estimate() == est);
+  REQUIRE(sk6to8.get_lower_bound(1) == lb);
+  REQUIRE(sk6to8.get_upper_bound(1) == ub);
+
+  hll_sketch sk8to4(sk8, HLL_4);
+  REQUIRE(sk8to4.get_estimate() == est);
+  REQUIRE(sk8to4.get_lower_bound(1) == lb);
+  REQUIRE(sk8to4.get_upper_bound(1) == ub);
+
+  hll_sketch sk8to6(sk8, HLL_6);
+  REQUIRE(sk8to6.get_estimate() == est);
+  REQUIRE(sk8to6.get_lower_bound(1) == lb);
+  REQUIRE(sk8to6.get_upper_bound(1) == ub);
+}
+
+TEST_CASE("cross counting: cross counting checks", "[cross_counting]") {
+  crossCountingCheck(4, 100);
+  crossCountingCheck(4, 10000);
+  crossCountingCheck(12, 7);
+  crossCountingCheck(12, 384);
+  crossCountingCheck(12, 10000);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/HllArrayTest.cpp b/hll/test/HllArrayTest.cpp
index 2f75625..72d0851 100644
--- a/hll/test/HllArrayTest.cpp
+++ b/hll/test/HllArrayTest.cpp
@@ -18,215 +18,174 @@
  */
 
 #include "hll.hpp"
-//#include "HllArray.hpp"
-//#include "HllSketch.hpp"
 
 #include <exception>
 #include <sstream>
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 
 namespace datasketches {
 
-class HllArrayTest : public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(HllArrayTest);
-  CPPUNIT_TEST(checkCompositeEstimate);
-  CPPUNIT_TEST(checkSerializeDeserialize);
-  CPPUNIT_TEST(checkIsCompact);
-  CPPUNIT_TEST(checkCorruptBytearray);
-  CPPUNIT_TEST(checkCorruptStream);
-  CPPUNIT_TEST_SUITE_END();
-
-  void testComposite(const int lgK, const target_hll_type tgtHllType, const int n) {
-    hll_union u(lgK);
-    hll_sketch sk(lgK, tgtHllType);
-    for (int i = 0; i < n; ++i) {
-      u.update(i);
-      sk.update(i);
-    }
-    u.update(sk); // merge
-    hll_sketch res = u.get_result(target_hll_type::HLL_8);
-    double est = res.get_composite_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est, sk.get_composite_estimate(), 0.0);
+static void testComposite(const int lgK, const target_hll_type tgtHllType, const int n) {
+  hll_union u(lgK);
+  hll_sketch sk(lgK, tgtHllType);
+  for (int i = 0; i < n; ++i) {
+    u.update(i);
+    sk.update(i);
   }
-
-  void checkCompositeEstimate() {
-    testComposite(4, target_hll_type::HLL_8, 10000);
-    testComposite(5, target_hll_type::HLL_8, 10000);
-    testComposite(6, target_hll_type::HLL_8, 10000);
-    testComposite(13, target_hll_type::HLL_8, 10000);
+  u.update(sk); // merge
+  hll_sketch res = u.get_result(target_hll_type::HLL_8);
+  double est = res.get_composite_estimate();
+  REQUIRE(sk.get_composite_estimate() == est);
+}
+
+TEST_CASE("hll array: check composite estimate", "[hll_array]") {
+  testComposite(4, target_hll_type::HLL_8, 10000);
+  testComposite(5, target_hll_type::HLL_8, 10000);
+  testComposite(6, target_hll_type::HLL_8, 10000);
+  testComposite(13, target_hll_type::HLL_8, 10000);
+}
+
+static void serializeDeserialize(const int lgK, target_hll_type tgtHllType, const int n) {
+  hll_sketch sk1(lgK, tgtHllType);
+
+  for (int i = 0; i < n; ++i) {
+    sk1.update(i);
   }
-
-  void checkSerializeDeserialize() {
-    int lgK = 4;
-    int n = 8;
-    serializeDeserialize(lgK, HLL_4, n);
-    serializeDeserialize(lgK, HLL_6, n);
-    serializeDeserialize(lgK, HLL_8, n);
-
-    lgK = 15;
-    n = (((1 << (lgK - 3))*3)/4) + 100;
-    serializeDeserialize(lgK, HLL_4, n);
-    serializeDeserialize(lgK, HLL_6, n);
-    serializeDeserialize(lgK, HLL_8, n);
-
-    lgK = 21;
-    n = (((1 << (lgK - 3))*3)/4) + 1000;
-    serializeDeserialize(lgK, HLL_4, n);
-    serializeDeserialize(lgK, HLL_6, n);
-    serializeDeserialize(lgK, HLL_8, n);
+  //REQUIRE(sk1.getCurrentMode() == CurMode::HLL);
+
+  double est1 = sk1.get_estimate();
+  REQUIRE(est1 == Approx(n).margin(n * 0.03));
+
+  // serialize as compact and updatable, deserialize, compare estimates are exact
+  std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
+  sk1.serialize_compact(ss);
+  hll_sketch sk2 = hll_sketch::deserialize(ss);
+  REQUIRE(sk1.get_estimate() == sk2.get_estimate());
+
+  ss.clear();
+  sk1.serialize_updatable(ss);
+  sk2 = hll_sketch::deserialize(ss);
+  REQUIRE(sk1.get_estimate() == sk2.get_estimate());
+
+  sk1.reset();
+  REQUIRE(sk1.get_estimate() == 0.0);
+}
+
+TEST_CASE("hll array: check serialize deserialize", "[hll_array]") {
+  int lgK = 4;
+  int n = 8;
+  serializeDeserialize(lgK, HLL_4, n);
+  serializeDeserialize(lgK, HLL_6, n);
+  serializeDeserialize(lgK, HLL_8, n);
+
+  lgK = 15;
+  n = (((1 << (lgK - 3))*3)/4) + 100;
+  serializeDeserialize(lgK, HLL_4, n);
+  serializeDeserialize(lgK, HLL_6, n);
+  serializeDeserialize(lgK, HLL_8, n);
+
+  lgK = 21;
+  n = (((1 << (lgK - 3))*3)/4) + 1000;
+  serializeDeserialize(lgK, HLL_4, n);
+  serializeDeserialize(lgK, HLL_6, n);
+  serializeDeserialize(lgK, HLL_8, n);
+}
+
+TEST_CASE("hll array: check is compact", "[hll_array]") {
+  hll_sketch sk(4);
+  for (int i = 0; i < 8; ++i) {
+    sk.update(i);
   }
-
-  void serializeDeserialize(const int lgK, target_hll_type tgtHllType, const int n) {
-    hll_sketch sk1(lgK, tgtHllType);
-
-    for (int i = 0; i < n; ++i) {
-      sk1.update(i);
-    }
-    //CPPUNIT_ASSERT(sk1.getCurrentMode() == CurMode::HLL);
-
-    double est1 = sk1.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(n, est1, n * 0.03);
-
-    // serialize as compact and updatable, deserialize, compare estimates are exact
-    std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
-    sk1.serialize_compact(ss);
-    hll_sketch sk2 = hll_sketch::deserialize(ss);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk2.get_estimate(), sk1.get_estimate(), 0.0);
-
-    ss.clear();
-    sk1.serialize_updatable(ss);
-    sk2 = hll_sketch::deserialize(ss);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk2.get_estimate(), sk1.get_estimate(), 0.0);
-
-    sk1.reset();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, sk1.get_estimate(), 0.0);
-  }
-
-  void checkIsCompact() {
-    hll_sketch sk(4);
-    for (int i = 0; i < 8; ++i) {
-      sk.update(i);
-    }
-    CPPUNIT_ASSERT(!sk.is_compact());
+  REQUIRE_FALSE(sk.is_compact());
+}
+
+TEST_CASE("hll array: check corrupt bytearray", "[hll_array]") {
+  int lgK = 8;
+  hll_sketch sk1(lgK, HLL_8);
+  for (int i = 0; i < 50; ++i) {
+    sk1.update(i);
   }
-
-  void checkCorruptBytearray() {
-    int lgK = 8;
-    hll_sketch sk1(lgK, HLL_8);
-    for (int i = 0; i < 50; ++i) {
-      sk1.update(i);
-    }
-    auto sketchBytes = sk1.serialize_compact();
-    uint8_t* bytes = sketchBytes.data();
-    const size_t size = sketchBytes.size();
-
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 HllArray<>::newHll(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::HLL_PREINTS;
-
-    bytes[HllUtil<>::SER_VER_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
-
-    bytes[HllUtil<>::FAMILY_BYTE] = 0;
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
-
-    uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
-    bytes[HllUtil<>::MODE_BYTE] = 0x10; // HLL_6, LIST
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode byte",
-                                 hll_sketch::deserialize(bytes, size),
-                                 std::invalid_argument);
-    bytes[HllUtil<>::MODE_BYTE] = tmp;
-
-    tmp = bytes[HllUtil<>::LG_ARR_BYTE];
-    bytes[HllUtil<>::LG_ARR_BYTE] = 0;
-    hll_sketch::deserialize(bytes, size);
-    // should work fine despite the corruption
-    bytes[HllUtil<>::LG_ARR_BYTE] = tmp;
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, size - 1),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialized length",
-                                 hll_sketch::deserialize(bytes, 3),
-                                 std::invalid_argument);
-    }
-
-  void checkCorruptStream() {
-    int lgK = 6;
-    hll_sketch sk1(lgK);
-    for (int i = 0; i < 50; ++i) {
-      sk1.update(i);
-    }
-    std::stringstream ss;
-    sk1.serialize_compact(ss);
-
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in preInts byte",
-                                 HllArray<>::newHll(ss),
-                                 std::invalid_argument);                                
-    ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
-    ss.put(HllUtil<>::HLL_PREINTS);
-
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in serialization version byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::SER_VER_BYTE);
-    ss.put(HllUtil<>::SER_VER);
-
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in family id byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::FAMILY_BYTE);
-    ss.put(HllUtil<>::FAMILY_ID);
-
-    ss.seekg(HllUtil<>::MODE_BYTE);
-    uint8_t tmp = ss.get();
-    ss.seekp(HllUtil<>::MODE_BYTE);
-    ss.put(0x11); // HLL_6, SET
-    ss.seekg(0);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect error in mode byte",
-                                 hll_sketch::deserialize(ss),
-                                 std::invalid_argument);
-    ss.seekp(HllUtil<>::MODE_BYTE);
-    ss.put(tmp);
-
-    ss.seekg(HllUtil<>::LG_ARR_BYTE);
-    tmp = ss.get();
-    ss.seekp(HllUtil<>::LG_ARR_BYTE);
-    ss.put(0);
-    ss.seekg(0);
-    hll_sketch::deserialize(ss);
-    // should work fine despite the corruption
-    ss.seekp(HllUtil<>::LG_ARR_BYTE);
-    ss.put(tmp);
+  auto sketchBytes = sk1.serialize_compact();
+  uint8_t* bytes = sketchBytes.data();
+  const size_t size = sketchBytes.size();
+
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  REQUIRE_THROWS_AS(HllArray<>::newHll(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::PREAMBLE_INTS_BYTE] = HllUtil<>::HLL_PREINTS;
+
+  bytes[HllUtil<>::SER_VER_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::SER_VER_BYTE] = HllUtil<>::SER_VER;
+
+  bytes[HllUtil<>::FAMILY_BYTE] = 0;
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::FAMILY_BYTE] = HllUtil<>::FAMILY_ID;
+
+  uint8_t tmp = bytes[HllUtil<>::MODE_BYTE];
+  bytes[HllUtil<>::MODE_BYTE] = 0x10; // HLL_6, LIST
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size), std::invalid_argument);
+  bytes[HllUtil<>::MODE_BYTE] = tmp;
+
+  tmp = bytes[HllUtil<>::LG_ARR_BYTE];
+  bytes[HllUtil<>::LG_ARR_BYTE] = 0;
+  hll_sketch::deserialize(bytes, size);
+  // should work fine despite the corruption
+  bytes[HllUtil<>::LG_ARR_BYTE] = tmp;
+
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, size - 1), std::invalid_argument);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(bytes, 3), std::invalid_argument);
+}
+
+TEST_CASE("hll array: check corrupt stream", "[hll_array]") {
+  int lgK = 6;
+  hll_sketch sk1(lgK);
+  for (int i = 0; i < 50; ++i) {
+    sk1.update(i);
   }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(HllArrayTest);
+  std::stringstream ss;
+  sk1.serialize_compact(ss);
+
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  REQUIRE_THROWS_AS(HllArray<>::newHll(ss), std::invalid_argument);                                
+  ss.seekp(HllUtil<>::PREAMBLE_INTS_BYTE);
+  ss.put(HllUtil<>::HLL_PREINTS);
+
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::SER_VER_BYTE);
+  ss.put(HllUtil<>::SER_VER);
+
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::FAMILY_BYTE);
+  ss.put(HllUtil<>::FAMILY_ID);
+
+  ss.seekg(HllUtil<>::MODE_BYTE);
+  uint8_t tmp = ss.get();
+  ss.seekp(HllUtil<>::MODE_BYTE);
+  ss.put(0x11); // HLL_6, SET
+  ss.seekg(0);
+  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
+  ss.seekp(HllUtil<>::MODE_BYTE);
+  ss.put(tmp);
+
+  ss.seekg(HllUtil<>::LG_ARR_BYTE);
+  tmp = ss.get();
+  ss.seekp(HllUtil<>::LG_ARR_BYTE);
+  ss.put(0);
+  ss.seekg(0);
+  hll_sketch::deserialize(ss);
+  // should work fine despite the corruption
+  ss.seekp(HllUtil<>::LG_ARR_BYTE);
+  ss.put(tmp);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/HllSketchTest.cpp b/hll/test/HllSketchTest.cpp
index 1e33c1a..1cd62c3 100644
--- a/hll/test/HllSketchTest.cpp
+++ b/hll/test/HllSketchTest.cpp
@@ -19,302 +19,249 @@
 
 #include "hll.hpp"
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 
 namespace datasketches {
 
-class hllSketchTest : public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(hllSketchTest);
-  CPPUNIT_TEST(checkCopies);
-  CPPUNIT_TEST(checkCopyAs);
-  CPPUNIT_TEST(checkMisc1);
-  CPPUNIT_TEST(checkNumStdDev);
-  CPPUNIT_TEST(checkSerSizes);
-  CPPUNIT_TEST(exerciseToString);
-  CPPUNIT_TEST(checkCompactFlag);
-  CPPUNIT_TEST(checkKLimits);
-  CPPUNIT_TEST(checkInputTypes);
-  CPPUNIT_TEST_SUITE_END();
-
-  void checkCopies() {
-    runCheckCopy(14, HLL_4);
-    runCheckCopy(8, HLL_6);
-    runCheckCopy(8, HLL_8);
-  }
-
-  void runCheckCopy(int lgConfigK, target_hll_type tgtHllType) {
-    hll_sketch sk(lgConfigK, tgtHllType);
-
-    for (int i = 0; i < 7; ++i) {
-      sk.update(i);
-    }
-
-    hll_sketch skCopy = sk;
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(skCopy.get_estimate(), sk.get_estimate(), 0.0);
-
-    // no access to hllSketchImpl, so we'll ensure those differ by adding more
-    // data to sk and ensuring the mode and estimates differ
-    for (int i = 7; i < 24; ++i) {
-      sk.update(i);
-    }
-    CPPUNIT_ASSERT(16.0 < (sk.get_estimate() - skCopy.get_estimate()));
-
-    skCopy = sk;
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(skCopy.get_estimate(), sk.get_estimate(), 0.0);
-
-    int u = (sk.get_target_type() == HLL_4) ? 100000 : 25;
-    for (int i = 24; i < u; ++i) {
-      sk.update(i);
-    }
-    CPPUNIT_ASSERT(sk.get_estimate() != skCopy.get_estimate()); // either 1 or 100k difference
+static void runCheckCopy(int lgConfigK, target_hll_type tgtHllType) {
+  hll_sketch sk(lgConfigK, tgtHllType);
 
-    skCopy = sk;
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(skCopy.get_estimate(), sk.get_estimate(), 0.0);
-
-  }
-
-  void checkCopyAs() {
-    copyAs(HLL_4, HLL_4);
-    copyAs(HLL_4, HLL_6);
-    copyAs(HLL_4, HLL_8);
-    copyAs(HLL_6, HLL_4);
-    copyAs(HLL_6, HLL_6);
-    copyAs(HLL_6, HLL_8);
-    copyAs(HLL_8, HLL_4);
-    copyAs(HLL_8, HLL_6);
-    copyAs(HLL_8, HLL_8);
-  }
-
-  void copyAs(target_hll_type srcType, target_hll_type dstType) {
-    int lgK = 8;
-    int n1 = 7;
-    int n2 = 24;
-    int n3 = 1000;
-    int base = 0;
-
-    hll_sketch src(lgK, srcType);
-    for (int i = 0; i < n1; ++i) {
-      src.update(i + base);
-    }
-    hll_sketch dst(src, dstType);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(dst.get_estimate(), src.get_estimate(), 0.0);
-
-    for (int i = n1; i < n2; ++i) {
-      src.update(i + base);
-    }
-    dst = hll_sketch(src, dstType);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(dst.get_estimate(), src.get_estimate(), 0.0);
-
-    for (int i = n2; i < n3; ++i) {
-      src.update(i + base);
-    }
-    dst = hll_sketch(src, dstType);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(dst.get_estimate(), src.get_estimate(), 0.0);
+  for (int i = 0; i < 7; ++i) {
+    sk.update(i);
   }
 
-  void checkMisc1() {
-    int lgConfigK = 8;
-    target_hll_type srcType = target_hll_type::HLL_8;
-    hll_sketch sk(lgConfigK, srcType);
-
-    for (int i = 0; i < 7; ++i) { sk.update(i); } // LIST
-    CPPUNIT_ASSERT_EQUAL(36, sk.get_compact_serialization_bytes());
-    CPPUNIT_ASSERT_EQUAL(40, sk.get_updatable_serialization_bytes());
+  hll_sketch skCopy = sk;
+  REQUIRE(sk.get_estimate() == skCopy.get_estimate());
 
-    for (int i = 7; i < 24; ++i) { sk.update(i); } // SET
-    CPPUNIT_ASSERT_EQUAL(108, sk.get_compact_serialization_bytes());
-    CPPUNIT_ASSERT_EQUAL(140, sk.get_updatable_serialization_bytes());
-
-    sk.update(24); // HLL
-    CPPUNIT_ASSERT_EQUAL(40 + 256, sk.get_updatable_serialization_bytes());
-
-    const int hllBytes = HllUtil<>::HLL_BYTE_ARR_START + (1 << lgConfigK);
-    CPPUNIT_ASSERT_EQUAL(hllBytes, sk.get_compact_serialization_bytes());
-    CPPUNIT_ASSERT_EQUAL(hllBytes, hll_sketch::get_max_updatable_serialization_bytes(lgConfigK, HLL_8));
-  }
-
-  void checkNumStdDev() {
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to throw on invalid number of std deviations",
-                                 HllUtil<>::checkNumStdDev(0), std::invalid_argument);
+  // no access to hllSketchImpl, so we'll ensure those differ by adding more
+  // data to sk and ensuring the mode and estimates differ
+  for (int i = 7; i < 24; ++i) {
+    sk.update(i);
   }
+  REQUIRE(16.0 < (sk.get_estimate() - skCopy.get_estimate()));
 
-  void checkSerSizes() {
-    checkSerializationSizes(8, HLL_8);
-    checkSerializationSizes(8, HLL_6);
-    checkSerializationSizes(8, HLL_4);
-  }
+  skCopy = sk;
+  REQUIRE(sk.get_estimate() == skCopy.get_estimate());
 
-  void checkSerializationSizes(const int lgConfigK, target_hll_type tgtHllType) {
-    hll_sketch sk(lgConfigK, tgtHllType);
-    int i;
-
-    // LIST
-    for (i = 0; i < 7; ++i) { sk.update(i); }
-    int expected = HllUtil<>::LIST_INT_ARR_START + (i << 2);
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_compact_serialization_bytes());
-    expected = HllUtil<>::LIST_INT_ARR_START + (4 << HllUtil<>::LG_INIT_LIST_SIZE);
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_updatable_serialization_bytes());
-
-    // SET
-    for (i = 7; i < 24; ++i) { sk.update(i); }
-    expected = HllUtil<>::HASH_SET_INT_ARR_START + (i << 2);
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_compact_serialization_bytes());
-    expected = HllUtil<>::HASH_SET_INT_ARR_START + (4 << HllUtil<>::LG_INIT_SET_SIZE);
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_updatable_serialization_bytes());
-
-    // HLL
+  int u = (sk.get_target_type() == HLL_4) ? 100000 : 25;
+  for (int i = 24; i < u; ++i) {
     sk.update(i);
-    //CPPUNIT_ASSERT_EQUAL(CurMode::HLL, s.getCurrentMode());
-    //HllArray* hllArr = (HllArray*) s.hllSketchImpl;
-
-  /*
-    int auxCountBytes = 0;
-    int auxArrBytes = 0;
-    if (hllArr.getTgtHllType() == target_hll_type::HLL_4) {
-      AuxHashMap* auxMap = hllArr.getAuxHashMap();
-      if (auxMap != nullptr) {
-        auxCountBytes = auxMap.getAuxCount() << 2;
-        auxArrBytes = 4 << auxMap.getLgAuxArrInts();
-      } else {
-        auxArrBytes = 4 << HllUtil<>::LG_AUX_ARR_INTS[lgConfigK];
-      }
-    }
-    int hllArrBytes = hllArr.getHllByteArrBytes();
-    expected = HllUtil<>::HLL_BYTE_ARR_START + hllArrBytes + auxCountBytes;
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_compact_serialization_bytes());
-    expected = HllUtil<>::HLL_BYTE_ARR_START + hllArrBytes + auxArrBytes;
-    CPPUNIT_ASSERT_EQUAL(expected, sk.get_updatable_serialization_bytes());
-    
-    int fullAuxArrBytes = (tgtHllType == HLL_4) ? (4 << HllUtil<>::LG_AUX_ARR_INTS[lgConfigK]) : 0;
-    expected = HllUtil<>::HLL_BYTE_ARR_START + hllArrBytes + fullAuxArrBytes;
-    CPPUNIT_ASSERT_EQUAL(expected,
-                         HllSketch::get_max_updatable_serialization_bytes(lgConfigK, tgtHllType));
-  */
   }
-
-  void exerciseToString() {
-    hll_sketch sk(15, HLL_4);
-    for (int i = 0; i < 25; ++i) { sk.update(i); }
-    std::ostringstream oss(std::ios::binary);
-    sk.to_string(oss, false, true, true, true);
-    for (int i = 25; i < (1 << 20); ++i) { sk.update(i); }
-    sk.to_string(oss, false, true, true, true);
-    sk.to_string(oss, false, true, true, false);
-
-    // need to delete old sketch?
-    sk = hll_sketch(8, HLL_8);
-    for (int i = 0; i < 25; ++i) { sk.update(i); }
-    sk.to_string(oss, false, true, true, true);
-  }
-  
-  void checkCompactFlag() {
-    int lgK = 8;
-    // unless/until we create non-updatable "direct" versions,
-    // deserialized image should never be compact
-    // LIST: follows serialization request
-    CPPUNIT_ASSERT(checkCompact(lgK, 7, HLL_8, false) == false);
-    CPPUNIT_ASSERT(checkCompact(lgK, 7, HLL_8, true) == false);
-
-    // SET: follows serialization request
-    CPPUNIT_ASSERT(checkCompact(lgK, 24, HLL_8, false) == false);
-    CPPUNIT_ASSERT(checkCompact(lgK, 24, HLL_8, true) == false);
-
-    // HLL8: always updatable
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_8, false) == false);
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_8, true) == false);
-
-    // HLL6: always updatable
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_6, false) == false);
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_6, true) == false);
-
-    // HLL4: follows serialization request
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_4, false) == false);
-    CPPUNIT_ASSERT(checkCompact(lgK, 25, HLL_4, true) == false);
+  REQUIRE(sk.get_estimate() != skCopy.get_estimate()); // either 1 or 100k difference
+
+  skCopy = sk;
+  REQUIRE(sk.get_estimate() == skCopy.get_estimate());
+}
+
+TEST_CASE("hll sketch: check copies", "[hll_sketch]") {
+  runCheckCopy(14, HLL_4);
+  runCheckCopy(8, HLL_6);
+  runCheckCopy(8, HLL_8);
+}
+
+static void copyAs(target_hll_type srcType, target_hll_type dstType) {
+  int lgK = 8;
+  int n1 = 7;
+  int n2 = 24;
+  int n3 = 1000;
+  int base = 0;
+
+  hll_sketch src(lgK, srcType);
+  for (int i = 0; i < n1; ++i) {
+    src.update(i + base);
   }
+  hll_sketch dst(src, dstType);
+  REQUIRE(src.get_estimate() == dst.get_estimate());
 
-  // Creates and serializes then deserializes sketch.
-  // Returns true if deserialized sketch is compact.
-  bool checkCompact(const int lgK, const int n, const target_hll_type type, bool compact) {
-    hll_sketch sk(lgK, type);
-    for (int i = 0; i < n; ++i) { sk.update(i); }
-    
-    std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
-    if (compact) { 
-      sk.serialize_compact(ss);
-      CPPUNIT_ASSERT_EQUAL(sk.get_compact_serialization_bytes(), (int) ss.tellp());
-    } else {
-      sk.serialize_updatable(ss);
-      CPPUNIT_ASSERT_EQUAL(sk.get_updatable_serialization_bytes(), (int) ss.tellp());
-    }
-    
-    hll_sketch sk2 = hll_sketch::deserialize(ss);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(n, sk2.get_estimate(), 0.01);
-    bool isCompact = sk2.is_compact();
-
-    return isCompact;
+  for (int i = n1; i < n2; ++i) {
+    src.update(i + base);
   }
+  dst = hll_sketch(src, dstType);
+  REQUIRE(src.get_estimate() == dst.get_estimate());
 
-  void checkKLimits() {
-    hll_sketch sketch1(HllUtil<>::MIN_LOG_K, target_hll_type::HLL_8);
-    hll_sketch sketch2(HllUtil<>::MAX_LOG_K, target_hll_type::HLL_4);
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to throw with lgK too small",
-                                 hll_sketch(HllUtil<>::MIN_LOG_K - 1),
-                                 std::invalid_argument);
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to throw with lgK too large",
-                                 hll_sketch(HllUtil<>::MAX_LOG_K + 1),
-                                 std::invalid_argument);
+  for (int i = n2; i < n3; ++i) {
+    src.update(i + base);
   }
-
-    void checkInputTypes() {
-    hll_sketch sk(8, target_hll_type::HLL_8);
-
-    // inserting the same value as a variety of input types
-    sk.update((uint8_t) 102);
-    sk.update((uint16_t) 102);
-    sk.update((uint32_t) 102);
-    sk.update((uint64_t) 102);
-    sk.update((int8_t) 102);
-    sk.update((int16_t) 102);
-    sk.update((int32_t) 102);
-    sk.update((int64_t) 102);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, sk.get_estimate(), 0.01);
-
-    // identical binary representations
-    // no unsigned in Java, but need to sign-extend both as Java would do 
-    sk.update((uint8_t) 255);
-    sk.update((int8_t) -1);
-
-    sk.update((float) -2.0);
-    sk.update((double) -2.0);
-
-    std::string str = "input string";
-    sk.update(str);
-    sk.update(str.c_str(), str.length());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, sk.get_estimate(), 0.01);
-
-    sk = hll_sketch(8, target_hll_type::HLL_6);
-    sk.update((float) 0.0);
-    sk.update((float) -0.0);
-    sk.update((double) 0.0);
-    sk.update((double) -0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, sk.get_estimate(), 0.01);
-
-    sk = hll_sketch(8, target_hll_type::HLL_4);
-    sk.update(std::nanf("3"));
-    sk.update(std::nan("9"));
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, sk.get_estimate(), 0.01);
-
-    sk = hll_sketch(8, target_hll_type::HLL_4);
-    sk.update(nullptr, 0);
-    sk.update("");
-    CPPUNIT_ASSERT(sk.is_empty());
+  dst = hll_sketch(src, dstType);
+  REQUIRE(src.get_estimate() == dst.get_estimate());
+}
+
+TEST_CASE("hll sketch: check copy as", "[hll_sketch]") {
+  copyAs(HLL_4, HLL_4);
+  copyAs(HLL_4, HLL_6);
+  copyAs(HLL_4, HLL_8);
+  copyAs(HLL_6, HLL_4);
+  copyAs(HLL_6, HLL_6);
+  copyAs(HLL_6, HLL_8);
+  copyAs(HLL_8, HLL_4);
+  copyAs(HLL_8, HLL_6);
+  copyAs(HLL_8, HLL_8);
+}
+
+TEST_CASE("hll sketch: check misc1", "[hll_sketch]") {
+  int lgConfigK = 8;
+  target_hll_type srcType = target_hll_type::HLL_8;
+  hll_sketch sk(lgConfigK, srcType);
+
+  for (int i = 0; i < 7; ++i) { sk.update(i); } // LIST
+  REQUIRE(sk.get_compact_serialization_bytes() == 36);
+  REQUIRE(sk.get_updatable_serialization_bytes() == 40);
+
+  for (int i = 7; i < 24; ++i) { sk.update(i); } // SET
+  REQUIRE(sk.get_compact_serialization_bytes() == 108);
+  REQUIRE(sk.get_updatable_serialization_bytes() == 140);
+
+  sk.update(24); // HLL
+  REQUIRE(sk.get_updatable_serialization_bytes() == 40 + 256);
+
+  const int hllBytes = HllUtil<>::HLL_BYTE_ARR_START + (1 << lgConfigK);
+  REQUIRE(sk.get_compact_serialization_bytes() == hllBytes);
+  REQUIRE(hll_sketch::get_max_updatable_serialization_bytes(lgConfigK, HLL_8) == hllBytes);
+}
+
+TEST_CASE("hll sketch: check num std dev", "[hll_sketch]") {
+  REQUIRE_THROWS_AS(HllUtil<>::checkNumStdDev(0), std::invalid_argument);
+}
+
+void checkSerializationSizes(const int lgConfigK, target_hll_type tgtHllType) {
+  hll_sketch sk(lgConfigK, tgtHllType);
+  int i;
+
+  // LIST
+  for (i = 0; i < 7; ++i) { sk.update(i); }
+  int expected = HllUtil<>::LIST_INT_ARR_START + (i << 2);
+  REQUIRE(sk.get_compact_serialization_bytes() == expected);
+  expected = HllUtil<>::LIST_INT_ARR_START + (4 << HllUtil<>::LG_INIT_LIST_SIZE);
+  REQUIRE(sk.get_updatable_serialization_bytes() == expected);
+
+  // SET
+  for (i = 7; i < 24; ++i) { sk.update(i); }
+  expected = HllUtil<>::HASH_SET_INT_ARR_START + (i << 2);
+  REQUIRE(sk.get_compact_serialization_bytes() == expected);
+  expected = HllUtil<>::HASH_SET_INT_ARR_START + (4 << HllUtil<>::LG_INIT_SET_SIZE);
+  REQUIRE(sk.get_updatable_serialization_bytes() == expected);
+}
+
+TEST_CASE("hll sketch: check ser sizes", "[hll_sketch]") {
+  checkSerializationSizes(8, HLL_8);
+  checkSerializationSizes(8, HLL_6);
+  checkSerializationSizes(8, HLL_4);
+}
+
+TEST_CASE("hll sketch: exercise to string", "[hll_sketch]") {
+  hll_sketch sk(15, HLL_4);
+  for (int i = 0; i < 25; ++i) { sk.update(i); }
+  std::ostringstream oss(std::ios::binary);
+  sk.to_string(oss, false, true, true, true);
+  for (int i = 25; i < (1 << 20); ++i) { sk.update(i); }
+  sk.to_string(oss, false, true, true, true);
+  sk.to_string(oss, false, true, true, false);
+
+  sk = hll_sketch(8, HLL_8);
+  for (int i = 0; i < 25; ++i) { sk.update(i); }
+  sk.to_string(oss, false, true, true, true);
+}
+
+// Creates and serializes then deserializes sketch.
+// Returns true if deserialized sketch is compact.
+static bool checkCompact(const int lgK, const int n, const target_hll_type type, bool compact) {
+  hll_sketch sk(lgK, type);
+  for (int i = 0; i < n; ++i) { sk.update(i); }
+  
+  std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
+  if (compact) { 
+    sk.serialize_compact(ss);
+    REQUIRE(ss.tellp() == sk.get_compact_serialization_bytes());
+  } else {
+    sk.serialize_updatable(ss);
+    REQUIRE(ss.tellp() == sk.get_updatable_serialization_bytes());
   }
+  
+  hll_sketch sk2 = hll_sketch::deserialize(ss);
+  REQUIRE(sk2.get_estimate() == Approx(n).margin(0.01));
+  bool isCompact = sk2.is_compact();
+
+  return isCompact;
+}
+
+TEST_CASE("hll sketch: check compact flag", "[hll_sketch]") {
+  int lgK = 8;
+  // unless/until we create non-updatable "direct" versions,
+  // deserialized image should never be compact
+  // LIST: follows serialization request
+  REQUIRE(checkCompact(lgK, 7, HLL_8, false) == false);
+  REQUIRE(checkCompact(lgK, 7, HLL_8, true) == false);
+
+  // SET: follows serialization request
+  REQUIRE(checkCompact(lgK, 24, HLL_8, false) == false);
+  REQUIRE(checkCompact(lgK, 24, HLL_8, true) == false);
+
+  // HLL8: always updatable
+  REQUIRE(checkCompact(lgK, 25, HLL_8, false) == false);
+  REQUIRE(checkCompact(lgK, 25, HLL_8, true) == false);
+
+  // HLL6: always updatable
+  REQUIRE(checkCompact(lgK, 25, HLL_6, false) == false);
+  REQUIRE(checkCompact(lgK, 25, HLL_6, true) == false);
+
+  // HLL4: follows serialization request
+  REQUIRE(checkCompact(lgK, 25, HLL_4, false) == false);
+  REQUIRE(checkCompact(lgK, 25, HLL_4, true) == false);
+}
+
+TEST_CASE("hll sketch: check k limits", "[hll_sketch]") {
+  hll_sketch sketch1(HllUtil<>::MIN_LOG_K, target_hll_type::HLL_8);
+  hll_sketch sketch2(HllUtil<>::MAX_LOG_K, target_hll_type::HLL_4);
+  REQUIRE_THROWS_AS(hll_sketch(HllUtil<>::MIN_LOG_K - 1), std::invalid_argument);
+
+  REQUIRE_THROWS_AS(hll_sketch(HllUtil<>::MAX_LOG_K + 1), std::invalid_argument);
+}
+
+TEST_CASE("hll sketch: check input types", "[hll_sketch]") {
+  hll_sketch sk(8, target_hll_type::HLL_8);
+
+  // inserting the same value as a variety of input types
+  sk.update((uint8_t) 102);
+  sk.update((uint16_t) 102);
+  sk.update((uint32_t) 102);
+  sk.update((uint64_t) 102);
+  sk.update((int8_t) 102);
+  sk.update((int16_t) 102);
+  sk.update((int32_t) 102);
+  sk.update((int64_t) 102);
+  REQUIRE(sk.get_estimate() == Approx(1.0).margin(0.01));
+
+  // identical binary representations
+  // no unsigned in Java, but need to sign-extend both as Java would do 
+  sk.update((uint8_t) 255);
+  sk.update((int8_t) -1);
+
+  sk.update((float) -2.0);
+  sk.update((double) -2.0);
+
+  std::string str = "input string";
+  sk.update(str);
+  sk.update(str.c_str(), str.length());
+  REQUIRE(sk.get_estimate() == Approx(4.0).margin(0.01));
+
+  sk = hll_sketch(8, target_hll_type::HLL_6);
+  sk.update((float) 0.0);
+  sk.update((float) -0.0);
+  sk.update((double) 0.0);
+  sk.update((double) -0.0);
+  REQUIRE(sk.get_estimate() == Approx(1.0).margin(0.01));
+
+  sk = hll_sketch(8, target_hll_type::HLL_4);
+  sk.update(std::nanf("3"));
+  sk.update(std::nan("9"));
+  REQUIRE(sk.get_estimate() == Approx(1.0).margin(0.01));
+
+  sk = hll_sketch(8, target_hll_type::HLL_4);
+  sk.update(nullptr, 0);
+  sk.update("");
+  REQUIRE(sk.is_empty());
+}
 
-};
 
-CPPUNIT_TEST_SUITE_REGISTRATION(hllSketchTest);
 
 } /* namespace datasketches */
diff --git a/hll/test/HllUnionTest.cpp b/hll/test/HllUnionTest.cpp
index a6481a6..23bdd1c 100644
--- a/hll/test/HllUnionTest.cpp
+++ b/hll/test/HllUnionTest.cpp
@@ -17,394 +17,369 @@
  * under the License.
  */
 
-#include "hll.hpp"
+#include <catch.hpp>
+#include <sstream>
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include "hll.hpp"
 
 namespace datasketches {
 
-class HllUnionTest : public CppUnit::TestFixture {
-
-  // list of values defined at bottom of file
-  static const int nArr[]; // = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
-
-  CPPUNIT_TEST_SUITE(HllUnionTest);
-  CPPUNIT_TEST(checkUnions);
-  CPPUNIT_TEST(checkToFrom);
-  CPPUNIT_TEST(checkCompositeEstimate);
-  CPPUNIT_TEST(checkConfigKLimits);
-  CPPUNIT_TEST(checkUbLb);
-  CPPUNIT_TEST(checkConversions);
-  CPPUNIT_TEST(checkMisc);
-  CPPUNIT_TEST(checkInputTypes);
-  CPPUNIT_TEST(check_hll_to_hll);
-  CPPUNIT_TEST_SUITE_END();
-
-  int min(int a, int b) {
-    return (a < b) ? a : b;
-  }
-
-  void println(std::string& str) {
-    //std::cout << str << "\n";
-  }
-
-  /**
-   * The task here is to check the transition boundaries as the sketch morphs between LIST to
-   * SET to HLL modes. The transition points vary as a function of lgConfigK. In addition,
-   * this checks that the union operation is operating properly based on the order the
-   * sketches are presented to the union.
-   */
-  void checkUnions() {
-    target_hll_type type1 = HLL_8;
-    target_hll_type type2 = HLL_8;
-    target_hll_type resultType = HLL_8;
-
-    uint64_t lgK1 = 7;
-    uint64_t lgK2 = 7;
-    uint64_t lgMaxK = 7;
-    uint64_t n1 = 7;
-    uint64_t n2 = 7;
-    basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-    n1 = 8;
-    n2 = 7;
-    basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-    n1 = 7;
-    n2 = 8;
-    basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-    n1 = 8;
-    n2 = 8;
-    basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-    n1 = 7;
-    n2 = 14;
-    basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-
-    int i = 0;
-    for (i = 7; i <= 13; ++i) {
-      lgK1 = i;
-      lgK2 = i;
-      lgMaxK = i;
-      {
-        n1 = ((1 << (i - 3)) * 3)/4; // compute the transition point
-        n2 = n1;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 -= 2;
-        n2 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-      }
-      lgK1 = i;
-      lgK2 = i + 1;
-      lgMaxK = i;
-      {
-        n1 = ((1 << (i - 3)) * 3)/4;
-        n2 = n1;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 -= 2;
-        n2 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-      }
-      lgK1 = i + 1;
-      lgK2 = i;
-      lgMaxK = i;
-      {
-        n1 = ((1 << (i - 3)) * 3)/4;
-        n2 = n1;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 -= 2;
-        n2 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-      }
-      lgK1 = i + 1;
-      lgK2 = i + 1;
-      lgMaxK = i;
-      {
-        n1 = ((1 << (i - 3)) * 3)/4;
-        n2 = n1;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 -= 2;
-        n2 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-        n1 += 2;
-        basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
-      }
-    }
-  }
+static const int nArr[] = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
 
-  void basicUnion(uint64_t n1, uint64_t n2,
-		  uint64_t lgk1, uint64_t lgk2, uint64_t lgMaxK,
-                  target_hll_type type1, target_hll_type type2, target_hll_type resultType) {
-    uint64_t v = 0;
-    //int tot = n1 + n2;
+static int min(int a, int b) {
+  return (a < b) ? a : b;
+}
 
-    hll_sketch h1(lgk1, type1);
-    hll_sketch h2(lgk2, type2);
-    int lgControlK = min(min(lgk1, lgk2), lgMaxK);
-    hll_sketch control(lgControlK, resultType);
+static void println(std::string& str) {
+  //std::cout << str << "\n";
+}
 
-    for (uint64_t i = 0; i < n1; ++i) {
-      h1.update(v + i);
-      control.update(v + i);
-    }
-    v += n1;
-    for (uint64_t i = 0; i < n2; ++i) {
-      h2.update(v + i);
-      control.update(v + i);
-    }
-    v += n2;
+static void basicUnion(uint64_t n1, uint64_t n2,
+		                   uint64_t lgk1, uint64_t lgk2, uint64_t lgMaxK,
+                       target_hll_type type1, target_hll_type type2, target_hll_type resultType) {
+  uint64_t v = 0;
+  //int tot = n1 + n2;
 
-    hll_union u(lgMaxK);
-    u.update(std::move(h1));
-    u.update(h2);
+  hll_sketch h1(lgk1, type1);
+  hll_sketch h2(lgk2, type2);
+  int lgControlK = min(min(lgk1, lgk2), lgMaxK);
+  hll_sketch control(lgControlK, resultType);
 
-    hll_sketch result = u.get_result(resultType);
-
-    // force non-HIP estimates to avoid issues with in- vs out-of-order
-    double uEst = result.get_composite_estimate();
-    double uUb = result.get_upper_bound(2);
-    double uLb = result.get_lower_bound(2);
-    //double rerr = ((uEst/tot) - 1.0) * 100;
-
-    double controlEst = control.get_composite_estimate();
-    double controlUb = control.get_upper_bound(2);
-    double controlLb = control.get_lower_bound(2);
-
-    CPPUNIT_ASSERT((controlUb - controlEst) >= 0.0);
-    CPPUNIT_ASSERT((uUb - uEst) >= 0.0);
-    CPPUNIT_ASSERT((controlEst - controlLb) >= 0.0);
-    CPPUNIT_ASSERT((uEst - uLb) >= 0.0);
-
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(controlEst, uEst, 0.0);
+  for (uint64_t i = 0; i < n1; ++i) {
+    h1.update(v + i);
+    control.update(v + i);
   }
-
-  void checkToFrom() {
-    for (int i = 0; i < 10; ++i) {
-      int n = nArr[i];
-      for (int lgK = 4; lgK <= 13; ++lgK) {
-        toFrom(lgK, HLL_4, n);
-        toFrom(lgK, HLL_6, n);
-        toFrom(lgK, HLL_8, n);
-      }
-    }
+  v += n1;
+  for (uint64_t i = 0; i < n2; ++i) {
+    h2.update(v + i);
+    control.update(v + i);
   }
+  v += n2;
 
-  void checkUnionEquality(hll_union& u1, hll_union& u2) {
-    hll_sketch sk1 = u1.get_result();
-    hll_sketch sk2 = u2.get_result();
-
-    CPPUNIT_ASSERT_EQUAL(sk1.get_lg_config_k(), sk2.get_lg_config_k());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_lower_bound(1), sk2.get_lower_bound(1), 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_estimate(), sk2.get_estimate(), 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_upper_bound(1), sk2.get_upper_bound(1), 0.0);
-    CPPUNIT_ASSERT_EQUAL(sk1.get_target_type(), sk2.get_target_type());
-  }
+  hll_union u(lgMaxK);
+  u.update(std::move(h1));
+  u.update(h2);
 
-  void toFrom(const int lgConfigK, const target_hll_type tgtHllType, const int n) {
-    hll_union srcU(lgConfigK);
-    hll_sketch srcSk(lgConfigK, tgtHllType);
-    for (int i = 0; i < n; ++i) {
-      srcSk.update(i);
-    }
-    srcU.update(srcSk);
-
-    std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
-    srcU.serialize_compact(ss);
-    hll_union dstU = hll_union::deserialize(ss);
-    checkUnionEquality(srcU, dstU);
-
-    auto bytes1 = srcU.serialize_compact();
-    dstU = hll_union::deserialize(bytes1.data(), bytes1.size());
-    checkUnionEquality(srcU, dstU);
-
-    ss.clear();
-    srcU.serialize_updatable(ss);
-    dstU = hll_union::deserialize(ss);
-    checkUnionEquality(srcU, dstU);
-
-    auto bytes2 = srcU.serialize_updatable();
-    dstU = hll_union::deserialize(bytes2.data(), bytes2.size());
-    checkUnionEquality(srcU, dstU);
-  } 
-
-  void checkCompositeEstimate() {
-    hll_union u(12);
-    CPPUNIT_ASSERT(u.is_empty());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, u.get_composite_estimate(), 0.03);
-    for (int i = 1; i <= 15; ++i) { u.update(i); }
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, u.get_composite_estimate(), 15 * 0.03);
-    for (int i = 16; i <= 1000; ++i) { u.update(i); }
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1000.0, u.get_composite_estimate(), 1000 * 0.03);
-  }
+  hll_sketch result = u.get_result(resultType);
 
-  void checkConfigKLimits() {
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect lgK too small",
-                                 hll_union(HllUtil<>::MIN_LOG_K - 1),
-                                 std::invalid_argument);
+  // force non-HIP estimates to avoid issues with in- vs out-of-order
+  double uEst = result.get_composite_estimate();
+  double uUb = result.get_upper_bound(2);
+  double uLb = result.get_lower_bound(2);
+  //double rerr = ((uEst/tot) - 1.0) * 100;
 
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to detect lgK too large",
-                                 hll_union(HllUtil<>::MAX_LOG_K + 1),
-                                 std::invalid_argument);
-  }
+  double controlEst = control.get_composite_estimate();
+  double controlUb = control.get_upper_bound(2);
+  double controlLb = control.get_lower_bound(2);
 
-  double getBound(int lgK, bool ub, bool oooFlag, int numStdDev, double est) {
-    double re = RelativeErrorTables<>::getRelErr(ub, oooFlag, lgK, numStdDev);
-    return est / (1.0 + re);
-  }
+  REQUIRE((controlUb - controlEst) >= 0.0);
+  REQUIRE((uUb - uEst) >= 0.0);
+  REQUIRE((controlEst - controlLb) >= 0.0);
+  REQUIRE((uEst - uLb) >= 0.0);
 
-  void checkUbLb() { // for lgK <= 12
-    int lgK = 4;
-    int n = 1 << 20;
-    bool oooFlag = false;
-    
-    double bound;
-    std::string str;
-
-    bound = (getBound(lgK, true, oooFlag, 3, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", UB3: " + std::to_string(bound);
-    println(str);
-    bound = (getBound(lgK, true, oooFlag, 2, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", UB2: " + std::to_string(bound);
-    println(str);
-    bound = (getBound(lgK, true, oooFlag, 1, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", UB1: " + std::to_string(bound);
-    println(str);
-    bound = (getBound(lgK, false, oooFlag, 1, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", LB1: " + std::to_string(bound);
-    println(str);
-    bound = (getBound(lgK, false, oooFlag, 2, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", LB2: " + std::to_string(bound);
-    println(str);
-    bound = (getBound(lgK, false, oooFlag, 3, n) / n) - 1;
-    str = "LgK=" + std::to_string(lgK) + ", LB3: " + std::to_string(bound);
-    println(str);
-  }
+  REQUIRE(controlEst == uEst);
+}
 
-  void checkConversions() {
-    int lgK = 4;
-    hll_sketch sk1(lgK, HLL_8);
-    hll_sketch sk2(lgK, HLL_8);
-    int n = 1 << 20;
-    for (int i = 0; i < n; ++i) {
-      sk1.update(i);
-      sk2.update(i + n);
+/**
+ * The task here is to check the transition boundaries as the sketch morphs between LIST to
+ * SET to HLL modes. The transition points vary as a function of lgConfigK. In addition,
+ * this checks that the union operation is operating properly based on the order the
+ * sketches are presented to the union.
+ */
+TEST_CASE("hll union: check unions", "[hll_union]") {
+  target_hll_type type1 = HLL_8;
+  target_hll_type type2 = HLL_8;
+  target_hll_type resultType = HLL_8;
+
+  uint64_t lgK1 = 7;
+  uint64_t lgK2 = 7;
+  uint64_t lgMaxK = 7;
+  uint64_t n1 = 7;
+  uint64_t n2 = 7;
+  basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+  n1 = 8;
+  n2 = 7;
+  basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+  n1 = 7;
+  n2 = 8;
+  basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+  n1 = 8;
+  n2 = 8;
+  basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+  n1 = 7;
+  n2 = 14;
+  basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+
+  int i = 0;
+  for (i = 7; i <= 13; ++i) {
+    lgK1 = i;
+    lgK2 = i;
+    lgMaxK = i;
+    {
+      n1 = ((1 << (i - 3)) * 3)/4; // compute the transition point
+      n2 = n1;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 -= 2;
+      n2 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+    }
+    lgK1 = i;
+    lgK2 = i + 1;
+    lgMaxK = i;
+    {
+      n1 = ((1 << (i - 3)) * 3)/4;
+      n2 = n1;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 -= 2;
+      n2 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+    }
+    lgK1 = i + 1;
+    lgK2 = i;
+    lgMaxK = i;
+    {
+      n1 = ((1 << (i - 3)) * 3)/4;
+      n2 = n1;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 -= 2;
+      n2 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+    }
+    lgK1 = i + 1;
+    lgK2 = i + 1;
+    lgMaxK = i;
+    {
+      n1 = ((1 << (i - 3)) * 3)/4;
+      n2 = n1;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 -= 2;
+      n2 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
+      n1 += 2;
+      basicUnion(n1, n2, lgK1, lgK2, lgMaxK, type1, type2, resultType);
     }
-    hll_union hllUnion(lgK);
-    hllUnion.update(sk1);
-    hllUnion.update(sk2);
-
-    hll_sketch rsk1 = hllUnion.get_result(HLL_4);
-    hll_sketch rsk2 = hllUnion.get_result(HLL_6);
-    hll_sketch rsk3 = hllUnion.get_result(HLL_8);
-    double est1 = rsk1.get_estimate();
-    double est2 = rsk2.get_estimate();
-    double est3 = rsk3.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est1, est2, 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(est1, est3, 0.0);
   }
-
-  // moved from UnionCaseTest in java
-  void checkMisc() {
-    hll_union u(12);
-    int bytes = u.get_compact_serialization_bytes();
-    CPPUNIT_ASSERT_EQUAL(8, bytes);
-    bytes = hll_union::get_max_serialization_bytes(7);
-    CPPUNIT_ASSERT_EQUAL(40 + 128, bytes);
-    double v = u.get_estimate();
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v, 0.0);
-    v = u.get_lower_bound(1);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v, 0.0);
-    v = u.get_upper_bound(1);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v, 0.0);
-    CPPUNIT_ASSERT(u.is_empty());
-    u.reset();
-    CPPUNIT_ASSERT(u.is_empty());
-    std::ostringstream oss(std::ios::binary);
-    u.serialize_compact(oss);
-    CPPUNIT_ASSERT_EQUAL(8, static_cast<int>(oss.tellp()));
+}
+
+static void checkUnionEquality(hll_union& u1, hll_union& u2) {
+  hll_sketch sk1 = u1.get_result();
+  hll_sketch sk2 = u2.get_result();
+
+  REQUIRE(sk2.get_lg_config_k() == sk1.get_lg_config_k());
+  REQUIRE(sk1.get_lower_bound(1) == sk2.get_lower_bound(1));
+  REQUIRE(sk1.get_estimate() == sk2.get_estimate());
+  REQUIRE(sk1.get_upper_bound(1) == sk2.get_upper_bound(1));
+  REQUIRE(sk2.get_target_type() == sk1.get_target_type());
+}
+
+static void toFrom(const int lgConfigK, const target_hll_type tgtHllType, const int n) {
+  hll_union srcU(lgConfigK);
+  hll_sketch srcSk(lgConfigK, tgtHllType);
+  for (int i = 0; i < n; ++i) {
+    srcSk.update(i);
   }
-
-  void checkInputTypes() {
-    hll_union u(8);
-
-    // inserting the same value as a variety of input types
-    u.update((uint8_t) 102);
-    u.update((uint16_t) 102);
-    u.update((uint32_t) 102);
-    u.update((uint64_t) 102);
-    u.update((int8_t) 102);
-    u.update((int16_t) 102);
-    u.update((int32_t) 102);
-    u.update((int64_t) 102);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, u.get_estimate(), 0.01);
-
-    // identical binary representations
-    // no unsigned in Java, but need to sign-extend both as Java would do
-    u.update((uint8_t) 255);
-    u.update((int8_t) -1);
-
-    u.update((float) -2.0);
-    u.update((double) -2.0);
-
-    std::string str = "input string";
-    u.update(str);
-    u.update(str.c_str(), str.length());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, u.get_estimate(), 0.01);
-
-    u = hll_union(8);
-    u.update((float) 0.0);
-    u.update((float) -0.0);
-    u.update((double) 0.0);
-    u.update((double) -0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, u.get_estimate(), 0.01);
-
-    u = hll_union(8);
-    u.update(std::nanf("3"));
-    u.update(std::nan("12"));
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, u.get_estimate(), 0.01);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(u.get_result().get_estimate(), u.get_estimate(), 0.01);
-
-    u = hll_union(8);
-    u.update(nullptr, 0);
-    u.update("");
-    CPPUNIT_ASSERT(u.is_empty());
+  srcU.update(srcSk);
+
+  std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
+  srcU.serialize_compact(ss);
+  hll_union dstU = hll_union::deserialize(ss);
+  checkUnionEquality(srcU, dstU);
+
+  auto bytes1 = srcU.serialize_compact();
+  dstU = hll_union::deserialize(bytes1.data(), bytes1.size());
+  checkUnionEquality(srcU, dstU);
+
+  ss.clear();
+  srcU.serialize_updatable(ss);
+  dstU = hll_union::deserialize(ss);
+  checkUnionEquality(srcU, dstU);
+
+  auto bytes2 = srcU.serialize_updatable();
+  dstU = hll_union::deserialize(bytes2.data(), bytes2.size());
+  checkUnionEquality(srcU, dstU);
+} 
+
+TEST_CASE("hll union: check to from", "[hll_union]") {
+  for (int i = 0; i < 10; ++i) {
+    int n = nArr[i];
+    for (int lgK = 4; lgK <= 13; ++lgK) {
+      toFrom(lgK, HLL_4, n);
+      toFrom(lgK, HLL_6, n);
+      toFrom(lgK, HLL_8, n);
+    }
   }
-
-  void union_two_sketches_with_overlap(int num, uint8_t lg_k, target_hll_type type) {
-    hll_sketch sketch1(lg_k, type);
-    for (int key = 0; key < num; key++) sketch1.update(key);
-
-    const int overlap = num / 2;
-    hll_sketch sketch2(lg_k, type);
-    for (int key = overlap; key < num + overlap; key++) sketch2.update(key);
-
-    hll_union u(lg_k);
-    u.update(sketch1);
-    u.update(sketch2);
-    hll_sketch sketch = u.get_result(type);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(num * 1.5, sketch.get_estimate(), num * 1.5 * 0.02);
+}
+
+TEST_CASE("hll union: check composite estimate", "[hll_union]") {
+  hll_union u(12);
+  REQUIRE(u.is_empty());
+  REQUIRE(u.get_composite_estimate() == Approx(0.0).margin(0.03));
+  for (int i = 1; i <= 15; ++i) { u.update(i); }
+  REQUIRE(u.get_composite_estimate() == Approx(15.0).margin(15 * 0.03));
+  for (int i = 16; i <= 1000; ++i) { u.update(i); }
+  REQUIRE(u.get_composite_estimate() == Approx(1000.0).margin(1000 * 0.03));
+}
+
+TEST_CASE("hll union: check config k limits", "[hll_union]") {
+  REQUIRE_THROWS_AS(hll_union(HllUtil<>::MIN_LOG_K - 1), std::invalid_argument);
+
+  REQUIRE_THROWS_AS(hll_union(HllUtil<>::MAX_LOG_K + 1), std::invalid_argument);
+}
+
+static double getBound(int lgK, bool ub, bool oooFlag, int numStdDev, double est) {
+  double re = RelativeErrorTables<>::getRelErr(ub, oooFlag, lgK, numStdDev);
+  return est / (1.0 + re);
+}
+
+TEST_CASE("hll union: check ub lb", "[hll_union]") {
+  int lgK = 4;
+  int n = 1 << 20;
+  bool oooFlag = false;
+  
+  double bound;
+  std::string str;
+
+  bound = (getBound(lgK, true, oooFlag, 3, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", UB3: " + std::to_string(bound);
+  println(str);
+  bound = (getBound(lgK, true, oooFlag, 2, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", UB2: " + std::to_string(bound);
+  println(str);
+  bound = (getBound(lgK, true, oooFlag, 1, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", UB1: " + std::to_string(bound);
+  println(str);
+  bound = (getBound(lgK, false, oooFlag, 1, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", LB1: " + std::to_string(bound);
+  println(str);
+  bound = (getBound(lgK, false, oooFlag, 2, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", LB2: " + std::to_string(bound);
+  println(str);
+  bound = (getBound(lgK, false, oooFlag, 3, n) / n) - 1;
+  str = "LgK=" + std::to_string(lgK) + ", LB3: " + std::to_string(bound);
+  println(str);
+}
+
+TEST_CASE("hll union: check conversions", "[hll_union]") {
+  int lgK = 4;
+  hll_sketch sk1(lgK, HLL_8);
+  hll_sketch sk2(lgK, HLL_8);
+  int n = 1 << 20;
+  for (int i = 0; i < n; ++i) {
+    sk1.update(i);
+    sk2.update(i + n);
   }
-
-  void check_hll_to_hll() {
-    union_two_sketches_with_overlap(1000000, 11, HLL_4);
-  }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(HllUnionTest);
-
-const int HllUnionTest::nArr[] = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
+  hll_union hllUnion(lgK);
+  hllUnion.update(sk1);
+  hllUnion.update(sk2);
+
+  hll_sketch rsk1 = hllUnion.get_result(HLL_4);
+  hll_sketch rsk2 = hllUnion.get_result(HLL_6);
+  hll_sketch rsk3 = hllUnion.get_result(HLL_8);
+  double est1 = rsk1.get_estimate();
+  double est2 = rsk2.get_estimate();
+  double est3 = rsk3.get_estimate();
+  REQUIRE(est1 == est2);
+  REQUIRE(est1 == est3);
+}
+
+// moved from UnionCaseTest in java
+TEST_CASE("hll union: check misc", "[hll_union]") {
+  hll_union u(12);
+  int bytes = u.get_compact_serialization_bytes();
+  REQUIRE(bytes == 8);
+  bytes = hll_union::get_max_serialization_bytes(7);
+  REQUIRE(bytes == 40 + 128);
+  double v = u.get_estimate();
+  REQUIRE(0.0 == v);
+  v = u.get_lower_bound(1);
+  REQUIRE(0.0 == v);
+  v = u.get_upper_bound(1);
+  REQUIRE(0.0 == v);
+  REQUIRE(u.is_empty());
+  u.reset();
+  REQUIRE(u.is_empty());
+  std::ostringstream oss(std::ios::binary);
+  u.serialize_compact(oss);
+  REQUIRE(static_cast<int>(oss.tellp()) == 8);
+}
+
+TEST_CASE("hll union: check input types", "[hll_union]") {
+  hll_union u(8);
+
+  // inserting the same value as a variety of input types
+  u.update((uint8_t) 102);
+  u.update((uint16_t) 102);
+  u.update((uint32_t) 102);
+  u.update((uint64_t) 102);
+  u.update((int8_t) 102);
+  u.update((int16_t) 102);
+  u.update((int32_t) 102);
+  u.update((int64_t) 102);
+  REQUIRE(u.get_estimate() == Approx(1.0).margin(0.01));
+
+  // identical binary representations
+  // no unsigned in Java, but need to sign-extend both as Java would do
+  u.update((uint8_t) 255);
+  u.update((int8_t) -1);
+
+  u.update((float) -2.0);
+  u.update((double) -2.0);
+
+  std::string str = "input string";
+  u.update(str);
+  u.update(str.c_str(), str.length());
+  REQUIRE(u.get_estimate() == Approx(4.0).margin(0.01));
+
+  u = hll_union(8);
+  u.update((float) 0.0);
+  u.update((float) -0.0);
+  u.update((double) 0.0);
+  u.update((double) -0.0);
+  REQUIRE(u.get_estimate() == Approx(1.0).margin(0.01));
+
+  u = hll_union(8);
+  u.update(std::nanf("3"));
+  u.update(std::nan("12"));
+  REQUIRE(1.0 == Approx(u.get_estimate()).margin(0.01));
+  REQUIRE(u.get_result().get_estimate() == Approx(u.get_estimate()).margin(0.01));
+
+  u = hll_union(8);
+  u.update(nullptr, 0);
+  u.update("");
+  REQUIRE(u.is_empty());
+}
+
+static void union_two_sketches_with_overlap(int num, uint8_t lg_k, target_hll_type type) {
+  hll_sketch sketch1(lg_k, type);
+  for (int key = 0; key < num; key++) sketch1.update(key);
+
+  const int overlap = num / 2;
+  hll_sketch sketch2(lg_k, type);
+  for (int key = overlap; key < num + overlap; key++) sketch2.update(key);
+
+  hll_union u(lg_k);
+  u.update(sketch1);
+  u.update(sketch2);
+  hll_sketch sketch = u.get_result(type);
+  REQUIRE(sketch.get_estimate() == Approx(num * 1.5).margin(num * 1.5 * 0.02));
+}
+
+TEST_CASE("hll union: check hll to hll", "[hll_union]") {
+  union_two_sketches_with_overlap(1000000, 11, HLL_4);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/IsomorphicTest.cpp b/hll/test/IsomorphicTest.cpp
index e030900..1370fb1 100644
--- a/hll/test/IsomorphicTest.cpp
+++ b/hll/test/IsomorphicTest.cpp
@@ -17,139 +17,120 @@
  * under the License.
  */
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/TestAssert.h>
+#include <catch.hpp>
 
 #include <cstdio>
 
 #include "hll.hpp"
 #include "HllUtil.hpp"
 
-// hex format for comparing serialized bytes
-namespace CppUnit {
-
-template<>
-struct assertion_traits<datasketches::hll_sketch::vector_bytes> {
-  static bool equal(const datasketches::hll_sketch::vector_bytes& a, const datasketches::hll_sketch::vector_bytes& b) {
-    return a == b;
-  }
+namespace datasketches {
 
-  static std::string toString(const datasketches::hll_sketch::vector_bytes& v) {
-     std::ostringstream s;
-     s << std::hex << std::setfill('0');
-     int cnt = 0;
-     for (uint8_t byte: v) {
-       if (cnt == 8) { // insert space after each 8 bytes for readability
-         s << ' ';
-         cnt = 0;
-       } else {
-         ++cnt;
-       }
-       s << std::setw(2) << static_cast<int>(byte);
+// hex format for comparing serialized bytes
+static std::string toString(const datasketches::hll_sketch::vector_bytes& v) {
+   std::ostringstream s;
+   s << std::hex << std::setfill('0');
+   int cnt = 0;
+   for (uint8_t byte: v) {
+     if (cnt == 8) { // insert space after each 8 bytes for readability
+       s << ' ';
+       cnt = 0;
+     } else {
+       ++cnt;
      }
-     return s.str();
-  }
-};
-
+     s << std::setw(2) << static_cast<int>(byte);
+   }
+   return s.str();
 }
 
-namespace datasketches {
+// if lg_k >= 8, mode != SET!
+static int get_n(int lg_k, hll_mode mode) {
+  if (mode == LIST) return 4;
+  if (mode == SET) return 1 << (lg_k - 4);
+  return ((lg_k < 8) && (mode == HLL)) ? (1 << lg_k) : 1 << (lg_k - 3);
+}
 
-class IsomorphicTest : public CppUnit::TestFixture {
-  long v = 0;
+static long v = 0;
 
-  CPPUNIT_TEST_SUITE(IsomorphicTest);
-  CPPUNIT_TEST(union_one_update_serialize_updatable);
-  CPPUNIT_TEST(union_one_update_serialize_compact);
-  CPPUNIT_TEST(convert_back_and_forth_serialize_updatable);
-  CPPUNIT_TEST(convert_back_and_forth_serialize_compact);
-  CPPUNIT_TEST_SUITE_END();
+static hll_sketch build_sketch(int lg_k, target_hll_type hll_type, hll_mode mode) {
+  hll_sketch sk(lg_k, hll_type);
+  int n = get_n(lg_k, mode);
+  for (int i = 0; i < n; i++) sk.update(static_cast<uint64_t>(i + v));
+  v += n;
+  return sk;
+}
 
-  // if lg_k >= 8, mode != SET!
-  static int get_n(int lg_k, hll_mode mode) {
-    if (mode == LIST) return 4;
-    if (mode == SET) return 1 << (lg_k - 4);
-    return ((lg_k < 8) && (mode == HLL)) ? (1 << lg_k) : 1 << (lg_k - 3);
+// merges a sketch to an empty union and gets result of the same type, checks binary equivalence
+static void union_one_update(bool compact) {
+  for (int lg_k = 4; lg_k <= 21; lg_k++) { // all lg_k
+    for (int mode = 0; mode <= 2; mode++) { // List, Set, Hll
+      if ((lg_k < 8) && (mode == 1)) continue; // lg_k < 8 list transitions directly to HLL
+      for (int t = 0; t <= 2; t++) { // HLL_4, HLL_6, HLL_8
+        target_hll_type hll_type = (target_hll_type) t;
+        hll_sketch sk1 = build_sketch(lg_k, hll_type, (hll_mode) mode);
+        hll_union u(lg_k);
+        u.update(sk1);
+        hll_sketch sk2 = u.get_result(hll_type);
+        auto bytes1 = compact ? sk1.serialize_compact() : sk1.serialize_updatable();
+        auto bytes2 = compact ? sk2.serialize_compact() : sk2.serialize_updatable();
+        auto msg = "LgK=" + std::to_string(lg_k)
+          + ", Mode=" + std::to_string(mode)
+          + ", Type=" + std::to_string(hll_type)
+          + "\n" + sk1.to_string(true, true, true, true)
+          + "\n" + sk2.to_string(true, true, true, true);
+        if (bytes1 != bytes2) {
+          std::cerr << msg << std::endl;
+          REQUIRE(bytes1 == bytes2);
+        }
+      }
+    }
   }
+}
 
-  hll_sketch build_sketch(int lg_k, target_hll_type hll_type, hll_mode mode) {
-    hll_sketch sk(lg_k, hll_type);
-    int n = get_n(lg_k, mode);
-    for (int i = 0; i < n; i++) sk.update(static_cast<uint64_t>(i + v));
-    v += n;
-    return sk;
-  }
+TEST_CASE("hll isomorphic: union one update serialize updatable", "[hll_isomorphic]") {
+  union_one_update(false);
+}
+
+TEST_CASE("hll isomorphic: union one update serialize compact", "[hll_isomorphic]") {
+  union_one_update(true);
+}
 
-  // merges a sketch to an empty union and gets result of the same type, checks binary equivalence
-  void union_one_update(bool compact) {
-    for (int lg_k = 4; lg_k <= 21; lg_k++) { // all lg_k
-      for (int mode = 0; mode <= 2; mode++) { // List, Set, Hll
-        if ((lg_k < 8) && (mode == 1)) continue; // lg_k < 8 list transitions directly to HLL
-        for (int t = 0; t <= 2; t++) { // HLL_4, HLL_6, HLL_8
-          target_hll_type hll_type = (target_hll_type) t;
-          hll_sketch sk1 = build_sketch(lg_k, hll_type, (hll_mode) mode);
-          hll_union u(lg_k);
-          u.update(sk1);
-          hll_sketch sk2 = u.get_result(hll_type);
-          auto bytes1 = compact ? sk1.serialize_compact() : sk1.serialize_updatable();
+// converts a sketch to a different type and converts back to the original type to check binary equivalence
+static void convert_back_and_forth(bool compact) {
+  for (int lg_k = 4; lg_k <= 21; lg_k++) { // all lg_k
+    for (int mode = 0; mode <= 2; mode++) { // List, Set, Hll
+      if ((lg_k < 8) && (mode == 1)) continue; // lg_k < 8 list transitions directly to HLL
+      for (int t1 = 0; t1 <= 2; t1++) { // HLL_4, HLL_6, HLL_8
+        target_hll_type hll_type1 = (target_hll_type) t1;
+        hll_sketch sk1 = build_sketch(lg_k, hll_type1, (hll_mode) mode);
+        auto bytes1 = compact ? sk1.serialize_compact() : sk1.serialize_updatable();
+        for (int t2 = 0; t2 <= 2; t2++) { // HLL_4, HLL_6, HLL_8
+          if (t2 == t1) continue;
+          target_hll_type hll_type2 = (target_hll_type) t2;
+          hll_sketch sk2(hll_sketch(sk1, hll_type2), hll_type1);
           auto bytes2 = compact ? sk2.serialize_compact() : sk2.serialize_updatable();
           auto msg = "LgK=" + std::to_string(lg_k)
             + ", Mode=" + std::to_string(mode)
-            + ", Type=" + std::to_string(hll_type)
+            + ", Type1=" + std::to_string(hll_type1)
+            + ", Type2=" + std::to_string(hll_type2)
             + "\n" + sk1.to_string(true, true, true, true)
             + "\n" + sk2.to_string(true, true, true, true);
-          CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, bytes1, bytes2);
-        }
-      }
-    }
-  }
-
-  void union_one_update_serialize_updatable() {
-    union_one_update(false);
-  }
-
-  void union_one_update_serialize_compact() {
-    union_one_update(true);
-  }
-
-  // converts a sketch to a different type and converts back to the original type to check binary equivalence
-  void convert_back_and_forth(bool compact) {
-    for (int lg_k = 4; lg_k <= 21; lg_k++) { // all lg_k
-      for (int mode = 0; mode <= 2; mode++) { // List, Set, Hll
-        if ((lg_k < 8) && (mode == 1)) continue; // lg_k < 8 list transitions directly to HLL
-        for (int t1 = 0; t1 <= 2; t1++) { // HLL_4, HLL_6, HLL_8
-          target_hll_type hll_type1 = (target_hll_type) t1;
-          hll_sketch sk1 = build_sketch(lg_k, hll_type1, (hll_mode) mode);
-          auto bytes1 = compact ? sk1.serialize_compact() : sk1.serialize_updatable();
-          for (int t2 = 0; t2 <= 2; t2++) { // HLL_4, HLL_6, HLL_8
-            if (t2 == t1) continue;
-            target_hll_type hll_type2 = (target_hll_type) t2;
-            hll_sketch sk2(hll_sketch(sk1, hll_type2), hll_type1);
-            auto bytes2 = compact ? sk2.serialize_compact() : sk2.serialize_updatable();
-            auto msg = "LgK=" + std::to_string(lg_k)
-              + ", Mode=" + std::to_string(mode)
-              + ", Type1=" + std::to_string(hll_type1)
-              + ", Type2=" + std::to_string(hll_type2)
-              + "\n" + sk1.to_string(true, true, true, true)
-              + "\n" + sk2.to_string(true, true, true, true);
-            CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, bytes1, bytes2);
+          if (bytes1 != bytes2) {
+            std::cerr << msg << std::endl;
+            REQUIRE(bytes1 == bytes2);
           }
         }
       }
     }
   }
+}
 
-  void convert_back_and_forth_serialize_updatable() {
-    convert_back_and_forth(false);
-  }
-
-  void convert_back_and_forth_serialize_compact() {
-    convert_back_and_forth(true);
-  }
-
-};
+TEST_CASE("hll isomorphic: convert back and forth serialize updatable", "[hll_isomorphic]") {
+  convert_back_and_forth(false);
+}
 
-CPPUNIT_TEST_SUITE_REGISTRATION(IsomorphicTest);
+TEST_CASE("hll isomorphic: convert back and forth serialize compact", "[hll_isomorphic]") {
+  convert_back_and_forth(true);
+}
 
 } /* namespace datasketches */
diff --git a/hll/test/TablesTest.cpp b/hll/test/TablesTest.cpp
index 15b13cc..aee0e9d 100644
--- a/hll/test/TablesTest.cpp
+++ b/hll/test/TablesTest.cpp
@@ -17,43 +17,28 @@
  * under the License.
  */
 
-#include "CubicInterpolation.hpp"
-
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
-
-namespace datasketches {
-
-class TablesTest : public CppUnit::TestFixture {
 
-  CPPUNIT_TEST_SUITE(TablesTest);
-  CPPUNIT_TEST(interpolationException);
-  CPPUNIT_TEST(checkCornerCase);
-  CPPUNIT_TEST_SUITE_END();
+#include <catch.hpp>
 
-  void interpolationException() {
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to throw with x = -1.0",
-                                 CubicInterpolation<>::usingXAndYTables(-1.0),
-                                 std::invalid_argument);
-
-    CPPUNIT_ASSERT_THROW_MESSAGE("Failed to throw with x = 1e12",
-                                 CubicInterpolation<>::usingXAndYTables(1e12),
-                                 std::invalid_argument);
-  }
-
-  void checkCornerCase() {
-    int len = 10;
-    double xArr[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
-    double yArr[] = {2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0};
-    double x = xArr[len - 1];
-    double y = CubicInterpolation<>::usingXAndYTables(xArr, yArr, len, x);
-    double yExp = yArr[len - 1];
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(yExp, y, 0.0);
-  }
+#include "CubicInterpolation.hpp"
 
-};
+namespace datasketches {
 
-CPPUNIT_TEST_SUITE_REGISTRATION(TablesTest);
+TEST_CASE("hll tables: interpolation exception", "[hll_tables]") {
+  REQUIRE_THROWS_AS(CubicInterpolation<>::usingXAndYTables(-1.0), std::invalid_argument);
+
+  REQUIRE_THROWS_AS(CubicInterpolation<>::usingXAndYTables(1e12), std::invalid_argument);
+}
+
+TEST_CASE("hll tables: check corner case", "[hll_tables]") {
+  int len = 10;
+  double xArr[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
+  double yArr[] = {2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0};
+  double x = xArr[len - 1];
+  double y = CubicInterpolation<>::usingXAndYTables(xArr, yArr, len, x);
+  double yExp = yArr[len - 1];
+  REQUIRE(y == yExp);
+}
 
 } /* namespace datasketches */
 
diff --git a/hll/test/ToFromByteArrayTest.cpp b/hll/test/ToFromByteArrayTest.cpp
index 5837ffa..db00731 100644
--- a/hll/test/ToFromByteArrayTest.cpp
+++ b/hll/test/ToFromByteArrayTest.cpp
@@ -17,167 +17,152 @@
  * under the License.
  */
 
-#include "hll.hpp"
+#include <catch.hpp>
+#include <fstream>
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include "hll.hpp"
 
 namespace datasketches {
 
-class ToFromByteArrayTest : public CppUnit::TestFixture {
-
-  // list of values defined at bottom of file
-  static const int nArr[]; // = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
-
-  CPPUNIT_TEST_SUITE(ToFromByteArrayTest);
-  CPPUNIT_TEST(deserializeFromJava);
-  CPPUNIT_TEST(toFromSketch);
-  CPPUNIT_TEST(doubleSerialize);
-  CPPUNIT_TEST_SUITE_END();
-
-  void doubleSerialize() {
-    hll_sketch sk(9, HLL_8);
-    for (int i = 0; i < 1024; ++i) {
-      sk.update(i);
-    }
-    
-    std::stringstream ss1;
-    sk.serialize_updatable(ss1);
-    auto ser1 = sk.serialize_updatable();
+static const int nArr[] = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
 
-    std::stringstream ss;
-    sk.serialize_updatable(ss);
-    std::string str = ss.str();
+TEST_CASE("hll to/from byte array: double serialize", "[hll_byte_array]") {
+  hll_sketch sk(9, HLL_8);
+  for (int i = 0; i < 1024; ++i) {
+    sk.update(i);
+  }
+  
+  std::stringstream ss1;
+  sk.serialize_updatable(ss1);
+  auto ser1 = sk.serialize_updatable();
 
+  std::stringstream ss;
+  sk.serialize_updatable(ss);
+  std::string str = ss.str();
 
-    hll_sketch sk2 = hll_sketch::deserialize(ser1.data(), ser1.size());
-    auto ser2 = sk.serialize_updatable();
+  hll_sketch sk2 = hll_sketch::deserialize(ser1.data(), ser1.size());
+  auto ser2 = sk.serialize_updatable();
 
-    CPPUNIT_ASSERT_EQUAL(ser1.size(), ser2.size());
-    int len = ser1.size();
-    uint8_t* b1 = ser1.data();
-    uint8_t* b2 = ser2.data();
+  REQUIRE(ser1.size() == ser2.size());
+  int len = ser1.size();
+  uint8_t* b1 = ser1.data();
+  uint8_t* b2 = ser2.data();
 
-    for (int i = 0; i < len; ++i) {
-      CPPUNIT_ASSERT_EQUAL(b1[i], b2[i]);
-    }
+  for (int i = 0; i < len; ++i) {
+    REQUIRE(b2[i] == b1[i]);
   }
+}
 
-  void deserializeFromJava() {
-    std::string inputPath;
+TEST_CASE("hll to/from byte array: deserialize from java", "[hll_byte_array]") {
+  std::string inputPath;
 #ifdef TEST_BINARY_INPUT_PATH
-      inputPath = TEST_BINARY_INPUT_PATH;
+    inputPath = TEST_BINARY_INPUT_PATH;
 #else
-      inputPath = "test/";
+    inputPath = "test/";
 #endif
 
-    std::ifstream ifs;
-    ifs.open(inputPath + "list_from_java.bin", std::ios::binary);
-    hll_sketch sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 7.0, 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 7.0, 1e-6); // java: 7.000000104308129
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 7.000350, 1e-5); // java: 7.000349609067664
-    ifs.close();
-
-    ifs.open(inputPath + "compact_set_from_java.bin", std::ios::binary);
-    sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 24.0, 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 24.0, 1e-5); // java: 24.00000137090692
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 24.001200, 1e-5); // java: 24.0011996729902
-    ifs.close();
-
-    ifs.open(inputPath + "updatable_set_from_java.bin", std::ios::binary);
-    sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 24.0, 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 24.0, 1e-5); // java: 24.00000137090692
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 24.001200, 1e-5); // java: 24.0011996729902
-    ifs.close();
-
-
-    ifs.open(inputPath + "array6_from_java.bin", std::ios::binary);
-    sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 9589.968564, 1e-5); // java: 9589.968564432073
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 10089.150211, 1e-5); // java: 10089.1502113328
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 10642.370492, 1e-5); // java: 10642.370491998483
-    ifs.close();
-
-
-    ifs.open(inputPath + "compact_array4_from_java.bin", std::ios::binary);
-    sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 9589.968564, 1e-5); // java: 9589.968564432073
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 10089.150211, 1e-5); // java: 10089.1502113328
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 10642.370492, 1e-5); // java: 10642.370491998483
-
-    ifs.close();
-
-
-    ifs.open(inputPath + "updatable_array4_from_java.bin", std::ios::binary);
-    sk = hll_sketch::deserialize(ifs);
-    CPPUNIT_ASSERT(sk.is_empty() == false);
-    CPPUNIT_ASSERT_EQUAL(sk.get_lg_config_k(), 8);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_lower_bound(1), 9589.968564, 1e-5); // java: 9589.968564432073
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_estimate(), 10089.150211, 1e-5); // java: 10089.1502113328
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk.get_upper_bound(1), 10642.370492, 1e-5); // java: 10642.370491998483
-    ifs.close();
-  }
-
-  void toFrom(const int lgConfigK, const target_hll_type tgtHllType, const int n) {
-    hll_sketch src(lgConfigK, tgtHllType);
-    for (int i = 0; i < n; ++i) {
-      src.update(i);
-    }
-
-    std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
-    src.serialize_compact(ss);
-    hll_sketch dst = hll_sketch::deserialize(ss);
-    checkSketchEquality(src, dst);
-
-    auto bytes1 = src.serialize_compact();
-    dst = hll_sketch::deserialize(bytes1.data(), bytes1.size());
-    checkSketchEquality(src, dst);
-
-    ss.clear();
-    src.serialize_updatable(ss);
-    dst = hll_sketch::deserialize(ss);
-    checkSketchEquality(src, dst);
-
-    auto bytes2 = src.serialize_updatable();
-    dst = hll_sketch::deserialize(bytes2.data(), bytes2.size());
-    checkSketchEquality(src, dst);
-  }
-
-  void checkSketchEquality(hll_sketch& sk1, hll_sketch& sk2) {
-    CPPUNIT_ASSERT_EQUAL(sk1.get_lg_config_k(), sk2.get_lg_config_k());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_lower_bound(1), sk2.get_lower_bound(1), 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_estimate(), sk2.get_estimate(), 0.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(sk1.get_upper_bound(1), sk2.get_upper_bound(1), 0.0);
-    CPPUNIT_ASSERT_EQUAL(sk1.get_target_type(), sk2.get_target_type());
+  std::ifstream ifs;
+  ifs.open(inputPath + "list_from_java.bin", std::ios::binary);
+  hll_sketch sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == 7.0);
+  REQUIRE(sk.get_estimate() == Approx(7.0).margin(1e-6));
+  REQUIRE(sk.get_upper_bound(1) == Approx(7.000350).margin(1e-5));
+  ifs.close();
+
+  ifs.open(inputPath + "compact_set_from_java.bin", std::ios::binary);
+  sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == 24.0);
+  REQUIRE(sk.get_estimate() == Approx(24.0).margin(1e-5));
+  REQUIRE(sk.get_upper_bound(1) == Approx(24.001200).margin(1e-5));
+  ifs.close();
+
+  ifs.open(inputPath + "updatable_set_from_java.bin", std::ios::binary);
+  sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == 24.0);
+  REQUIRE(sk.get_estimate() == Approx(24.0).margin(1e-5));
+  REQUIRE(sk.get_upper_bound(1) == Approx(24.001200).margin(1e-5));
+  ifs.close();
+
+
+  ifs.open(inputPath + "array6_from_java.bin", std::ios::binary);
+  sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == Approx(9589.968564).margin(1e-5));
+  REQUIRE(sk.get_estimate() == Approx(10089.150211).margin(1e-5));
+  REQUIRE(sk.get_upper_bound(1) == Approx(10642.370492).margin(1e-5));
+  ifs.close();
+
+
+  ifs.open(inputPath + "compact_array4_from_java.bin", std::ios::binary);
+  sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == Approx(9589.968564).margin(1e-5));
+  REQUIRE(sk.get_estimate() == Approx(10089.150211).margin(1e-5));
+  REQUIRE(sk.get_upper_bound(1) == Approx(10642.370492).margin(1e-5));
+
+  ifs.close();
+
+
+  ifs.open(inputPath + "updatable_array4_from_java.bin", std::ios::binary);
+  sk = hll_sketch::deserialize(ifs);
+  REQUIRE(sk.is_empty() == false);
+  REQUIRE(sk.get_lg_config_k() == 8);
+  REQUIRE(sk.get_lower_bound(1) == Approx(9589.968564).margin(1e-5));
+  REQUIRE(sk.get_estimate() == Approx(10089.150211).margin(1e-5));
+  REQUIRE(sk.get_upper_bound(1) == Approx(10642.370492).margin(1e-5));
+  ifs.close();
+}
+
+static void checkSketchEquality(hll_sketch& sk1, hll_sketch& sk2) {
+  REQUIRE(sk1.get_lg_config_k() == sk2.get_lg_config_k());
+  REQUIRE(sk1.get_lower_bound(1) == sk2.get_lower_bound(1));
+  REQUIRE(sk1.get_estimate() == sk2.get_estimate());
+  REQUIRE(sk1.get_upper_bound(1) == sk2.get_upper_bound(1));
+  REQUIRE(sk1.get_target_type() == sk2.get_target_type());
+}
+
+static void toFrom(const int lgConfigK, const target_hll_type tgtHllType, const int n) {
+  hll_sketch src(lgConfigK, tgtHllType);
+  for (int i = 0; i < n; ++i) {
+    src.update(i);
   }
 
-  void toFromSketch() {
-    for (int i = 0; i < 10; ++i) {
-      int n = nArr[i];
-      for (int lgK = 4; lgK <= 13; ++lgK) {
-        toFrom(lgK, HLL_4, n);
-        toFrom(lgK, HLL_6, n);
-        toFrom(lgK, HLL_8, n);
-      }
+  std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
+  src.serialize_compact(ss);
+  hll_sketch dst = hll_sketch::deserialize(ss);
+  checkSketchEquality(src, dst);
+
+  auto bytes1 = src.serialize_compact();
+  dst = hll_sketch::deserialize(bytes1.data(), bytes1.size());
+  checkSketchEquality(src, dst);
+
+  ss.clear();
+  src.serialize_updatable(ss);
+  dst = hll_sketch::deserialize(ss);
+  checkSketchEquality(src, dst);
+
+  auto bytes2 = src.serialize_updatable();
+  dst = hll_sketch::deserialize(bytes2.data(), bytes2.size());
+  checkSketchEquality(src, dst);
+}
+
+TEST_CASE("hll to/from byte array: to from sketch", "[hll_byte_array]") {
+  for (int i = 0; i < 10; ++i) {
+    int n = nArr[i];
+    for (int lgK = 4; lgK <= 13; ++lgK) {
+      toFrom(lgK, HLL_4, n);
+      toFrom(lgK, HLL_6, n);
+      toFrom(lgK, HLL_8, n);
     }
   }
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(ToFromByteArrayTest);
-  
-const int ToFromByteArrayTest::nArr[] = {1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000};
+}
 
 } /* namespace datasketches */
diff --git a/kll/test/CMakeLists.txt b/kll/test/CMakeLists.txt
index fc18587..61747c2 100644
--- a/kll/test/CMakeLists.txt
+++ b/kll/test/CMakeLists.txt
@@ -17,7 +17,7 @@
 
 add_executable(kll_test)
 
-target_link_libraries(kll_test kll catch_test)
+target_link_libraries(kll_test kll common_test)
 
 set_target_properties(kll_test PROPERTIES
   CXX_STANDARD 11
@@ -41,8 +41,3 @@ target_sources(kll_test
     kll_sketch_custom_type_test.cpp
     kll_sketch_validation.cpp
 )
-
-target_include_directories(kll_test
-  PRIVATE
-    ../../common/test
-)
diff --git a/sampling/test/CMakeLists.txt b/sampling/test/CMakeLists.txt
index a921d1f..886abb3 100644
--- a/sampling/test/CMakeLists.txt
+++ b/sampling/test/CMakeLists.txt
@@ -17,7 +17,7 @@
 
 add_executable(sampling_test)
 
-target_link_libraries(sampling_test sampling catch_test)
+target_link_libraries(sampling_test sampling common_test)
 
 set_target_properties(sampling_test PROPERTIES
   CXX_STANDARD 11
@@ -40,8 +40,3 @@ target_sources(sampling_test
   var_opt_sketch_test.cpp
   var_opt_union_test.cpp
 )
-
-target_include_directories(sampling_test
-  PRIVATE
-    ../../common/test
-)
diff --git a/theta/test/theta_a_not_b_test.cpp b/theta/test/theta_a_not_b_test.cpp
index cb7efdd..54bd3fa 100644
--- a/theta/test/theta_a_not_b_test.cpp
+++ b/theta/test/theta_a_not_b_test.cpp
@@ -17,223 +17,204 @@
  * under the License.
  */
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 
 #include <theta_a_not_b.hpp>
 
 namespace datasketches {
 
-class theta_a_not_b_test: public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(theta_a_not_b_test);
-  CPPUNIT_TEST(empty);
-  CPPUNIT_TEST(non_empty_no_retained_keys);
-  CPPUNIT_TEST(exact_mode_half_overlap);
-  CPPUNIT_TEST(exact_mode_disjoint);
-  CPPUNIT_TEST(exact_mode_full_overlap);
-  CPPUNIT_TEST(estimation_mode_half_overlap);
-  CPPUNIT_TEST(estimation_mode_disjoint);
-  CPPUNIT_TEST(estimation_mode_full_overlap);
-  CPPUNIT_TEST(seed_mismatch);
-  CPPUNIT_TEST_SUITE_END();
-
-  void empty() {
-    theta_a_not_b a_not_b;
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    update_theta_sketch b = update_theta_sketch::builder().build();
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void non_empty_no_retained_keys() {
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    a.update(1);
-    update_theta_sketch b = update_theta_sketch::builder().set_p(0.001).build();
-    theta_a_not_b a_not_b;
-
-    // B is still empty
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1U, result.get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_EQUAL(1.0, result.get_estimate());
-
-    // B is not empty in estimation mode and no entries
-    b.update(1);
-    CPPUNIT_ASSERT_EQUAL(0U, b.get_num_retained());
-
-    result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void exact_mode_half_overlap() {
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) a.update(value++);
-
-    update_theta_sketch b = update_theta_sketch::builder().build();
-    value = 500;
-    for (int i = 0; i < 1000; i++) b.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs, ordered result
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT(result.is_ordered());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
-
-    // unordered inputs, unordered result
-    result = a_not_b.compute(a, b, false);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT(!result.is_ordered());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
-
-    // ordered inputs
-    result = a_not_b.compute(a.compact(), b.compact());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT(result.is_ordered());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
-
-    // A is ordered, so the result is ordered regardless
-    result = a_not_b.compute(a.compact(), b, false);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT(result.is_ordered());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
+TEST_CASE("theta a-not-b: empty", "[theta_a_not_b]") {
+  theta_a_not_b a_not_b;
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  update_theta_sketch b = update_theta_sketch::builder().build();
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
 }
 
-  void exact_mode_disjoint() {
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) a.update(value++);
-
-    update_theta_sketch b = update_theta_sketch::builder().build();
-    for (int i = 0; i < 1000; i++) b.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1000.0, result.get_estimate());
-
-    // ordered inputs
-    result = a_not_b.compute(a.compact(), b.compact());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1000.0, result.get_estimate());
-  }
-
-  void exact_mode_full_overlap() {
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs
-    compact_theta_sketch result = a_not_b.compute(sketch, sketch);
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-
-    // ordered inputs
-    result = a_not_b.compute(sketch.compact(), sketch.compact());
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void estimation_mode_half_overlap() {
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) a.update(value++);
-
-    update_theta_sketch b = update_theta_sketch::builder().build();
-    value = 5000;
-    for (int i = 0; i < 10000; i++) b.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02);
-
-    // ordered inputs
-    result = a_not_b.compute(a.compact(), b.compact());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02);
-  }
-
-  void estimation_mode_disjoint() {
-    update_theta_sketch a = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) a.update(value++);
-
-    update_theta_sketch b = update_theta_sketch::builder().build();
-    for (int i = 0; i < 10000; i++) b.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs
-    compact_theta_sketch result = a_not_b.compute(a, b);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(10000, result.get_estimate(), 10000 * 0.02);
-
-    // ordered inputs
-    result = a_not_b.compute(a.compact(), b.compact());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(10000, result.get_estimate(), 10000 * 0.02);
-  }
-
-  void estimation_mode_full_overlap() {
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch.update(value++);
-
-    theta_a_not_b a_not_b;
-
-    // unordered inputs
-    compact_theta_sketch result = a_not_b.compute(sketch, sketch);
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-
-    // ordered inputs
-    result = a_not_b.compute(sketch.compact(), sketch.compact());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void seed_mismatch() {
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    sketch.update(1); // non-empty should not be ignored
-    theta_a_not_b a_not_b(123);
-    CPPUNIT_ASSERT_THROW(a_not_b.compute(sketch, sketch), std::invalid_argument);
-  }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(theta_a_not_b_test);
+TEST_CASE("theta a-not-b: non empty no retained keys", "[theta_a_not_b]") {
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  a.update(1);
+  update_theta_sketch b = update_theta_sketch::builder().set_p(0.001).build();
+  theta_a_not_b a_not_b;
+
+  // B is still empty
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_num_retained() == 1);
+  REQUIRE(result.get_theta() == Approx(1).margin(1e-10));
+  REQUIRE(result.get_estimate() == 1.0);
+
+  // B is not empty in estimation mode and no entries
+  b.update(1);
+  REQUIRE(b.get_num_retained() == 0U);
+
+  result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE(result.get_theta() == Approx(0.001).margin(1e-10));
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta a-not-b: exact mode half overlap", "[theta_a_not_b]") {
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) a.update(value++);
+
+  update_theta_sketch b = update_theta_sketch::builder().build();
+  value = 500;
+  for (int i = 0; i < 1000; i++) b.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs, ordered result
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.is_ordered());
+  REQUIRE(result.get_estimate() == 500.0);
+
+  // unordered inputs, unordered result
+  result = a_not_b.compute(a, b, false);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE_FALSE(result.is_ordered());
+  REQUIRE(result.get_estimate() == 500.0);
+
+  // ordered inputs
+  result = a_not_b.compute(a.compact(), b.compact());
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.is_ordered());
+  REQUIRE(result.get_estimate() == 500.0);
+
+  // A is ordered, so the result is ordered regardless
+  result = a_not_b.compute(a.compact(), b, false);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.is_ordered());
+  REQUIRE(result.get_estimate() == 500.0);
+}
+
+TEST_CASE("theta a-not-b: exact mode disjoint", "[theta_a_not_b]") {
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) a.update(value++);
+
+  update_theta_sketch b = update_theta_sketch::builder().build();
+  for (int i = 0; i < 1000; i++) b.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 1000.0);
+
+  // ordered inputs
+  result = a_not_b.compute(a.compact(), b.compact());
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 1000.0);
+}
+
+TEST_CASE("theta a-not-b: exact mode full overlap", "[theta_a_not_b]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs
+  compact_theta_sketch result = a_not_b.compute(sketch, sketch);
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+
+  // ordered inputs
+  result = a_not_b.compute(sketch.compact(), sketch.compact());
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta a-not-b: estimation mode half overlap", "[theta_a_not_b]") {
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) a.update(value++);
+
+  update_theta_sketch b = update_theta_sketch::builder().build();
+  value = 5000;
+  for (int i = 0; i < 10000; i++) b.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(5000).margin(5000 * 0.02));
+
+  // ordered inputs
+  result = a_not_b.compute(a.compact(), b.compact());
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(5000).margin(5000 * 0.02));
+}
+
+TEST_CASE("theta a-not-b: estimation mode disjoint", "[theta_a_not_b]") {
+  update_theta_sketch a = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) a.update(value++);
+
+  update_theta_sketch b = update_theta_sketch::builder().build();
+  for (int i = 0; i < 10000; i++) b.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs
+  compact_theta_sketch result = a_not_b.compute(a, b);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(10000).margin(10000 * 0.02));
+
+  // ordered inputs
+  result = a_not_b.compute(a.compact(), b.compact());
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(10000).margin(10000 * 0.02));
+}
+
+TEST_CASE("theta a-not-b: estimation mode full overlap", "[theta_a_not_b]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch.update(value++);
+
+  theta_a_not_b a_not_b;
+
+  // unordered inputs
+  compact_theta_sketch result = a_not_b.compute(sketch, sketch);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+
+  // ordered inputs
+  result = a_not_b.compute(sketch.compact(), sketch.compact());
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta a-not-b: seed mismatch", "[theta_a_not_b]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  sketch.update(1); // non-empty should not be ignored
+  theta_a_not_b a_not_b(123);
+  REQUIRE_THROWS_AS(a_not_b.compute(sketch, sketch), std::invalid_argument);
+}
 
 } /* namespace datasketches */
diff --git a/theta/test/theta_intersection_test.cpp b/theta/test/theta_intersection_test.cpp
index 3cd010f..2c8d6c0 100644
--- a/theta/test/theta_intersection_test.cpp
+++ b/theta/test/theta_intersection_test.cpp
@@ -17,224 +17,202 @@
  * under the License.
  */
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 
 #include <theta_intersection.hpp>
 
 namespace datasketches {
 
-class theta_intersection_test: public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(theta_intersection_test);
-  CPPUNIT_TEST(invalid);
-  CPPUNIT_TEST(empty);
-  CPPUNIT_TEST(non_empty_no_retained_keys);
-  CPPUNIT_TEST(exact_mode_half_overlap_unordered);
-  CPPUNIT_TEST(exact_mode_half_overlap_ordered);
-  CPPUNIT_TEST(exact_mode_disjoint_unordered);
-  CPPUNIT_TEST(exact_mode_disjoint_ordered);
-  CPPUNIT_TEST(estimation_mode_half_overlap_unordered);
-  CPPUNIT_TEST(estimation_mode_half_overlap_ordered);
-  CPPUNIT_TEST(estimation_mode_disjoint_unordered);
-  CPPUNIT_TEST(estimation_mode_disjoint_ordered);
-  CPPUNIT_TEST(seed_mismatch);
-  CPPUNIT_TEST_SUITE_END();
-
-  void invalid() {
-    theta_intersection intersection;
-    CPPUNIT_ASSERT(!intersection.has_result());
-    CPPUNIT_ASSERT_THROW(intersection.get_result(), std::invalid_argument);
-  }
-
-  void empty() {
-    theta_intersection intersection;
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    intersection.update(sketch);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-
-    intersection.update(sketch);
-    result = intersection.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void non_empty_no_retained_keys() {
-    update_theta_sketch sketch = update_theta_sketch::builder().set_p(0.001).build();
-    sketch.update(1);
-    theta_intersection intersection;
-    intersection.update(sketch);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-
-    intersection.update(sketch);
-    result = intersection.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained());
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void exact_mode_half_overlap_unordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 500;
-    for (int i = 0; i < 1000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1);
-    intersection.update(sketch2);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
-  }
-
-  void exact_mode_half_overlap_ordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 500;
-    for (int i = 0; i < 1000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1.compact());
-    intersection.update(sketch2.compact());
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate());
-  }
-
-  void exact_mode_disjoint_unordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    for (int i = 0; i < 1000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1);
-    intersection.update(sketch2);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void exact_mode_disjoint_ordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    for (int i = 0; i < 1000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1.compact());
-    intersection.update(sketch2.compact());
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(result.is_empty());
-    CPPUNIT_ASSERT(!result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void estimation_mode_half_overlap_unordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 5000;
-    for (int i = 0; i < 10000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1);
-    intersection.update(sketch2);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02);
-  }
-
-  void estimation_mode_half_overlap_ordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 5000;
-    for (int i = 0; i < 10000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1.compact());
-    intersection.update(sketch2.compact());
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02);
-  }
-
-  void estimation_mode_disjoint_unordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    for (int i = 0; i < 10000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1);
-    intersection.update(sketch2);
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void estimation_mode_disjoint_ordered() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    for (int i = 0; i < 10000; i++) sketch2.update(value++);
-
-    theta_intersection intersection;
-    intersection.update(sketch1.compact());
-    intersection.update(sketch2.compact());
-    compact_theta_sketch result = intersection.get_result();
-    CPPUNIT_ASSERT(!result.is_empty());
-    CPPUNIT_ASSERT(result.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate());
-  }
-
-  void seed_mismatch() {
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    sketch.update(1); // non-empty should not be ignored
-    theta_intersection intersection(123);
-    CPPUNIT_ASSERT_THROW(intersection.update(sketch), std::invalid_argument);
-  }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(theta_intersection_test);
+TEST_CASE("theta intersection: invalid", "[theta_intersection]") {
+  theta_intersection intersection;
+  REQUIRE_FALSE(intersection.has_result());
+  REQUIRE_THROWS_AS(intersection.get_result(), std::invalid_argument);
+}
+
+TEST_CASE("theta intersection: empty", "[theta_intersection]") {
+  theta_intersection intersection;
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  intersection.update(sketch);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+
+  intersection.update(sketch);
+  result = intersection.get_result();
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: non empty no retained keys", "[theta_intersection]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().set_p(0.001).build();
+  sketch.update(1);
+  theta_intersection intersection;
+  intersection.update(sketch);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_theta() == Approx(0.001).margin(1e-10));
+  REQUIRE(result.get_estimate() == 0.0);
+
+  intersection.update(sketch);
+  result = intersection.get_result();
+  REQUIRE(result.get_num_retained() == 0);
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_theta() == Approx(0.001).margin(1e-10));
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: exact mode half overlap unordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 500;
+  for (int i = 0; i < 1000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1);
+  intersection.update(sketch2);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 500.0);
+}
+
+TEST_CASE("theta intersection: exact mode half overlap ordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 500;
+  for (int i = 0; i < 1000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1.compact());
+  intersection.update(sketch2.compact());
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 500.0);
+}
+
+TEST_CASE("theta intersection: exact mode disjoint unordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  for (int i = 0; i < 1000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1);
+  intersection.update(sketch2);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: exact mode disjoint ordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  for (int i = 0; i < 1000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1.compact());
+  intersection.update(sketch2.compact());
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE(result.is_empty());
+  REQUIRE_FALSE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: estimation mode half overlap unordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 5000;
+  for (int i = 0; i < 10000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1);
+  intersection.update(sketch2);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(5000).margin(5000 * 0.02));
+}
+
+TEST_CASE("theta intersection: estimation mode half overlap ordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 5000;
+  for (int i = 0; i < 10000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1.compact());
+  intersection.update(sketch2.compact());
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == Approx(5000).margin(5000 * 0.02));
+}
+
+TEST_CASE("theta intersection: estimation mode disjoint unordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  for (int i = 0; i < 10000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1);
+  intersection.update(sketch2);
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: estimation mode disjoint ordered", "[theta_intersection]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  for (int i = 0; i < 10000; i++) sketch2.update(value++);
+
+  theta_intersection intersection;
+  intersection.update(sketch1.compact());
+  intersection.update(sketch2.compact());
+  compact_theta_sketch result = intersection.get_result();
+  REQUIRE_FALSE(result.is_empty());
+  REQUIRE(result.is_estimation_mode());
+  REQUIRE(result.get_estimate() == 0.0);
+}
+
+TEST_CASE("theta intersection: seed mismatch", "[theta_intersection]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  sketch.update(1); // non-empty should not be ignored
+  theta_intersection intersection(123);
+  REQUIRE_THROWS_AS(intersection.update(sketch), std::invalid_argument);
+}
 
 } /* namespace datasketches */
diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp
index 47e6231..007c267 100644
--- a/theta/test/theta_sketch_test.cpp
+++ b/theta/test/theta_sketch_test.cpp
@@ -17,435 +17,406 @@
  * under the License.
  */
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
+#include <fstream>
+#include <sstream>
 
 #include <theta_sketch.hpp>
 
 namespace datasketches {
 
-class theta_sketch_test: public CppUnit::TestFixture {
-
-  // optional prefix for input binary files
-  // default value defined at end of file
-  static const std::string inputPath;
-
-  CPPUNIT_TEST_SUITE(theta_sketch_test);
-  CPPUNIT_TEST(empty);
-  CPPUNIT_TEST(non_empty_no_retained_keys);
-  CPPUNIT_TEST(single_item);
-  CPPUNIT_TEST(resize_exact);
-  CPPUNIT_TEST(estimation);
-  CPPUNIT_TEST(deserialize_update_empty_from_java_as_base);
-  CPPUNIT_TEST(deserialize_update_empty_from_java_as_subclass);
-  CPPUNIT_TEST(deserialize_update_estimation_from_java_as_base);
-  CPPUNIT_TEST(deserialize_update_estimation_from_java_as_subclass);
-  CPPUNIT_TEST(deserialize_compact_empty_from_java_as_base);
-  CPPUNIT_TEST(deserialize_compact_empty_from_java_as_subclass);
-  CPPUNIT_TEST(deserialize_single_item_from_java_as_base);
-  CPPUNIT_TEST(deserialize_single_item_from_java_as_subclass);
-  CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_base);
-  CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_subclass);
-  CPPUNIT_TEST(serialize_deserialize_stream_and_bytes_equivalency);
-  CPPUNIT_TEST_SUITE_END();
-
-  void empty() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    CPPUNIT_ASSERT(update_sketch.is_empty());
-    CPPUNIT_ASSERT(!update_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_upper_bound(1));
-
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    CPPUNIT_ASSERT(compact_sketch.is_empty());
-    CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_upper_bound(1));
-  }
+#ifdef TEST_BINARY_INPUT_PATH
+const std::string inputPath = TEST_BINARY_INPUT_PATH;
+#else
+const std::string inputPath = "test/";
+#endif
 
-  void non_empty_no_retained_keys() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build();
-    update_sketch.update(1);
-    //update_sketch.to_stream(std::cerr);
-    CPPUNIT_ASSERT_EQUAL(0U, update_sketch.get_num_retained());
-    CPPUNIT_ASSERT(!update_sketch.is_empty());
-    CPPUNIT_ASSERT(update_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT(update_sketch.get_upper_bound(1) > 0);
-
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    CPPUNIT_ASSERT_EQUAL(0U, compact_sketch.get_num_retained());
-    CPPUNIT_ASSERT(!compact_sketch.is_empty());
-    CPPUNIT_ASSERT(compact_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT(compact_sketch.get_upper_bound(1) > 0);
+TEST_CASE("theta sketch: empty", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  REQUIRE(update_sketch.is_empty());
+  REQUIRE_FALSE(update_sketch.is_estimation_mode());
+  REQUIRE(update_sketch.get_theta() == 1.0);
+  REQUIRE(update_sketch.get_estimate() == 0.0);
+  REQUIRE(update_sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(update_sketch.get_upper_bound(1) == 0.0);
+
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  REQUIRE(compact_sketch.is_empty());
+  REQUIRE_FALSE(compact_sketch.is_estimation_mode());
+  REQUIRE(compact_sketch.get_theta() == 1.0);
+  REQUIRE(compact_sketch.get_estimate() == 0.0);
+  REQUIRE(compact_sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(compact_sketch.get_upper_bound(1) == 0.0);
 }
 
-  void single_item() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    update_sketch.update(1);
-    CPPUNIT_ASSERT(!update_sketch.is_empty());
-    CPPUNIT_ASSERT(!update_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_upper_bound(1));
-
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    CPPUNIT_ASSERT(!compact_sketch.is_empty());
-    CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: non empty no retained keys", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build();
+  update_sketch.update(1);
+  //update_sketch.to_stream(std::cerr);
+  REQUIRE(update_sketch.get_num_retained() == 0);
+  REQUIRE_FALSE(update_sketch.is_empty());
+  REQUIRE(update_sketch.is_estimation_mode());
+  REQUIRE(update_sketch.get_estimate() == 0.0);
+  REQUIRE(update_sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(update_sketch.get_upper_bound(1) > 0);
+
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  REQUIRE(compact_sketch.get_num_retained() == 0);
+  REQUIRE_FALSE(compact_sketch.is_empty());
+  REQUIRE(compact_sketch.is_estimation_mode());
+  REQUIRE(compact_sketch.get_estimate() == 0.0);
+  REQUIRE(compact_sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(compact_sketch.get_upper_bound(1) > 0);
+}
 
-  void resize_exact() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    for (int i = 0; i < 2000; i++) update_sketch.update(i);
-    CPPUNIT_ASSERT(!update_sketch.is_empty());
-    CPPUNIT_ASSERT(!update_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_upper_bound(1));
-
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    CPPUNIT_ASSERT(!compact_sketch.is_empty());
-    CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_upper_bound(1));
+TEST_CASE("theta sketch: single item", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  update_sketch.update(1);
+  REQUIRE_FALSE(update_sketch.is_empty());
+  REQUIRE_FALSE(update_sketch.is_estimation_mode());
+  REQUIRE(update_sketch.get_theta() == 1.0);
+  REQUIRE(update_sketch.get_estimate() == 1.0);
+  REQUIRE(update_sketch.get_lower_bound(1) == 1.0);
+  REQUIRE(update_sketch.get_upper_bound(1) == 1.0);
+
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  REQUIRE_FALSE(compact_sketch.is_empty());
+  REQUIRE_FALSE(compact_sketch.is_estimation_mode());
+  REQUIRE(compact_sketch.get_theta() == 1.0);
+  REQUIRE(compact_sketch.get_estimate() == 1.0);
+  REQUIRE(compact_sketch.get_lower_bound(1) == 1.0);
+  REQUIRE(compact_sketch.get_upper_bound(1) == 1.0);
 }
 
-  void estimation() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().set_resize_factor(update_theta_sketch::resize_factor::X1).build();
-    const int n = 8000;
-    for (int i = 0; i < n; i++) update_sketch.update(i);
-    //update_sketch.to_stream(std::cerr);
-    CPPUNIT_ASSERT(!update_sketch.is_empty());
-    CPPUNIT_ASSERT(update_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT(update_sketch.get_theta() < 1.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, update_sketch.get_estimate(), n * 0.01);
-    CPPUNIT_ASSERT(update_sketch.get_lower_bound(1) < n);
-    CPPUNIT_ASSERT(update_sketch.get_upper_bound(1) > n);
-
-    const uint32_t k = 1 << update_theta_sketch::builder::DEFAULT_LG_K;
-    CPPUNIT_ASSERT(update_sketch.get_num_retained() >= k);
-    update_sketch.trim();
-    CPPUNIT_ASSERT_EQUAL(k, update_sketch.get_num_retained());
-
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    CPPUNIT_ASSERT(!compact_sketch.is_empty());
-    CPPUNIT_ASSERT(compact_sketch.is_ordered());
-    CPPUNIT_ASSERT(compact_sketch.is_estimation_mode());
-    CPPUNIT_ASSERT(compact_sketch.get_theta() < 1.0);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, compact_sketch.get_estimate(), n * 0.01);
-    CPPUNIT_ASSERT(compact_sketch.get_lower_bound(1) < n);
-    CPPUNIT_ASSERT(compact_sketch.get_upper_bound(1) > n);
+TEST_CASE("theta sketch: resize exact", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  for (int i = 0; i < 2000; i++) update_sketch.update(i);
+  REQUIRE_FALSE(update_sketch.is_empty());
+  REQUIRE_FALSE(update_sketch.is_estimation_mode());
+  REQUIRE(update_sketch.get_theta() == 1.0);
+  REQUIRE(update_sketch.get_estimate() == 2000.0);
+  REQUIRE(update_sketch.get_lower_bound(1) == 2000.0);
+  REQUIRE(update_sketch.get_upper_bound(1) == 2000.0);
+
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  REQUIRE_FALSE(compact_sketch.is_empty());
+  REQUIRE_FALSE(compact_sketch.is_estimation_mode());
+  REQUIRE(compact_sketch.get_theta() == 1.0);
+  REQUIRE(compact_sketch.get_estimate() == 2000.0);
+  REQUIRE(compact_sketch.get_lower_bound(1) == 2000.0);
+  REQUIRE(compact_sketch.get_upper_bound(1) == 2000.0);
 }
 
-  void deserialize_update_empty_from_java_as_base() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_update_empty_from_java.bin", std::ios::binary);
-    auto sketchptr = theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(sketchptr->is_empty());
-    CPPUNIT_ASSERT(!sketchptr->is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: estimation", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().set_resize_factor(update_theta_sketch::resize_factor::X1).build();
+  const int n = 8000;
+  for (int i = 0; i < n; i++) update_sketch.update(i);
+  //update_sketch.to_stream(std::cerr);
+  REQUIRE_FALSE(update_sketch.is_empty());
+  REQUIRE(update_sketch.is_estimation_mode());
+  REQUIRE(update_sketch.get_theta() < 1.0);
+  REQUIRE(update_sketch.get_estimate() == Approx((double) n).margin(n * 0.01));
+  REQUIRE(update_sketch.get_lower_bound(1) < n);
+  REQUIRE(update_sketch.get_upper_bound(1) > n);
+
+  const uint32_t k = 1 << update_theta_sketch::builder::DEFAULT_LG_K;
+  REQUIRE(update_sketch.get_num_retained() >= k);
+  update_sketch.trim();
+  REQUIRE(update_sketch.get_num_retained() == k);
+
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  REQUIRE_FALSE(compact_sketch.is_empty());
+  REQUIRE(compact_sketch.is_ordered());
+  REQUIRE(compact_sketch.is_estimation_mode());
+  REQUIRE(compact_sketch.get_theta() < 1.0);
+  REQUIRE(compact_sketch.get_estimate() == Approx((double) n).margin(n * 0.01));
+  REQUIRE(compact_sketch.get_lower_bound(1) < n);
+  REQUIRE(compact_sketch.get_upper_bound(1) > n);
+}
 
-  void deserialize_update_empty_from_java_as_subclass() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_update_empty_from_java.bin", std::ios::binary);
-    auto sketch = update_theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(sketch.is_empty());
-    CPPUNIT_ASSERT(!sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: deserialize update empty from java as base", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_update_empty_from_java.bin", std::ios::binary);
+  auto sketchptr = theta_sketch::deserialize(is);
+  REQUIRE(sketchptr->is_empty());
+  REQUIRE_FALSE(sketchptr->is_estimation_mode());
+  REQUIRE(sketchptr->get_num_retained() == 0);
+  REQUIRE(sketchptr->get_theta() == 1.0);
+  REQUIRE(sketchptr->get_estimate() == 0.0);
+  REQUIRE(sketchptr->get_lower_bound(1) == 0.0);
+  REQUIRE(sketchptr->get_upper_bound(1) == 0.0);
+}
 
-  void deserialize_update_estimation_from_java_as_base() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_update_estimation_from_java.bin", std::ios::binary);
-    auto sketchptr = theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketchptr->is_empty());
-    CPPUNIT_ASSERT(sketchptr->is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(5324U, sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketchptr->get_estimate(), 10000 * 0.01);
-    CPPUNIT_ASSERT(sketchptr->get_lower_bound(1) < 10000);
-    CPPUNIT_ASSERT(sketchptr->get_upper_bound(1) > 10000);
-  }
+TEST_CASE("theta sketch: deserialize update empty from java as subclass", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_update_empty_from_java.bin", std::ios::binary);
+  auto sketch = update_theta_sketch::deserialize(is);
+  REQUIRE(sketch.is_empty());
+  REQUIRE_FALSE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_num_retained() == 0);
+  REQUIRE(sketch.get_theta() == 1.0);
+  REQUIRE(sketch.get_estimate() == 0.0);
+  REQUIRE(sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(sketch.get_upper_bound(1) == 0.0);
+}
 
-  void deserialize_update_estimation_from_java_as_subclass() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_update_estimation_from_java.bin", std::ios::binary);
-    auto sketch = update_theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketch.is_empty());
-    CPPUNIT_ASSERT(sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(5324U, sketch.get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketch.get_estimate(), 10000 * 0.01);
-    CPPUNIT_ASSERT(sketch.get_lower_bound(1) < 10000);
-    CPPUNIT_ASSERT(sketch.get_upper_bound(1) > 10000);
-  }
+TEST_CASE("theta sketch: deserialize update estimation from java as base", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_update_estimation_from_java.bin", std::ios::binary);
+  auto sketchptr = theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketchptr->is_empty());
+  REQUIRE(sketchptr->is_estimation_mode());
+  REQUIRE(sketchptr->get_num_retained() == 5324);
+  REQUIRE(sketchptr->get_estimate() == Approx(10000.0).margin(10000 * 0.01));
+  REQUIRE(sketchptr->get_lower_bound(1) < 10000);
+  REQUIRE(sketchptr->get_upper_bound(1) > 10000);
+}
 
-  void deserialize_compact_empty_from_java_as_base() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_empty_from_java.bin", std::ios::binary);
-    auto sketchptr = theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(sketchptr->is_empty());
-    CPPUNIT_ASSERT(!sketchptr->is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: deserialize update estimation from java as subclass", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_update_estimation_from_java.bin", std::ios::binary);
+  auto sketch = update_theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketch.is_empty());
+  REQUIRE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_num_retained() == 5324);
+  REQUIRE(sketch.get_estimate() == Approx(10000.0).margin(10000 * 0.01));
+  REQUIRE(sketch.get_lower_bound(1) < 10000);
+  REQUIRE(sketch.get_upper_bound(1) > 10000);
+}
 
-  void deserialize_compact_empty_from_java_as_subclass() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_empty_from_java.bin", std::ios::binary);
-    auto sketch = compact_theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(sketch.is_empty());
-    CPPUNIT_ASSERT(!sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: deserialize compact empty from java as base", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_empty_from_java.bin", std::ios::binary);
+  auto sketchptr = theta_sketch::deserialize(is);
+  REQUIRE(sketchptr->is_empty());
+  REQUIRE_FALSE(sketchptr->is_estimation_mode());
+  REQUIRE(sketchptr->get_num_retained() == 0);
+  REQUIRE(sketchptr->get_theta() == 1.0);
+  REQUIRE(sketchptr->get_estimate() == 0.0);
+  REQUIRE(sketchptr->get_lower_bound(1) == 0.0);
+  REQUIRE(sketchptr->get_upper_bound(1) == 0.0);
+}
 
-  void deserialize_single_item_from_java_as_base() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_single_item_from_java.bin", std::ios::binary);
-    auto sketchptr = theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketchptr->is_empty());
-    CPPUNIT_ASSERT(!sketchptr->is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1U, sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_estimate());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: deserialize compact empty from java as subclass", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_empty_from_java.bin", std::ios::binary);
+  auto sketch = compact_theta_sketch::deserialize(is);
+  REQUIRE(sketch.is_empty());
+  REQUIRE_FALSE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_num_retained() == 0);
+  REQUIRE(sketch.get_theta() == 1.0);
+  REQUIRE(sketch.get_estimate() == 0.0);
+  REQUIRE(sketch.get_lower_bound(1) == 0.0);
+  REQUIRE(sketch.get_upper_bound(1) == 0.0);
+}
 
-  void deserialize_single_item_from_java_as_subclass() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_single_item_from_java.bin", std::ios::binary);
-    auto sketch = compact_theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketch.is_empty());
-    CPPUNIT_ASSERT(!sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(1U, sketch.get_num_retained());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_estimate());
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_lower_bound(1));
-    CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_upper_bound(1));
-  }
+TEST_CASE("theta sketch: deserialize single item from java as base", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_single_item_from_java.bin", std::ios::binary);
+  auto sketchptr = theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketchptr->is_empty());
+  REQUIRE_FALSE(sketchptr->is_estimation_mode());
+  REQUIRE(sketchptr->get_num_retained() == 1);
+  REQUIRE(sketchptr->get_theta() == 1.0);
+  REQUIRE(sketchptr->get_estimate() == 1.0);
+  REQUIRE(sketchptr->get_lower_bound(1) == 1.0);
+  REQUIRE(sketchptr->get_upper_bound(1) == 1.0);
+}
 
-  void deserialize_compact_estimation_from_java_as_base() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_estimation_from_java.bin", std::ios::binary);
-    auto sketchptr = theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketchptr->is_empty());
-    CPPUNIT_ASSERT(sketchptr->is_estimation_mode());
-    CPPUNIT_ASSERT(sketchptr->is_ordered());
-    CPPUNIT_ASSERT_EQUAL(4342U, sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketchptr->get_theta(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketchptr->get_estimate(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(7996.956955317471, sketchptr->get_lower_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(8339.090301078124, sketchptr->get_upper_bound(2), 1e-10);
-
-    // the same construction process in Java must have produced exactly the same sketch
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    const int n = 8192;
-    for (int i = 0; i < n; i++) update_sketch.update(i);
-    CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketchptr->get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketchptr->get_theta(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketchptr->get_estimate(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(1), sketchptr->get_lower_bound(1), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(1), sketchptr->get_upper_bound(1), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(2), sketchptr->get_lower_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(2), sketchptr->get_upper_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(3), sketchptr->get_lower_bound(3), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(3), sketchptr->get_upper_bound(3), 1e-10);
-    compact_theta_sketch compact_sketch = update_sketch.compact();
-    // the sketches are ordered, so the iteration sequence must match exactly
-    auto iter = sketchptr->begin();
-    for (auto key: compact_sketch) {
-      CPPUNIT_ASSERT_EQUAL(key, *iter);
-      ++iter;
-    }
-  }
+TEST_CASE("theta sketch: deserialize single item from java as subclass", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_single_item_from_java.bin", std::ios::binary);
+  auto sketch = compact_theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketch.is_empty());
+  REQUIRE_FALSE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_num_retained() == 1);
+  REQUIRE(sketch.get_theta() == 1.0);
+  REQUIRE(sketch.get_estimate() == 1.0);
+  REQUIRE(sketch.get_lower_bound(1) == 1.0);
+  REQUIRE(sketch.get_upper_bound(1) == 1.0);
+}
 
-  void deserialize_compact_estimation_from_java_as_subclass() {
-    std::ifstream is;
-    is.exceptions(std::ios::failbit | std::ios::badbit);
-    is.open(inputPath + "theta_compact_estimation_from_java.bin", std::ios::binary);
-    auto sketch = compact_theta_sketch::deserialize(is);
-    CPPUNIT_ASSERT(!sketch.is_empty());
-    CPPUNIT_ASSERT(sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_EQUAL(4342U, sketch.get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketch.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketch.get_estimate(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(7996.956955317471, sketch.get_lower_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(8339.090301078124, sketch.get_upper_bound(2), 1e-10);
-
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    const int n = 8192;
-    for (int i = 0; i < n; i++) update_sketch.update(i);
-    CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketch.get_num_retained());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketch.get_theta(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketch.get_estimate(), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(1), sketch.get_lower_bound(1), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(1), sketch.get_upper_bound(1), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(2), sketch.get_lower_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(2), sketch.get_upper_bound(2), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(3), sketch.get_lower_bound(3), 1e-10);
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(3), sketch.get_upper_bound(3), 1e-10);
+TEST_CASE("theta sketch: deserialize compact estimation from java as base", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_estimation_from_java.bin", std::ios::binary);
+  auto sketchptr = theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketchptr->is_empty());
+  REQUIRE(sketchptr->is_estimation_mode());
+  REQUIRE(sketchptr->is_ordered());
+  REQUIRE(sketchptr->get_num_retained() == 4342);
+  REQUIRE(sketchptr->get_theta() == Approx(0.531700444213199).margin(1e-10));
+  REQUIRE(sketchptr->get_estimate() == Approx(8166.25234614053).margin(1e-10));
+  REQUIRE(sketchptr->get_lower_bound(2) == Approx(7996.956955317471).margin(1e-10));
+  REQUIRE(sketchptr->get_upper_bound(2) == Approx(8339.090301078124).margin(1e-10));
+
+  // the same construction process in Java must have produced exactly the same sketch
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  const int n = 8192;
+  for (int i = 0; i < n; i++) update_sketch.update(i);
+  REQUIRE(sketchptr->get_num_retained() == update_sketch.get_num_retained());
+  REQUIRE(sketchptr->get_theta() == Approx(update_sketch.get_theta()).margin(1e-10));
+  REQUIRE(sketchptr->get_estimate() == Approx(update_sketch.get_estimate()).margin(1e-10));
+  REQUIRE(sketchptr->get_lower_bound(1) == Approx(update_sketch.get_lower_bound(1)).margin(1e-10));
+  REQUIRE(sketchptr->get_upper_bound(1) == Approx(update_sketch.get_upper_bound(1)).margin(1e-10));
+  REQUIRE(sketchptr->get_lower_bound(2) == Approx(update_sketch.get_lower_bound(2)).margin(1e-10));
+  REQUIRE(sketchptr->get_upper_bound(2) == Approx(update_sketch.get_upper_bound(2)).margin(1e-10));
+  REQUIRE(sketchptr->get_lower_bound(3) == Approx(update_sketch.get_lower_bound(3)).margin(1e-10));
+  REQUIRE(sketchptr->get_upper_bound(3) == Approx(update_sketch.get_upper_bound(3)).margin(1e-10));
+  compact_theta_sketch compact_sketch = update_sketch.compact();
+  // the sketches are ordered, so the iteration sequence must match exactly
+  auto iter = sketchptr->begin();
+  for (auto key: compact_sketch) {
+    REQUIRE(*iter == key);
+    ++iter;
   }
+}
 
-  void serialize_deserialize_stream_and_bytes_equivalency() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().build();
-    const int n = 8192;
-    for (int i = 0; i < n; i++) update_sketch.update(i);
+TEST_CASE("theta sketch: deserialize compact estimation from java as subclass", "[theta_sketch]") {
+  std::ifstream is;
+  is.exceptions(std::ios::failbit | std::ios::badbit);
+  is.open(inputPath + "theta_compact_estimation_from_java.bin", std::ios::binary);
+  auto sketch = compact_theta_sketch::deserialize(is);
+  REQUIRE_FALSE(sketch.is_empty());
+  REQUIRE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_num_retained() == 4342);
+  REQUIRE(sketch.get_theta() == Approx(0.531700444213199).margin(1e-10));
+  REQUIRE(sketch.get_estimate() == Approx(8166.25234614053).margin(1e-10));
+  REQUIRE(sketch.get_lower_bound(2) == Approx(7996.956955317471).margin(1e-10));
+  REQUIRE(sketch.get_upper_bound(2) == Approx(8339.090301078124).margin(1e-10));
+
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  const int n = 8192;
+  for (int i = 0; i < n; i++) update_sketch.update(i);
+  REQUIRE(sketch.get_num_retained() == update_sketch.get_num_retained());
+  REQUIRE(sketch.get_theta() == Approx(update_sketch.get_theta()).margin(1e-10));
+  REQUIRE(sketch.get_estimate() == Approx(update_sketch.get_estimate()).margin(1e-10));
+  REQUIRE(sketch.get_lower_bound(1) == Approx(update_sketch.get_lower_bound(1)).margin(1e-10));
+  REQUIRE(sketch.get_upper_bound(1) == Approx(update_sketch.get_upper_bound(1)).margin(1e-10));
+  REQUIRE(sketch.get_lower_bound(2) == Approx(update_sketch.get_lower_bound(2)).margin(1e-10));
+  REQUIRE(sketch.get_upper_bound(2) == Approx(update_sketch.get_upper_bound(2)).margin(1e-10));
+  REQUIRE(sketch.get_lower_bound(3) == Approx(update_sketch.get_lower_bound(3)).margin(1e-10));
+  REQUIRE(sketch.get_upper_bound(3) == Approx(update_sketch.get_upper_bound(3)).margin(1e-10));
+}
+
+TEST_CASE("theta sketch: serialize deserialize stream and bytes equivalency", "[theta_sketch]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
+  const int n = 8192;
+  for (int i = 0; i < n; i++) update_sketch.update(i);
+
+  // update sketch stream and bytes comparison
+  {
+    std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
+    update_sketch.serialize(s);
+    auto bytes = update_sketch.serialize();
+    REQUIRE(bytes.size() == s.tellp());
+    for (size_t i = 0; i < bytes.size(); ++i) {
+      REQUIRE(((char*)bytes.data())[i] == (char)s.get());
+    }
 
-    // update sketch stream and bytes comparison
+    // deserialize as base class
     {
-      std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
-      update_sketch.serialize(s);
-      auto bytes = update_sketch.serialize();
-      CPPUNIT_ASSERT_EQUAL((size_t) s.tellp(), bytes.size());
-      for (size_t i = 0; i < bytes.size(); ++i) {
-        CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)bytes.data())[i]);
+      s.seekg(0); // rewind
+      auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s);
+      auto deserialized_sketch_ptr2 = theta_sketch::deserialize(bytes.data(), bytes.size());
+      REQUIRE(s.tellg() == bytes.size());
+      REQUIRE(deserialized_sketch_ptr2->is_empty() == deserialized_sketch_ptr1->is_empty());
+      REQUIRE(deserialized_sketch_ptr2->is_ordered() == deserialized_sketch_ptr1->is_ordered());
+      REQUIRE(deserialized_sketch_ptr2->get_num_retained() == deserialized_sketch_ptr1->get_num_retained());
+      REQUIRE(deserialized_sketch_ptr2->get_theta() == deserialized_sketch_ptr1->get_theta());
+      REQUIRE(deserialized_sketch_ptr2->get_estimate() == deserialized_sketch_ptr1->get_estimate());
+      REQUIRE(deserialized_sketch_ptr2->get_lower_bound(1) == deserialized_sketch_ptr1->get_lower_bound(1));
+      REQUIRE(deserialized_sketch_ptr2->get_upper_bound(1) == deserialized_sketch_ptr1->get_upper_bound(1));
+      // hash tables must be identical since they are restored from dumps, and iteration is deterministic
+      auto iter = deserialized_sketch_ptr1->begin();
+      for (auto key: *deserialized_sketch_ptr2) {
+        REQUIRE(*iter == key);
+        ++iter;
       }
+    }
 
-      // deserialize as base class
-      {
-        s.seekg(0); // rewind
-        auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s);
-        auto deserialized_sketch_ptr2 = theta_sketch::deserialize(bytes.data(), bytes.size());
-        CPPUNIT_ASSERT_EQUAL(bytes.size(), (size_t) s.tellg());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_lower_bound(1), deserialized_sketch_ptr2->get_lower_bound(1));
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_upper_bound(1), deserialized_sketch_ptr2->get_upper_bound(1));
-        // hash tables must be identical since they are restored from dumps, and iteration is deterministic
-        auto iter = deserialized_sketch_ptr1->begin();
-        for (auto key: *deserialized_sketch_ptr2) {
-          CPPUNIT_ASSERT_EQUAL(key, *iter);
-          ++iter;
-        }
+    // deserialize as subclass
+    {
+      s.seekg(0); // rewind
+      update_theta_sketch deserialized_sketch1 = update_theta_sketch::deserialize(s);
+      update_theta_sketch deserialized_sketch2 = update_theta_sketch::deserialize(bytes.data(), bytes.size());
+      REQUIRE(s.tellg() == bytes.size());
+      REQUIRE(deserialized_sketch2.is_empty() == deserialized_sketch1.is_empty());
+      REQUIRE(deserialized_sketch2.is_ordered() == deserialized_sketch1.is_ordered());
+      REQUIRE(deserialized_sketch2.get_num_retained() == deserialized_sketch1.get_num_retained());
+      REQUIRE(deserialized_sketch2.get_theta() == deserialized_sketch1.get_theta());
+      REQUIRE(deserialized_sketch2.get_estimate() == deserialized_sketch1.get_estimate());
+      REQUIRE(deserialized_sketch2.get_lower_bound(1) == deserialized_sketch1.get_lower_bound(1));
+      REQUIRE(deserialized_sketch2.get_upper_bound(1) == deserialized_sketch1.get_upper_bound(1));
+      // hash tables must be identical since they are restored from dumps, and iteration is deterministic
+      auto iter = deserialized_sketch1.begin();
+      for (auto key: deserialized_sketch2) {
+        REQUIRE(*iter == key);
+        ++iter;
       }
+    }
+  }
 
-      // deserialize as subclass
-      {
-        s.seekg(0); // rewind
-        update_theta_sketch deserialized_sketch1 = update_theta_sketch::deserialize(s);
-        update_theta_sketch deserialized_sketch2 = update_theta_sketch::deserialize(bytes.data(), bytes.size());
-        CPPUNIT_ASSERT_EQUAL(bytes.size(), (size_t) s.tellg());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_lower_bound(1), deserialized_sketch2.get_lower_bound(1));
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_upper_bound(1), deserialized_sketch2.get_upper_bound(1));
-        // hash tables must be identical since they are restored from dumps, and iteration is deterministic
-        auto iter = deserialized_sketch1.begin();
-        for (auto key: deserialized_sketch2) {
-          CPPUNIT_ASSERT_EQUAL(key, *iter);
-          ++iter;
-        }
-      }
+  // compact sketch stream and bytes comparison
+  {
+    std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
+    update_sketch.compact().serialize(s);
+    auto bytes = update_sketch.compact().serialize();
+    REQUIRE(bytes.size() == s.tellp());
+    for (size_t i = 0; i < bytes.size(); ++i) {
+      REQUIRE(((char*)bytes.data())[i] == (char)s.get());
     }
 
-    // compact sketch stream and bytes comparison
+    // deserialize as base class
     {
-      std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
-      update_sketch.compact().serialize(s);
-      auto bytes = update_sketch.compact().serialize();
-      CPPUNIT_ASSERT_EQUAL((size_t) s.tellp(), bytes.size());
-      for (size_t i = 0; i < bytes.size(); ++i) {
-        CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)bytes.data())[i]);
-      }
-
-      // deserialize as base class
-      {
-        s.seekg(0); // rewind
-        auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s);
-        auto deserialized_sketch_ptr2 = theta_sketch::deserialize(bytes.data(), bytes.size());
-        CPPUNIT_ASSERT_EQUAL(bytes.size(), (size_t) s.tellg());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_lower_bound(1), deserialized_sketch_ptr2->get_lower_bound(1));
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_upper_bound(1), deserialized_sketch_ptr2->get_upper_bound(1));
-        // the sketches are ordered, so the iteration sequence must match exactly
-        auto iter = deserialized_sketch_ptr1->begin();
-        for (auto key: *deserialized_sketch_ptr2) {
-          CPPUNIT_ASSERT_EQUAL(key, *iter);
-          ++iter;
-        }
+      s.seekg(0); // rewind
+      auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s);
+      auto deserialized_sketch_ptr2 = theta_sketch::deserialize(bytes.data(), bytes.size());
+      REQUIRE(s.tellg() == bytes.size());
+      REQUIRE(deserialized_sketch_ptr2->is_empty() == deserialized_sketch_ptr1->is_empty());
+      REQUIRE(deserialized_sketch_ptr2->is_ordered() == deserialized_sketch_ptr1->is_ordered());
+      REQUIRE(deserialized_sketch_ptr2->get_num_retained() == deserialized_sketch_ptr1->get_num_retained());
+      REQUIRE(deserialized_sketch_ptr2->get_theta() == deserialized_sketch_ptr1->get_theta());
+      REQUIRE(deserialized_sketch_ptr2->get_estimate() == deserialized_sketch_ptr1->get_estimate());
+      REQUIRE(deserialized_sketch_ptr2->get_lower_bound(1) == deserialized_sketch_ptr1->get_lower_bound(1));
+      REQUIRE(deserialized_sketch_ptr2->get_upper_bound(1) == deserialized_sketch_ptr1->get_upper_bound(1));
+      // the sketches are ordered, so the iteration sequence must match exactly
+      auto iter = deserialized_sketch_ptr1->begin();
+      for (auto key: *deserialized_sketch_ptr2) {
+        REQUIRE(*iter == key);
+        ++iter;
       }
+    }
 
-      // deserialize as subclass
-      {
-        s.seekg(0); // rewind
-        compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s);
-        compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(bytes.data(), bytes.size());
-        CPPUNIT_ASSERT_EQUAL(bytes.size(), (size_t) s.tellg());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate());
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_lower_bound(1), deserialized_sketch2.get_lower_bound(1));
-        CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_upper_bound(1), deserialized_sketch2.get_upper_bound(1));
-        // the sketches are ordered, so the iteration sequence must match exactly
-        auto iter = deserialized_sketch1.begin();
-        for (auto key: deserialized_sketch2) {
-          CPPUNIT_ASSERT_EQUAL(key, *iter);
-          ++iter;
-        }
+    // deserialize as subclass
+    {
+      s.seekg(0); // rewind
+      compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s);
+      compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(bytes.data(), bytes.size());
+      REQUIRE(s.tellg() == bytes.size());
+      REQUIRE(deserialized_sketch2.is_empty() == deserialized_sketch1.is_empty());
+      REQUIRE(deserialized_sketch2.is_ordered() == deserialized_sketch1.is_ordered());
+      REQUIRE(deserialized_sketch2.get_num_retained() == deserialized_sketch1.get_num_retained());
+      REQUIRE(deserialized_sketch2.get_theta() == deserialized_sketch1.get_theta());
+      REQUIRE(deserialized_sketch2.get_estimate() == deserialized_sketch1.get_estimate());
+      REQUIRE(deserialized_sketch2.get_lower_bound(1) == deserialized_sketch1.get_lower_bound(1));
+      REQUIRE(deserialized_sketch2.get_upper_bound(1) == deserialized_sketch1.get_upper_bound(1));
+      // the sketches are ordered, so the iteration sequence must match exactly
+      auto iter = deserialized_sketch1.begin();
+      for (auto key: deserialized_sketch2) {
+        REQUIRE(*iter == key);
+        ++iter;
       }
     }
   }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(theta_sketch_test);
-
-#ifdef TEST_BINARY_INPUT_PATH
-const std::string theta_sketch_test::inputPath = TEST_BINARY_INPUT_PATH;
-#else
-const std::string theta_sketch_test::inputPath = "test/";
-#endif
-
+}
 
 } /* namespace datasketches */
diff --git a/theta/test/theta_union_test.cpp b/theta/test/theta_union_test.cpp
index 48d6336..17db018 100644
--- a/theta/test/theta_union_test.cpp
+++ b/theta/test/theta_union_test.cpp
@@ -17,96 +17,81 @@
  * under the License.
  */
 
-#include <cppunit/TestFixture.h>
-#include <cppunit/extensions/HelperMacros.h>
+#include <catch.hpp>
 
 #include <theta_union.hpp>
 
 namespace datasketches {
 
-class theta_union_test: public CppUnit::TestFixture {
-
-  CPPUNIT_TEST_SUITE(theta_union_test);
-  CPPUNIT_TEST(empty);
-  CPPUNIT_TEST(non_empty_no_retained_keys);
-  CPPUNIT_TEST(exact_mode_half_overlap);
-  CPPUNIT_TEST(estimation_mode_half_overlap);
-  CPPUNIT_TEST(seed_mismatch);
-  CPPUNIT_TEST_SUITE_END();
-
-  void empty() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    theta_union u = theta_union::builder().build();
-    compact_theta_sketch sketch2 = u.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, sketch2.get_num_retained());
-    CPPUNIT_ASSERT(sketch2.is_empty());
-    CPPUNIT_ASSERT(!sketch2.is_estimation_mode());
-
-    u.update(sketch1);
-    sketch2 = u.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, sketch2.get_num_retained());
-    CPPUNIT_ASSERT(sketch2.is_empty());
-    CPPUNIT_ASSERT(!sketch2.is_estimation_mode());
-  }
-
-  void non_empty_no_retained_keys() {
-    update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build();
-    update_sketch.update(1);
-    theta_union u = theta_union::builder().build();
-    u.update(update_sketch);
-    compact_theta_sketch sketch = u.get_result();
-    CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained());
-    CPPUNIT_ASSERT(!sketch.is_empty());
-    CPPUNIT_ASSERT(sketch.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, sketch.get_theta(), 1e-10);
-  }
-
-  void exact_mode_half_overlap() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 1000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 500;
-    for (int i = 0; i < 1000; i++) sketch2.update(value++);
-
-    theta_union u = theta_union::builder().build();
-    u.update(sketch1);
-    u.update(sketch2);
-    compact_theta_sketch sketch3 = u.get_result();
-    CPPUNIT_ASSERT(!sketch3.is_empty());
-    CPPUNIT_ASSERT(!sketch3.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(1500, sketch3.get_estimate(), 1500 * 0.01);
-  }
-
-  void estimation_mode_half_overlap() {
-    update_theta_sketch sketch1 = update_theta_sketch::builder().build();
-    int value = 0;
-    for (int i = 0; i < 10000; i++) sketch1.update(value++);
-
-    update_theta_sketch sketch2 = update_theta_sketch::builder().build();
-    value = 5000;
-    for (int i = 0; i < 10000; i++) sketch2.update(value++);
-
-    theta_union u = theta_union::builder().build();
-    u.update(sketch1);
-    u.update(sketch2);
-    compact_theta_sketch sketch3 = u.get_result();
-    CPPUNIT_ASSERT(!sketch3.is_empty());
-    CPPUNIT_ASSERT(sketch3.is_estimation_mode());
-    CPPUNIT_ASSERT_DOUBLES_EQUAL(15000, sketch3.get_estimate(), 15000 * 0.01);
-    //sketch3.to_stream(std::cerr, true);
-  }
-
-  void seed_mismatch() {
-    update_theta_sketch sketch = update_theta_sketch::builder().build();
-    sketch.update(1); // non-empty should not be ignored
-    theta_union u = theta_union::builder().set_seed(123).build();
-    CPPUNIT_ASSERT_THROW(u.update(sketch), std::invalid_argument);
-  }
-
-};
-
-CPPUNIT_TEST_SUITE_REGISTRATION(theta_union_test);
+TEST_CASE("theta union: empty", "[theta_union]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  theta_union u = theta_union::builder().build();
+  compact_theta_sketch sketch2 = u.get_result();
+  REQUIRE(sketch2.get_num_retained() == 0);
+  REQUIRE(sketch2.is_empty());
+  REQUIRE_FALSE(sketch2.is_estimation_mode());
+
+  u.update(sketch1);
+  sketch2 = u.get_result();
+  REQUIRE(sketch2.get_num_retained() == 0);
+  REQUIRE(sketch2.is_empty());
+  REQUIRE_FALSE(sketch2.is_estimation_mode());
+}
+
+TEST_CASE("theta union: non empty no retained keys", "[theta_union]") {
+  update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build();
+  update_sketch.update(1);
+  theta_union u = theta_union::builder().build();
+  u.update(update_sketch);
+  compact_theta_sketch sketch = u.get_result();
+  REQUIRE(sketch.get_num_retained() == 0);
+  REQUIRE_FALSE(sketch.is_empty());
+  REQUIRE(sketch.is_estimation_mode());
+  REQUIRE(sketch.get_theta() == Approx(0.001).margin(1e-10));
+}
+
+TEST_CASE("theta union: exact mode half overlap", "[theta_union]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 1000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 500;
+  for (int i = 0; i < 1000; i++) sketch2.update(value++);
+
+  theta_union u = theta_union::builder().build();
+  u.update(sketch1);
+  u.update(sketch2);
+  compact_theta_sketch sketch3 = u.get_result();
+  REQUIRE_FALSE(sketch3.is_empty());
+  REQUIRE_FALSE(sketch3.is_estimation_mode());
+  REQUIRE(sketch3.get_estimate() == Approx(1500).margin(1500 * 0.01));
+}
+
+TEST_CASE("theta union: estimation mode half overlap", "[theta_union]") {
+  update_theta_sketch sketch1 = update_theta_sketch::builder().build();
+  int value = 0;
+  for (int i = 0; i < 10000; i++) sketch1.update(value++);
+
+  update_theta_sketch sketch2 = update_theta_sketch::builder().build();
+  value = 5000;
+  for (int i = 0; i < 10000; i++) sketch2.update(value++);
+
+  theta_union u = theta_union::builder().build();
+  u.update(sketch1);
+  u.update(sketch2);
+  compact_theta_sketch sketch3 = u.get_result();
+  REQUIRE_FALSE(sketch3.is_empty());
+  REQUIRE(sketch3.is_estimation_mode());
+  REQUIRE(sketch3.get_estimate() == Approx(15000).margin(15000 * 0.01));
+  //sketch3.to_stream(std::cerr, true);
+}
+
+TEST_CASE("theta union: seed mismatch", "[theta_union]") {
+  update_theta_sketch sketch = update_theta_sketch::builder().build();
+  sketch.update(1); // non-empty should not be ignored
+  theta_union u = theta_union::builder().set_seed(123).build();
+  REQUIRE_THROWS_AS(u.update(sketch), std::invalid_argument);
+}
 
 } /* namespace datasketches */


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org