Tests: Refactor test_block_header.c.

test_block_header now achieves higher test coverage. Also the
test will now compile and skip properly if encoders or decoders
are disabled.

Thanks to Sebastian Andrzej Siewior.
This commit is contained in:
Jia Tan 2022-10-06 17:00:38 +08:00 committed by Lasse Collin
parent 8496331895
commit 827ac5b482
1 changed files with 408 additions and 154 deletions

View File

@ -3,7 +3,8 @@
/// \file test_block_header.c /// \file test_block_header.c
/// \brief Tests Block Header coders /// \brief Tests Block Header coders
// //
// Author: Lasse Collin // Authors: Lasse Collin
// Jia Tan
// //
// This file has been put into the public domain. // This file has been put into the public domain.
// You can do whatever you want with this file. // You can do whatever you want with this file.
@ -13,12 +14,10 @@
#include "tests.h" #include "tests.h"
static uint8_t buf[LZMA_BLOCK_HEADER_SIZE_MAX];
static lzma_block known_options;
static lzma_block decoded_options;
static lzma_options_lzma opt_lzma; static lzma_options_lzma opt_lzma;
#ifdef HAVE_ENCODERS
static lzma_filter filters_none[1] = { static lzma_filter filters_none[1] = {
{ {
.id = LZMA_VLI_UNKNOWN, .id = LZMA_VLI_UNKNOWN,
@ -75,163 +74,423 @@ static lzma_filter filters_five[6] = {
.id = LZMA_VLI_UNKNOWN, .id = LZMA_VLI_UNKNOWN,
} }
}; };
#endif
static void static void
code(void) test_lzma_block_header_size(void)
{ {
assert_lzma_ret(lzma_block_header_encode(&known_options, buf), #ifndef HAVE_ENCODERS
LZMA_OK); assert_skip("Encoder support disabled");
#else
if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86))
assert_skip("x86 BCJ encoder is disabled");
lzma_filter filters[LZMA_FILTERS_MAX + 1]; lzma_block block = {
memcrap(filters, sizeof(filters)); .version = 0,
memcrap(&decoded_options, sizeof(decoded_options));
decoded_options.header_size = known_options.header_size;
decoded_options.check = known_options.check;
decoded_options.filters = filters;
assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf),
LZMA_OK);
assert_uint_eq(decoded_options.compressed_size,
known_options.compressed_size);
assert_uint_eq(decoded_options.uncompressed_size,
known_options.uncompressed_size);
for (size_t i = 0; known_options.filters[i].id
!= LZMA_VLI_UNKNOWN; ++i)
assert_uint_eq(filters[i].id, known_options.filters[i].id);
for (size_t i = 0; i < LZMA_FILTERS_MAX; ++i)
free(decoded_options.filters[i].options);
}
static void
test1(void)
{
known_options = (lzma_block){
.check = LZMA_CHECK_NONE,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.filters = NULL,
};
assert_lzma_ret(lzma_block_header_size(&known_options),
LZMA_PROG_ERROR);
known_options.filters = filters_none;
assert_lzma_ret(lzma_block_header_size(&known_options),
LZMA_PROG_ERROR);
known_options.filters = filters_five;
assert_lzma_ret(lzma_block_header_size(&known_options),
LZMA_PROG_ERROR);
known_options.filters = filters_one;
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
// Some invalid value, which gets ignored.
known_options.check = (lzma_check)(99);
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
known_options.compressed_size = 5;
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
known_options.compressed_size = 0; // Cannot be zero.
assert_lzma_ret(lzma_block_header_size(&known_options),
LZMA_PROG_ERROR);
// LZMA_VLI_MAX is too big to keep the total size of the Block
// a valid VLI, but lzma_block_header_size() is not meant
// to validate it. (lzma_block_header_encode() must validate it.)
known_options.compressed_size = LZMA_VLI_MAX;
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
known_options.compressed_size = LZMA_VLI_UNKNOWN;
known_options.uncompressed_size = 0;
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
known_options.uncompressed_size = LZMA_VLI_MAX + 1;
assert_lzma_ret(lzma_block_header_size(&known_options),
LZMA_PROG_ERROR);
}
static void
test2(void)
{
known_options = (lzma_block){
.check = LZMA_CHECK_CRC32,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.filters = filters_four,
};
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
code();
known_options.compressed_size = 123456;
known_options.uncompressed_size = 234567;
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK);
code();
// We can make the sizes smaller while keeping the header size
// the same.
known_options.compressed_size = 12;
known_options.uncompressed_size = 23;
code();
}
static void
test3(void)
{
known_options = (lzma_block){
.check = LZMA_CHECK_CRC32,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.filters = filters_one, .filters = filters_one,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.check = LZMA_CHECK_CRC32
}; };
assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); // Test that all initial options are valid
known_options.header_size += 4; assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_lzma_ret(lzma_block_header_encode(&known_options, buf), assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
LZMA_OK); assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
lzma_filter filters[LZMA_FILTERS_MAX + 1]; // Test invalid version number
decoded_options.header_size = known_options.header_size; for (uint32_t i = 2; i < 20; i++) {
decoded_options.check = known_options.check; block.version = i;
decoded_options.filters = filters; assert_lzma_ret(lzma_block_header_size(&block),
LZMA_OPTIONS_ERROR);
}
// Wrong size block.version = 1;
++buf[0];
assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), // Test invalid compressed size
block.compressed_size = 0;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.compressed_size = LZMA_VLI_MAX + 1;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.compressed_size = LZMA_VLI_UNKNOWN;
// Test invalid uncompressed size
block.uncompressed_size = LZMA_VLI_MAX + 1;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.uncompressed_size = LZMA_VLI_MAX;
// Test invalid filters
block.filters = NULL;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.filters = filters_none;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.filters = filters_five;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR);
block.filters = filters_one;
// Test setting compressed_size to something valid
block.compressed_size = 4096;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
// Test setting uncompressed_size to something valid
block.uncompressed_size = 4096;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
// This should pass, but header_size will be an invalid value
// because the total block size will not be able to fit in a valid
// lzma_vli. This way a temporary value can be used to reserve
// space for the header and later the actual value can be set.
block.compressed_size = LZMA_VLI_MAX;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
// Use an invalid value for a filter option. This should still pass
// because the size of the LZMA2 properties is known by liblzma
// without reading any of the options so it doesn't validate them.
lzma_options_lzma bad_ops;
assert_false(lzma_lzma_preset(&bad_ops, 1));
bad_ops.pb = 0x1000;
lzma_filter bad_filters[2] = {
{
.id = LZMA_FILTER_LZMA2,
.options = &bad_ops
},
{
.id = LZMA_VLI_UNKNOWN,
.options = NULL
}
};
block.filters = bad_filters;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
// Use an invalid block option. The check type isn't stored in
// the Block Header and so _header_size ignores it.
block.check = 0x1000;
block.ignore_check = false;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN);
assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX);
assert_uint_eq(block.header_size % 4, 0);
#endif
}
static void
test_lzma_block_header_encode(void)
{
#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS)
assert_skip("Encoder or decoder support disabled");
#else
if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86)
|| !lzma_filter_decoder_is_supported(LZMA_FILTER_X86))
assert_skip("x86 BCJ encoder and/or decoder "
"is disabled");
lzma_block block = {
.version = 1,
.filters = filters_one,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.check = LZMA_CHECK_CRC32,
};
// Ensure all block options are valid before changes are tested
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
uint8_t out[LZMA_BLOCK_HEADER_SIZE_MAX];
// Test invalid block version
for (uint32_t i = 2; i < 20; i++) {
block.version = i;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
}
block.version = 1;
// Test invalid header size (< min, > max, % 4 != 0)
block.header_size = LZMA_BLOCK_HEADER_SIZE_MIN - 4;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR); LZMA_PROG_ERROR);
--buf[0]; block.header_size = LZMA_BLOCK_HEADER_SIZE_MIN + 2;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.header_size = LZMA_BLOCK_HEADER_SIZE_MAX + 4;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
// Wrong CRC32 // Test invalid compressed_size
buf[known_options.header_size - 1] ^= 1; block.compressed_size = 0;
assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.compressed_size = LZMA_VLI_MAX + 1;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
// This test passes test_lzma_block_header_size, but should
// fail here because there is not enough space to encode the
// proper block size because the total size is too big to fit
// in an lzma_vli
block.compressed_size = LZMA_VLI_MAX;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.compressed_size = LZMA_VLI_UNKNOWN;
// Test invalid uncompressed size
block.uncompressed_size = LZMA_VLI_MAX + 1;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.uncompressed_size = LZMA_VLI_UNKNOWN;
// Test invalid block check
block.check = 0x1000;
block.ignore_check = false;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.check = LZMA_CHECK_CRC32;
// Test invalid filters
block.filters = NULL;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.filters = filters_none;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
block.filters = filters_five;
block.header_size = LZMA_BLOCK_HEADER_SIZE_MAX - 4;
assert_lzma_ret(lzma_block_header_encode(&block, out),
LZMA_PROG_ERROR);
// Test valid encoding and verify bytes of block header.
// More complicated tests for encoding headers are included
// in test_lzma_block_header_decode.
block.filters = filters_one;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK);
// First read block header size from out and verify
// that it == (encoded size + 1) * 4
uint32_t header_size = (out[0] + 1U) * 4;
assert_uint_eq(header_size, block.header_size);
// Next read block flags
uint8_t flags = out[1];
// Should have number of filters = 1
assert_uint_eq((flags & 0x3) + 1, 1);
// Bits 2-7 must be empty not set
assert_uint_eq(flags & (0xFF - 0x3), 0);
// Verify filter flags
// Decode Filter ID
lzma_vli filter_id = 0;
size_t pos = 2;
assert_lzma_ret(lzma_vli_decode(&filter_id, NULL, out,
&pos, header_size), LZMA_OK);
assert_uint_eq(filter_id, filters_one[0].id);
// Decode Size of Properties
lzma_vli prop_size = 0;
assert_lzma_ret(lzma_vli_decode(&prop_size, NULL, out,
&pos, header_size), LZMA_OK);
// LZMA2 has 1 byte prop size
assert_uint_eq(prop_size, 1);
uint8_t expected_filter_props = 0;
assert_lzma_ret(lzma_properties_encode(filters_one,
&expected_filter_props), LZMA_OK);
assert_uint_eq(out[pos], expected_filter_props);
pos++;
// Check null-padding
for (size_t i = pos; i < header_size - 4; i++)
assert_uint_eq(out[i], 0);
// Check CRC32
assert_uint_eq(read32le(&out[header_size - 4]), lzma_crc32(out,
header_size - 4, 0));
#endif
}
#if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS)
// Helper function to compare two lzma_block structures field by field
static void
compare_blocks(lzma_block *block_expected, lzma_block *block_actual)
{
assert_uint_eq(block_actual->version, block_expected->version);
assert_uint_eq(block_actual->compressed_size,
block_expected->compressed_size);
assert_uint_eq(block_actual->uncompressed_size,
block_expected->uncompressed_size);
assert_uint_eq(block_actual->check, block_expected->check);
assert_uint_eq(block_actual->header_size, block_expected->header_size);
// Compare filter IDs
assert_true(block_expected->filters && block_actual->filters);
lzma_filter expected_filter = block_expected->filters[0];
uint32_t filter_count = 0;
while (expected_filter.id != LZMA_VLI_UNKNOWN) {
assert_uint_eq(block_actual->filters[filter_count].id,
expected_filter.id);
expected_filter = block_expected->filters[++filter_count];
}
assert_uint_eq(block_actual->filters[filter_count].id,
LZMA_VLI_UNKNOWN);
}
#endif
static void
test_lzma_block_header_decode(void)
{
#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS)
assert_skip("Encoder or decoder support disabled");
#else
if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86)
|| !lzma_filter_decoder_is_supported(LZMA_FILTER_X86))
assert_skip("x86 BCJ encoder and/or decoder "
"is disabled");
lzma_block block = {
.filters = filters_one,
.compressed_size = LZMA_VLI_UNKNOWN,
.uncompressed_size = LZMA_VLI_UNKNOWN,
.check = LZMA_CHECK_CRC32,
.version = 0
};
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
// Encode block header with simple options
uint8_t out[LZMA_BLOCK_HEADER_SIZE_MAX];
assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK);
// Decode block header and check that the options match
lzma_filter decoded_filters[LZMA_FILTERS_MAX + 1];
lzma_block decoded_block = {
.version = 0,
.filters = decoded_filters,
.check = LZMA_CHECK_CRC32
};
decoded_block.header_size = lzma_block_header_size_decode(out[0]);
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OK);
compare_blocks(&block, &decoded_block);
// Reset output buffer and decoded_block
memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX);
memzero(&decoded_block, sizeof(lzma_block));
decoded_block.filters = decoded_filters;
decoded_block.check = LZMA_CHECK_CRC32;
// Test with compressed size set
block.compressed_size = 4096;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK);
decoded_block.header_size = lzma_block_header_size_decode(out[0]);
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OK);
compare_blocks(&block, &decoded_block);
memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX);
memzero(&decoded_block, sizeof(lzma_block));
decoded_block.filters = decoded_filters;
decoded_block.check = LZMA_CHECK_CRC32;
// Test with uncompressed size set
block.uncompressed_size = 4096;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK);
decoded_block.header_size = lzma_block_header_size_decode(out[0]);
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OK);
compare_blocks(&block, &decoded_block);
memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX);
memzero(&decoded_block, sizeof(lzma_block));
decoded_block.filters = decoded_filters;
decoded_block.check = LZMA_CHECK_CRC32;
// Test with multiple filters
block.filters = filters_four;
assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK);
assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK);
decoded_block.header_size = lzma_block_header_size_decode(out[0]);
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OK);
compare_blocks(&block, &decoded_block);
memzero(&decoded_block, sizeof(lzma_block));
decoded_block.filters = decoded_filters;
decoded_block.check = LZMA_CHECK_CRC32;
decoded_block.header_size = lzma_block_header_size_decode(out[0]);
// Test with too high version. The decoder will set it to a version
// that it supports.
decoded_block.version = 2;
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OK);
assert_uint_eq(decoded_block.version, 1);
// Test bad check type
decoded_block.check = LZMA_CHECK_ID_MAX + 1;
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_PROG_ERROR);
decoded_block.check = LZMA_CHECK_CRC32;
// Test bad check value
out[decoded_block.header_size - 1] -= 10;
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_DATA_ERROR); LZMA_DATA_ERROR);
buf[known_options.header_size - 1] ^= 1; out[decoded_block.header_size - 1] += 10;
// Unsupported filter // Test non-NULL padding
// NOTE: This may need updating when new IDs become supported. out[decoded_block.header_size - 5] = 1;
buf[2] ^= 0x1F;
write32le(buf + known_options.header_size - 4,
lzma_crc32(buf, known_options.header_size - 4, 0));
assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf),
LZMA_OPTIONS_ERROR);
buf[2] ^= 0x1F;
// Non-nul Padding // Recompute CRC32
buf[known_options.header_size - 4 - 1] ^= 1; write32le(&out[decoded_block.header_size - 4], lzma_crc32(out,
write32le(buf + known_options.header_size - 4, decoded_block.header_size - 4, 0));
lzma_crc32(buf, known_options.header_size - 4, 0)); assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf),
LZMA_OPTIONS_ERROR); LZMA_OPTIONS_ERROR);
buf[known_options.header_size - 4 - 1] ^= 1;
// Test unsupported flags
out[1] = 0xFF;
// Recompute CRC32
write32le(&out[decoded_block.header_size - 4], lzma_crc32(out,
decoded_block.header_size - 4, 0));
assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out),
LZMA_OPTIONS_ERROR);
#endif
} }
@ -240,17 +499,12 @@ main(int argc, char **argv)
{ {
tuktest_start(argc, argv); tuktest_start(argc, argv);
if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86)
|| !lzma_filter_decoder_is_supported(LZMA_FILTER_X86))
tuktest_early_skip("x86 BCJ encoder and/or decoder "
"is disabled");
if (lzma_lzma_preset(&opt_lzma, 1)) if (lzma_lzma_preset(&opt_lzma, 1))
tuktest_error("lzma_lzma_preset() failed"); tuktest_error("lzma_lzma_preset() failed");
tuktest_run(test1); tuktest_run(test_lzma_block_header_size);
tuktest_run(test2); tuktest_run(test_lzma_block_header_encode);
tuktest_run(test3); tuktest_run(test_lzma_block_header_decode);
return tuktest_end(); return tuktest_end();
} }