blob: c9b2e70827b32c1a470373c73e11533117a702fa [file] [log] [blame]
/* liblouis Braille Translation and Back-Translation Library
Copyright (C) 2008 Eitan Isaacson <eitan@ascender.com>
Copyright (C) 2012 James Teh <jamie@nvaccess.org>
Copyright (C) 2012 Bert Frees <bertfrees@gmail.com>
Copyright (C) 2014 Mesar Hameed <mesar.hameed@gmail.com>
Copyright (C) 2015 Mike Gray <mgray@aph.org>
Copyright (C) 2010-2017 Swiss Library for the Blind, Visually Impaired and Print
Disabled
Copyright (C) 2016-2017 Davy Kager <mail@davykager.nl>
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without any warranty.
*/
/**
* @file
* @brief Test helper functions
*/
#include <config.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "liblouis.h"
#include "internal.h"
#include "brl_checks.h"
#include "unistr.h"
static void
print_int_array(const char *prefix, int *pos_list, int len) {
int i;
fprintf(stderr, "%s ", prefix);
for (i = 0; i < len; i++) fprintf(stderr, "%d ", pos_list[i]);
fprintf(stderr, "\n");
}
static void
print_typeform(const formtype *typeform, int len) {
int i;
fprintf(stderr, "Typeform: ");
for (i = 0; i < len; i++) fprintf(stderr, "%hi", typeform[i]);
fprintf(stderr, "\n");
}
static void
print_widechars(widechar *buffer, int length) {
uint8_t *result_buf;
size_t result_len;
#ifdef WIDECHARS_ARE_UCS4
result_buf = u32_to_u8(buffer, length, NULL, &result_len);
#else
result_buf = u16_to_u8(buffer, length, NULL, &result_len);
#endif
fprintf(stderr, "%.*s", (int)result_len, result_buf);
free(result_buf);
}
/* direction, 0=forward, 1=backwards, 2=both directions. If diagnostics is 1 then
* print diagnostics in case where the translation is not as
* expected */
int
check_base(const char *tableList, const char *input, const char *expected,
optional_test_params in) {
int i, retval = 0;
int direction = in.direction;
const int *expected_inputPos = in.expected_inputPos;
const int *expected_outputPos = in.expected_outputPos;
if (in.direction < 0 || in.direction > 2) {
fprintf(stderr, "Invalid direction.\n");
return 1;
}
if (in.direction != 0 && in.typeform != NULL) {
// Currently, in backward translation, nothing is done with the initial value of
// the typeform argument, and on return it always contains all zeros, so it
// doesn't make any sense to use typeforms in backward translation tests.
fprintf(stderr, "typeforms only supported with testmode 'forward'\n");
return 1;
}
if (in.direction == 2 && in.cursorPos >= 0) {
fprintf(stderr, "cursorPos not supported with testmode 'bothDirections'\n");
return 1;
}
if (in.direction == 2 && in.max_outlen >= 0) {
fprintf(stderr, "maxOutputLength not supported with testmode 'bothDirections'\n");
return 1;
}
if (in.real_inlen >= 0 && in.max_outlen < 0) {
fprintf(stderr,
"realInputLength not supported when maxOutputLength is not specified\n");
return 1;
}
while (1) {
widechar *inbuf, *outbuf, *expectedbuf;
int inlen = strlen(input);
int actualInlen;
const int outlen_multiplier = 4 + sizeof(widechar) * 2;
int outlen = inlen * outlen_multiplier;
int expectedlen = strlen(expected);
int funcStatus = 0;
formtype *typeformbuf = NULL;
int *inputPos = NULL;
int *outputPos = NULL;
int cursorPos = 0;
inbuf = malloc(sizeof(widechar) * inlen);
outbuf = malloc(sizeof(widechar) * outlen);
expectedbuf = malloc(sizeof(widechar) * expectedlen);
if (in.typeform != NULL) {
typeformbuf = malloc(outlen * sizeof(formtype));
memcpy(typeformbuf, in.typeform, inlen * sizeof(formtype));
}
if (in.cursorPos >= 0) {
cursorPos = in.cursorPos;
}
if (in.max_outlen >= 0) {
outlen = in.max_outlen;
}
inlen = _lou_extParseChars(input, inbuf);
if (!inlen) {
fprintf(stderr, "Cannot parse input string.\n");
retval = 1;
goto fail;
}
if (in.real_inlen > inlen) {
fprintf(stderr,
"expected realInputLength (%d) may not exceed total input length "
"(%d)\n",
in.real_inlen, inlen);
return 1;
}
if (expected_inputPos) {
inputPos = malloc(sizeof(int) * outlen);
}
if (expected_outputPos) {
outputPos = malloc(sizeof(int) * inlen);
}
actualInlen = inlen;
// Note that this loop is not strictly needed to make the current tests pass, but
// in the general case it is needed because it is theoretically possible that we
// provided a too short output buffer.
for (int k = 1; k <= 3; k++) {
if (direction == 1) {
funcStatus = lou_backTranslate(tableList, inbuf, &actualInlen, outbuf,
&outlen, typeformbuf, NULL, outputPos, inputPos, &cursorPos,
in.mode);
} else {
funcStatus = lou_translate(tableList, inbuf, &actualInlen, outbuf,
&outlen, typeformbuf, NULL, outputPos, inputPos, &cursorPos,
in.mode);
}
if (!funcStatus) {
fprintf(stderr, "Translation failed.\n");
retval = 1;
goto fail;
}
if (in.max_outlen >= 0 || inlen == actualInlen) {
break;
} else if (k < 3) {
// Hm, something is not quite right. Try again with a larger outbuf
free(outbuf);
outlen = inlen * outlen_multiplier * (k + 1);
outbuf = malloc(sizeof(widechar) * outlen);
if (expected_inputPos) {
free(inputPos);
inputPos = malloc(sizeof(int) * outlen);
}
fprintf(stderr,
"Warning: For %s: returned inlen (%d) differs from passed inlen "
"(%d) "
"using outbuf of size %d. Trying again with bigger outbuf "
"(%d).\n",
input, actualInlen, inlen, inlen * outlen_multiplier * k, outlen);
actualInlen = inlen;
}
}
expectedlen = _lou_extParseChars(expected, expectedbuf);
for (i = 0; i < outlen && i < expectedlen && expectedbuf[i] == outbuf[i]; i++)
;
if (i < outlen || i < expectedlen) {
retval = 1;
if (in.diagnostics) {
outbuf[outlen] = 0;
fprintf(stderr, "Input: '%s'\n", input);
/* Print the original typeform not the typeformbuf, as the
* latter has been modified by the translation and contains some
* information about outbuf */
if (in.typeform != NULL) print_typeform(in.typeform, inlen);
if (in.cursorPos >= 0) fprintf(stderr, "Cursor: %d\n", in.cursorPos);
fprintf(stderr, "Expected: '%s' (length %d)\n", expected, expectedlen);
fprintf(stderr, "Received: '");
print_widechars(outbuf, outlen);
fprintf(stderr, "' (length %d)\n", outlen);
uint8_t *expected_utf8;
uint8_t *out_utf8;
size_t expected_utf8_len;
size_t out_utf8_len;
#ifdef WIDECHARS_ARE_UCS4
expected_utf8 = u32_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len);
out_utf8 = u32_to_u8(&outbuf[i], 1, NULL, &out_utf8_len);
#else
expected_utf8 = u16_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len);
out_utf8 = u16_to_u8(&outbuf[i], 1, NULL, &out_utf8_len);
#endif
if (i < outlen && i < expectedlen) {
fprintf(stderr,
"Diff: Expected '%.*s' but received '%.*s' in index %d\n",
(int)expected_utf8_len, expected_utf8, (int)out_utf8_len,
out_utf8, i);
} else if (i < expectedlen) {
fprintf(stderr,
"Diff: Expected '%.*s' but received nothing in index "
"%d\n",
(int)expected_utf8_len, expected_utf8, i);
} else {
fprintf(stderr,
"Diff: Expected nothing but received '%.*s' in index "
"%d\n",
(int)out_utf8_len, out_utf8, i);
}
free(expected_utf8);
free(out_utf8);
}
}
if (expected_inputPos) {
int error_printed = 0;
for (i = 0; i < outlen; i++) {
if (expected_inputPos[i] != inputPos[i]) {
retval = 1;
if (in.diagnostics) {
if (!error_printed) { // Print only once
fprintf(stderr, "Input position failure:\n");
error_printed = 1;
}
fprintf(stderr, "Expected %d, received %d in index %d\n",
expected_inputPos[i], inputPos[i], i);
}
}
}
}
if (expected_outputPos) {
int error_printed = 0;
for (i = 0; i < inlen; i++) {
if (expected_outputPos[i] != outputPos[i]) {
retval = 1;
if (in.diagnostics) {
if (!error_printed) { // Print only once
fprintf(stderr, "Output position failure:\n");
error_printed = 1;
}
fprintf(stderr, "Expected %d, received %d in index %d\n",
expected_outputPos[i], outputPos[i], i);
}
}
}
}
if ((in.expected_cursorPos >= 0) && (cursorPos != in.expected_cursorPos)) {
retval = 1;
if (in.diagnostics) {
fprintf(stderr, "Cursor position failure:\n");
fprintf(stderr, "Initial:%d Expected:%d Actual:%d \n", in.cursorPos,
in.expected_cursorPos, cursorPos);
}
}
if (in.max_outlen < 0 && inlen != actualInlen) {
retval = 1;
if (in.diagnostics) {
fprintf(stderr,
"Unexpected error happened: input length is not the same before "
"as "
"after the translation:\n");
fprintf(stderr, "Before: %d After: %d \n", inlen, actualInlen);
}
} else if (actualInlen > inlen) {
retval = 1;
if (in.diagnostics) {
fprintf(stderr,
"Unexpected error happened: returned input length (%d) exceeds "
"total input length (%d)\n",
actualInlen, inlen);
}
} else if (in.real_inlen >= 0 && in.real_inlen != actualInlen) {
retval = 1;
if (in.diagnostics) {
fprintf(stderr, "Real input length failure:\n");
fprintf(stderr, "Expected: %d, received: %d\n", in.real_inlen,
actualInlen);
}
}
// on error print the table name, as it isn't always
// clear which table we are testing. In checkyaml for
// example you can define a test for multiple tables.
if (retval != 0 && in.diagnostics) {
fprintf(stderr, "Table: %s\n", tableList);
// add an empty line after each error
fprintf(stderr, "\n");
}
fail:
free(inbuf);
free(outbuf);
free(expectedbuf);
free(typeformbuf);
free(inputPos);
free(outputPos);
if (direction == 2) {
const char *tmp = input;
input = expected;
expected = tmp;
expected_inputPos = in.expected_outputPos;
expected_outputPos = in.expected_inputPos;
direction = 1;
continue;
} else {
break;
}
}
return retval;
}
/* Helper function to convert a typeform string of '0's, '1's, '2's etc.
* to the required format, which is an array of 0s, 1s, 2s, etc.
* For example, "0000011111000" is converted to {0,0,0,0,0,1,1,1,1,1,0,0,0}
* The caller is responsible for freeing the returned array. */
formtype *
convert_typeform(const char *typeform_string) {
int len = strlen(typeform_string);
formtype *typeform = malloc(len * sizeof(formtype));
int i;
for (i = 0; i < len; i++) typeform[i] = typeform_string[i] - '0';
return typeform;
}
void
update_typeform(const char *typeform_string, formtype *typeform, const typeforms kind) {
int len = strlen(typeform_string);
int i;
for (i = 0; i < len; i++)
if (typeform_string[i] != ' ') typeform[i] |= kind;
}
int
check_cursor_pos(const char *tableList, const char *str, const int *expected_pos) {
widechar *inbuf;
widechar *outbuf;
int *inpos, *outpos;
int inlen = strlen(str);
int outlen = inlen;
int cursor_pos;
int i, retval = 0;
inbuf = malloc(sizeof(widechar) * inlen);
outbuf = malloc(sizeof(widechar) * inlen);
inpos = malloc(sizeof(int) * inlen);
outpos = malloc(sizeof(int) * inlen);
inlen = _lou_extParseChars(str, inbuf);
for (i = 0; i < inlen; i++) {
cursor_pos = i;
if (!lou_translate(tableList, inbuf, &inlen, outbuf, &outlen, NULL, NULL, NULL,
NULL, &cursor_pos, compbrlAtCursor)) {
fprintf(stderr, "Translation failed.\n");
retval = 1;
goto fail;
}
if (expected_pos[i] != cursor_pos) {
if (!retval) // Print only once
fprintf(stderr, "Cursorpos failure:\n");
fprintf(stderr,
"string='%s' cursor=%d ('%c') expected=%d received=%d ('%c')\n", str,
i, str[i], expected_pos[i], cursor_pos, (char)outbuf[cursor_pos]);
retval = 1;
}
}
fail:
free(inbuf);
free(outbuf);
free(inpos);
free(outpos);
return retval;
}
/* Check if a string is hyphenated as expected, by passing the
* expected hyphenation position array.
*
* @return 0 if the hyphenation is as expected and 1 otherwise.
*/
int
check_hyphenation_pos(const char *tableList, const char *str, const char *expected) {
widechar *inbuf;
char *hyphens = NULL;
int inlen = strlen(str);
int retval = 0;
inbuf = malloc(sizeof(widechar) * inlen);
inlen = _lou_extParseChars(str, inbuf);
if (!inlen) {
fprintf(stderr, "Cannot parse input string.\n");
retval = 1;
goto fail;
}
hyphens = calloc(inlen + 1, sizeof(char));
if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, 0)) {
fprintf(stderr, "Hyphenation failed.\n");
retval = 1;
goto fail;
}
if (strcmp(expected, hyphens)) {
fprintf(stderr, "Input: '%s'\n", str);
fprintf(stderr, "Expected: '%s'\n", expected);
fprintf(stderr, "Received: '%s'\n", hyphens);
retval = 1;
}
fail:
free(inbuf);
free(hyphens);
return retval;
}
/** Check if a string is hyphenated as expected.
*
* @return 0 if the hyphenation is as expected and 1 otherwise.
*/
int
check_hyphenation(const char *tableList, const char *str, const char *expected) {
widechar *inbuf;
widechar *hyphenatedbuf = NULL;
uint8_t *hyphenated = NULL;
char *hyphens = NULL;
int inlen = strlen(str);
size_t hyphenatedlen = inlen * 2;
int retval = 0;
inbuf = malloc(sizeof(widechar) * inlen);
inlen = _lou_extParseChars(str, inbuf);
if (!inlen) {
fprintf(stderr, "Cannot parse input string.\n");
retval = 1;
goto fail;
}
hyphens = calloc(inlen + 1, sizeof(char));
if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, 0)) {
fprintf(stderr, "Hyphenation failed.\n");
retval = 1;
goto fail;
}
if (hyphens[0] != '0') {
fprintf(stderr, "Unexpected output from lou_hyphenate.\n");
retval = 1;
goto fail;
}
hyphenatedbuf = malloc(sizeof(widechar) * hyphenatedlen);
int i = 0;
int j = 0;
hyphenatedbuf[i++] = inbuf[j++];
for (; j < inlen; j++) {
if (hyphens[j] != '0') hyphenatedbuf[i++] = (widechar)'-';
hyphenatedbuf[i++] = inbuf[j];
}
#ifdef WIDECHARS_ARE_UCS4
hyphenated = u32_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen);
#else
hyphenated = u16_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen);
#endif
if (!hyphenated) {
fprintf(stderr, "Unexpected error during UTF-8 encoding\n");
free(hyphenatedbuf);
retval = 2;
goto fail;
}
if (strlen(expected) != hyphenatedlen ||
strncmp(expected, (const char *)hyphenated, hyphenatedlen)) {
fprintf(stderr, "Input: '%s'\n", str);
fprintf(stderr, "Expected: '%s'\n", expected);
fprintf(stderr, "Received: '%.*s'\n", (int)hyphenatedlen, hyphenated);
retval = 1;
}
free(hyphenatedbuf);
free(hyphenated);
fail:
free(inbuf);
free(hyphens);
return retval;
}