Files
c_progarm/lzw-ab/lzwtester.c

318 lines
9.8 KiB
C
Raw Permalink Normal View History

2023-12-02 11:52:15 +08:00
////////////////////////////////////////////////////////////////////////////
// **** LZW-AB **** //
// Adjusted Binary LZW Compressor/Decompressor //
// Copyright (c) 2016-2020 David Bryant //
// All Rights Reserved //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include "lzwlib.h"
/* This module provides a command-line test harness for the lzw library.
* Given a list of files, it will read each one and byte-for-byte verify
* the data after a round-trip through a compression / decompression cycle
* at each of the 8 available maximum symbol size settings.
*
* It can also optionally perform fuzz testing by randomly corrupting the
* compressed bitstream. Obviously this will introduce integrity failures,
* but it should not cause a crash. It also has an "exhaustive" mode that
* creates hundreds of simulated images from each input file by successive
* truncation from both ends.
*/
static const char *usage =
" Usage: lzwtester [options] file [...]\n\n"
" Options: -1 ... -8 = test using only specified max symbol size (9 - 16)\n"
" -0 = cycle through all maximum symbol sizes (default)\n"
" -e = exhaustive test (by successive truncation)\n"
" -f = fuzz test (randomly corrupt compressed data)\n"
" -q = quiet mode (only reports errors and summary)\n\n"
" Web: Visit www.github.com/dbry/lzw-ab for latest version and info\n\n";
typedef struct {
unsigned int size, index, wrapped, byte_errors, first_error, fuzz_testing;
unsigned char *buffer;
} streamer;
static int read_buff (void *ctx)
{
streamer *stream = ctx;
if (stream->index == stream->size)
return EOF;
return stream->buffer [stream->index++];
}
static void write_buff (int value, void *ctx)
{
streamer *stream = ctx;
// for fuzz testing, randomly corrupt 1 byte in every 65536 (on average)
if (stream->fuzz_testing) {
static unsigned long long kernel = 0x3141592653589793;
kernel = ((kernel << 4) - kernel) ^ 1;
kernel = ((kernel << 4) - kernel) ^ 1;
kernel = ((kernel << 4) - kernel) ^ 1;
if (!(kernel >> 48))
value ^= (int)(kernel >> 40);
}
if (stream->index == stream->size) {
stream->index = 0;
stream->wrapped++;
}
stream->buffer [stream->index++] = value;
}
static void check_buff (int value, void *ctx)
{
streamer *stream = ctx;
if (stream->index == stream->size) {
stream->wrapped++;
return;
}
if (stream->buffer [stream->index] != value)
if (!stream->byte_errors++)
stream->first_error = stream->index;
stream->index++;
}
#ifdef _WIN32
long long DoGetFileSize (FILE *hFile)
{
LARGE_INTEGER Size;
HANDLE fHandle;
if (hFile == NULL)
return 0;
fHandle = (HANDLE)_get_osfhandle(_fileno(hFile));
if (fHandle == INVALID_HANDLE_VALUE)
return 0;
Size.u.LowPart = GetFileSize(fHandle, &Size.u.HighPart);
if (Size.u.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
return 0;
return (long long)Size.QuadPart;
}
#else
long long DoGetFileSize (FILE *hFile)
{
struct stat statbuf;
if (!hFile || fstat (fileno (hFile), &statbuf) || !S_ISREG(statbuf.st_mode))
return 0;
return (long long) statbuf.st_size;
}
#endif
int main (int argc, char **argv)
{
int index, checked = 0, tests = 0, skipped = 0, errors = 0;
int set_maxbits = 0, quiet_mode = 0, exhaustive_mode = 0;
long long total_input_bytes = 0, total_output_bytes = 0;
streamer reader, writer, checker;
memset (&reader, 0, sizeof (reader));
memset (&writer, 0, sizeof (writer));
memset (&checker, 0, sizeof (checker));
if (argc < 2) {
printf ("%s", usage);
return 0;
}
for (index = 1; index < argc; ++index) {
const char *filename = argv [index];
int test_size, bytes_read, maxbits;
unsigned char *file_buffer;
long long file_size;
FILE *infile;
if (!strcmp (filename, "-q")) {
quiet_mode = 1;
continue;
}
if (!strcmp (filename, "-e")) {
exhaustive_mode = 1;
continue;
}
if (!strcmp (filename, "-f")) {
writer.fuzz_testing = 1;
continue;
}
if (strlen (filename) == 2 && filename [0] == '-' && filename [1] >= '0' && filename [1] <= '8') {
if (filename [1] > '0')
set_maxbits = filename [1] - '0' + 8;
else
set_maxbits = 0;
continue;
}
infile = fopen (filename, "rb");
if (!infile) {
printf ("\ncan't open file %s!\n", filename);
skipped++;
continue;
}
file_size = DoGetFileSize (infile);
if (!file_size) {
printf ("\ncan't get file size of %s (may be zero)!\n", filename);
skipped++;
continue;
}
if (file_size > 1024LL * 1024LL * 1024LL) {
printf ("\nfile %s is too big!\n", filename);
skipped++;
continue;
}
file_buffer = malloc (file_size);
writer.size = (unsigned int)(file_size * 2 + 10);
writer.buffer = malloc (writer.size);
if (!file_buffer || !writer.buffer) {
printf ("\nfile %s is too big!\n", filename);
if (writer.buffer) free (writer.buffer);
if (file_buffer) free (file_buffer);
skipped++;
continue;
}
bytes_read = fread (file_buffer, 1, (int) file_size, infile);
fclose (infile);
if (bytes_read != (int) file_size) {
printf ("\nfile %s could not be read!\n", filename);
free (writer.buffer);
free (file_buffer);
skipped++;
continue;
}
if (!quiet_mode)
printf ("\n");
test_size = file_size;
checked++;
do {
for (maxbits = set_maxbits ? set_maxbits : 9; maxbits <= (set_maxbits ? set_maxbits : 16); ++maxbits) {
int res, got_error = 0;
reader.buffer = file_buffer + (file_size - test_size) / 2;
reader.size = test_size;
reader.index = writer.index = writer.wrapped = 0;
if (lzw_compress (write_buff, &writer, read_buff, &reader, maxbits)) {
printf ("\nlzw_compress() returned error on file %s, maxbits = %d\n", filename, maxbits);
errors++;
continue;
}
if (writer.wrapped) {
printf ("\nover 100%% inflation on file %s, maxbits = %d!\n", filename, maxbits);
errors++;
continue;
}
checker.buffer = reader.buffer;
checker.size = reader.size;
checker.wrapped = checker.byte_errors = checker.index = 0;
reader.buffer = writer.buffer;
reader.size = writer.index;
reader.index = 0;
res = lzw_decompress (check_buff, &checker, read_buff, &reader);
reader.buffer = checker.buffer;
reader.size = checker.size;
got_error = res || checker.index != checker.size || checker.wrapped || checker.byte_errors;
if (!quiet_mode || got_error)
printf ("file %s, maxbits = %2d: %u bytes --> %u bytes, %.2f%%\n", filename, maxbits,
reader.size, writer.index, writer.index * 100.0 / reader.size);
if (got_error) {
if (res)
printf ("decompressor returned an error\n");
if (!checker.index)
printf ("decompression didn't generate any data\n");
else if (checker.index != checker.size)
printf ("decompression terminated %u bytes early\n", checker.size - checker.index);
else if (checker.wrapped)
printf ("decompression generated %u extra bytes\n", checker.wrapped);
if (checker.byte_errors)
printf ("there were %u byte data errors starting at index %u\n",
checker.byte_errors, checker.first_error);
else if (checker.index != checker.size || checker.wrapped)
printf ("(but the data generated was all correct)\n");
printf ("\n");
errors++;
}
else {
total_input_bytes += reader.size;
total_output_bytes += writer.index;
}
tests++;
if (exhaustive_mode)
test_size -= (test_size + 98) / 100;
}
} while (exhaustive_mode && test_size > 1 && test_size > file_size / 100);
free (writer.buffer);
free (file_buffer);
}
if (errors)
printf ("\n***** %d errors detected in %d tests using %d files (%d skipped) *****\n\n", errors, tests, checked, skipped);
else {
printf ("\nsuccessfully ran %d tests using %d files (%d skipped) with no errors detected\n", tests, checked, skipped);
printf ("cumulative results: %llu bytes --> %llu bytes, %.2f%%\n\n", total_input_bytes, total_output_bytes,
total_output_bytes * 100.0 / total_input_bytes);
}
return errors;
}