]> git.draconx.ca Git - upkg.git/commitdiff
engine: Add a unit test for the PCX run-length encoder.
authorNick Bowler <nbowler@draconx.ca>
Fri, 11 May 2012 23:30:57 +0000 (19:30 -0400)
committerNick Bowler <nbowler@draconx.ca>
Fri, 11 May 2012 23:38:34 +0000 (19:38 -0400)
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
test/.gitignore
test/common.c [new file with mode: 0644]
test/common.h [new file with mode: 0644]
test/decodeindex.c
test/pcxrle.c [new file with mode: 0644]
tests/engine-pcx-rlencode.sh [new file with mode: 0644]

index fed99c164e25cdc88871d592c9ac63ba1ce6fb7e..70238856f3ea0b6acf4299265aa32efee89e9f2d 100644 (file)
@@ -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
index cd79e46cb913bcd08cbdff07d6de4b8b8ed7b840..17145802b486f73d4b135489a0807fa2400bf99b 100644 (file)
@@ -1 +1,2 @@
 decodeindex
+pcxrle
diff --git a/test/common.c b/test/common.c
new file mode 100644 (file)
index 0000000..c998624
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+#include <stdlib.h>
+#include <ctype.h>
+
+#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 (file)
index 0000000..907ee5a
--- /dev/null
@@ -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
index 415fa7904868598104ebed07c51cf271bc8bd1fd..c072a07c25f72a41c800aa5a7ef368dc02489d3e 100644 (file)
 #include <config.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <ctype.h>
 #include <getopt.h>
 
 #include <upkg.h>
+#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 (file)
index 0000000..6824fd0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <engine/pcx.h>
+#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 <http://gnu.org/licenses/gpl.html>.");
+       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 (file)
index 0000000..3a05444
--- /dev/null
@@ -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