blob: 360f07f21f295c58b9b3b26aadfb92d27fdd7235 [file]
/*
* 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.
*/
#include <catch2/catch.hpp>
#include "hll.hpp"
namespace datasketches {
// preamble byte 0 is preInts, which identifies the current mode
static const uint8_t LIST_PREINTS = 2;
static const uint8_t HLL_PREINTS = 10;
// preamble byte 5 is the flags byte; bit 32 is reserved and must never be written
static const size_t FLAGS_BYTE = 5;
static const uint8_t RESERVED_BIT_32 = 32;
static uint8_t preints_of(const hll_sketch& sk) {
return sk.serialize_compact()[0];
}
static hll_sketch make(uint8_t lg_k, target_hll_type type, bool full_size, uint64_t n) {
hll_sketch sk(lg_k, type, full_size);
for (uint64_t i = 0; i < n; ++i) sk.update(i);
return sk;
}
TEST_CASE("hll full size: reserved bit 32 is never written", "[hll_full_size]") {
for (auto type: {HLL_4, HLL_6, HLL_8}) {
for (bool full_size: {false, true}) {
for (uint64_t n: {uint64_t(0), uint64_t(5), uint64_t(5000)}) {
const hll_sketch sk = make(8, type, full_size, n);
REQUIRE((sk.serialize_compact()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
REQUIRE((sk.serialize_updatable()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
}
}
}
}
TEST_CASE("hll full size: start_full_size starts in HLL mode", "[hll_full_size]") {
REQUIRE(preints_of(make(8, HLL_8, true, 0)) == HLL_PREINTS);
REQUIRE(preints_of(make(8, HLL_8, false, 0)) == LIST_PREINTS);
}
TEST_CASE("hll full size: reset takes the mode as an argument", "[hll_full_size]") {
for (auto type: {HLL_4, HLL_6, HLL_8}) {
// full size is not remembered: a plain reset() returns to coupon collection mode
hll_sketch sk = make(8, type, true, 5000);
sk.reset();
REQUIRE(sk.is_empty());
REQUIRE(sk.get_estimate() == 0.0);
REQUIRE(preints_of(sk) == LIST_PREINTS);
// ...and must be asked for explicitly
hll_sketch sk2 = make(8, type, false, 5000);
sk2.reset(true);
REQUIRE(sk2.is_empty());
REQUIRE(sk2.get_estimate() == 0.0);
REQUIRE(preints_of(sk2) == HLL_PREINTS);
REQUIRE(sk2.get_lg_config_k() == 8);
REQUIRE(sk2.get_target_type() == type);
// reset(false) is the same as reset()
hll_sketch sk3 = make(8, type, true, 5000);
sk3.reset(false);
REQUIRE(preints_of(sk3) == LIST_PREINTS);
}
}
TEST_CASE("hll full size: a reset sketch is usable again", "[hll_full_size]") {
hll_sketch sk = make(8, HLL_8, false, 100);
sk.reset(true);
for (uint64_t i = 0; i < 1000; ++i) sk.update(i);
REQUIRE_FALSE(sk.is_empty());
REQUIRE(sk.get_estimate() == Approx(1000).epsilon(0.2));
}
TEST_CASE("hll full size: an image with the reserved bit set is read as if it were clear",
"[hll_full_size]") {
// an image produced by another implementation may have bit 32 set: datasketches-java uses it
// as its union rebuild flag. It must not change how this implementation reads the sketch.
for (auto type: {HLL_4, HLL_6, HLL_8}) {
const hll_sketch sk = make(8, type, false, 5000);
auto bytes = sk.serialize_updatable();
auto tampered = bytes;
tampered[FLAGS_BYTE] |= RESERVED_BIT_32;
const hll_sketch clean = hll_sketch::deserialize(bytes.data(), bytes.size());
hll_sketch tainted = hll_sketch::deserialize(tampered.data(), tampered.size());
REQUIRE(tainted.get_estimate() == clean.get_estimate());
REQUIRE(tainted.serialize_updatable() == bytes); // the bit is not propagated back out
tainted.reset();
REQUIRE(preints_of(tainted) == LIST_PREINTS); // and does not alter reset() behaviour
}
}
TEST_CASE("hll full size: a union never inherits full size from an input sketch",
"[hll_full_size]") {
// the gadget is an internal detail; unioning a full-size sketch must not change how the
// union resets, nor put the reserved bit into the union's result
const hll_sketch full = make(8, HLL_8, true, 5000);
hll_union u(8);
u.update(full);
const hll_sketch result = u.get_result(HLL_8);
REQUIRE((result.serialize_compact()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
REQUIRE((result.serialize_updatable()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
u.reset();
REQUIRE(u.is_empty());
REQUIRE(preints_of(u.get_result(HLL_8)) == LIST_PREINTS);
// same through the rvalue overload, which moves the sketch into the gadget
hll_union u2(8);
u2.update(make(8, HLL_8, true, 5000));
u2.reset();
REQUIRE(u2.is_empty());
REQUIRE(preints_of(u2.get_result(HLL_8)) == LIST_PREINTS);
}
} /* namespace datasketches */