syncfiles/convert/convert_test.c
Dietrich Epp 5ad207f785 Embed character map tables in executable
This simplifies the conversion test, since we don't need to be careful
about which data we run the conversion test in. It will also simplify
the command-line conversion tool and its distribution. The classic Mac
OS version of this program will continue to embed conversion tables in
the resource fork.
2022-03-24 23:44:37 -04:00

312 lines
6.6 KiB
C

/* Converter test. */
#define _XOPEN_SOURCE 500
#include "convert/convert.h"
#include "convert/data.h"
#include "convert/test.h"
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum
{
kInitialBufSize = 4 * 1024,
kConvertBufferSize = 1024
};
static int gFailCount;
static char gTestName[128];
static void Failf(const char *msg, ...) __attribute__((format(printf, 1, 2)));
static void Failf(const char *msg, ...)
{
va_list ap;
gFailCount++;
fputs("Error: ", stderr);
fputs(gTestName, stderr);
fputs(": ", stderr);
va_start(ap, msg);
vfprintf(stderr, msg, ap);
va_end(ap);
fputc('\n', stderr);
if (gFailCount >= 10) {
exit(1);
}
}
static const char *const kErrorNames[] = {"ok", "no memory", "bad data"};
static const char *ErrorName(int err)
{
if (err < 0 || (int)(sizeof(kErrorNames) / sizeof(*kErrorNames)) <= err) {
Dief("bad error code: %d", err);
}
return kErrorNames[err];
}
static void StringPrintf(char *dest, size_t destsz, const char *fmt, ...)
__attribute__((format(printf, 3, 4)));
static void StringPrintf(char *dest, size_t destsz, const char *fmt, ...)
{
va_list ap;
int n;
va_start(ap, fmt);
n = vsnprintf(dest, destsz, fmt, ap);
va_end(ap);
if (n < 0 || n >= (int)destsz) {
Dief("snprintf: overflow");
}
}
static UInt8 *gBuffer[3];
static void PrintQuotedString(const UInt8 *buf, int len)
{
int i, c;
fputc('"', stderr);
for (i = 0; i < len; i++) {
c = buf[i];
if (32 <= c && c <= 126) {
if (c == '\\' || c == '"') {
fputc('\\', stderr);
}
fputc(c, stderr);
} else {
fprintf(stderr, "\\x%02x", c);
}
}
fputc('"', stderr);
}
static void Check(const void *exbuf, int exlen, const void *inbuf, int inlen,
const void *outbuf, int outlen)
{
int i, n, col, diffcol, c1, c2;
if (exlen == outlen && memcmp(exbuf, outbuf, outlen) == 0) {
return;
}
Failf("incorrect output");
n = exlen;
if (n > outlen) {
n = outlen;
}
diffcol = -1;
col = 0;
for (i = 0; i < n; i++) {
c1 = ((const UInt8 *)exbuf)[i];
c2 = ((const UInt8 *)outbuf)[i];
if (c1 != c2) {
diffcol = col;
break;
}
if (32 <= c1 && c1 <= 126) {
col++;
if (c1 == '\\' || c1 == '"') {
col++;
}
} else {
col += 4;
}
}
fputs("Input: ", stderr);
PrintQuotedString(inbuf, inlen);
fputc('\n', stderr);
fputs("Expect: ", stderr);
PrintQuotedString(exbuf, exlen);
fputc('\n', stderr);
fputs("Output: ", stderr);
PrintQuotedString(outbuf, outlen);
fputc('\n', stderr);
if (diffcol >= 0) {
for (i = 0; i < diffcol + 9; i++) {
fputc(' ', stderr);
}
fputc('^', stderr);
}
fputc('\n', stderr);
}
static const char *const kLineBreakData[4] = {
"Line Break\nA\n\nB\rC\r\rD\r\nE\r\n\r\n",
"Line Break\nA\n\nB\nC\n\nD\nE\n\n",
"Line Break\rA\r\rB\rC\r\rD\rE\r\r",
"Line Break\r\nA\r\n\r\nB\r\nC\r\n\r\nD\r\nE\r\n\r\n",
};
static const char *const kLineBreakName[4] = {"keep", "LF", "CR", "CRLF"};
static void TestConverter(const char *name, struct CharmapData data)
{
Ptr datap;
Handle datah;
struct Converter cf, cr, cc;
struct ConverterState st;
int r, i, j, k, jmax, len0, len1, len2;
OSErr err;
UInt8 *ptr;
const UInt8 *iptr, *iend, *istart;
UInt8 *optr, *oend;
int lblen[4];
cf.data = NULL;
cr.data = NULL;
StringPrintf(gTestName, sizeof(gTestName), "%s", name);
/* Load the converter into memory and build the conversion table. */
datap = (void *)data.ptr;
datah = &datap;
r = ConverterBuild(&cf, datah, data.size, kToUTF8, &err);
if (r != 0) {
Failf("ConverterBuild: to UTF-8: %s", ErrorName(r));
goto done;
}
r = ConverterBuild(&cr, datah, data.size, kFromUTF8, &err);
if (r != 0) {
Failf("ConverterBuild: from UTF-8: %s", ErrorName(r));
goto done;
}
/* Create sample data to convert: 0-255, followed by 0. */
len0 = 257;
ptr = gBuffer[0];
for (i = 0; i < 256; i++) {
ptr[i] = i;
}
ptr[256] = 0;
/* Convert sample data. */
iptr = gBuffer[0];
iend = iptr + 257;
optr = gBuffer[1];
oend = optr + kConvertBufferSize;
st.data = 0;
cf.run(*cf.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
if (iptr != iend) {
Failf("some data failed to convert");
goto done;
}
len1 = optr - gBuffer[1];
/* Convert back, in three calls. The middle call will be to a 1-4 byte slice
in the middle. */
for (i = 1; i < len1 - 2; i++) {
jmax = len1 - i;
if (jmax > 4) {
jmax = 4;
}
for (j = 1; j <= jmax; j++) {
StringPrintf(gTestName, sizeof(gTestName), "%s reverse i=%d j=%d",
name, i, j);
st.data = 0;
iptr = gBuffer[1];
optr = gBuffer[2];
oend = optr + kConvertBufferSize;
iend = gBuffer[1] + i;
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
iend = gBuffer[1] + i + j;
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
iend = gBuffer[1] + len1;
cr.run(*cr.data, kLineBreakKeep, &st, &optr, oend, &iptr, iend);
if (iptr != iend) {
Failf("some data failed to convert");
} else {
len2 = optr - gBuffer[2];
Check(gBuffer[0], len0, gBuffer[1], len1, gBuffer[2], len2);
}
}
}
for (i = 0; i < 4; i++) {
lblen[i] = strlen(kLineBreakData[i]) + 1;
}
istart = (const UInt8 *)kLineBreakData[0];
for (k = 0; k < 2; k++) {
cc = k == 0 ? cf : cr;
for (i = 0; i < 4; i++) {
len1 = lblen[0]; /* Input data */
len0 = lblen[i]; /* Expected output */
for (j = 1; j < len1; j++) {
StringPrintf(gTestName, sizeof(gTestName),
"%s %s linebreak %s split=%d", name,
k == 0 ? "forward" : "backward", kLineBreakName[i],
j);
st.data = 0;
iptr = istart;
optr = gBuffer[0];
oend = optr + kConvertBufferSize;
iend = istart + j;
cc.run(*cc.data, i, &st, &optr, oend, &iptr, iend);
iend = istart + len1;
cc.run(*cc.data, i, &st, &optr, oend, &iptr, iend);
if (iptr != iend) {
Failf("some data failed to convert");
} else {
len2 = optr - gBuffer[0];
Check(kLineBreakData[i], len0, kLineBreakData[0], len1,
gBuffer[0], len2);
}
}
}
}
done:
if (cf.data != NULL) {
DisposeHandle(cf.data);
}
if (cr.data != NULL) {
DisposeHandle(cr.data);
}
}
int main(int argc, char **argv)
{
void *buf;
struct CharmapData data;
const char *name;
int i;
(void)argc;
(void)argv;
for (i = 0; i < 3; i++) {
buf = malloc(kConvertBufferSize);
if (buf == NULL) {
DieErrorf(errno, "malloc");
}
gBuffer[i] = buf;
}
for (i = 0;; i++) {
name = CharmapName(i);
if (name == NULL) {
break;
}
data = CharmapData(i);
if (data.ptr != NULL) {
TestConverter(name, data);
}
}
for (i = 0; i < 3; i++) {
free(gBuffer[i]);
}
if (gFailCount > 0) {
fputs("failed\n", stderr);
return 1;
}
fputs("ok\n", stderr);
return 0;
}