206 lines
6.0 KiB
C
206 lines
6.0 KiB
C
/*
|
|
* test_lz4.c — Tests for LZ4 HC compression wrappers.
|
|
*
|
|
* Ports from internal/cbm/lz4_test.go:
|
|
* TestLZ4RoundTrip, TestLZ4CompressionRatio,
|
|
* TestLZ4DecompressWrongLen, TestLZ4RandomData
|
|
*/
|
|
#include "test_framework.h"
|
|
#include <time.h>
|
|
|
|
/* lz4_store.c has no header — declare extern */
|
|
extern int cbm_lz4_compress_hc(const char *src, int srcLen, char *dst, int dstCap);
|
|
extern int cbm_lz4_decompress(const char *src, int srcLen, char *dst, int originalLen);
|
|
extern int cbm_lz4_bound(int inputSize);
|
|
|
|
/* ── Helper: compress + decompress round-trip ──────────────────── */
|
|
|
|
static int lz4_roundtrip(const char *data, int len) {
|
|
if (len == 0) {
|
|
/* Empty input: Go wrapper returns nil without calling LZ4.
|
|
* LZ4_compress_HC actually returns 1 (end-of-stream marker) for empty input.
|
|
* Match Go behavior: empty in → empty out is correct. */
|
|
return 0;
|
|
}
|
|
|
|
int bound = cbm_lz4_bound(len);
|
|
if (bound <= 0)
|
|
return -1;
|
|
|
|
char *cbuf = malloc(bound);
|
|
if (!cbuf)
|
|
return -1;
|
|
|
|
int clen = cbm_lz4_compress_hc(data, len, cbuf, bound);
|
|
if (clen <= 0) {
|
|
free(cbuf);
|
|
return -1;
|
|
}
|
|
|
|
char *dbuf = malloc(len);
|
|
if (!dbuf) {
|
|
free(cbuf);
|
|
return -1;
|
|
}
|
|
|
|
int dlen = cbm_lz4_decompress(cbuf, clen, dbuf, len);
|
|
int ok = (dlen == len && memcmp(dbuf, data, len) == 0) ? 0 : -1;
|
|
|
|
free(cbuf);
|
|
free(dbuf);
|
|
return ok;
|
|
}
|
|
|
|
/* ── Tests ─────────────────────────────────────────────────────── */
|
|
|
|
TEST(lz4_roundtrip_empty) {
|
|
/* Empty input: compress returns 0 (no output). Go wrapper returns nil. */
|
|
int rc = lz4_roundtrip(NULL, 0);
|
|
ASSERT_EQ(rc, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_roundtrip_small) {
|
|
const char *data = "hello world";
|
|
int rc = lz4_roundtrip(data, (int)strlen(data));
|
|
ASSERT_EQ(rc, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_roundtrip_repeated) {
|
|
/* 40,000 bytes of repeated "ABCD" — highly compressible */
|
|
int len = 10000 * 4;
|
|
char *data = malloc(len);
|
|
ASSERT_NOT_NULL(data);
|
|
for (int i = 0; i < 10000; i++)
|
|
memcpy(data + i * 4, "ABCD", 4);
|
|
|
|
int rc = lz4_roundtrip(data, len);
|
|
free(data);
|
|
ASSERT_EQ(rc, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_roundtrip_source_code) {
|
|
const char *code = "package main\n"
|
|
"import \"fmt\"\n"
|
|
"func main() {\n"
|
|
"\tfor i := 0; i < 100; i++ {\n"
|
|
"\t\tfmt.Println(\"Hello, World!\", i)\n"
|
|
"\t}\n"
|
|
"}\n";
|
|
int rc = lz4_roundtrip(code, (int)strlen(code));
|
|
ASSERT_EQ(rc, 0);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_compression_ratio) {
|
|
/* Repetitive source code should compress >2x with HC */
|
|
const char *line = "func handleRequest(w http.ResponseWriter, r *http.Request) {\n";
|
|
int line_len = (int)strlen(line);
|
|
int total = line_len * 1000;
|
|
|
|
char *data = malloc(total);
|
|
ASSERT_NOT_NULL(data);
|
|
for (int i = 0; i < 1000; i++)
|
|
memcpy(data + i * line_len, line, line_len);
|
|
|
|
int bound = cbm_lz4_bound(total);
|
|
char *cbuf = malloc(bound);
|
|
ASSERT_NOT_NULL(cbuf);
|
|
|
|
int clen = cbm_lz4_compress_hc(data, total, cbuf, bound);
|
|
ASSERT_GT(clen, 0);
|
|
|
|
double ratio = (double)total / (double)clen;
|
|
/* Expect >2x compression on repetitive source code */
|
|
ASSERT_TRUE(ratio >= 2.0);
|
|
|
|
free(data);
|
|
free(cbuf);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_decompress_wrong_len) {
|
|
/* Decompress with wrong originalLen — must not crash (safety property) */
|
|
const char *src = "test data for decompression";
|
|
int src_len = (int)strlen(src);
|
|
|
|
int bound = cbm_lz4_bound(src_len);
|
|
char *cbuf = malloc(bound);
|
|
ASSERT_NOT_NULL(cbuf);
|
|
|
|
int clen = cbm_lz4_compress_hc(src, src_len, cbuf, bound);
|
|
ASSERT_GT(clen, 0);
|
|
|
|
/* Allocate larger buffer, pass wrong originalLen */
|
|
int wrong_len = src_len + 100;
|
|
char *dbuf = malloc(wrong_len);
|
|
ASSERT_NOT_NULL(dbuf);
|
|
|
|
/* LZ4_decompress_safe won't crash — it either succeeds partially
|
|
* or returns a negative error code. The key property is no buffer overflow. */
|
|
int dlen = cbm_lz4_decompress(cbuf, clen, dbuf, wrong_len);
|
|
(void)dlen; /* result may vary; safety is what matters */
|
|
|
|
free(cbuf);
|
|
free(dbuf);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_random_data) {
|
|
/* Random data is incompressible — LZ4 should still handle it */
|
|
int len = 4096;
|
|
char *data = malloc(len);
|
|
ASSERT_NOT_NULL(data);
|
|
|
|
/* Simple PRNG seeded from time — not crypto, just needs to be non-repeating */
|
|
unsigned int seed = (unsigned int)time(NULL);
|
|
for (int i = 0; i < len; i++) {
|
|
seed = seed * 1103515245 + 12345;
|
|
data[i] = (char)(seed >> 16);
|
|
}
|
|
|
|
int bound = cbm_lz4_bound(len);
|
|
char *cbuf = malloc(bound);
|
|
ASSERT_NOT_NULL(cbuf);
|
|
|
|
int clen = cbm_lz4_compress_hc(data, len, cbuf, bound);
|
|
ASSERT_GT(clen, 0);
|
|
|
|
char *dbuf = malloc(len);
|
|
ASSERT_NOT_NULL(dbuf);
|
|
|
|
int dlen = cbm_lz4_decompress(cbuf, clen, dbuf, len);
|
|
ASSERT_EQ(dlen, len);
|
|
ASSERT_MEM_EQ(dbuf, data, len);
|
|
|
|
free(data);
|
|
free(cbuf);
|
|
free(dbuf);
|
|
PASS();
|
|
}
|
|
|
|
TEST(lz4_bound_positive) {
|
|
/* Bound should be positive for any positive input size */
|
|
ASSERT_GT(cbm_lz4_bound(1), 0);
|
|
ASSERT_GT(cbm_lz4_bound(100), 0);
|
|
ASSERT_GT(cbm_lz4_bound(1000000), 0);
|
|
/* Bound should be >= input size (compressed can't be smaller than worst case) */
|
|
ASSERT_GTE(cbm_lz4_bound(100), 100);
|
|
PASS();
|
|
}
|
|
|
|
/* ── Suite ─────────────────────────────────────────────────────── */
|
|
|
|
SUITE(lz4) {
|
|
RUN_TEST(lz4_roundtrip_empty);
|
|
RUN_TEST(lz4_roundtrip_small);
|
|
RUN_TEST(lz4_roundtrip_repeated);
|
|
RUN_TEST(lz4_roundtrip_source_code);
|
|
RUN_TEST(lz4_compression_ratio);
|
|
RUN_TEST(lz4_decompress_wrong_len);
|
|
RUN_TEST(lz4_random_data);
|
|
RUN_TEST(lz4_bound_positive);
|
|
}
|