Author: brane
Date: Tue Feb 3 05:53:32 2026
New Revision: 1931681
Log:
Add streaming/framed LZ4 compression.
* subversion/include/private/svn_subr_private.h
(svn_lz4__compress_ctx_t,
svn_lz4__decompress_ctx_t): New types.
(svn_lz4__header_size_max,
svn_lz4__compress_create,
svn_lz4__compress_bound,
svn_lz4__compress_update,
svn_lz4__compress_flush,
svn_lz4__compress_end,
svn_lz4__decompress_create,
svn_lz4__decompress): New prototypes.
* subversion/libsvn_subr/compress_lz4.c: Include svn_error.h,
lz2frame.h and lz4hc.c.
(free_lz4_cctx, free_lz4_dctx, check_compress_status): New helper functions.
(svn_lz4__compress_ctx_t,
svn_lz4__decompress_ctx_t): Define the new types.
(svn_lz4__header_size_max,
svn_lz4__compress_create,
svn_lz4__compress_bound,
svn_lz4__compress_update,
svn_lz4__compress_flush,
svn_lz4__compress_end,
svn_lz4__decompress_create,
svn_lz4__decompress): Implement the LZ4 compression functions.
* subversion/tests/svn_test.h
(SVN_TEST_BUFFER_ASSERT): New test assertion for comparing binary buffers.
* subversion/tests/libsvn_subr/compress-test.c: Include apr_time.h,
svn_error.h and svn_string.h, but not svn_pools.h which was not used.
(uncompressed, uncompressed_size,
compressed_block, compressed_block_size,
compressed_frame, compressed_frame_size,
empty_frame, empty_frame_size): New, constant data.
(test_decompress_lz4,
test_compress_lz4): Use the new constant data.
(de_compress_lz4): New helper function.
(test_compress_lz4_random): New test.
(test_compress_lz4_empty): Use de_compress_lz4().
(compress_lz4_stream,
decompress_lz4_stream,
decompress_lz4_frame,
de_compress_lz4_stream,
de_compress_lz4_frame_random): New helper functions.
(test_decompress_lz4_frame,
test_decompress_lz4_frame_stable,
test_decompress_lz4_frame_empty,
test_compress_lz4_frame_random,
test_compress_lz4_frame_random_stable,
test_compress_lz4_frame_no_header_space): New tests.
(test_funcs): Register the new tests.
Modified:
subversion/branches/better-pristines/subversion/include/private/svn_subr_private.h
subversion/branches/better-pristines/subversion/libsvn_subr/compress_lz4.c
subversion/branches/better-pristines/subversion/tests/libsvn_subr/compress-test.c
subversion/branches/better-pristines/subversion/tests/svn_test.h
Modified: subversion/branches/better-pristines/subversion/include/private/svn_subr_private.h
==============================================================================
--- subversion/branches/better-pristines/subversion/include/private/svn_subr_private.h Tue Feb 3 05:31:33 2026 (r1931680)
+++ subversion/branches/better-pristines/subversion/include/private/svn_subr_private.h Tue Feb 3 05:53:32 2026 (r1931681)
@@ -625,6 +625,100 @@ svn__decompress_lz4(const void *data, ap
svn_stringbuf_t *out,
apr_size_t limit);
+/*
+ * LZ4 stream compression with framing.
+ */
+
+/* The largest size of an LZ4 frame header; see: LZ4F_HEADER_SIZE_MAX */
+apr_size_t
+svn_lz4__header_size_max(void);
+
+/* LZ4 stream compression and decompression contexts. */
+typedef struct svn_lz4__compress_ctx_t svn_lz4__compress_ctx_t;
+typedef struct svn_lz4__decompress_ctx_t svn_lz4__decompress_ctx_t;
+
+/* Creates a new LZ4 compression context CCTX, allocated from POOL.
+ * The context will be freed when the pool is cleared.
+ *
+ * If STABLE_INPUT is TRUE, the source buffer must be contiguous for the whole
+ * compression; this means that all uncompressed source data is available to
+ * svn_lz4__compress_update() through negative offsets from the INPUT pointer.
+ * When this option can be set, the compressor can avoid copying and caching
+ * source data in CCTX.
+ */
+svn_error_t *
+svn_lz4__compress_create(svn_lz4__compress_ctx_t **cctx,
+ svn_boolean_t stable_input,
+ apr_pool_t *pool);
+
+/* Returns the minimum CAPACITY of the output buffer to guarantee that
+ * the next svn_lz4__compress_update() with CCTX and SIZE amount
+ * of input data will succeed. If SIZE is 0, the returned value is
+ * computed for svn_lz4__compress_flush()/_end().
+ */
+apr_size_t
+svn_lz4__compress_bound(svn_lz4__compress_ctx_t *cctx, apr_size_t size);
+
+/* Using the context CCTX, compresses SIZE bytes from INPUT into the OUTPUT
+ * buffer of size CAPACITY. *LENGTH will be the number of bytes actually
+ * written to OUTPUT and can be 0 if the input data was buffered in the
+ * context.
+ *
+ * At the first call of svn_lz4__compress_update(), CAPACITY must be
+ * at least svn_lz4__header_size_max() to accomodate the frame header.
+ */
+svn_error_t *
+svn_lz4__compress_update(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity,
+ const void *input, apr_size_t size);
+
+/* Flushes any data buffered in CCTX to the OUTPUT buffer of size CAPACITY
+ * and returns the number of bytes written in *LENGTH.
+ */
+svn_error_t *
+svn_lz4__compress_flush(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity);
+
+/* Ends the compression stream, returning the number of bytes written
+ * to OUTPUT in *LENGTH. Afterwards, CCTX will be available for to start
+ * a new compression stream with svn_lz4__compress_update().
+ */
+svn_error_t *
+svn_lz4__compress_end(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity);
+
+/* Creates a new LZ4 decompression context DCTX, allocated from POOL.
+ * The context will be freed when the pool is cleared.
+ *
+ * If STABLE_OUTPUT is TRUE, the output buffer must be contiguous for the whole
+ * decompression; this means that previously decompressed content is available
+ * to svn_lz4__decompress() through negative offsets from the OUTPUT pointer.
+ * This is the case, for example, when we store the decompressed data to a
+ * stringbuf, which remains contiguous even when it's reallocated. When this
+ * option can be set, the decompressor can avoid copying and caching output
+ * data in DCTX.
+ */
+svn_error_t *
+svn_lz4__decompress_create(svn_lz4__decompress_ctx_t **dctx,
+ svn_boolean_t stable_output,
+ apr_pool_t *pool);
+
+/* Using DCTX, read at most *INPUT_SIZE compressed bytes from the INPUT buffer
+ * and decompress them to the OUTPUT buffer of size *OUTPUT_SIZE. Upon return,
+ * *INPUT_SIZE will be the number of bytes actally read from INPUT and
+ * *OUTPUT_SIZE will be the number of bytes written to *OUTPUT. The returned
+ * *SIZE_HINT is a hint for the expected OUTPUT_SIZE for the next call, or 0
+ * if the frame has been completely decoded.
+ */
+svn_error_t *
+svn_lz4__decompress(apr_size_t *size_hint,
+ svn_lz4__decompress_ctx_t *dctx,
+ void *output, apr_size_t *output_size,
+ const void *input, apr_size_t *input_size);
+
/** @} */
/**
Modified: subversion/branches/better-pristines/subversion/libsvn_subr/compress_lz4.c
==============================================================================
--- subversion/branches/better-pristines/subversion/libsvn_subr/compress_lz4.c Tue Feb 3 05:31:33 2026 (r1931680)
+++ subversion/branches/better-pristines/subversion/libsvn_subr/compress_lz4.c Tue Feb 3 05:53:32 2026 (r1931681)
@@ -23,16 +23,26 @@
#include <assert.h>
+#include "svn_error.h"
#include "private/svn_subr_private.h"
#include "svn_private_config.h"
+#include "svn_types.h"
#ifdef SVN_INTERNAL_LZ4
#include "lz4/lz4internal.h"
+#include "lz4/lz4frame.h"
+#include "lz4/lz4hc.h"
#else
#include <lz4.h>
+#include <lz4frame.h>
+#include <lz4hc.h>
#endif
+/*
+ * Simple compression and decompression
+ */
+
svn_error_t *
svn__compress_lz4(const void *data, apr_size_t len,
svn_stringbuf_t *out)
@@ -127,6 +137,212 @@ svn__decompress_lz4(const void *data, ap
return SVN_NO_ERROR;
}
+/*
+ * Streamy compression
+ */
+
+apr_size_t
+svn_lz4__header_size_max(void)
+{
+ return LZ4F_HEADER_SIZE_MAX;
+}
+
+struct svn_lz4__compress_ctx_t
+{
+ LZ4F_cctx *ctx;
+ unsigned started;
+ LZ4F_preferences_t prefs;
+ LZ4F_compressOptions_t options;
+};
+
+static apr_status_t free_lz4_cctx(void *data)
+{
+ svn_lz4__compress_ctx_t *const cctx = data;
+ const LZ4F_errorCode_t code = LZ4F_freeCompressionContext(cctx->ctx);
+ if (LZ4F_isError(code))
+ return SVN_ERR_LZ4_COMPRESSION_FAILED;
+ return APR_SUCCESS;
+}
+
+svn_error_t *
+svn_lz4__compress_create(svn_lz4__compress_ctx_t **cctx_out,
+ svn_boolean_t stable_input,
+ apr_pool_t *pool)
+{
+ static const LZ4F_preferences_t compression_prefs = {
+ {
+ LZ4F_max64KB, /* frameInfo.blockSizeID */
+ LZ4F_blockLinked, /* frameInfo.blockMode */
+ LZ4F_noContentChecksum, /* frameInfo.contentChecksumFlag */
+ LZ4F_frame, /* frameInfo.frameType */
+ 0, /* frameInfo.contentSize */
+ 0, /* frameInfo.dictID */
+ LZ4F_blockChecksumEnabled /* frameInfo.blockChecksumFlag */
+ },
+ /* NOTE: With LZ4 1.10+, the compression level will be 2, faster but less
+ compressed than with older versions of LZ4, where the value of
+ this constant was 3. This does not affect compression format
+ backward compatibility. */
+ LZ4HC_CLEVEL_MIN, /* compressionLevel */
+ 0, /* autoFlush */
+ 1, /* favorDecSpeed */
+ { 0 } /* reserved */
+ };
+
+ LZ4F_cctx *ctx;
+ svn_lz4__compress_ctx_t *cctx;
+ LZ4F_errorCode_t code = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
+
+ if (LZ4F_isError(code))
+ return svn_error_createf(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
+ _("Create LZ4 compression context: %s"),
+ LZ4F_getErrorName(code));
+
+ cctx = apr_pcalloc(pool, sizeof(*cctx));
+ cctx->ctx = ctx;
+ cctx->prefs = compression_prefs;
+ cctx->options.stableSrc = !!stable_input;
+ apr_pool_cleanup_register(pool, cctx, free_lz4_cctx, apr_pool_cleanup_null);
+ *cctx_out = cctx;
+ return SVN_NO_ERROR;
+}
+
+static SVN__FORCE_INLINE svn_error_t *
+check_compress_status(apr_size_t *length, apr_size_t status, apr_size_t extra)
+{
+ if (LZ4F_isError(status))
+ return svn_error_createf(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
+ _("LZ4 compress: %s"),
+ LZ4F_getErrorName(status));
+
+ *length = status + extra;
+ return SVN_NO_ERROR;
+}
+
+apr_size_t
+svn_lz4__compress_bound(svn_lz4__compress_ctx_t *cctx,
+ apr_size_t size)
+{
+ return LZ4F_compressBound(size, &cctx->prefs);
+}
+
+svn_error_t *
+svn_lz4__compress_update(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity,
+ const void *input, apr_size_t size)
+{
+ apr_size_t header_size = 0;
+ apr_size_t status;
+
+ if (!cctx->started)
+ {
+ if (capacity < LZ4F_HEADER_SIZE_MAX)
+ return svn_error_create(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
+ _("LZ4 compress: no space for frame header"));
+
+ status = LZ4F_compressBegin(cctx->ctx, output, capacity, &cctx->prefs);
+ SVN_ERR(check_compress_status(&header_size, status, 0));
+ output = (char*)output + header_size;
+ capacity -= header_size;
+ cctx->started = TRUE;
+ }
+
+ status = LZ4F_compressUpdate(cctx->ctx, output, capacity,
+ input, size, &cctx->options);
+ return svn_error_trace(check_compress_status(length, status, header_size));
+}
+
+svn_error_t *
+svn_lz4__compress_flush(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity)
+{
+ const apr_size_t status = LZ4F_flush(cctx->ctx,
+ output, capacity,
+ &cctx->options);
+ return svn_error_trace(check_compress_status(length, status, 0));
+}
+
+svn_error_t *
+svn_lz4__compress_end(apr_size_t *length,
+ svn_lz4__compress_ctx_t *cctx,
+ void *output, apr_size_t capacity)
+{
+ const apr_size_t status = LZ4F_compressEnd(cctx->ctx,
+ output, capacity,
+ &cctx->options);
+ svn_error_t *const err = check_compress_status(length, status, 0);
+ if (!err)
+ cctx->started = FALSE;
+ return svn_error_trace(err);
+}
+
+/*
+ * Streamy decompression
+ */
+
+struct svn_lz4__decompress_ctx_t
+{
+ LZ4F_dctx* ctx;
+ LZ4F_decompressOptions_t options;
+};
+
+static apr_status_t free_lz4_dctx(void *data)
+{
+ svn_lz4__decompress_ctx_t *const dctx = data;
+ const LZ4F_errorCode_t code = LZ4F_freeDecompressionContext(dctx->ctx);
+ if (LZ4F_isError(code))
+ return SVN_ERR_LZ4_DECOMPRESSION_FAILED;
+ return APR_SUCCESS;
+}
+
+svn_error_t *
+svn_lz4__decompress_create(svn_lz4__decompress_ctx_t **dctx_out,
+ svn_boolean_t stable_output,
+ apr_pool_t *pool)
+{
+ LZ4F_dctx* ctx;
+ svn_lz4__decompress_ctx_t *dctx;
+ LZ4F_errorCode_t code = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
+
+ if (LZ4F_isError(code))
+ return svn_error_createf(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL,
+ _("Create LZ4 decompression context: %s"),
+ LZ4F_getErrorName(code));
+
+ dctx = apr_pcalloc(pool, sizeof(*dctx));
+ dctx->ctx = ctx;
+ dctx->options.stableDst = !!stable_output;
+ apr_pool_cleanup_register(pool, dctx, free_lz4_dctx, apr_pool_cleanup_null);
+ *dctx_out = dctx;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_lz4__decompress(apr_size_t *size_hint,
+ svn_lz4__decompress_ctx_t *dctx,
+ void *output, apr_size_t *output_size,
+ const void *input, apr_size_t *input_size)
+{
+ const apr_size_t status = LZ4F_decompress(dctx->ctx,
+ output, output_size,
+ input, input_size,
+ &dctx->options);
+
+ if (LZ4F_isError(status))
+ return svn_error_createf(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL,
+ _("LZ4 decompress: %s"),
+ LZ4F_getErrorName(status));
+
+ *size_hint = status;
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Library version
+ */
+
const char *
svn_lz4__compiled_version(void)
{
Modified: subversion/branches/better-pristines/subversion/tests/libsvn_subr/compress-test.c
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/libsvn_subr/compress-test.c Tue Feb 3 05:31:33 2026 (r1931680)
+++ subversion/branches/better-pristines/subversion/tests/libsvn_subr/compress-test.c Tue Feb 3 05:53:32 2026 (r1931681)
@@ -21,25 +21,55 @@
* ====================================================================
*/
-#include "svn_pools.h"
+#include <apr_time.h>
+
#include "private/svn_subr_private.h"
#include "../svn_test.h"
+#include "svn_error.h"
+#include "svn_string.h"
+
+
+static const char uncompressed[] =
+ "aaaabbbbccccaaaaccccbbbbaaaabbbb"
+ "aaaabbbbccccaaaaccccbbbbaaaabbbb"
+ "aaaabbbbccccaaaaccccbbbbaaaabbbb";
+static const apr_size_t uncompressed_size = sizeof(uncompressed);
+
+static const char compressed_block[] =
+ "\x61\xc0\x61\x61\x61\x61\x62\x62\x62\x62\x63\x63\x63\x63\x0c\x00\x00\x08"
+ "\x00\x00\x10\x00\x00\x0c\x00\x08\x08\x00\x00\x18\x00\x00\x14\x00\x00\x08"
+ "\x00\x08\x18\x00\x00\x14\x00\x00\x10\x00\x00\x18\x00\x00\x0c\x00\x00\x08"
+ "\x00\x00\x10\x00\x90\x61\x61\x61\x61\x62\x62\x62\x62";
+static const apr_size_t compressed_block_size = sizeof(compressed_block);
+
+/* Generated by piping the uncompressed data through lz4:
+ * echo -n "..." | lz4 --best --no-frame-crc -BD -BX \
+ * | hexdump -v -e '/1 "\\x%02x"'
+ */
+static const char compressed_frame[] =
+ "\x04\x22\x4d\x18\x70\x40\xad\x22\x00\x00\x00\xc0\x61\x61\x61\x61\x62\x62"
+ "\x62\x62\x63\x63\x63\x63\x0c\x00\x00\x08\x00\x00\x10\x00\x04\x18\x00\x0f"
+ "\x20\x00\x28\x50\x61\x62\x62\x62\x62\x78\x16\xca\xbf\x00\x00\x00\x00";
+static const apr_size_t compressed_frame_size = sizeof(compressed_frame) - 1;
+
+/* Generated with:
+ * echo -n "" | lz4 --best --no-frame-crc -BD -BX \
+ * | hexdump -v -e '/1 "\\x%02x"'
+ */
+static const char empty_frame[] =
+ "\x04\x22\x4d\x18\x70\x40\xad\x00\x00\x00\x00";
+static const apr_size_t empty_frame_size = sizeof(empty_frame) - 1;
+
static svn_error_t *
test_decompress_lz4(apr_pool_t *pool)
{
- const char input[] =
- "\x61\xc0\x61\x61\x61\x61\x62\x62\x62\x62\x63\x63\x63\x63\x0c\x00\x00\x08"
- "\x00\x00\x10\x00\x00\x0c\x00\x08\x08\x00\x00\x18\x00\x00\x14\x00\x00\x08"
- "\x00\x08\x18\x00\x00\x14\x00\x00\x10\x00\x00\x18\x00\x00\x0c\x00\x00\x08"
- "\x00\x00\x10\x00\x90\x61\x61\x61\x61\x62\x62\x62\x62";
svn_stringbuf_t *decompressed = svn_stringbuf_create_empty(pool);
- SVN_ERR(svn__decompress_lz4(input, sizeof(input), decompressed, 100));
- SVN_TEST_STRING_ASSERT(decompressed->data,
- "aaaabbbbccccaaaaccccbbbbaaaabbbb"
- "aaaabbbbccccaaaaccccbbbbaaaabbbb"
- "aaaabbbbccccaaaaccccbbbbaaaabbbb");
+ SVN_ERR(svn__decompress_lz4(compressed_block, compressed_block_size,
+ decompressed, uncompressed_size));
+ SVN_TEST_INT_ASSERT(decompressed->len, uncompressed_size);
+ SVN_TEST_STRING_ASSERT(decompressed->data, uncompressed);
return SVN_NO_ERROR;
}
@@ -47,35 +77,227 @@ test_decompress_lz4(apr_pool_t *pool)
static svn_error_t *
test_compress_lz4(apr_pool_t *pool)
{
- const char input[] =
- "aaaabbbbccccaaaaccccbbbbaaaabbbb"
- "aaaabbbbccccaaaaccccbbbbaaaabbbb"
- "aaaabbbbccccaaaaccccbbbbaaaabbbb";
+ svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn__compress_lz4(uncompressed, uncompressed_size, compressed));
+ SVN_TEST_INT_ASSERT(compressed->len, compressed_block_size);
+ SVN_TEST_BUFFER_ASSERT(compressed->data, compressed_block, compressed->len);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+de_compress_lz4(const char input[], apr_size_t length, apr_pool_t *pool)
+{
svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
svn_stringbuf_t *decompressed = svn_stringbuf_create_empty(pool);
- SVN_ERR(svn__compress_lz4(input, sizeof(input), compressed));
+ SVN_ERR(svn__compress_lz4(input, length, compressed));
SVN_ERR(svn__decompress_lz4(compressed->data, compressed->len,
- decompressed, 100));
- SVN_TEST_STRING_ASSERT(decompressed->data, input);
+ decompressed, length));
+ SVN_TEST_INT_ASSERT(decompressed->len, length);
+ SVN_TEST_BUFFER_ASSERT(decompressed->data, input, decompressed->len);
return SVN_NO_ERROR;
}
static svn_error_t *
+test_compress_lz4_random(apr_pool_t *pool)
+{
+ const apr_size_t length = 2047;
+ const apr_uint32_t initial_seed = (apr_uint32_t)apr_time_now();
+ apr_uint32_t seed = initial_seed;
+ void *data = svn_test_make_random_data(&seed, length, pool);
+ svn_error_t *err = de_compress_lz4(data, length, pool);
+ if (err)
+ fprintf(stderr, "SEED: %lu\n", (unsigned long) initial_seed);
+ return err;
+}
+
+static svn_error_t *
test_compress_lz4_empty(apr_pool_t *pool)
{
- svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
- svn_stringbuf_t *decompressed = svn_stringbuf_create_empty(pool);
+ return de_compress_lz4("", 0, pool);
+}
- SVN_ERR(svn__compress_lz4("", 0, compressed));
- SVN_ERR(svn__decompress_lz4(compressed->data, compressed->len,
- decompressed, 100));
- SVN_TEST_STRING_ASSERT(decompressed->data, "");
+static svn_error_t *
+compress_lz4_stream(svn_stringbuf_t **compressed,
+ const char *input, apr_size_t length,
+ svn_boolean_t stable, apr_pool_t *pool)
+{
+ const apr_size_t block_size = 2 * svn_lz4__header_size_max();
+
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
+ svn_lz4__compress_ctx_t *cctx;
+ apr_size_t input_read = 0;
+ apr_size_t output_size;
+
+ SVN_ERR(svn_lz4__compress_create(&cctx, stable, pool));
+ while (input_read < length)
+ {
+ const apr_size_t remaining = length - input_read;
+ const apr_size_t input_size = (remaining > block_size
+ ? block_size : remaining);
+
+ output_size = svn_lz4__compress_bound(cctx, input_size);
+ svn_stringbuf_ensure(buf, buf->len + output_size);
+ SVN_ERR(svn_lz4__compress_update(&output_size, cctx,
+ buf->data + buf->len, output_size,
+ input, input_size));
+ buf->len += output_size;
+ input_read += input_size;
+ input += input_size;
+ }
+
+ output_size = svn_lz4__compress_bound(cctx, 0);
+ svn_stringbuf_ensure(buf, buf->len + output_size);
+ SVN_ERR(svn_lz4__compress_end(&output_size, cctx,
+ buf->data + buf->len, output_size));
+ buf->len += output_size;
+
+ *compressed = buf;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+decompress_lz4_stream(svn_stringbuf_t **decompressed,
+ const char *input, apr_size_t length,
+ svn_boolean_t stable, apr_pool_t *pool)
+{
+ const apr_size_t block_size = 2 * svn_lz4__header_size_max();
+
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
+ svn_lz4__decompress_ctx_t *dctx;
+ apr_size_t input_read = 0;
+ apr_size_t size_hint = 1;
+
+ SVN_ERR(svn_lz4__decompress_create(&dctx, stable, pool));
+ while (input_read <= length && size_hint > 0)
+ {
+ const apr_size_t remaining = length - input_read;
+ apr_size_t input_size = (remaining > block_size
+ ? block_size : remaining);
+ apr_size_t output_size = block_size;
+
+ svn_stringbuf_ensure(buf, buf->len + output_size);
+ SVN_ERR(svn_lz4__decompress(&size_hint, dctx,
+ buf->data + buf->len, &output_size,
+ input, &input_size));
+ buf->len += output_size;
+ input_read += input_size;
+ input += input_size;
+ }
+ SVN_TEST_INT_ASSERT(size_hint, 0);
+ SVN_TEST_INT_ASSERT(input_read, length);
+
+ *decompressed = buf;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+decompress_lz4_frame(svn_boolean_t stable, apr_pool_t *pool)
+{
+ svn_stringbuf_t *decompressed;
+
+ SVN_ERR(decompress_lz4_stream(&decompressed,
+ compressed_frame,
+ compressed_frame_size,
+ stable, pool));
+
+ /* Make sure the output buffer is NUL-terminated */
+ svn_stringbuf_appendbyte(decompressed, '\0');
+ SVN_TEST_STRING_ASSERT(decompressed->data, uncompressed);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_decompress_lz4_frame(apr_pool_t *pool)
+{
+ return decompress_lz4_frame(FALSE, pool);
+}
+
+static svn_error_t *
+test_decompress_lz4_frame_stable(apr_pool_t *pool)
+{
+ return decompress_lz4_frame(TRUE, pool);
+}
+
+static svn_error_t *
+test_decompress_lz4_frame_empty(apr_pool_t *pool)
+{
+ svn_stringbuf_t *decompressed;
+ SVN_ERR(decompress_lz4_stream(&decompressed,
+ empty_frame, empty_frame_size,
+ FALSE, pool));
+ SVN_TEST_INT_ASSERT(decompressed->len, 0);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+de_compress_lz4_stream(const char *input, apr_size_t length,
+ svn_boolean_t stable, apr_pool_t *pool)
+{
+ svn_stringbuf_t *compressed;
+ svn_stringbuf_t *decompressed;
+
+ SVN_ERR(compress_lz4_stream(&compressed, input, length, stable, pool));
+ SVN_ERR(decompress_lz4_stream(&decompressed,
+ compressed->data, compressed->len,
+ stable, pool));
+ SVN_TEST_INT_ASSERT(decompressed->len, length);
+ SVN_TEST_BUFFER_ASSERT(decompressed->data, input, decompressed->len);
return SVN_NO_ERROR;
}
+static svn_error_t *
+de_compress_lz4_frame_random(svn_boolean_t stable, apr_pool_t *pool)
+{
+ const apr_size_t length = 2047;
+ const apr_uint32_t initial_seed = (apr_uint32_t)apr_time_now();
+ apr_uint32_t seed = initial_seed;
+ void *data = svn_test_make_random_data(&seed, length, pool);
+ svn_error_t *err = de_compress_lz4_stream(data, length, stable, pool);
+ if (err)
+ fprintf(stderr, "SEED: %lu\n", (unsigned long) initial_seed);
+ return err;
+}
+
+static svn_error_t *
+test_compress_lz4_frame_random(apr_pool_t *pool)
+{
+ return de_compress_lz4_frame_random(FALSE, pool);
+}
+
+static svn_error_t *
+test_compress_lz4_frame_random_stable(apr_pool_t *pool)
+{
+ return de_compress_lz4_frame_random(TRUE, pool);
+}
+
+static svn_error_t *
+test_compress_lz4_frame_no_header_space(apr_pool_t *pool)
+{
+ const apr_size_t block_size = svn_lz4__header_size_max() - 1;
+ svn_stringbuf_t *compressed = svn_stringbuf_create_ensure(block_size, pool);
+ svn_lz4__compress_ctx_t *cctx;
+ apr_size_t length;
+ svn_error_t *err;
+
+ SVN_ERR(svn_lz4__compress_create(&cctx, FALSE, pool));
+
+ /* First update checks there's space for the frame header
+ before calling LZ4F_compressBegin(), and we just made
+ the space too small. */
+ err = svn_lz4__compress_update(&length, cctx,
+ compressed->data, block_size,
+ "", 0);
+ svn_error_clear(err);
+ SVN_TEST_ASSERT(err != SVN_NO_ERROR);
+ return SVN_NO_ERROR;
+}
+
+
static int max_threads = -1;
static struct svn_test_descriptor_t test_funcs[] =
@@ -85,8 +307,22 @@ static struct svn_test_descriptor_t test
"test svn__decompress_lz4()"),
SVN_TEST_PASS2(test_compress_lz4,
"test svn__compress_lz4()"),
+ SVN_TEST_PASS2(test_compress_lz4_random,
+ "test svn__de/compress_lz4() with random input"),
SVN_TEST_PASS2(test_compress_lz4_empty,
- "test svn__compress_lz4() with empty input"),
+ "test svn__de/compress_lz4() with empty input"),
+ SVN_TEST_PASS2(test_decompress_lz4_frame,
+ "test svn_lz4__decompress()"),
+ SVN_TEST_PASS2(test_decompress_lz4_frame_stable,
+ "test svn_lz4__decompress() with stable output"),
+ SVN_TEST_PASS2(test_decompress_lz4_frame_empty,
+ "test svn_lz4__decompress() with empty input"),
+ SVN_TEST_PASS2(test_compress_lz4_frame_random,
+ "test svn_lz4__de/compress() with random input"),
+ SVN_TEST_PASS2(test_compress_lz4_frame_random_stable,
+ "test svn_lz4__de/compress() with stable output"),
+ SVN_TEST_PASS2(test_compress_lz4_frame_no_header_space,
+ "test svn_lz4__compress() with small buffer"),
SVN_TEST_NULL
};
Modified: subversion/branches/better-pristines/subversion/tests/svn_test.h
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/svn_test.h Tue Feb 3 05:31:33 2026 (r1931680)
+++ subversion/branches/better-pristines/subversion/tests/svn_test.h Tue Feb 3 05:53:32 2026 (r1931681)
@@ -141,6 +141,40 @@ extern "C" {
tst_str2, tst_str1, __FILE__, __LINE__); \
} while(0)
+/** Handy macro for testing buffer equality.
+ *
+ * EXPR and/or EXPECTED_EXPR may be NULL which compares equal to NULL and
+ * not equal to any non-NULL buffer.
+ */
+#define SVN_TEST_BUFFER_ASSERT(expr, expected_expr, size) \
+ do { \
+ const char *tst_buf1 = (expr); \
+ const char *tst_buf2 = (expected_expr); \
+ const apr_size_t tst_size = (size); \
+ \
+ if (tst_buf2 == NULL && tst_buf1 == NULL) \
+ break; \
+ if (tst_buf1 == NULL) \
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, \
+ "Buffers not equal\n Expected: data\n Found: NULL" \
+ "\n at %s:%d", \
+ __FILE__, __LINE__); \
+ if (tst_buf2 == NULL) \
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, \
+ "Buffers not equal\n Expected: NULL\n Found: data" \
+ "\n at %s:%d", \
+ __FILE__, __LINE__); \
+ if (memcmp(tst_buf2, tst_buf1, tst_size) != 0) { \
+ apr_size_t i = 0; \
+ while (i < tst_size && tst_buf2[i] == tst_buf1[i]) \
+ ++i; \
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, \
+ "Buffers not equal at position %" APR_SIZE_T_FMT \
+ " of %" APR_SIZE_T_FMT " at %s:%d", \
+ i, tst_size, __FILE__, __LINE__); \
+ } \
+ } while(0)
+
/** Handy macro for testing integer equality.
*/
#define SVN_TEST_INT_ASSERT(expr, expected_expr) \
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.