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

tisonkun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new b941327  test: add count-min C++ serialization compatibility tests 
(#92)
b941327 is described below

commit b94132742497c32fdec5316cec60bf8f279a81aa
Author: Chojan Shang <[email protected]>
AuthorDate: Thu Jun 25 11:27:51 2026 +0800

    test: add count-min C++ serialization compatibility tests (#92)
    
    * Add Count-Min C++ serialization compatibility tests
    
    * test: use count-min C++ binary snapshots
---
 datasketches/tests/countmin_serialization_test.rs | 71 +++++++++++++++++++++++
 datasketches/tests/countmin_test.rs               | 35 ++++++-----
 tools/generate_serialization_test_data.py         | 20 ++++---
 3 files changed, 103 insertions(+), 23 deletions(-)

diff --git a/datasketches/tests/countmin_serialization_test.rs 
b/datasketches/tests/countmin_serialization_test.rs
new file mode 100644
index 0000000..4492a55
--- /dev/null
+++ b/datasketches/tests/countmin_serialization_test.rs
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![cfg(feature = "countmin")]
+
+mod common;
+
+use std::fs;
+
+use common::serialization_test_data;
+use datasketches::countmin::CountMinSketch;
+use googletest::assert_that;
+use googletest::prelude::contains_substring;
+
+// This test validates binary format compatibility (deserialize + byte 
round-trip) for
+// C++ Count-Min snapshots. It intentionally does not assert estimate 
equivalence against
+// original input keys because per-row hash seed derivation differs across 
implementations.
+fn assert_cpp_snapshot(
+    filename: &str,
+    seed: u64,
+    expected_num_hashes: u8,
+    expected_num_buckets: u32,
+    expected_total_weight: u64,
+) {
+    let path = serialization_test_data("cpp_generated_files", filename);
+    let bytes = fs::read(&path).unwrap();
+
+    let sketch = CountMinSketch::<u64>::deserialize_with_seed(&bytes, 
seed).unwrap();
+
+    assert_eq!(sketch.num_hashes(), expected_num_hashes);
+    assert_eq!(sketch.num_buckets(), expected_num_buckets);
+    assert_eq!(sketch.seed(), seed);
+    assert_eq!(sketch.total_weight(), expected_total_weight);
+    assert_eq!(sketch.is_empty(), expected_total_weight == 0);
+
+    let roundtrip = sketch.serialize();
+    assert_eq!(roundtrip, bytes, "round-trip bytes differ for {filename}");
+}
+
+#[test]
+fn test_deserialize_cpp_empty_snapshot() {
+    assert_cpp_snapshot("count_min-empty.bin", 9001, 1, 5, 0);
+}
+
+#[test]
+fn test_deserialize_cpp_non_empty_snapshot() {
+    assert_cpp_snapshot("count_min-non-empty.bin", 9001, 3, 1024, 2850);
+}
+
+#[test]
+fn test_deserialize_cpp_snapshot_with_wrong_seed() {
+    let path = serialization_test_data("cpp_generated_files", 
"count_min-non-empty.bin");
+    let bytes = fs::read(&path).unwrap();
+
+    let err = CountMinSketch::<u64>::deserialize_with_seed(&bytes, 
9000).unwrap_err();
+    assert_that!(err.message(), contains_substring("incompatible seed hash"));
+}
diff --git a/datasketches/tests/countmin_test.rs 
b/datasketches/tests/countmin_test.rs
index a8681e4..fe5419b 100644
--- a/datasketches/tests/countmin_test.rs
+++ b/datasketches/tests/countmin_test.rs
@@ -18,6 +18,9 @@
 #![cfg(feature = "countmin")]
 
 use datasketches::countmin::CountMinSketch;
+use googletest::assert_that;
+use googletest::prelude::ge;
+use googletest::prelude::le;
 
 #[test]
 fn test_init_defaults() {
@@ -43,7 +46,7 @@ fn test_parameter_suggestions() {
 
     let buckets = CountMinSketch::<i64>::suggest_num_buckets(0.1);
     let sketch = CountMinSketch::<i64>::new(3, buckets);
-    assert!(sketch.relative_error() <= 0.1);
+    assert_that!(sketch.relative_error(), le(0.1));
 }
 
 #[test]
@@ -56,8 +59,8 @@ fn test_update_and_bounds() {
     let estimate = sketch.estimate("x");
     let upper = sketch.upper_bound("x");
     let lower = sketch.lower_bound("x");
-    assert!(lower <= estimate);
-    assert!(estimate <= upper);
+    assert_that!(estimate, ge(lower));
+    assert_that!(estimate, le(upper));
 }
 
 #[test]
@@ -69,8 +72,8 @@ fn test_update_and_bounds_with_scaling() {
     let upper = sketch.upper_bound("x");
     let lower = sketch.lower_bound("x");
     assert_eq!(estimate, 10);
-    assert!(lower <= estimate);
-    assert!(estimate <= upper);
+    assert_that!(estimate, ge(lower));
+    assert_that!(estimate, le(upper));
 
     let eps = sketch.relative_error();
 
@@ -80,8 +83,8 @@ fn test_update_and_bounds_with_scaling() {
     let lower = sketch.lower_bound("x");
     assert_eq!(sketch.total_weight(), 5);
     assert_eq!(estimate, 5);
-    assert!(lower <= estimate);
-    assert!(estimate <= upper);
+    assert_that!(estimate, ge(lower));
+    assert_that!(estimate, le(upper));
     assert_eq!(
         upper,
         estimate + (eps * sketch.total_weight() as f64) as u64
@@ -93,8 +96,8 @@ fn test_update_and_bounds_with_scaling() {
     let lower = sketch.lower_bound("x");
     assert_eq!(sketch.total_weight(), 2);
     assert_eq!(estimate, 2);
-    assert!(lower <= estimate);
-    assert!(estimate <= upper);
+    assert_that!(estimate, ge(lower));
+    assert_that!(estimate, le(upper));
     assert_eq!(
         upper,
         estimate + (eps * sketch.total_weight() as f64) as u64
@@ -124,13 +127,13 @@ fn test_halve() {
     }
 
     for i in 0..1000usize {
-        assert!(sketch.estimate(i as u64) >= i as u64);
+        assert_that!(sketch.estimate(i as u64), ge(i as u64));
     }
 
     sketch.halve();
 
     for i in 0..1000usize {
-        assert!(sketch.estimate(i as u64) >= (i as u64) / 2);
+        assert_that!(sketch.estimate(i as u64), ge((i as u64) / 2));
     }
 }
 
@@ -147,7 +150,7 @@ fn test_decay() {
     }
 
     for i in 0..1000usize {
-        assert!(sketch.estimate(i as u64) >= i as u64);
+        assert_that!(sketch.estimate(i as u64), ge(i as u64));
     }
 
     const FACTOR: f64 = 0.5;
@@ -155,7 +158,7 @@ fn test_decay() {
 
     for i in 0..1000usize {
         let expected = ((i as f64) * FACTOR).floor() as u64;
-        assert!(sketch.estimate(i as u64) >= expected);
+        assert_that!(sketch.estimate(i as u64), ge(expected));
     }
 }
 
@@ -172,8 +175,8 @@ fn test_merge() {
     }
     left.merge(&right);
     assert_eq!(left.total_weight(), 18);
-    assert!(left.estimate("a") >= 14);
-    assert!(left.estimate("b") >= 4);
+    assert_that!(left.estimate("a"), ge(14));
+    assert_that!(left.estimate("b"), ge(4));
 }
 
 #[test]
@@ -247,6 +250,6 @@ fn test_increment_multi_like_rust_count_min_sketch() {
         sketch.update(i % 100);
     }
     for key in 0..100u64 {
-        assert!(sketch.estimate(key) >= 9_000);
+        assert_that!(sketch.estimate(key), ge(9_000));
     }
 }
diff --git a/tools/generate_serialization_test_data.py 
b/tools/generate_serialization_test_data.py
index c965f39..28314b1 100755
--- a/tools/generate_serialization_test_data.py
+++ b/tools/generate_serialization_test_data.py
@@ -134,6 +134,10 @@ def generate_cpp_files(workspace_dir, project_root):
     # 4. Clone repository
     repo_url = "https://github.com/apache/datasketches-cpp.git";
     branch = "master"
+    # Temporary e2e checkout for apache/datasketches-cpp#505. After that PR is
+    # merged, pin this to the merged master commit and remove the extra fetch.
+    commit = "af4436280bdab53e0063268e92ff29b3fdcb1b07"
+    fetch_ref = "refs/pull/505/head"
     run_command([
         "git", "clone",
         "--depth", "1",
@@ -142,6 +146,8 @@ def generate_cpp_files(workspace_dir, project_root):
         repo_url,
         str(temp_dir)
     ])
+    run_command(["git", "fetch", "--depth", "1", "origin", fetch_ref], 
cwd=temp_dir)
+    run_command(["git", "checkout", "--detach", commit], cwd=temp_dir)
 
     # 5. Build and Run CMake
     build_dir = temp_dir / "build"
@@ -166,15 +172,15 @@ def generate_cpp_files(workspace_dir, project_root):
     output_dir.mkdir(parents=True, exist_ok=True)
 
     files_copied = 0
-    # Search recursively in build directory for *_cpp.sk
-    for file_path in build_dir.rglob("*_cpp.sk"):
-        # Avoid copying from CMakeFiles or other intermediate dirs if 
possible, but the pattern is specific enough
-        shutil.copy2(file_path, output_dir)
-        print(f"Copied: {file_path.name}")
-        files_copied += 1
+
+    for pattern in ("*_cpp.sk", "count_min-*.bin"):
+        for file_path in build_dir.rglob(pattern):
+            shutil.copy2(file_path, output_dir)
+            print(f"Copied: {file_path.name}")
+            files_copied += 1
 
     if files_copied == 0:
-        print("Warning: No *_cpp.sk files were found to copy.")
+        print("Warning: No C++ serialization snapshots were found to copy.")
     else:
         print(f"Successfully copied {files_copied} files.")
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to