]> git.draconx.ca Git - upkg.git/blob - test/pcxrle.c
engine: Add a unit test for the PCX run-length encoder.
[upkg.git] / test / pcxrle.c
1 /*
2  * Tool for testing PCX run-length encoding.
3  * Copyright © 2012 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <getopt.h>
24 #include <errno.h>
25 #include <assert.h>
26
27 #include <engine/pcx.h>
28 #include "common.h"
29
30 #define PROGNAME "pcxrle"
31 static const char *progname = PROGNAME;
32 static const char sopts[] = "VH";
33 static const struct option lopts[] = {
34         { "version", 0, NULL, 'V' },
35         { "help",    0, NULL, 'H' },
36         { 0 }
37 };
38
39 static void print_usage(FILE *f)
40 {
41         fprintf(f, "Usage: %s [options] bytes [bytes ...]\n", progname);
42 }
43
44 static void print_version(void)
45 {
46         printf("%s (%s) %s\n", PROGNAME, PACKAGE_NAME, PACKAGE_VERSION);
47         puts("Copyright (C) 2012 Nick Bowler.");
48         puts("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.");
49         puts("This is free software: you are free to change and redistribute it.");
50         puts("There is NO WARRANTY, to the extent permitted by law.");
51 }
52
53 static void rle_error(const unsigned char *raw, size_t n, const char *str)
54 {
55         fprintf(stderr, "%s: %s\n", progname, str);
56         fprintf(stderr, "%s: raw input was:\n", progname);
57
58         fprintf(stderr, "%*s", 4, "");
59         for (size_t i = 0; i < n; i++)
60                 printf("%.2hhx", raw[i]);
61         putchar('\n');
62 }
63
64 static int compare_rle(const unsigned char *rle_buf, size_t rle_len,
65                        const unsigned char *raw_buf, size_t raw_len)
66 {
67         unsigned char tmp[raw_len];
68         size_t offset = 0;
69
70         for (size_t i = 0; i < rle_len; i++) {
71                 size_t count = 1;
72
73                 if ((rle_buf[i] & 0xc0) == 0xc0) {
74                         count = rle_buf[i] & 0x3f;
75                         if (++i >= rle_len) {
76                                 rle_error(raw_buf, raw_len, "invalid RLE encoding");
77                                 return -1;
78                         }
79                 }
80
81                 if (offset + count > raw_len) {
82                         rle_error(raw_buf, raw_len, "RLE expansion exceeds raw size");
83                         return -1;
84                 }
85
86                 memset(tmp+offset, rle_buf[i], count);
87                 offset += count;
88         }
89
90         if (offset < raw_len) {
91                 rle_error(raw_buf, raw_len, "RLE expansion falls short of raw size");
92                 return -1;
93         }
94
95         if (memcmp(raw_buf, tmp, raw_len) != 0) {
96                 rle_error(raw_buf, raw_len, "RLE expansion does not match raw data");
97                 return -1;
98         }
99
100         return 0;
101 }
102
103 static int encode_rle(FILE *tmp, const char *hex)
104 {
105         unsigned char buf[256], rle_buf[sizeof buf*2];
106         struct pcx_head head;
107         int rc, ret = -1;
108         long offset;
109         size_t n;
110
111         n = test_decode_hex(hex, buf, sizeof buf);
112         if (n == -1) {
113                 fprintf(stderr, "%s: invalid hex sequence: %s\n",
114                                 progname, hex);
115                 return -1;
116         } else if (n == 0) {
117                 fprintf(stderr, "%s: empty argument\n", progname);
118                 return -1;
119         } else if (n > sizeof buf) {
120                 fprintf(stderr, "%s: hex sequence too long: %s\n",
121                                 progname, hex);
122                 return -1;
123         }
124
125         rewind(tmp);
126         head.width = head.height = n;
127         rc = pcx_write_scanline(&head, buf, tmp);
128         if (rc != 0) {
129                 fprintf(stderr, "%s: failed to encode data: %s\n",
130                                 progname, strerror(errno));
131                 return -1;
132         }
133
134         offset = ftell(tmp);
135         if (offset < 0) {
136                 fprintf(stderr, "%s: failed to get file offset: %s\n",
137                                 progname, strerror(errno));
138                 return -1;
139         }
140         rewind(tmp);
141
142         if (fread(rle_buf, offset, 1, tmp) != 1) {
143                 fprintf(stderr, "%s: failed to read RLE data: %s\n",
144                                 progname, strerror(errno));
145                 return -1;
146         }
147
148         ret = compare_rle(rle_buf, offset, buf, n);
149         for (long i = 0; i < offset; i++)
150                 printf("%.2hhx", rle_buf[i]);
151         putchar('\n');
152         return ret;
153 }
154
155 int main(int argc, char **argv)
156 {
157         int opt, ret = EXIT_SUCCESS;
158         FILE *tmp;
159
160         if (argc > 0)
161                 progname = argv[0];
162
163         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
164                 switch (opt) {
165                 case 'V':
166                         print_version();
167                         return EXIT_SUCCESS;
168                 case 'H':
169                         print_usage(stdout);
170                         return EXIT_SUCCESS;
171                 default:
172                         print_usage(stderr);
173                         return EXIT_FAILURE;
174                 }
175         }
176
177         if (!argv[optind]) {
178                 print_usage(stderr);
179                 return EXIT_FAILURE;
180         }
181
182         tmp = tmpfile();
183         if (!tmp) {
184                 fprintf(stderr, "%s: failed to create temporary file: %s\n",
185                                 progname, strerror(errno));
186                 return EXIT_FAILURE;
187         }
188
189         for (int i = optind; i < argc; i++) {
190                 if (encode_rle(tmp, argv[i]) != 0) {
191                         ret = EXIT_FAILURE;
192                 }
193         }
194
195         return ret;
196 }