blob: 6970b938711802a3530ba593889bd4a6b7ae6b70 [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.
*/
// Targeted coverage tests that exercise paths missed by the per-codec
// roundtrip tests: type-mismatch error returns, has_remaining variants,
// SIMD/scalar batch branches, floating-point special values, dictionary
// decoder/encoder, and reset cycles.
#include <cmath>
#include <limits>
#include <vector>
#include "common/allocator/byte_stream.h"
#include "encoding/dictionary_decoder.h"
#include "encoding/dictionary_encoder.h"
#include "encoding/gorilla_decoder.h"
#include "encoding/gorilla_encoder.h"
#include "encoding/int32_rle_decoder.h"
#include "encoding/int32_rle_encoder.h"
#include "encoding/int64_rle_decoder.h"
#include "encoding/int64_rle_encoder.h"
#include "encoding/plain_decoder.h"
#include "encoding/plain_encoder.h"
#include "encoding/ts2diff_decoder.h"
#include "encoding/ts2diff_encoder.h"
#include "encoding/zigzag_decoder.h"
#include "encoding/zigzag_encoder.h"
#include "gtest/gtest.h"
namespace storage {
// ── Type-mismatch returns ────────────────────────────────────────────────
//
// Every codec exposes read_boolean / read_int32 / read_int64 / read_float /
// read_double / read_String. Most of them only implement one or two and
// return E_TYPE_NOT_MATCH for the rest, but those return paths were never
// hit by the existing per-codec tests (which only call the one supported
// method per codec).
TEST(EncodingCoverage, TypeMismatchReturnsAreReachable) {
common::ByteStream s(64, common::MOD_DEFAULT);
common::PageArena pa;
pa.init(512, common::MOD_DEFAULT);
bool b;
float f;
double d;
int64_t i64;
common::String str;
// Each decoder returns an error sentinel (E_TYPE_NOT_MATCH or
// E_NOT_SUPPORT depending on codec) for the read_* variants it
// doesn't implement. We only care that the unsupported path returns
// an error rather than a corrupted value. Note that GorillaDecoder
// implements its unsupported paths with `ASSERT(false)`; calling
// those in Debug builds aborts, so we exercise only the codecs that
// return cleanly (Zigzag, RLE).
auto NE_OK = [](int r) { EXPECT_NE(r, common::E_OK); };
IntZigzagDecoder zz;
NE_OK(zz.read_boolean(b, s));
NE_OK(zz.read_float(f, s));
NE_OK(zz.read_double(d, s));
NE_OK(zz.read_String(str, pa, s));
Int32RleDecoder rle32;
NE_OK(rle32.read_int64(i64, s));
NE_OK(rle32.read_float(f, s));
NE_OK(rle32.read_double(d, s));
NE_OK(rle32.read_String(str, pa, s));
Int64RleDecoder rle64;
int32_t i32;
NE_OK(rle64.read_boolean(b, s));
NE_OK(rle64.read_int32(i32, s));
NE_OK(rle64.read_float(f, s));
NE_OK(rle64.read_double(d, s));
NE_OK(rle64.read_String(str, pa, s));
(void)i32;
(void)i64;
}
// ── Reset cycles ────────────────────────────────────────────────────────
//
// Each codec defines a reset() that resets internal state; nothing in the
// roundtrip tests calls it. Encode → reset → re-encode should still
// produce a stream that decodes to the second batch's values.
TEST(EncodingCoverage, ResetClearsState) {
{
IntZigzagEncoder enc;
IntZigzagDecoder dec;
common::ByteStream s(64, common::MOD_DEFAULT);
EXPECT_EQ(enc.encode(123, s), common::E_OK);
enc.flush(s);
EXPECT_EQ(dec.decode(s), 123);
dec.reset();
common::ByteStream s2(64, common::MOD_DEFAULT);
EXPECT_EQ(enc.encode(-456, s2), common::E_OK);
enc.flush(s2);
EXPECT_EQ(dec.decode(s2), -456);
}
{
IntGorillaEncoder enc;
IntGorillaDecoder dec;
common::ByteStream s(64, common::MOD_DEFAULT);
EXPECT_EQ(enc.encode(7, s), common::E_OK);
EXPECT_EQ(enc.encode(7, s), common::E_OK);
enc.flush(s);
int32_t v;
EXPECT_EQ(dec.read_int32(v, s), common::E_OK);
EXPECT_EQ(v, 7);
dec.reset();
enc.reset();
common::ByteStream s2(64, common::MOD_DEFAULT);
EXPECT_EQ(enc.encode(42, s2), common::E_OK);
EXPECT_EQ(enc.encode(42, s2), common::E_OK);
enc.flush(s2);
EXPECT_EQ(dec.read_int32(v, s2), common::E_OK);
EXPECT_EQ(v, 42);
}
}
// ── has_remaining variants ──────────────────────────────────────────────
TEST(EncodingCoverage, HasRemainingOnEmptyAndAfterDrain) {
common::ByteStream empty(64, common::MOD_DEFAULT);
{
IntZigzagDecoder zz;
EXPECT_FALSE(zz.has_remaining(empty));
}
{
IntGorillaDecoder g;
EXPECT_FALSE(g.has_remaining(empty));
}
{
Int32RleDecoder rle;
EXPECT_FALSE(rle.has_remaining(empty));
}
{
TS2DIFFDecoder<int32_t> t;
EXPECT_FALSE(t.has_remaining(empty));
}
{
PlainDecoder p;
EXPECT_FALSE(p.has_remaining(empty));
}
}
// ── Gorilla floating-point special values ──────────────────────────────
//
// FloatGorillaDecoder / DoubleGorillaDecoder run different VALUE_BITS and
// ending-sentinel paths. Verify they round-trip NaN, infinity, -0.0 and
// denormals — none of which the existing happy-path roundtrip exercises.
TEST(EncodingCoverage, GorillaFloatSpecialValues) {
FloatGorillaEncoder enc;
common::ByteStream s(256, common::MOD_DEFAULT);
std::vector<float> values = {
0.0f,
-0.0f,
std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::epsilon(),
1.0f,
-1.0f,
std::numeric_limits<float>::max(),
std::numeric_limits<float>::lowest(),
};
for (float v : values) ASSERT_EQ(enc.encode(v, s), common::E_OK);
enc.flush(s);
FloatGorillaDecoder dec;
float out;
for (size_t i = 0; i < values.size(); i++) {
ASSERT_EQ(dec.read_float(out, s), common::E_OK) << "i=" << i;
if (std::isnan(values[i])) {
EXPECT_TRUE(std::isnan(out));
} else {
// Bitwise compare to catch -0.0 vs 0.0 etc.
uint32_t a, b;
memcpy(&a, &values[i], sizeof(float));
memcpy(&b, &out, sizeof(float));
EXPECT_EQ(a, b) << "i=" << i;
}
}
}
TEST(EncodingCoverage, GorillaDoubleSpecialValues) {
DoubleGorillaEncoder enc;
common::ByteStream s(256, common::MOD_DEFAULT);
std::vector<double> values = {
0.0,
-0.0,
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::min(),
std::numeric_limits<double>::denorm_min(),
std::numeric_limits<double>::epsilon(),
1.0,
-1.0,
std::numeric_limits<double>::max(),
std::numeric_limits<double>::lowest(),
};
for (double v : values) ASSERT_EQ(enc.encode(v, s), common::E_OK);
enc.flush(s);
DoubleGorillaDecoder dec;
double out;
for (size_t i = 0; i < values.size(); i++) {
ASSERT_EQ(dec.read_double(out, s), common::E_OK) << "i=" << i;
uint64_t a, b;
memcpy(&a, &values[i], sizeof(double));
memcpy(&b, &out, sizeof(double));
EXPECT_EQ(a, b) << "i=" << i;
}
}
// ── Gorilla skip path ───────────────────────────────────────────────────
TEST(EncodingCoverage, GorillaSkipInt32Roundtrip) {
IntGorillaEncoder enc;
common::ByteStream stream(1024, common::MOD_DEFAULT);
const int N = 200;
std::vector<int32_t> values(N);
for (int i = 0; i < N; i++) {
values[i] = i * 11 - 5;
ASSERT_EQ(enc.encode(values[i], stream), common::E_OK);
}
enc.flush(stream);
// Wrap into contiguous buffer for batch_skip_raw.
uint32_t total = stream.total_size();
std::vector<uint8_t> buf(total);
uint32_t got = 0;
stream.read_buf(buf.data(), total, got);
common::ByteStream wrapped(common::MOD_DEFAULT);
wrapped.wrap_from((const char*)buf.data(), total);
IntGorillaDecoder dec;
int skipped = 0;
ASSERT_EQ(dec.skip_int32(50, skipped, wrapped), common::E_OK);
EXPECT_EQ(skipped, 50);
int32_t out[N];
int actual = 0;
ASSERT_EQ(dec.read_batch_int32(out, N - 50, actual, wrapped), common::E_OK);
EXPECT_EQ(actual, N - 50);
for (int i = 0; i < N - 50; i++) {
EXPECT_EQ(out[i], values[50 + i]) << "i=" << i;
}
}
// ── TS2DIFF batch decode hits SIMD block + scalar tail ─────────────────
TEST(EncodingCoverage, TS2DIFFBatchInt32MultipleBlocks) {
TS2DIFFEncoder<int32_t> enc;
common::ByteStream s(8192, common::MOD_DEFAULT);
// Encode 500 values to span ~4 blocks (default block size 128).
const int N = 500;
std::vector<int32_t> values(N);
for (int i = 0; i < N; i++) {
values[i] = i * 7 + 3;
ASSERT_EQ(enc.encode(values[i], s), common::E_OK);
}
ASSERT_EQ(enc.flush(s), common::E_OK);
// Wrap-from for the SIMD/scalar block fast path.
uint32_t total = s.total_size();
std::vector<uint8_t> buf(total);
uint32_t got = 0;
s.read_buf(buf.data(), total, got);
common::ByteStream wrapped(common::MOD_DEFAULT);
wrapped.wrap_from((const char*)buf.data(), total);
TS2DIFFDecoder<int32_t> dec;
std::vector<int32_t> out(N);
int total_decoded = 0;
while (dec.has_remaining(wrapped) && total_decoded < N) {
int actual = 0;
ASSERT_EQ(dec.read_batch_int32(out.data() + total_decoded,
N - total_decoded, actual, wrapped),
common::E_OK);
if (actual == 0) break;
total_decoded += actual;
}
EXPECT_EQ(total_decoded, N);
for (int i = 0; i < N; i++) EXPECT_EQ(out[i], values[i]) << "i=" << i;
}
TEST(EncodingCoverage, TS2DIFFBatchInt64MultipleBlocks) {
TS2DIFFEncoder<int64_t> enc;
common::ByteStream s(8192, common::MOD_DEFAULT);
const int N = 500;
std::vector<int64_t> values(N);
for (int i = 0; i < N; i++) {
values[i] = static_cast<int64_t>(i) * 17 + 41;
ASSERT_EQ(enc.encode(values[i], s), common::E_OK);
}
ASSERT_EQ(enc.flush(s), common::E_OK);
uint32_t total = s.total_size();
std::vector<uint8_t> buf(total);
uint32_t got = 0;
s.read_buf(buf.data(), total, got);
common::ByteStream wrapped(common::MOD_DEFAULT);
wrapped.wrap_from((const char*)buf.data(), total);
TS2DIFFDecoder<int64_t> dec;
std::vector<int64_t> out(N);
int total_decoded = 0;
while (dec.has_remaining(wrapped) && total_decoded < N) {
int actual = 0;
ASSERT_EQ(dec.read_batch_int64(out.data() + total_decoded,
N - total_decoded, actual, wrapped),
common::E_OK);
if (actual == 0) break;
total_decoded += actual;
}
EXPECT_EQ(total_decoded, N);
for (int i = 0; i < N; i++) EXPECT_EQ(out[i], values[i]) << "i=" << i;
}
// ── Plain encoder: encode_batch fast paths for each type ───────────────
TEST(EncodingCoverage, PlainEncoderBatchAllTypes) {
PlainEncoder enc;
PlainDecoder dec;
// Float batch.
{
common::ByteStream s(1024, common::MOD_DEFAULT);
const uint32_t N = 100;
float v[N];
for (uint32_t i = 0; i < N; i++) v[i] = i * 0.5f - 1.0f;
ASSERT_EQ(enc.encode_batch(v, N, s), common::E_OK);
float out[N];
int actual = 0;
ASSERT_EQ(dec.read_batch_float(out, N, actual, s), common::E_OK);
EXPECT_EQ(actual, static_cast<int>(N));
for (uint32_t i = 0; i < N; i++) EXPECT_FLOAT_EQ(out[i], v[i]);
}
// Int64 batch.
{
common::ByteStream s(1024, common::MOD_DEFAULT);
const uint32_t N = 100;
int64_t v[N];
for (uint32_t i = 0; i < N; i++) v[i] = i * 1000 - 50;
ASSERT_EQ(enc.encode_batch(v, N, s), common::E_OK);
int64_t out[N];
int actual = 0;
ASSERT_EQ(dec.read_batch_int64(out, N, actual, s), common::E_OK);
EXPECT_EQ(actual, static_cast<int>(N));
for (uint32_t i = 0; i < N; i++) EXPECT_EQ(out[i], v[i]);
}
}
// ── PlainDecoder skip paths (wrapped + paged) ──────────────────────────
TEST(EncodingCoverage, PlainSkipPagedStream) {
PlainEncoder enc;
PlainDecoder dec;
// Paged ByteStream (tiny page) forces the fallback path.
common::ByteStream s(16, common::MOD_DEFAULT);
for (int i = 0; i < 32; i++)
ASSERT_EQ(enc.encode((int64_t)i, s), common::E_OK);
int skipped = 0;
ASSERT_EQ(dec.skip_int64(10, skipped, s), common::E_OK);
EXPECT_EQ(skipped, 10);
int64_t out;
ASSERT_EQ(dec.read_int64(out, s), common::E_OK);
EXPECT_EQ(out, 10);
}
// ── Dictionary codec roundtrip ─────────────────────────────────────────
TEST(EncodingCoverage, DictionaryStringRoundTrip) {
DictionaryEncoder enc;
common::ByteStream s(1024, common::MOD_DEFAULT);
std::vector<std::string> raw = {"apple", "banana", "apple",
"cherry", "banana", "apple"};
for (const auto& r : raw) {
common::String str(const_cast<char*>(r.c_str()), r.size());
ASSERT_EQ(enc.encode(str, s), common::E_OK);
}
enc.flush(s);
DictionaryDecoder dec;
common::PageArena pa;
pa.init(512, common::MOD_DEFAULT);
for (const auto& r : raw) {
common::String out;
ASSERT_EQ(dec.read_String(out, pa, s), common::E_OK);
ASSERT_EQ(out.len_, r.size());
EXPECT_EQ(std::string(out.buf_, out.len_), r);
}
}
} // namespace storage