From ca9488ce89bd4895db0341db393ec03696b4ebd3 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Fri, 11 May 2012 19:30:57 -0400 Subject: [PATCH] engine: Add a unit test for the PCX run-length encoder. Since there ended up being an off-by-one bug here, we should test it. We use the same hex decoder as decodeindex, so move that into a common test library. --- Makefile.am | 12 ++- test/.gitignore | 1 + test/common.c | 57 ++++++++++ test/common.h | 6 ++ test/decodeindex.c | 40 +------ test/pcxrle.c | 196 +++++++++++++++++++++++++++++++++++ tests/engine-pcx-rlencode.sh | 27 +++++ 7 files changed, 298 insertions(+), 41 deletions(-) create mode 100644 test/common.c create mode 100644 test/common.h create mode 100644 test/pcxrle.c create mode 100644 tests/engine-pcx-rlencode.sh diff --git a/Makefile.am b/Makefile.am index fed99c1..7023885 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,15 +76,21 @@ engine_la_SOURCES += src/engine/music-modplug.c src/engine/modplug-types.h engine_la_LIBADD += $(LIBMODPLUG_LIBS) endif -check_PROGRAMS = test/decodeindex -test_decodeindex_LDADD = libupkg.la libgnu.la +check_LTLIBRARIES = libtest.la +check_PROGRAMS = test/decodeindex test/pcxrle + +libtest_la_SOURCES = test/common.c + +test_decodeindex_LDADD = libupkg.la libgnu.la libtest.la $(test_decodeindex_OBJECTS): $(gnulib_headers) +test_pcxrle_LDADD = src/engine/pcx.lo libupkg.la libgnu.la libtest.la +$(test_pcxrle_OBJECTS): $(gnulib_headers) TESTS_ENVIRONMENT = SHELL='$(SHELL)' EXEEXT='$(EXEEXT)' TEST_EXTENSIONS = .sh SH_LOG_COMPILER = $(SHELL) -TESTS = tests/libupkg-index-decode.sh +TESTS = tests/libupkg-index-decode.sh tests/engine-pcx-rlencode.sh EXTRA_DIST += $(TESTS) # Supporting rules for GObject Builder diff --git a/test/.gitignore b/test/.gitignore index cd79e46..1714580 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1 +1,2 @@ decodeindex +pcxrle diff --git a/test/common.c b/test/common.c new file mode 100644 index 0000000..c998624 --- /dev/null +++ b/test/common.c @@ -0,0 +1,57 @@ +/* + * Helper functions for test programs. + * Copyright © 2012 Nick Bowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +#include "common.h" + +/* + * Decode a hexadecimal string into a sequence of bytes. If there are an + * odd number of nibbles, treat the first character as the least significant + * nibble of the first byte. The result is written to the buffer specified by + * buf. At most n bytes are written to the buffer. + * + * Returns the number of bytes that would be written provided that n was large + * enough, or (size_t)-1 if the input is not valid. + */ +size_t test_decode_hex(const char *hex, unsigned char *buf, size_t n) +{ + size_t len, count = 0; + char tmp[] = "00"; + + for (len = 0; hex[len]; len++) { + if (!isxdigit(hex[len])) + return -1; + } + + if (!len) + return 0; + + switch (len % 2) { + while (len > 0) { + case 0: tmp[0] = *hex++; len--; + case 1: tmp[1] = *hex++; len--; + + if (count < n) + buf[count] = strtoul(tmp, NULL, 16); + count++; + } + } + + return count; +} diff --git a/test/common.h b/test/common.h new file mode 100644 index 0000000..907ee5a --- /dev/null +++ b/test/common.h @@ -0,0 +1,6 @@ +#ifndef TEST_COMMON_H_ +#define TEST_COMMON_H_ + +size_t test_decode_hex(const char *hex, unsigned char *buf, size_t n); + +#endif diff --git a/test/decodeindex.c b/test/decodeindex.c index 415fa79..c072a07 100644 --- a/test/decodeindex.c +++ b/test/decodeindex.c @@ -19,10 +19,10 @@ #include #include #include -#include #include #include +#include "common.h" #define PROGNAME "decodeindex" static const char *progname = PROGNAME; @@ -33,42 +33,6 @@ static const struct option lopts[] = { { 0 } }; -/* - * Decode a hexadecimal string into a sequence of bytes. If there are an - * odd number of nibbles, treat the first character as the least significant - * nibble of the first byte. The result is written to the buffer specified by - * buf. At most n bytes are written to the buffer. - * - * Returns the number of bytes that would be written provided that n was large - * enough, or (size_t)-1 if the input is not valid. - */ -static size_t decode_hex(const char *hex, unsigned char *buf, size_t n) -{ - size_t len, count = 0; - char tmp[] = "00"; - - for (len = 0; hex[len]; len++) { - if (!isxdigit(hex[len])) - return -1; - } - - if (!len) - return 0; - - switch (len % 2) { - while (len > 0) { - case 0: tmp[0] = *hex++; len--; - case 1: tmp[1] = *hex++; len--; - - if (count < n) - buf[count] = strtoul(tmp, NULL, 16); - count++; - } - } - - return count; -} - static void print_usage(FILE *f) { fprintf(f, "Usage: %s [options] index [index ...]\n", progname); @@ -104,7 +68,7 @@ static int print_index(const char *hex) size_t n, rc; int ret = -1; - n = decode_hex(hex, buf, sizeof buf); + n = test_decode_hex(hex, buf, sizeof buf); if (n == -1) { fprintf(stderr, "%s: invalid hex sequence: %s\n", progname, hex); diff --git a/test/pcxrle.c b/test/pcxrle.c new file mode 100644 index 0000000..6824fd0 --- /dev/null +++ b/test/pcxrle.c @@ -0,0 +1,196 @@ +/* + * Tool for testing PCX run-length encoding. + * Copyright © 2012 Nick Bowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "common.h" + +#define PROGNAME "pcxrle" +static const char *progname = PROGNAME; +static const char sopts[] = "VH"; +static const struct option lopts[] = { + { "version", 0, NULL, 'V' }, + { "help", 0, NULL, 'H' }, + { 0 } +}; + +static void print_usage(FILE *f) +{ + fprintf(f, "Usage: %s [options] bytes [bytes ...]\n", progname); +} + +static void print_version(void) +{ + printf("%s (%s) %s\n", PROGNAME, PACKAGE_NAME, PACKAGE_VERSION); + puts("Copyright (C) 2012 Nick Bowler."); + puts("License GPLv3+: GNU GPL version 3 or later ."); + puts("This is free software: you are free to change and redistribute it."); + puts("There is NO WARRANTY, to the extent permitted by law."); +} + +static void rle_error(const unsigned char *raw, size_t n, const char *str) +{ + fprintf(stderr, "%s: %s\n", progname, str); + fprintf(stderr, "%s: raw input was:\n", progname); + + fprintf(stderr, "%*s", 4, ""); + for (size_t i = 0; i < n; i++) + printf("%.2hhx", raw[i]); + putchar('\n'); +} + +static int compare_rle(const unsigned char *rle_buf, size_t rle_len, + const unsigned char *raw_buf, size_t raw_len) +{ + unsigned char tmp[raw_len]; + size_t offset = 0; + + for (size_t i = 0; i < rle_len; i++) { + size_t count = 1; + + if ((rle_buf[i] & 0xc0) == 0xc0) { + count = rle_buf[i] & 0x3f; + if (++i >= rle_len) { + rle_error(raw_buf, raw_len, "invalid RLE encoding"); + return -1; + } + } + + if (offset + count > raw_len) { + rle_error(raw_buf, raw_len, "RLE expansion exceeds raw size"); + return -1; + } + + memset(tmp+offset, rle_buf[i], count); + offset += count; + } + + if (offset < raw_len) { + rle_error(raw_buf, raw_len, "RLE expansion falls short of raw size"); + return -1; + } + + if (memcmp(raw_buf, tmp, raw_len) != 0) { + rle_error(raw_buf, raw_len, "RLE expansion does not match raw data"); + return -1; + } + + return 0; +} + +static int encode_rle(FILE *tmp, const char *hex) +{ + unsigned char buf[256], rle_buf[sizeof buf*2]; + struct pcx_head head; + int rc, ret = -1; + long offset; + size_t n; + + n = test_decode_hex(hex, buf, sizeof buf); + if (n == -1) { + fprintf(stderr, "%s: invalid hex sequence: %s\n", + progname, hex); + return -1; + } else if (n == 0) { + fprintf(stderr, "%s: empty argument\n", progname); + return -1; + } else if (n > sizeof buf) { + fprintf(stderr, "%s: hex sequence too long: %s\n", + progname, hex); + return -1; + } + + rewind(tmp); + head.width = head.height = n; + rc = pcx_write_scanline(&head, buf, tmp); + if (rc != 0) { + fprintf(stderr, "%s: failed to encode data: %s\n", + progname, strerror(errno)); + return -1; + } + + offset = ftell(tmp); + if (offset < 0) { + fprintf(stderr, "%s: failed to get file offset: %s\n", + progname, strerror(errno)); + return -1; + } + rewind(tmp); + + if (fread(rle_buf, offset, 1, tmp) != 1) { + fprintf(stderr, "%s: failed to read RLE data: %s\n", + progname, strerror(errno)); + return -1; + } + + ret = compare_rle(rle_buf, offset, buf, n); + for (long i = 0; i < offset; i++) + printf("%.2hhx", rle_buf[i]); + putchar('\n'); + return ret; +} + +int main(int argc, char **argv) +{ + int opt, ret = EXIT_SUCCESS; + FILE *tmp; + + if (argc > 0) + progname = argv[0]; + + while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { + switch (opt) { + case 'V': + print_version(); + return EXIT_SUCCESS; + case 'H': + print_usage(stdout); + return EXIT_SUCCESS; + default: + print_usage(stderr); + return EXIT_FAILURE; + } + } + + if (!argv[optind]) { + print_usage(stderr); + return EXIT_FAILURE; + } + + tmp = tmpfile(); + if (!tmp) { + fprintf(stderr, "%s: failed to create temporary file: %s\n", + progname, strerror(errno)); + return EXIT_FAILURE; + } + + for (int i = optind; i < argc; i++) { + if (encode_rle(tmp, argv[i]) != 0) { + ret = EXIT_FAILURE; + } + } + + return ret; +} diff --git a/tests/engine-pcx-rlencode.sh b/tests/engine-pcx-rlencode.sh new file mode 100644 index 0000000..3a05444 --- /dev/null +++ b/tests/engine-pcx-rlencode.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# Check corner cases of the PCX run-length encoder. +# Copyright © 2012 Nick Bowler +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. + +pcxrle=test/pcxrle$EXEEXT + +$pcxrle 00 || exit 1 +$pcxrle 0000 || exit 1 +$pcxrle c100 || exit 1 + +# Test RLE rollover +t1=00 +t2=$t1$t1 +t4=$t2$t2 +t8=$t4$t4 +t16=$t8$t8 +t32=$t16$t16 +t64=$t32$t32 + +$pcxrle "$t32$t16$t8$t4$t2$t1" || exit 1 # maximum possible run +$pcxrle "$t64" || exit 1 +$pcxrle "$t64$t64$t64" || exit 1 -- 2.43.0