From d1ec6b97a5a3bb8e4c7fe816d49190ae353c9a4b Mon Sep 17 00:00:00 2001 From: jaideeppyne Date: Thu, 1 Oct 2026 12:16:10 +0530 Subject: [PATCH 1/2] test(theta): verify Java binary parity --- ...heta_sketch_deserialize_from_java_test.cpp | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/theta/test/theta_sketch_deserialize_from_java_test.cpp b/theta/test/theta_sketch_deserialize_from_java_test.cpp index 691320a4..8bc99931 100644 --- a/theta/test/theta_sketch_deserialize_from_java_test.cpp +++ b/theta/test/theta_sketch_deserialize_from_java_test.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace datasketches { @@ -27,13 +28,26 @@ namespace datasketches { // in the subdirectory called "java" in the root directory of this project static std::string testBinaryInputPath = std::string(TEST_BINARY_INPUT_PATH) + "../../java/"; +static std::vector read_java_bytes(const std::string& file_name) { + std::ifstream is(testBinaryInputPath + file_name, std::ios::binary | std::ios::ate); + is.exceptions(std::ios::failbit | std::ios::badbit); + const auto size = is.tellg(); + std::vector bytes(static_cast(size)); + is.seekg(0); + is.read(reinterpret_cast(bytes.data()), size); + return bytes; +} + +template +static std::vector to_std_vector(const Vector& bytes) { + return std::vector(bytes.begin(), bytes.end()); +} + TEST_CASE("theta sketch", "[serde_compat]") { const unsigned n_arr[] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000}; for (const unsigned n: n_arr) { - std::ifstream is; - is.exceptions(std::ios::failbit | std::ios::badbit); - is.open(testBinaryInputPath + "theta_n" + std::to_string(n) + "_java.sk", std::ios::binary); - const auto sketch = compact_theta_sketch::deserialize(is); + const auto bytes = read_java_bytes("theta_n" + std::to_string(n) + "_java.sk"); + const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); REQUIRE(sketch.is_empty() == (n == 0)); REQUIRE(sketch.is_estimation_mode() == (n > 1000)); REQUIRE(sketch.get_estimate() == Approx(n).margin(n * 0.03)); @@ -42,16 +56,15 @@ TEST_CASE("theta sketch", "[serde_compat]") { } REQUIRE(sketch.is_ordered()); REQUIRE(std::is_sorted(sketch.begin(), sketch.end())); + REQUIRE(to_std_vector(sketch.serialize()) == bytes); } } TEST_CASE("theta sketch compressed", "[serde_compat]") { const unsigned n_arr[] = {10, 100, 1000, 10000, 100000, 1000000}; for (const unsigned n: n_arr) { - std::ifstream is; - is.exceptions(std::ios::failbit | std::ios::badbit); - is.open(testBinaryInputPath + "theta_compressed_n" + std::to_string(n) + "_java.sk", std::ios::binary); - const auto sketch = compact_theta_sketch::deserialize(is); + const auto bytes = read_java_bytes("theta_compressed_n" + std::to_string(n) + "_java.sk"); + const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); REQUIRE(sketch.is_estimation_mode() == (n > 1000)); REQUIRE(sketch.get_estimate() == Approx(n).margin(n * 0.03)); for (const auto hash: sketch) { @@ -59,16 +72,16 @@ TEST_CASE("theta sketch compressed", "[serde_compat]") { } REQUIRE(sketch.is_ordered()); REQUIRE(std::is_sorted(sketch.begin(), sketch.end())); + REQUIRE(to_std_vector(sketch.serialize_compressed()) == bytes); } } TEST_CASE("theta sketch non-empty no entries", "[serde_compat]") { - std::ifstream is; - is.exceptions(std::ios::failbit | std::ios::badbit); - is.open(testBinaryInputPath + "theta_non_empty_no_entries_java.sk", std::ios::binary); - const auto sketch = compact_theta_sketch::deserialize(is); + const auto bytes = read_java_bytes("theta_non_empty_no_entries_java.sk"); + const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); REQUIRE_FALSE(sketch.is_empty()); REQUIRE(sketch.get_num_retained() == 0); + REQUIRE(to_std_vector(sketch.serialize()) == bytes); } } /* namespace datasketches */ From 641c3f4571b618df028301a62ebe7f98113073dc Mon Sep 17 00:00:00 2001 From: jaideeppyne Date: Sat, 3 Oct 2026 10:13:53 +0530 Subject: [PATCH 2/2] test(theta): retain stream compatibility coverage --- ...heta_sketch_deserialize_from_java_test.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/theta/test/theta_sketch_deserialize_from_java_test.cpp b/theta/test/theta_sketch_deserialize_from_java_test.cpp index 8bc99931..a7e4112c 100644 --- a/theta/test/theta_sketch_deserialize_from_java_test.cpp +++ b/theta/test/theta_sketch_deserialize_from_java_test.cpp @@ -38,6 +38,12 @@ static std::vector read_java_bytes(const std::string& file_name) { return bytes; } +static compact_theta_sketch deserialize_java_stream(const std::string& file_name) { + std::ifstream is(testBinaryInputPath + file_name, std::ios::binary); + is.exceptions(std::ios::failbit | std::ios::badbit); + return compact_theta_sketch::deserialize(is); +} + template static std::vector to_std_vector(const Vector& bytes) { return std::vector(bytes.begin(), bytes.end()); @@ -46,8 +52,10 @@ static std::vector to_std_vector(const Vector& bytes) { TEST_CASE("theta sketch", "[serde_compat]") { const unsigned n_arr[] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000}; for (const unsigned n: n_arr) { - const auto bytes = read_java_bytes("theta_n" + std::to_string(n) + "_java.sk"); + const auto file_name = "theta_n" + std::to_string(n) + "_java.sk"; + const auto bytes = read_java_bytes(file_name); const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); + const auto stream_sketch = deserialize_java_stream(file_name); REQUIRE(sketch.is_empty() == (n == 0)); REQUIRE(sketch.is_estimation_mode() == (n > 1000)); REQUIRE(sketch.get_estimate() == Approx(n).margin(n * 0.03)); @@ -57,14 +65,17 @@ TEST_CASE("theta sketch", "[serde_compat]") { REQUIRE(sketch.is_ordered()); REQUIRE(std::is_sorted(sketch.begin(), sketch.end())); REQUIRE(to_std_vector(sketch.serialize()) == bytes); + REQUIRE(to_std_vector(stream_sketch.serialize()) == bytes); } } TEST_CASE("theta sketch compressed", "[serde_compat]") { const unsigned n_arr[] = {10, 100, 1000, 10000, 100000, 1000000}; for (const unsigned n: n_arr) { - const auto bytes = read_java_bytes("theta_compressed_n" + std::to_string(n) + "_java.sk"); + const auto file_name = "theta_compressed_n" + std::to_string(n) + "_java.sk"; + const auto bytes = read_java_bytes(file_name); const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); + const auto stream_sketch = deserialize_java_stream(file_name); REQUIRE(sketch.is_estimation_mode() == (n > 1000)); REQUIRE(sketch.get_estimate() == Approx(n).margin(n * 0.03)); for (const auto hash: sketch) { @@ -73,15 +84,19 @@ TEST_CASE("theta sketch compressed", "[serde_compat]") { REQUIRE(sketch.is_ordered()); REQUIRE(std::is_sorted(sketch.begin(), sketch.end())); REQUIRE(to_std_vector(sketch.serialize_compressed()) == bytes); + REQUIRE(to_std_vector(stream_sketch.serialize_compressed()) == bytes); } } TEST_CASE("theta sketch non-empty no entries", "[serde_compat]") { - const auto bytes = read_java_bytes("theta_non_empty_no_entries_java.sk"); + const std::string file_name = "theta_non_empty_no_entries_java.sk"; + const auto bytes = read_java_bytes(file_name); const auto sketch = compact_theta_sketch::deserialize(bytes.data(), bytes.size()); + const auto stream_sketch = deserialize_java_stream(file_name); REQUIRE_FALSE(sketch.is_empty()); REQUIRE(sketch.get_num_retained() == 0); REQUIRE(to_std_vector(sketch.serialize()) == bytes); + REQUIRE(to_std_vector(stream_sketch.serialize()) == bytes); } } /* namespace datasketches */